Skip to content

Commit

Permalink
New checker unnecessary-list-index-lookup (pylint-dev#4525)
Browse files Browse the repository at this point in the history
  • Loading branch information
timmartin committed Feb 27, 2022
1 parent 6622d10 commit 7e626dd
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 0 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ Release date: TBA

* ``fatal`` was added to the variables permitted in score evaluation expressions.

* Added new checker ``unnecessary-list-index-lookup`` for indexing into a list while
iterating over ``enumerate()``.

Closes #4525

* The default score evaluation now uses a floor of 0.

Closes #2399
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ New checkers

Closes #5281

* Added new checker ``unnecessary-list-index-lookup`` for indexing into a list while
iterating over ``enumerate()``.

Closes #4525

* ``unnecessary-ellipsis``: Emitted when the ellipsis constant is used unnecessarily.

Closes #5460
Expand Down
70 changes: 70 additions & 0 deletions pylint/checkers/refactoring/refactoring_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,13 @@ class RefactoringChecker(checkers.BaseTokenChecker):
"Emitted when using dict() to create an empty dictionary instead of the literal {}. "
"The literal is faster as it avoids an additional function call.",
),
"R1736": (
"Unnecessary list index lookup, use '%s' instead",
"unnecessary-list-index-lookup",
"Emitted when iterating over an enumeration and accessing the "
"value by index lookup. "
"The value can be accessed directly instead.",
),
}
options = (
(
Expand Down Expand Up @@ -628,10 +635,12 @@ def _check_redefined_argument_from_local(self, name_node):
"redefined-argument-from-local",
"too-many-nested-blocks",
"unnecessary-dict-index-lookup",
"unnecessary-list-index-lookup",
)
def visit_for(self, node: nodes.For) -> None:
self._check_nested_blocks(node)
self._check_unnecessary_dict_index_lookup(node)
self._check_unnecessary_list_index_lookup(node)

for name in node.target.nodes_of_class(nodes.AssignName):
self._check_redefined_argument_from_local(name)
Expand Down Expand Up @@ -1552,6 +1561,7 @@ def visit_augassign(self, node: nodes.AugAssign) -> None:
def visit_comprehension(self, node: nodes.Comprehension) -> None:
self._check_unnecessary_comprehension(node)
self._check_unnecessary_dict_index_lookup(node)
self._check_unnecessary_list_index_lookup(node)

def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None:
if (
Expand Down Expand Up @@ -1953,3 +1963,63 @@ def _check_unnecessary_dict_index_lookup(
node=subscript,
args=("1".join(value.as_string().rsplit("0", maxsplit=1)),),
)

def _check_unnecessary_list_index_lookup(
self, node: Union[nodes.For, nodes.Comprehension]
) -> None:
if (
isinstance(node.iter, nodes.Call)
and isinstance(node.iter.func, nodes.Name)
and node.iter.func.name == "enumerate"
and isinstance(node.iter.args[0], nodes.Name)
):
iterating_object_name = node.iter.args[0].name
value_variable = node.target.elts[1]

children = (
node.body if isinstance(node, nodes.For) else node.parent.get_children()
)
for child in children:
for subscript in child.nodes_of_class(nodes.Subscript):
if isinstance(node, nodes.For) and (
isinstance(subscript.parent, nodes.Assign)
and subscript in subscript.parent.targets
or isinstance(subscript.parent, nodes.AugAssign)
and subscript == subscript.parent.target
):
# Ignore this subscript if it is the target of an assignment
# Early termination; after reassignment index lookup will be necessary
return

if isinstance(subscript.parent, nodes.Delete):
# Ignore this subscript if it's used with the delete keyword
return

index = subscript.slice
if isinstance(index, nodes.Name):
if (index.name != node.target.elts[0].name
or iterating_object_name != subscript.value.as_string()
):
continue

if (
isinstance(node, nodes.For)
and index.lookup(index.name)[1][-1].lineno > node.lineno
):
# Ignore this subscript if it has been redefined after
# the for loop.
continue

if (
isinstance(node, nodes.For)
and index.lookup(value_variable.name)[1][-1].lineno > node.lineno
):
# The variable holding the value from iteration has been
# reassigned on a later line, so it can't be used.
continue

self.add_message(
"unnecessary-list-index-lookup",
node=subscript,
args=(node.target.elts[1].name,)
)
44 changes: 44 additions & 0 deletions tests/functional/u/unnecessary/unnecessary_list_index_lookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# pylint: disable=missing-docstring, too-few-public-methods, expression-not-assigned, line-too-long, unused-variable

my_list = ['a', 'b']

for idx, val in enumerate(my_list):
print(my_list[idx]) # [unnecessary-list-index-lookup]

for idx, _ in enumerate(my_list):
print(my_list[0])
if idx > 0:
print(my_list[idx - 1])

for idx, val in enumerate(my_list):
del my_list[idx]

for idx, val in enumerate(my_list):
my_list[idx] = 42

def process_list(data):
for index, value in enumerate(data):
index = 1
print(data[index])

def process_list_again(data):
for index, value in enumerate(data):
value = 1
print(data[index]) # Can't use value here, it's been redefined

for idx, val in enumerate(my_list):
if (val := 42) and my_list[idx] == 'b':
print(1)

other_list = [1, 2]
for idx, val in enumerate(my_list):
print(other_list[idx])

OTHER_INDEX = 0
for idx, val in enumerate(my_list):
print(my_list[OTHER_INDEX])

result = [val for idx, val in enumerate(my_list) if my_list[idx] == 'a'] # [unnecessary-list-index-lookup]
result = [val for idx, val in enumerate(my_list) if idx > 0 and my_list[idx - 1] == 'a']
result = [val for idx, val in enumerate(my_list) if other_list[idx] == 'a']
result = [my_list[idx] for idx, val in enumerate(my_list)] # [unnecessary-list-index-lookup]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
unnecessary-list-index-lookup:6:10:6:22::Unnecessary list index lookup, use 'val' instead:UNDEFINED
unnecessary-list-index-lookup:41:52:41:64::Unnecessary list index lookup, use 'val' instead:UNDEFINED
unnecessary-list-index-lookup:44:10:44:22::Unnecessary list index lookup, use 'val' instead:UNDEFINED

0 comments on commit 7e626dd

Please sign in to comment.