Skip to content

Commit

Permalink
Add B020 check to find for-loop control variable overiding iter set (#…
Browse files Browse the repository at this point in the history
…220)

* Implement rule B020: for-loop control variable overrides iter set

* Update tests/b020.py

Co-authored-by: Cooper Lees <me@cooperlees.com>
  • Loading branch information
Korben11 and cooperlees committed Jan 27, 2022
1 parent 1c47a16 commit f1d1a53
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -63,3 +63,6 @@ target/
.ipynb_checkpoints

.vscode

# JetBrains
.idea/
7 changes: 7 additions & 0 deletions README.rst
Expand Up @@ -132,6 +132,8 @@ data available in ``ex``.

**B018**: Found useless expression. Either assign it to a variable or remove it.

**B020**: Loop control variable overrides iterable it iterates


Opinionated warnings
~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -245,6 +247,11 @@ MIT
Change Log
----------

Unreleased
~~~~~~~~~~

* B020: ensure loop control variable doesn't overrides iterable it iterates (#220)

22.1.11
~~~~~~~~~~

Expand Down
21 changes: 21 additions & 0 deletions bugbear.py
Expand Up @@ -333,6 +333,7 @@ def visit_Assign(self, node):

def visit_For(self, node):
self.check_for_b007(node)
self.check_for_b020(node)
self.generic_visit(node)

def visit_Assert(self, node):
Expand Down Expand Up @@ -506,6 +507,20 @@ def check_for_b017(self, node):
):
self.errors.append(B017(node.lineno, node.col_offset))

def check_for_b020(self, node):
targets = NameFinder()
targets.visit(node.target)
ctrl_names = set(targets.names)

iterset = NameFinder()
iterset.visit(node.iter)
iterset_names = set(iterset.names)

for name in sorted(ctrl_names):
if name in iterset_names:
n = targets.names[name][0]
self.errors.append(B020(n.lineno, n.col_offset, vars=(name,)))

def check_for_b904(self, node):
"""Checks `raise` without `from` inside an `except` clause.
Expand Down Expand Up @@ -871,6 +886,12 @@ def visit(self, node):
"B018 Found useless expression. Either assign it to a variable or remove it."
)
)
B020 = Error(
message=(
"B020 Found for loop that reassigns the iterable it is iterating "
+ "with each iterable value."
)
)

# Warnings disabled by default.
B901 = Error(
Expand Down
22 changes: 22 additions & 0 deletions tests/b020.py
@@ -0,0 +1,22 @@
"""
Should emit:
B020 - on lines 8 and 21
"""

items = [1, 2, 3]

for items in items:
print(items)

items = [1, 2, 3]

for item in items:
print(item)

values = {"secret": 123}

for key, value in values.items():
print(f"{key}, {value}")

for key, values in values.items():
print(f"{key}, {values}")
13 changes: 13 additions & 0 deletions tests/test_bugbear.py
Expand Up @@ -29,6 +29,7 @@
B016,
B017,
B018,
B020,
B901,
B902,
B903,
Expand Down Expand Up @@ -249,6 +250,18 @@ def test_b018_classes(self):
expected.append(B018(33, 4))
self.assertEqual(errors, self.errors(*expected))

def test_b020(self):
filename = Path(__file__).absolute().parent / "b020.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
self.assertEqual(
errors,
self.errors(
B020(8, 4, vars=("items",)),
B020(21, 9, vars=("values",)),
),
)

def test_b901(self):
filename = Path(__file__).absolute().parent / "b901.py"
bbc = BugBearChecker(filename=str(filename))
Expand Down

0 comments on commit f1d1a53

Please sign in to comment.