1import ast 2import sys 3 4 5def _get_last_child_with_lineno(node): 6 """ 7 Return the last direct child of `node` that has a lineno attribute, 8 or None if `node` has no such children. 9 10 Almost all node._field lists are sorted by the order in which they 11 appear in source code. For some nodes however, we have to skip some 12 fields that either don't have line numbers (e.g., "ctx" and "names") 13 or that are in the wrong position (e.g., "decorator_list" and 14 "returns"). Then we choose the first field (i.e., the field with the 15 highest line number) that actually contains a node. If it contains a 16 list of nodes, we return the last one. 17 18 """ 19 ignored_fields = {"ctx", "decorator_list", "names", "returns"} 20 fields = node._fields 21 # The fields of ast.Call are in the wrong order. 22 if isinstance(node, ast.Call): 23 fields = ("func", "args", "starargs", "keywords", "kwargs") 24 for name in reversed(fields): 25 if name in ignored_fields: 26 continue 27 28 try: 29 last_field = getattr(node, name) 30 except AttributeError: 31 continue 32 33 # Ignore non-AST objects like "is_async", "level" and "nl". 34 if isinstance(last_field, ast.AST): 35 return last_field 36 elif isinstance(last_field, list) and last_field: 37 return last_field[-1] 38 return None 39 40 41def get_last_line_number(node): 42 """Estimate last line number of the given AST node. 43 44 The estimate is based on the line number of the last descendant of 45 `node` that has a lineno attribute. Therefore, it underestimates the 46 size of code ending with, e.g., multiline strings and comments. 47 48 When traversing the tree, we may see a mix of nodes with line 49 numbers and nodes without line numbers. We therefore, store the 50 maximum line number seen so far and report it at the end. A more 51 accurate (but also slower to compute) estimate would traverse all 52 children, instead of just the last one, since choosing the last one 53 may lead to a path that ends with a node without line number. 54 55 """ 56 max_lineno = node.lineno 57 while True: 58 last_child = _get_last_child_with_lineno(node) 59 if last_child is None: 60 return max_lineno 61 else: 62 try: 63 max_lineno = max(max_lineno, last_child.lineno) 64 except AttributeError: 65 pass 66 node = last_child 67 68 69def get_first_line_number(node): 70 """ 71 From Python 3.8 onwards, lineno for decorated objects is the line at which 72 the object definition starts, which is different from what Python < 3.8 73 reported -- the lineno of the first decorator. To preserve this behaviour 74 of Vulture for newer Python versions, which is also more accurate for 75 counting the size of the unused code chunk (if the property is unused, we 76 also don't need it's decorators), we return the lineno of the first 77 decorator, if there are any. 78 """ 79 if sys.version_info >= (3, 8): 80 decorators = getattr(node, "decorator_list", []) 81 if decorators: 82 return decorators[0].lineno 83 return node.lineno 84