Skip to content

Commit

Permalink
feat(b038): Change B038 to B909 and make it optional (#456)
Browse files Browse the repository at this point in the history
B038 lead to some false positives that stem from methods defined in the
standard library that have the same name as mutating functions for
container types like lists and dicts.
Thus we decided to make this rule optional.
See #455 for the related
discussion.
  • Loading branch information
mimre25 committed Feb 7, 2024
1 parent 5c3f0bd commit 82357df
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 29 deletions.
32 changes: 21 additions & 11 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ def visit_For(self, node):
self.check_for_b020(node)
self.check_for_b023(node)
self.check_for_b031(node)
self.check_for_b038(node)
self.check_for_b909(node)
self.generic_visit(node)

def visit_AsyncFor(self, node):
Expand Down Expand Up @@ -1574,17 +1574,17 @@ def check(num_args, param_name):
elif node.func.attr == "split":
check(2, "maxsplit")

def check_for_b038(self, node: ast.For):
def check_for_b909(self, node: ast.For):
if isinstance(node.iter, ast.Name):
name = _to_name_str(node.iter)
elif isinstance(node.iter, ast.Attribute):
name = _to_name_str(node.iter)
else:
return
checker = B038Checker(name)
checker = B909Checker(name)
checker.visit(node.body)
for mutation in checker.mutations:
self.errors.append(B038(mutation.lineno, mutation.col_offset))
self.errors.append(B909(mutation.lineno, mutation.col_offset))


def compose_call_path(node):
Expand All @@ -1597,7 +1597,7 @@ def compose_call_path(node):
yield node.id


class B038Checker(ast.NodeVisitor):
class B909Checker(ast.NodeVisitor):
# https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
MUTATING_FUNCTIONS = (
"append",
Expand Down Expand Up @@ -2146,12 +2146,22 @@ def visit_Lambda(self, node):
" statement."
)
)

B950 = Error(message="B950 line too long ({} > {} characters)")

B038 = Error(
B909 = Error(
message=(
"B038 editing a loop's mutable iterable often leads to unexpected results/bugs"
"B909 editing a loop's mutable iterable often leads to unexpected results/bugs"
)
)
disabled_by_default = ["B901", "B902", "B903", "B904", "B905", "B906", "B908", "B950"]
B950 = Error(message="B950 line too long ({} > {} characters)")


disabled_by_default = [
"B901",
"B902",
"B903",
"B904",
"B905",
"B906",
"B908",
"B909",
"B950",
]
File renamed without changes.
36 changes: 18 additions & 18 deletions tests/test_bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
B035,
B036,
B037,
B038,
B901,
B902,
B903,
Expand All @@ -55,6 +54,7 @@
B906,
B907,
B908,
B909,
B950,
BugBearChecker,
BugBearVisitor,
Expand Down Expand Up @@ -969,27 +969,27 @@ def test_selfclean_test_bugbear(self):
self.assertEqual(proc.stdout, b"")
self.assertEqual(proc.stderr, b"")

def test_b038(self):
filename = Path(__file__).absolute().parent / "b038.py"
mock_options = Namespace(select=[], extend_select=["B038"])
def test_b909(self):
filename = Path(__file__).absolute().parent / "b909.py"
mock_options = Namespace(select=[], extend_select=["B909"])
bbc = BugBearChecker(filename=str(filename), options=mock_options)
errors = list(bbc.run())
print(errors)
expected = [
B038(11, 8),
B038(26, 8),
B038(27, 8),
B038(41, 8),
B038(47, 8),
B038(56, 8),
B038(57, 8),
B038(58, 8),
B038(59, 8),
B038(60, 8),
B038(61, 8),
B038(62, 8),
B038(63, 8),
B038(74, 8),
B909(11, 8),
B909(26, 8),
B909(27, 8),
B909(41, 8),
B909(47, 8),
B909(56, 8),
B909(57, 8),
B909(58, 8),
B909(59, 8),
B909(60, 8),
B909(61, 8),
B909(62, 8),
B909(63, 8),
B909(74, 8),
]
self.assertEqual(errors, self.errors(*expected))

Expand Down

0 comments on commit 82357df

Please sign in to comment.