summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYury Gribov <tetra2005@gmail.com>2022-05-19 01:27:48 +0900
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-05-19 09:39:36 +0200
commit1bc2a5948e5364c4fc060cb42bec7320e3f1407d (patch)
treeb369f368a3f062d339b13ea7c80b142ad29c6d4d
parenta6ae75a62020aba78eccd2988cc0b1b02f9aee05 (diff)
downloadpylint-git-first-patch-2471.tar.gz
Subject: [PATCH] Initial commit.first-patch-2471
Proposed in #2471
-rw-r--r--pylint/checkers/modified_iterating_checker.py64
1 files changed, 63 insertions, 1 deletions
diff --git a/pylint/checkers/modified_iterating_checker.py b/pylint/checkers/modified_iterating_checker.py
index a4e852093..333b131f5 100644
--- a/pylint/checkers/modified_iterating_checker.py
+++ b/pylint/checkers/modified_iterating_checker.py
@@ -6,10 +6,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+import astroid
from astroid import nodes
from pylint import checkers, interfaces
-from pylint.checkers import utils
+from pylint.checkers import BaseChecker, utils
if TYPE_CHECKING:
from pylint.lint import PyLinter
@@ -151,5 +152,66 @@ class ModifiedIterationChecker(checkers.BaseChecker):
)
+class ChangedIterateeChecker(BaseChecker):
+ msgs = {
+ "E0130": (
+ "Iterated object %r is modified inside the loop",
+ "changed-iteratee",
+ "Used when iterated object is modified " "inside the loop.",
+ ),
+ }
+
+ class _LoopContext:
+ def __init__(self, node, klass, name):
+ self._node = node
+ self._klass = klass
+ self._name = name
+
+ def __init__(self, linter):
+ BaseChecker.__init__(self, linter)
+ self._loops = []
+
+ @staticmethod
+ def _locate_iterated_objects(node):
+ # TODO: other classes which provide __iter__ ?
+ if isinstance(node, astroid.Name):
+ klass = utils.safe_infer(node)
+ if isinstance(klass, astroid.Dict) or isinstance(klass, astroid.List):
+ return ChangedIterateeChecker._LoopContext(node, klass, node.name)
+ if (
+ isinstance(node, astroid.Call)
+ and isinstance(node.func, astroid.Attribute)
+ and isinstance(node.func.expr, astroid.Name)
+ ):
+ # Look for dict.values, dict.items or dict.keys
+ expr = node.func.expr
+ klass = utils.safe_infer(expr)
+ if isinstance(klass, astroid.Dict) and node.func.attrname in (
+ "keys",
+ "values",
+ "items",
+ ):
+ return ChangedIterateeChecker._LoopContext(node, klass, node.func.expr.name)
+ return None
+
+ def visit_for(self, node):
+ self._loops.append(self._locate_iterated_objects(node.iter))
+
+ def leave_for(self, node):
+ self._loops.pop()
+
+ @utils.check_messages("changed-iteratee")
+ def visit_assignname(self, node):
+ if isinstance(node, astroid.AssignName):
+ for loop in self._loops:
+ if loop._name == node.name:
+ self.add_message("changed-iteratee", node=node, args=(node.name,))
+
+
+# TODO: detect other modification types:
+# * del x[i], x.append(), x.insert(), x.setdefault(), etc.
+
+
def register(linter: PyLinter) -> None:
linter.register_checker(ModifiedIterationChecker(linter))
+ linter.register_checker(ChangedIterateeChecker(linter))