redefined_loop_name.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Optional checker to warn when loop variables are overwritten in the loop's body."""
  5. from __future__ import annotations
  6. from astroid import nodes
  7. from pylint import checkers
  8. from pylint.checkers import utils
  9. from pylint.interfaces import HIGH
  10. from pylint.lint import PyLinter
  11. class RedefinedLoopNameChecker(checkers.BaseChecker):
  12. name = "redefined-loop-name"
  13. msgs = {
  14. "W2901": (
  15. "Redefining %r from loop (line %s)",
  16. "redefined-loop-name",
  17. "Used when a loop variable is overwritten in the loop body.",
  18. ),
  19. }
  20. def __init__(self, linter: PyLinter) -> None:
  21. super().__init__(linter)
  22. self._loop_variables: list[
  23. tuple[nodes.For, list[str], nodes.LocalsDictNodeNG]
  24. ] = []
  25. @utils.only_required_for_messages("redefined-loop-name")
  26. def visit_assignname(self, node: nodes.AssignName) -> None:
  27. assign_type = node.assign_type()
  28. if not isinstance(assign_type, (nodes.Assign, nodes.AugAssign)):
  29. return
  30. node_scope = node.scope()
  31. for outer_for, outer_variables, outer_for_scope in self._loop_variables:
  32. if node_scope is not outer_for_scope:
  33. continue
  34. if node.name in outer_variables and not utils.in_for_else_branch(
  35. outer_for, node
  36. ):
  37. self.add_message(
  38. "redefined-loop-name",
  39. args=(node.name, outer_for.fromlineno),
  40. node=node,
  41. confidence=HIGH,
  42. )
  43. break
  44. @utils.only_required_for_messages("redefined-loop-name")
  45. def visit_for(self, node: nodes.For) -> None:
  46. assigned_to = [a.name for a in node.target.nodes_of_class(nodes.AssignName)]
  47. # Only check variables that are used
  48. assigned_to = [
  49. var
  50. for var in assigned_to
  51. if not self.linter.config.dummy_variables_rgx.match(var)
  52. ]
  53. node_scope = node.scope()
  54. for variable in assigned_to:
  55. for outer_for, outer_variables, outer_for_scope in self._loop_variables:
  56. if node_scope is not outer_for_scope:
  57. continue
  58. if variable in outer_variables and not utils.in_for_else_branch(
  59. outer_for, node
  60. ):
  61. self.add_message(
  62. "redefined-loop-name",
  63. args=(variable, outer_for.fromlineno),
  64. node=node,
  65. confidence=HIGH,
  66. )
  67. break
  68. self._loop_variables.append((node, assigned_to, node.scope()))
  69. @utils.only_required_for_messages("redefined-loop-name")
  70. def leave_for(self, node: nodes.For) -> None: # pylint: disable=unused-argument
  71. self._loop_variables.pop()
  72. def register(linter: PyLinter) -> None:
  73. linter.register_checker(RedefinedLoopNameChecker(linter))