• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 19300648625

12 Nov 2025 02:22PM UTC coverage: 93.909% (-0.4%) from 94.325%
19300648625

push

github

web-flow
Optimized performance for 'test_examples.py' (#1251)

8 of 8 new or added lines in 2 files covered. (100.0%)

16 existing lines in 7 files now uncovered.

3515 of 3743 relevant lines covered (93.91%)

17.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.83
/python_ta/checkers/unnecessary_indexing_checker.py
1
"""Checker for unnecessary indexing in a loop."""
2

3
from __future__ import annotations
20✔
4

5
from typing import TYPE_CHECKING, Optional, Union
20✔
6

7
from astroid import nodes
20✔
8
from astroid.const import Context
20✔
9
from pylint.checkers import BaseChecker
20✔
10
from pylint.checkers.utils import only_required_for_messages
20✔
11

12
if TYPE_CHECKING:
13
    from pylint.lint import PyLinter
14

15

16
class UnnecessaryIndexingChecker(BaseChecker):
20✔
17
    """A checker class that reports unnecessary indexing in loops and comprehensions.
18
    Indexing is unnecessary when the elements can be accessed directly"""
19

20
    name = "unnecessary_indexing"
20✔
21
    msgs = {
20✔
22
        "E9994": (
23
            "Loop/comprehension index `%s` can be simplified by accessing the elements directly in the for loop or "
24
            "comprehension, "
25
            "for example, `for my_variable in %s`.",
26
            "unnecessary-indexing",
27
            "Used when you have an index variable in a for loop/comprehension "
28
            "where its only usage is to index the iterable",
29
        )
30
    }
31

32
    @only_required_for_messages("unnecessary-indexing")
20✔
33
    def visit_for(self, node: nodes.For) -> None:
20✔
34
        """Visits for node"""
35
        # Check if the iterable of the for loop is of the form "range(len(<variable-name>))".
36
        iterable = _iterable_if_range(node.iter)
20✔
37
        if iterable is not None and _is_unnecessary_indexing(node):
20✔
38
            args = node.target.as_string(), iterable
20✔
39
            self.add_message("unnecessary-indexing", node=node.target, args=args)
20✔
40

41
    @only_required_for_messages("unnecessary-indexing")
20✔
42
    def visit_comprehension(self, node: nodes.Comprehension) -> None:
20✔
43
        """Visits comprehension node"""
44
        iterable = _iterable_if_range(node.iter)
20✔
45
        if iterable is not None and _is_unnecessary_indexing(node):
20✔
46
            args = node.target.as_string(), iterable
20✔
47
            self.add_message("unnecessary-indexing", node=node.target, args=args)
20✔
48

49

50
# Helper functions
51
def _is_unnecessary_indexing(node: Union[nodes.For, nodes.Comprehension]) -> bool:
20✔
52
    """Return whether the index variable in the for loop/comprehension is ONLY used to index the iterable.
53

54
    True if unnecessary usage, False otherwise or if index variable not used at all.
55
    """
56
    index_nodes = []
20✔
57
    for assign_name_node in node.target.nodes_of_class((nodes.AssignName, nodes.Name)):
20✔
58
        index_nodes.extend(_index_name_nodes(assign_name_node.name, node))
20✔
59
    return all(_is_redundant(index_node, node) for index_node in index_nodes) and index_nodes
20✔
60

61

62
def _iterable_if_range(node: nodes.NodeNG) -> Optional[str]:
20✔
63
    """Return the iterable's name if this node is in "range" form, or None otherwise.
64

65
    Check for three forms:
66
      - range(len(<variable-name>))
67
      - range(0, len(<variable-name>))
68
      - range(0, len(<variable-name>), 1)
69
    """
70
    # Check outer function call is range
71
    if (
20✔
72
        not isinstance(node, nodes.Call)
73
        or not isinstance(node.func, nodes.Name)
74
        or not node.func.name == "range"
75
    ):
76
        return None
20✔
77

78
    # Check arguments to range
79
    if len(node.args) > 1:
20✔
80
        # Check that args[0] == Const(0)
81
        arg1 = node.args[0]
20✔
82
        if not isinstance(arg1, nodes.Const) or arg1.value != 0:
20✔
UNCOV
83
            return None
×
84
        if len(node.args) == 3 and (
20✔
85
            not isinstance(node.args[2], nodes.Const) or node.args[2].value != 1
86
        ):
UNCOV
87
            return None
×
88

89
    # Finally, check 'stop' argument is of the form len(<variable-name>).
90
    if len(node.args) == 1:
20✔
91
        stop_arg = node.args[0]
20✔
92
    else:
93
        stop_arg = node.args[1]
20✔
94

95
    if (
20✔
96
        isinstance(stop_arg, nodes.Call)
97
        and isinstance(stop_arg.func, nodes.Name)
98
        and stop_arg.func.name == "len"
99
        and len(stop_arg.args) == 1
100
        and isinstance(stop_arg.args[0], nodes.Name)
101
    ):
102
        return stop_arg.args[0].name
20✔
103

104

105
def _is_load_subscript(
20✔
106
    index_node: nodes.Name, loop_node: Union[nodes.For, nodes.Comprehension]
107
) -> bool:
108
    """Return whether or not <index_node> is used to subscript the iterable of <loop_node>
109
    and the subscript item is being loaded from, e.g., s += iterable[index_node].
110

111
    NOTE: Index node is deprecated in Python 3.9
112

113
    Returns True if the following conditions are met:
114
    (3.9)
115
        - The <index_node> Name node is inside of a Subscript node
116
        - The item that is being indexed is the iterable of the for loop/comprehension
117
        - The Subscript node is being used in a load context
118
    (3.8)
119
        - The <index_node> Name node is inside of an Index node
120
        - The Index node is inside of a Subscript node
121
        - The item that is being indexed is the iterable of the for loop/comprehension
122
        - The Subscript node is being used in a load context
123
    """
124
    iterable = _iterable_if_range(loop_node.iter)
20✔
125

126
    return (
20✔
127
        isinstance(index_node.parent, nodes.Subscript)
128
        and isinstance(index_node.parent.value, nodes.Name)
129
        and index_node.parent.value.name == iterable
130
        and index_node.parent.ctx == Context.Load
131
    )
132

133

134
def _is_redundant(
20✔
135
    index_node: Union[nodes.AssignName, nodes.Name],
136
    loop_node: Union[nodes.For, nodes.Comprehension],
137
) -> bool:
138
    """Return whether or not <index_node> is redundant in <loop_node>.
139

140
    The lookup method is used in case the original index variable is shadowed
141
    in the for loop/comprehension's body.
142
    """
143
    _, assignments = index_node.lookup(index_node.name)
20✔
144
    if not assignments:
20✔
145
        return False
×
146
    elif isinstance(index_node, nodes.AssignName):
20✔
147
        return assignments[0] != loop_node.target
20✔
148
    else:  # isinstance(index_node, nodes.Name)
149
        return assignments[0] != loop_node.target or _is_load_subscript(index_node, loop_node)
20✔
150

151

152
def _index_name_nodes(
20✔
153
    index: str, loop_node: Union[nodes.For, nodes.Comprehension]
154
) -> list[Union[nodes.AssignName, nodes.Name]]:
155
    """Return a list of <index> AssignName and Name nodes contained in the body of <loop_node>.
156

157
    Remove uses of variables that shadow <index>.
158
    """
159
    scope = loop_node.scope()
20✔
160

161
    if isinstance(loop_node, nodes.For):
20✔
162
        body = loop_node
20✔
163
    else:
164
        body = loop_node.parent
20✔
165

166
    return [
20✔
167
        name_node
168
        for name_node in body.nodes_of_class((nodes.AssignName, nodes.Name))
169
        if name_node.name == index
170
        and name_node != loop_node.target
171
        and name_node.lookup(name_node.name)[0] == scope
172
    ]
173

174

175
def register(linter: PyLinter) -> None:
20✔
176
    """Required method to auto-register this checker to the linter"""
177
    linter.register_checker(UnnecessaryIndexingChecker(linter))
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc