Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add B020 check to find for-loop control variable overiding iter set #220

Merged
merged 10 commits into from Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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]
Korben11 marked this conversation as resolved.
Show resolved Hide resolved

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