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