Skip to content

Commit f1d1a53

Browse files
Korben11cooperlees
andauthored
Add B020 check to find for-loop control variable overiding iter set (#220)
* Implement rule B020: for-loop control variable overrides iter set * Update tests/b020.py Co-authored-by: Cooper Lees <[email protected]>
1 parent 1c47a16 commit f1d1a53

File tree

5 files changed

+66
-0
lines changed

5 files changed

+66
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ target/
6363
.ipynb_checkpoints
6464

6565
.vscode
66+
67+
# JetBrains
68+
.idea/

README.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ data available in ``ex``.
132132

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

135+
**B020**: Loop control variable overrides iterable it iterates
136+
135137

136138
Opinionated warnings
137139
~~~~~~~~~~~~~~~~~~~~
@@ -245,6 +247,11 @@ MIT
245247
Change Log
246248
----------
247249

250+
Unreleased
251+
~~~~~~~~~~
252+
253+
* B020: ensure loop control variable doesn't overrides iterable it iterates (#220)
254+
248255
22.1.11
249256
~~~~~~~~~~
250257

bugbear.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ def visit_Assign(self, node):
333333

334334
def visit_For(self, node):
335335
self.check_for_b007(node)
336+
self.check_for_b020(node)
336337
self.generic_visit(node)
337338

338339
def visit_Assert(self, node):
@@ -506,6 +507,20 @@ def check_for_b017(self, node):
506507
):
507508
self.errors.append(B017(node.lineno, node.col_offset))
508509

510+
def check_for_b020(self, node):
511+
targets = NameFinder()
512+
targets.visit(node.target)
513+
ctrl_names = set(targets.names)
514+
515+
iterset = NameFinder()
516+
iterset.visit(node.iter)
517+
iterset_names = set(iterset.names)
518+
519+
for name in sorted(ctrl_names):
520+
if name in iterset_names:
521+
n = targets.names[name][0]
522+
self.errors.append(B020(n.lineno, n.col_offset, vars=(name,)))
523+
509524
def check_for_b904(self, node):
510525
"""Checks `raise` without `from` inside an `except` clause.
511526
@@ -871,6 +886,12 @@ def visit(self, node):
871886
"B018 Found useless expression. Either assign it to a variable or remove it."
872887
)
873888
)
889+
B020 = Error(
890+
message=(
891+
"B020 Found for loop that reassigns the iterable it is iterating "
892+
+ "with each iterable value."
893+
)
894+
)
874895

875896
# Warnings disabled by default.
876897
B901 = Error(

tests/b020.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Should emit:
3+
B020 - on lines 8 and 21
4+
"""
5+
6+
items = [1, 2, 3]
7+
8+
for items in items:
9+
print(items)
10+
11+
items = [1, 2, 3]
12+
13+
for item in items:
14+
print(item)
15+
16+
values = {"secret": 123}
17+
18+
for key, value in values.items():
19+
print(f"{key}, {value}")
20+
21+
for key, values in values.items():
22+
print(f"{key}, {values}")

tests/test_bugbear.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
B016,
3030
B017,
3131
B018,
32+
B020,
3233
B901,
3334
B902,
3435
B903,
@@ -249,6 +250,18 @@ def test_b018_classes(self):
249250
expected.append(B018(33, 4))
250251
self.assertEqual(errors, self.errors(*expected))
251252

253+
def test_b020(self):
254+
filename = Path(__file__).absolute().parent / "b020.py"
255+
bbc = BugBearChecker(filename=str(filename))
256+
errors = list(bbc.run())
257+
self.assertEqual(
258+
errors,
259+
self.errors(
260+
B020(8, 4, vars=("items",)),
261+
B020(21, 9, vars=("values",)),
262+
),
263+
)
264+
252265
def test_b901(self):
253266
filename = Path(__file__).absolute().parent / "b901.py"
254267
bbc = BugBearChecker(filename=str(filename))

0 commit comments

Comments
 (0)