|
16 | 16 | from pylint import utils as lint_utils |
17 | 17 | from pylint.checkers import utils |
18 | 18 | from pylint.checkers.utils import node_frame_class |
| 19 | +from pylint.interfaces import HIGH |
19 | 20 |
|
20 | 21 | KNOWN_INFINITE_ITERATORS = {"itertools.count"} |
21 | 22 | BUILTIN_EXIT_FUNCS = frozenset(("quit", "exit")) |
@@ -430,6 +431,13 @@ class RefactoringChecker(checkers.BaseTokenChecker): |
430 | 431 | "Emitted when using dict() to create an empty dictionary instead of the literal {}. " |
431 | 432 | "The literal is faster as it avoids an additional function call.", |
432 | 433 | ), |
| 434 | + "R1736": ( |
| 435 | + "Unnecessary list index lookup, use '%s' instead", |
| 436 | + "unnecessary-list-index-lookup", |
| 437 | + "Emitted when iterating over an enumeration and accessing the " |
| 438 | + "value by index lookup. " |
| 439 | + "The value can be accessed directly instead.", |
| 440 | + ), |
433 | 441 | } |
434 | 442 | options = ( |
435 | 443 | ( |
@@ -628,10 +636,12 @@ def _check_redefined_argument_from_local(self, name_node): |
628 | 636 | "redefined-argument-from-local", |
629 | 637 | "too-many-nested-blocks", |
630 | 638 | "unnecessary-dict-index-lookup", |
| 639 | + "unnecessary-list-index-lookup", |
631 | 640 | ) |
632 | 641 | def visit_for(self, node: nodes.For) -> None: |
633 | 642 | self._check_nested_blocks(node) |
634 | 643 | self._check_unnecessary_dict_index_lookup(node) |
| 644 | + self._check_unnecessary_list_index_lookup(node) |
635 | 645 |
|
636 | 646 | for name in node.target.nodes_of_class(nodes.AssignName): |
637 | 647 | self._check_redefined_argument_from_local(name) |
@@ -1548,10 +1558,15 @@ def _check_consider_using_join(self, aug_assign): |
1548 | 1558 | def visit_augassign(self, node: nodes.AugAssign) -> None: |
1549 | 1559 | self._check_consider_using_join(node) |
1550 | 1560 |
|
1551 | | - @utils.check_messages("unnecessary-comprehension", "unnecessary-dict-index-lookup") |
| 1561 | + @utils.check_messages( |
| 1562 | + "unnecessary-comprehension", |
| 1563 | + "unnecessary-dict-index-lookup", |
| 1564 | + "unnecessary-list-index-lookup", |
| 1565 | + ) |
1552 | 1566 | def visit_comprehension(self, node: nodes.Comprehension) -> None: |
1553 | 1567 | self._check_unnecessary_comprehension(node) |
1554 | 1568 | self._check_unnecessary_dict_index_lookup(node) |
| 1569 | + self._check_unnecessary_list_index_lookup(node) |
1555 | 1570 |
|
1556 | 1571 | def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: |
1557 | 1572 | if ( |
@@ -1953,3 +1968,70 @@ def _check_unnecessary_dict_index_lookup( |
1953 | 1968 | node=subscript, |
1954 | 1969 | args=("1".join(value.as_string().rsplit("0", maxsplit=1)),), |
1955 | 1970 | ) |
| 1971 | + |
| 1972 | + def _check_unnecessary_list_index_lookup( |
| 1973 | + self, node: Union[nodes.For, nodes.Comprehension] |
| 1974 | + ) -> None: |
| 1975 | + if ( |
| 1976 | + not isinstance(node.iter, nodes.Call) |
| 1977 | + or not isinstance(node.iter.func, nodes.Name) |
| 1978 | + or not node.iter.func.name == "enumerate" |
| 1979 | + or not isinstance(node.iter.args[0], nodes.Name) |
| 1980 | + or not isinstance(node.target, nodes.Tuple) |
| 1981 | + or len(node.target.elts) < 2 |
| 1982 | + ): |
| 1983 | + return |
| 1984 | + |
| 1985 | + iterating_object_name = node.iter.args[0].name |
| 1986 | + value_variable = node.target.elts[1] |
| 1987 | + |
| 1988 | + children = ( |
| 1989 | + node.body if isinstance(node, nodes.For) else node.parent.get_children() |
| 1990 | + ) |
| 1991 | + for child in children: |
| 1992 | + for subscript in child.nodes_of_class(nodes.Subscript): |
| 1993 | + if isinstance(node, nodes.For) and ( |
| 1994 | + isinstance(subscript.parent, nodes.Assign) |
| 1995 | + and subscript in subscript.parent.targets |
| 1996 | + or isinstance(subscript.parent, nodes.AugAssign) |
| 1997 | + and subscript == subscript.parent.target |
| 1998 | + ): |
| 1999 | + # Ignore this subscript if it is the target of an assignment |
| 2000 | + # Early termination; after reassignment index lookup will be necessary |
| 2001 | + return |
| 2002 | + |
| 2003 | + if isinstance(subscript.parent, nodes.Delete): |
| 2004 | + # Ignore this subscript if it's used with the delete keyword |
| 2005 | + return |
| 2006 | + |
| 2007 | + index = subscript.slice |
| 2008 | + if isinstance(index, nodes.Name): |
| 2009 | + if ( |
| 2010 | + index.name != node.target.elts[0].name |
| 2011 | + or iterating_object_name != subscript.value.as_string() |
| 2012 | + ): |
| 2013 | + continue |
| 2014 | + |
| 2015 | + if ( |
| 2016 | + isinstance(node, nodes.For) |
| 2017 | + and index.lookup(index.name)[1][-1].lineno > node.lineno |
| 2018 | + ): |
| 2019 | + # Ignore this subscript if it has been redefined after |
| 2020 | + # the for loop. |
| 2021 | + continue |
| 2022 | + |
| 2023 | + if ( |
| 2024 | + isinstance(node, nodes.For) |
| 2025 | + and index.lookup(value_variable.name)[1][-1].lineno |
| 2026 | + > node.lineno |
| 2027 | + ): |
| 2028 | + # The variable holding the value from iteration has been |
| 2029 | + # reassigned on a later line, so it can't be used. |
| 2030 | + continue |
| 2031 | + |
| 2032 | + self.add_message( |
| 2033 | + "unnecessary-list-index-lookup", |
| 2034 | + node=subscript, |
| 2035 | + args=(node.target.elts[1].name,), |
| 2036 | + confidence=HIGH, |
| 2037 | + ) |
0 commit comments