|
18 | 18 | from pylint import utils as lint_utils |
19 | 19 | from pylint.checkers import utils |
20 | 20 | from pylint.checkers.utils import node_frame_class |
| 21 | +from pylint.interfaces import HIGH |
21 | 22 |
|
22 | 23 | if sys.version_info >= (3, 8): |
23 | 24 | from functools import cached_property |
@@ -437,6 +438,13 @@ class RefactoringChecker(checkers.BaseTokenChecker): |
437 | 438 | "Emitted when using dict() to create an empty dictionary instead of the literal {}. " |
438 | 439 | "The literal is faster as it avoids an additional function call.", |
439 | 440 | ), |
| 441 | + "R1736": ( |
| 442 | + "Unnecessary list index lookup, use '%s' instead", |
| 443 | + "unnecessary-list-index-lookup", |
| 444 | + "Emitted when iterating over an enumeration and accessing the " |
| 445 | + "value by index lookup. " |
| 446 | + "The value can be accessed directly instead.", |
| 447 | + ), |
440 | 448 | } |
441 | 449 | options = ( |
442 | 450 | ( |
@@ -635,10 +643,12 @@ def _check_redefined_argument_from_local(self, name_node): |
635 | 643 | "redefined-argument-from-local", |
636 | 644 | "too-many-nested-blocks", |
637 | 645 | "unnecessary-dict-index-lookup", |
| 646 | + "unnecessary-list-index-lookup", |
638 | 647 | ) |
639 | 648 | def visit_for(self, node: nodes.For) -> None: |
640 | 649 | self._check_nested_blocks(node) |
641 | 650 | self._check_unnecessary_dict_index_lookup(node) |
| 651 | + self._check_unnecessary_list_index_lookup(node) |
642 | 652 |
|
643 | 653 | for name in node.target.nodes_of_class(nodes.AssignName): |
644 | 654 | self._check_redefined_argument_from_local(name) |
@@ -1556,10 +1566,15 @@ def _check_consider_using_join(self, aug_assign): |
1556 | 1566 | def visit_augassign(self, node: nodes.AugAssign) -> None: |
1557 | 1567 | self._check_consider_using_join(node) |
1558 | 1568 |
|
1559 | | - @utils.check_messages("unnecessary-comprehension", "unnecessary-dict-index-lookup") |
| 1569 | + @utils.check_messages( |
| 1570 | + "unnecessary-comprehension", |
| 1571 | + "unnecessary-dict-index-lookup", |
| 1572 | + "unnecessary-list-index-lookup", |
| 1573 | + ) |
1560 | 1574 | def visit_comprehension(self, node: nodes.Comprehension) -> None: |
1561 | 1575 | self._check_unnecessary_comprehension(node) |
1562 | 1576 | self._check_unnecessary_dict_index_lookup(node) |
| 1577 | + self._check_unnecessary_list_index_lookup(node) |
1563 | 1578 |
|
1564 | 1579 | def _check_unnecessary_comprehension(self, node: nodes.Comprehension) -> None: |
1565 | 1580 | if ( |
@@ -1963,3 +1978,72 @@ def _check_unnecessary_dict_index_lookup( |
1963 | 1978 | node=subscript, |
1964 | 1979 | args=("1".join(value.as_string().rsplit("0", maxsplit=1)),), |
1965 | 1980 | ) |
| 1981 | + |
| 1982 | + def _check_unnecessary_list_index_lookup( |
| 1983 | + self, node: Union[nodes.For, nodes.Comprehension] |
| 1984 | + ) -> None: |
| 1985 | + if ( |
| 1986 | + not isinstance(node.iter, nodes.Call) |
| 1987 | + or not isinstance(node.iter.func, nodes.Name) |
| 1988 | + or not node.iter.func.name == "enumerate" |
| 1989 | + or not isinstance(node.iter.args[0], nodes.Name) |
| 1990 | + ): |
| 1991 | + return |
| 1992 | + |
| 1993 | + if not isinstance(node.target, nodes.Tuple) or len(node.target.elts) < 2: |
| 1994 | + # enumerate() result is being assigned without destructuring |
| 1995 | + return |
| 1996 | + |
| 1997 | + iterating_object_name = node.iter.args[0].name |
| 1998 | + value_variable = node.target.elts[1] |
| 1999 | + |
| 2000 | + children = ( |
| 2001 | + node.body if isinstance(node, nodes.For) else node.parent.get_children() |
| 2002 | + ) |
| 2003 | + for child in children: |
| 2004 | + for subscript in child.nodes_of_class(nodes.Subscript): |
| 2005 | + if isinstance(node, nodes.For) and ( |
| 2006 | + isinstance(subscript.parent, nodes.Assign) |
| 2007 | + and subscript in subscript.parent.targets |
| 2008 | + or isinstance(subscript.parent, nodes.AugAssign) |
| 2009 | + and subscript == subscript.parent.target |
| 2010 | + ): |
| 2011 | + # Ignore this subscript if it is the target of an assignment |
| 2012 | + # Early termination; after reassignment index lookup will be necessary |
| 2013 | + return |
| 2014 | + |
| 2015 | + if isinstance(subscript.parent, nodes.Delete): |
| 2016 | + # Ignore this subscript if it's used with the delete keyword |
| 2017 | + return |
| 2018 | + |
| 2019 | + index = subscript.slice |
| 2020 | + if isinstance(index, nodes.Name): |
| 2021 | + if ( |
| 2022 | + index.name != node.target.elts[0].name |
| 2023 | + or iterating_object_name != subscript.value.as_string() |
| 2024 | + ): |
| 2025 | + continue |
| 2026 | + |
| 2027 | + if ( |
| 2028 | + isinstance(node, nodes.For) |
| 2029 | + and index.lookup(index.name)[1][-1].lineno > node.lineno |
| 2030 | + ): |
| 2031 | + # Ignore this subscript if it has been redefined after |
| 2032 | + # the for loop. |
| 2033 | + continue |
| 2034 | + |
| 2035 | + if ( |
| 2036 | + isinstance(node, nodes.For) |
| 2037 | + and index.lookup(value_variable.name)[1][-1].lineno |
| 2038 | + > node.lineno |
| 2039 | + ): |
| 2040 | + # The variable holding the value from iteration has been |
| 2041 | + # reassigned on a later line, so it can't be used. |
| 2042 | + continue |
| 2043 | + |
| 2044 | + self.add_message( |
| 2045 | + "unnecessary-list-index-lookup", |
| 2046 | + node=subscript, |
| 2047 | + args=(node.target.elts[1].name,), |
| 2048 | + confidence=HIGH, |
| 2049 | + ) |
0 commit comments