From 33f83590f29a13744f137d0208a26f3370e69b10 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 22 May 2025 16:38:33 +0200
Subject: [PATCH 1/7] revamp testing infra
---
bugbear.py | 862 ++++++++++++++---------------
tests/b001.py | 9 +-
tests/b002.py | 9 +-
tests/b003.py | 2 +-
tests/b004.py | 4 +-
tests/b005.py | 50 +-
tests/b006_b008.py | 36 +-
tests/b007.py | 6 +-
tests/b008_extended.py | 4 +-
tests/b009_b010.py | 14 +-
tests/b011.py | 4 +-
tests/b012.py | 20 +-
tests/b012_py311.py | 6 +-
tests/b013.py | 4 +-
tests/b013_py311.py | 4 +-
tests/b014.py | 14 +-
tests/b014_py311.py | 14 +-
tests/b015.py | 8 +-
tests/b016.py | 8 +-
tests/b017.py | 14 +-
tests/b018_classes.py | 28 +-
tests/b018_functions.py | 28 +-
tests/b018_modules.py | 24 +-
tests/b018_nested.py | 24 +-
tests/b019.py | 18 +-
tests/b020.py | 8 +-
tests/b021.py | 20 +-
tests/b022.py | 2 +-
tests/b023.py | 44 +-
tests/b024.py | 14 +-
tests/b025.py | 6 +-
tests/b025_py311.py | 6 +-
tests/b026.py | 14 +-
tests/b027.py | 10 +-
tests/b028.py | 4 +-
tests/b029.py | 4 +-
tests/b029_py311.py | 4 +-
tests/b030.py | 6 +-
tests/b030_py311.py | 6 +-
tests/b031.py | 6 +-
tests/b032.py | 16 +-
tests/b033.py | 18 +-
tests/b034.py | 18 +-
tests/b035.py | 10 +-
tests/b036.py | 12 +-
tests/b036_py311.py | 12 +-
tests/b037.py | 12 +-
tests/b039.py | 10 +-
tests/b040.py | 12 +-
tests/b041.py | 14 +-
tests/b901.py | 10 +-
tests/b902.py | 20 +-
tests/b902_extended.py | 34 +-
tests/b902_py38.py | 20 +-
tests/b903.py | 4 +-
tests/b904.py | 8 +-
tests/b904_py311.py | 8 +-
tests/b905_py310.py | 16 +-
tests/b906.py | 2 +-
tests/b907.py | 114 ----
tests/b907_py312.py | 116 ++++
tests/b908.py | 16 +-
tests/b909.py | 64 +--
tests/b910.py | 4 +-
tests/b911_py313.py | 12 +-
tests/b950.py | 12 +-
tests/test_bugbear.py | 1144 +++------------------------------------
tox.ini | 2 +-
68 files changed, 1026 insertions(+), 2082 deletions(-)
delete mode 100644 tests/b907.py
create mode 100644 tests/b907_py312.py
diff --git a/bugbear.py b/bugbear.py
index 81d6137..49e658d 100644
--- a/bugbear.py
+++ b/bugbear.py
@@ -12,7 +12,7 @@
from contextlib import suppress
from functools import lru_cache
from keyword import iskeyword
-from typing import Dict, Iterable, Iterator, List, Sequence, Set, Union, cast
+from typing import Dict, Iterable, Iterator, List, Protocol, Sequence, Set, Union, cast
import attr
import pycodestyle # type: ignore[import-untyped]
@@ -121,7 +121,9 @@ def gen_line_based_checks(self):
if skip and not too_many_leading_white_spaces:
continue
- yield B950(lineno, length, vars=(length, self.max_line_length))
+ yield error_codes["B950"](
+ lineno, length, vars=(length, self.max_line_length)
+ )
@classmethod
def adapt_error(cls, e: error) -> tuple[int, int, str, type]:
@@ -274,7 +276,7 @@ def _check_redundant_excepthandlers(
if good != names:
desc = good[0] if len(good) == 1 else "({})".format(", ".join(good))
as_ = " as " + node.name if node.name is not None else ""
- return B014(
+ return error_codes["B014"](
node.lineno,
node.col_offset,
vars=(", ".join(names), as_, desc, in_trystar),
@@ -375,6 +377,11 @@ class B041VariableKeyType:
name: str
+class AstPositionNode(Protocol):
+ lineno: int
+ col_offset: int
+
+
@attr.s
class BugBearVisitor(ast.NodeVisitor):
filename = attr.ib()
@@ -387,7 +394,7 @@ class BugBearVisitor(ast.NodeVisitor):
b040_caught_exception: B040CaughtException | None = attr.ib(default=None)
NODE_WINDOW_SIZE = 4
- _b023_seen: set[error] = attr.ib(factory=set, init=False)
+ _b023_seen: set[ast.Name] = attr.ib(factory=set, init=False)
_b005_imports: set[str] = attr.ib(factory=set, init=False)
# set to "*" when inside a try/except*, for correctly printing errors
@@ -400,6 +407,9 @@ def __getattr__(self, name: str): # type: ignore[unreachable]
print(name)
return self.__getattribute__(name)
+ def add_error(self, code: str, node: AstPositionNode, *vars: object) -> None:
+ self.errors.append(error_codes[code](node.lineno, node.col_offset, vars=vars))
+
@property
def node_stack(self):
if len(self.contexts) == 0:
@@ -419,17 +429,17 @@ def in_class_init(self) -> bool:
def visit_Return(self, node: ast.Return) -> None:
if self.in_class_init():
if node.value is not None:
- self.errors.append(B037(node.lineno, node.col_offset))
+ self.add_error("B037", node)
self.generic_visit(node)
def visit_Yield(self, node: ast.Yield) -> None:
if self.in_class_init():
- self.errors.append(B037(node.lineno, node.col_offset))
+ self.add_error("B037", node)
self.generic_visit(node)
def visit_YieldFrom(self, node: ast.YieldFrom) -> None:
if self.in_class_init():
- self.errors.append(B037(node.lineno, node.col_offset))
+ self.add_error("B037", node)
self.generic_visit(node)
def visit(self, node) -> None:
@@ -452,7 +462,7 @@ def visit(self, node) -> None:
def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None:
if node.type is None:
- self.errors.append(B001(node.lineno, node.col_offset))
+ self.add_error("B001", node)
self.generic_visit(node)
return
@@ -468,7 +478,7 @@ def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None:
"BaseException" in names
and not ExceptBaseExceptionVisitor(node).re_raised()
):
- self.errors.append(B036(node.lineno, node.col_offset))
+ self.add_error("B036", node)
self.generic_visit(node)
@@ -476,14 +486,14 @@ def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None:
self.b040_caught_exception is not None
and self.b040_caught_exception.has_note
):
- self.errors.append(B040(node.lineno, node.col_offset))
+ self.add_error("B040", node)
self.b040_caught_exception = old_b040_caught_exception
def visit_UAdd(self, node: ast.UAdd) -> None:
trailing_nodes = list(map(type, self.node_window[-4:]))
if trailing_nodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
originator = cast(ast.UnaryOp, self.node_window[-4])
- self.errors.append(B002(originator.lineno, originator.col_offset))
+ self.add_error("B002", originator)
self.generic_visit(node)
def visit_Call(self, node) -> None:
@@ -497,14 +507,14 @@ def visit_Call(self, node) -> None:
node.func.id in ("getattr", "hasattr")
and node.args[1].value == "__call__"
):
- self.errors.append(B004(node.lineno, node.col_offset))
+ self.add_error("B004", node)
if (
node.func.id == "getattr"
and len(node.args) == 2
and _is_identifier(node.args[1])
and not iskeyword(node.args[1].value)
):
- self.errors.append(B009(node.lineno, node.col_offset))
+ self.add_error("B009", node)
elif (
not any(isinstance(n, ast.Lambda) for n in self.node_stack)
and node.func.id == "setattr"
@@ -512,7 +522,7 @@ def visit_Call(self, node) -> None:
and _is_identifier(node.args[1])
and not iskeyword(node.args[1].value)
):
- self.errors.append(B010(node.lineno, node.col_offset))
+ self.add_error("B010", node)
self.check_for_b026(node)
self.check_for_b028(node)
@@ -545,7 +555,7 @@ def visit_Assign(self, node: ast.Assign) -> None:
if isinstance(t, ast.Attribute) and isinstance(t.value, ast.Name):
if (t.value.id, t.attr) == ("os", "environ"):
- self.errors.append(B003(node.lineno, node.col_offset))
+ self.add_error("B003", node)
self.generic_visit(node)
def visit_For(self, node: ast.For) -> None:
@@ -682,7 +692,7 @@ def convert_to_value(item):
value = convert_to_value(node.values[index])
if value in seen:
key_node = node.keys[index]
- self.errors.append(B041(key_node.lineno, key_node.col_offset))
+ self.add_error("B041", key_node)
seen.add(value)
def check_for_b005(self, node) -> None:
@@ -716,11 +726,13 @@ def check_for_b005(self, node) -> None:
if len(value) == len(set(value)):
return # no characters appear more than once
- self.errors.append(B005(node.lineno, node.col_offset))
+ self.add_error("B005", node)
def check_for_b006_and_b008(self, node) -> None:
visitor = FunctionDefDefaultsVisitor(
- B006, B008, self.b008_b039_extend_immutable_calls
+ error_codes["B006"],
+ error_codes["B008"],
+ self.b008_b039_extend_immutable_calls,
)
visitor.visit(node.args.defaults + node.args.kw_defaults)
self.errors.extend(visitor.errors)
@@ -744,7 +756,9 @@ def check_for_b039(self, node: ast.Call) -> None:
return
visitor = FunctionDefDefaultsVisitor(
- B039, B039, self.b008_b039_extend_immutable_calls
+ error_codes["B039"],
+ error_codes["B039"],
+ self.b008_b039_extend_immutable_calls,
)
visitor.visit(kw.value)
self.errors.extend(visitor.errors)
@@ -759,11 +773,11 @@ def check_for_b007(self, node) -> None:
used_names = set(body.names)
for name in sorted(ctrl_names - used_names):
n = targets.names[name][0]
- self.errors.append(B007(n.lineno, n.col_offset, vars=(name,)))
+ self.add_error("B007", n, name)
def check_for_b011(self, node) -> None:
if isinstance(node.test, ast.Constant) and node.test.value is False:
- self.errors.append(B011(node.lineno, node.col_offset))
+ self.add_error("B011", node)
def check_for_b012(self, node) -> None:
def _loop(node, bad_node_types) -> None:
@@ -774,9 +788,7 @@ def _loop(node, bad_node_types) -> None:
bad_node_types = (ast.Return,)
elif isinstance(node, bad_node_types):
- self.errors.append(
- B012(node.lineno, node.col_offset, vars=(self.in_trystar,))
- )
+ self.add_error("B012", node, self.in_trystar)
for child in ast.iter_child_nodes(node):
_loop(child, bad_node_types)
@@ -802,10 +814,12 @@ def check_for_b013_b014_b029_b030(self, node: ast.ExceptHandler) -> list[str]:
else:
bad_handlers.append(handler)
if bad_handlers:
- self.errors.append(B030(node.lineno, node.col_offset))
+ self.add_error("B030", node)
if len(names) == 0 and not bad_handlers and not ignored_handlers:
- self.errors.append(
- B029(node.lineno, node.col_offset, vars=(self.in_trystar,))
+ self.add_error(
+ "B029",
+ node,
+ self.in_trystar,
)
elif (
len(names) == 1
@@ -813,15 +827,11 @@ def check_for_b013_b014_b029_b030(self, node: ast.ExceptHandler) -> list[str]:
and not ignored_handlers
and isinstance(node.type, ast.Tuple)
):
- self.errors.append(
- B013(
- node.lineno,
- node.col_offset,
- vars=(
- *names,
- self.in_trystar,
- ),
- )
+ self.add_error(
+ "B013",
+ node,
+ *names,
+ self.in_trystar,
)
else:
maybe_error = _check_redundant_excepthandlers(names, node, self.in_trystar)
@@ -831,7 +841,7 @@ def check_for_b013_b014_b029_b030(self, node: ast.ExceptHandler) -> list[str]:
def check_for_b015(self, node) -> None:
if isinstance(self.node_stack[-2], ast.Expr):
- self.errors.append(B015(node.lineno, node.col_offset))
+ self.add_error("B015", node)
def check_for_b016(self, node) -> None:
if isinstance(node.exc, ast.JoinedStr) or (
@@ -841,7 +851,7 @@ def check_for_b016(self, node) -> None:
or node.exc.value is None
)
):
- self.errors.append(B016(node.lineno, node.col_offset))
+ self.add_error("B016", node)
def check_for_b017(self, node) -> None:
"""Checks for use of the evil syntax 'with assertRaises(Exception):'
@@ -885,7 +895,7 @@ def check_for_b017(self, node) -> None:
and item_context.args[0].id in {"Exception", "BaseException"}
and not item.optional_vars
):
- self.errors.append(B017(node.lineno, node.col_offset))
+ self.add_error("B017", node)
def check_for_b019(self, node) -> None:
if (
@@ -905,12 +915,7 @@ def check_for_b019(self, node) -> None:
return
if decorator in B019_CACHES:
- self.errors.append(
- B019(
- node.decorator_list[idx].lineno,
- node.decorator_list[idx].col_offset,
- )
- )
+ self.add_error("B019", node.decorator_list[idx])
return
def check_for_b020(self, node) -> None:
@@ -925,7 +930,7 @@ def check_for_b020(self, node) -> None:
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,)))
+ self.add_error("B020", n, name)
def check_for_b023( # noqa: C901
self,
@@ -1000,22 +1005,20 @@ def check_for_b023( # noqa: C901
for name in body_nodes:
if isinstance(name, ast.Name) and name.id not in argnames:
if isinstance(name.ctx, ast.Load):
- errors.append(
- B023(name.lineno, name.col_offset, vars=(name.id,))
- )
+ errors.append(name)
elif isinstance(name.ctx, ast.Store):
argnames.add(name.id)
for err in errors:
- if err.vars[0] not in argnames and err not in self._b023_seen:
+ if err.id not in argnames and err not in self._b023_seen:
self._b023_seen.add(err) # dedupe across nested loops
suspicious_variables.append(err)
if suspicious_variables:
reassigned_in_loop = set(self._get_assigned_names(loop_node))
- for err in sorted(suspicious_variables):
- if reassigned_in_loop.issuperset(err.vars):
- self.errors.append(err)
+ for err in sorted(suspicious_variables, key=lambda n: n.id):
+ if err.id in reassigned_in_loop:
+ self.add_error("B023", err, err.id)
def check_for_b024_and_b027(self, node: ast.ClassDef) -> None: # noqa: C901
"""Check for inheritance from abstract classes in abc and lack of
@@ -1092,12 +1095,10 @@ def is_str_or_ellipsis(node):
and empty_body(stmt.body)
and not any(map(is_overload, stmt.decorator_list))
):
- self.errors.append(
- B027(stmt.lineno, stmt.col_offset, vars=(stmt.name,))
- )
+ self.add_error("B027", stmt, stmt.name)
if has_method and not has_abstract_method:
- self.errors.append(B024(node.lineno, node.col_offset, vars=(node.name,)))
+ self.add_error("B024", node, node.name)
def check_for_b026(self, call: ast.Call) -> None:
if not call.keywords:
@@ -1113,7 +1114,7 @@ def check_for_b026(self, call: ast.Call) -> None:
first_keyword.lineno,
first_keyword.col_offset,
):
- self.errors.append(B026(starred.lineno, starred.col_offset))
+ self.add_error("B026", starred)
def check_for_b031(self, loop_node) -> None: # noqa: C901
"""Check that `itertools.groupby` isn't iterated over more than once.
@@ -1149,21 +1150,13 @@ def check_for_b031(self, loop_node) -> None: # noqa: C901
isinstance(nested_node, ast.Name)
and nested_node.id == group_name
):
- self.errors.append(
- B031(
- nested_node.lineno,
- nested_node.col_offset,
- vars=(nested_node.id,),
- )
- )
+ self.add_error("B031", nested_node, nested_node.id)
# Handle multiple uses
if isinstance(node, ast.Name) and node.id == group_name:
num_usages += 1
if num_usages > 1:
- self.errors.append(
- B031(node.lineno, node.col_offset, vars=(node.id,))
- )
+ self.add_error("B031", node, node.id)
def _get_names_from_tuple(self, node: ast.Tuple):
for dim in node.elts:
@@ -1191,16 +1184,12 @@ def check_for_b035(self, node: ast.DictComp) -> None:
or a variable that isn't coming from the generator expression.
"""
if isinstance(node.key, ast.Constant):
- self.errors.append(
- B035(node.key.lineno, node.key.col_offset, vars=(node.key.value,))
- )
+ self.add_error("B035", node.key, node.key.value)
elif isinstance(node.key, ast.Name):
if node.key.id not in self._get_dict_comp_loop_and_named_expr_var_names(
node
):
- self.errors.append(
- B035(node.key.lineno, node.key.col_offset, vars=(node.key.id,))
- )
+ self.add_error("B035", node.key, node.key.id)
def check_for_b040_add_note(self, node: ast.Attribute) -> bool:
if (
@@ -1251,9 +1240,7 @@ def check_for_b904(self, node) -> None:
and not (isinstance(node.exc, ast.Name) and node.exc.id.islower())
and any(isinstance(n, ast.ExceptHandler) for n in self.node_stack)
):
- self.errors.append(
- B904(node.lineno, node.col_offset, vars=(self.in_trystar,))
- )
+ self.add_error("B904", node, self.in_trystar)
def walk_function_body(self, node):
def _loop(parent, node):
@@ -1301,7 +1288,7 @@ def check_for_b901(self, node: ast.FunctionDef) -> None:
return_node = x
if has_yield and return_node is not None:
- self.errors.append(B901(return_node.lineno, return_node.col_offset))
+ self.add_error("B901", return_node)
# taken from pep8-naming
@classmethod
@@ -1361,30 +1348,25 @@ def is_classmethod(decorators: Set[str]) -> bool:
if args:
actual_first_arg = args[0].arg
- lineno = args[0].lineno
- col = args[0].col_offset
+ err_node = args[0]
elif vararg:
actual_first_arg = "*" + vararg.arg
- lineno = vararg.lineno
- col = vararg.col_offset
+ err_node = vararg
elif kwarg:
actual_first_arg = "**" + kwarg.arg
- lineno = kwarg.lineno
- col = kwarg.col_offset
+ err_node = kwarg
elif kwonlyargs:
actual_first_arg = "*, " + kwonlyargs[0].arg
- lineno = kwonlyargs[0].lineno
- col = kwonlyargs[0].col_offset
+ err_node = kwonlyargs[0]
else:
actual_first_arg = "(none)"
- lineno = node.lineno
- col = node.col_offset
+ err_node = node
if actual_first_arg not in expected_first_args:
if not actual_first_arg.startswith(("(", "*")):
actual_first_arg = repr(actual_first_arg)
- self.errors.append(
- B902(lineno, col, vars=(actual_first_arg, kind, expected_first_args[0]))
+ self.add_error(
+ "B902", err_node, actual_first_arg, kind, expected_first_args[0]
)
def check_for_b903(self, node) -> None:
@@ -1416,7 +1398,7 @@ def check_for_b903(self, node) -> None:
if not isinstance(stmt.value, ast.Name):
return
- self.errors.append(B903(node.lineno, node.col_offset))
+ self.add_error("B903", node)
def check_for_b018(self, node) -> None:
if not isinstance(node, ast.Expr):
@@ -1436,13 +1418,7 @@ def check_for_b018(self, node) -> None:
or node.value.value is None
)
):
- self.errors.append(
- B018(
- node.lineno,
- node.col_offset,
- vars=(node.value.__class__.__name__,),
- )
- )
+ self.add_error("B018", node, node.value.__class__.__name__)
def check_for_b021(self, node) -> None:
if (
@@ -1450,9 +1426,7 @@ def check_for_b021(self, node) -> None:
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.JoinedStr)
):
- self.errors.append(
- B021(node.body[0].value.lineno, node.body[0].value.col_offset)
- )
+ self.add_error("B021", node.body[0].value)
def check_for_b022(self, node) -> None:
item = node.items[0]
@@ -1466,7 +1440,7 @@ def check_for_b022(self, node) -> None:
and item_context.func.attr == "suppress"
and len(item_context.args) == 0
):
- self.errors.append(B022(node.lineno, node.col_offset))
+ self.add_error("B022", node)
@staticmethod
def _is_assertRaises_like(node: ast.withitem) -> bool:
@@ -1499,7 +1473,7 @@ def check_for_b908(self, node: ast.With) -> None:
return
for node_item in node.items:
if self._is_assertRaises_like(node_item):
- self.errors.append(B908(node.lineno, node.col_offset))
+ self.add_error("B908", node)
def check_for_b025(self, node) -> None:
seen = []
@@ -1517,9 +1491,7 @@ def check_for_b025(self, node) -> None:
# sort to have a deterministic output
duplicates = sorted({x for x in seen if seen.count(x) > 1})
for duplicate in duplicates:
- self.errors.append(
- B025(node.lineno, node.col_offset, vars=(duplicate, self.in_trystar))
- )
+ self.add_error("B025", node, duplicate, self.in_trystar)
@staticmethod
def _is_infinite_iterator(node: ast.expr) -> bool:
@@ -1561,7 +1533,7 @@ def check_for_b905(self, node) -> None:
if self._is_infinite_iterator(arg):
return
if not any(kw.arg == "strict" for kw in node.keywords):
- self.errors.append(B905(node.lineno, node.col_offset))
+ self.add_error("B905", node)
def check_for_b906(self, node: ast.FunctionDef) -> None:
if not node.name.startswith("visit_"):
@@ -1609,7 +1581,7 @@ def check_for_b906(self, node: ast.FunctionDef) -> None:
):
break
else:
- self.errors.append(B906(node.lineno, node.col_offset))
+ self.add_error("B906", node)
def check_for_b907(self, node: ast.JoinedStr) -> None: # noqa: C901
quote_marks = "'\""
@@ -1626,13 +1598,7 @@ def check_for_b907(self, node: ast.JoinedStr) -> None: # noqa: C901
and variable is not None
and value.value[0] == current_mark
):
- self.errors.append(
- B907(
- variable.lineno,
- variable.col_offset,
- vars=(ast.unparse(variable.value),),
- )
- )
+ self.add_error("B907", variable, ast.unparse(variable.value))
current_mark = variable = None
# don't continue with length>1, so we can detect a new pre-mark
# in the same string as a post-mark, e.g. `"{foo}" "{bar}"`
@@ -1711,7 +1677,7 @@ def check_for_b028(self, node) -> None:
and not any(isinstance(a, ast.Starred) for a in node.args)
and not any(kw.arg is None for kw in node.keywords)
):
- self.errors.append(B028(node.lineno, node.col_offset))
+ self.add_error("B028", node)
def check_for_b032(self, node) -> None:
if (
@@ -1726,7 +1692,7 @@ def check_for_b032(self, node) -> None:
)
)
):
- self.errors.append(B032(node.lineno, node.col_offset))
+ self.add_error("B032", node)
def check_for_b033(self, node) -> None:
seen = set()
@@ -1734,9 +1700,7 @@ def check_for_b033(self, node) -> None:
if not isinstance(elt, ast.Constant):
continue
if elt.value in seen:
- self.errors.append(
- B033(elt.lineno, elt.col_offset, vars=(repr(elt.value),))
- )
+ self.add_error("B033", elt, repr(elt.value))
else:
seen.add(elt.value)
@@ -1750,9 +1714,7 @@ def check_for_b034(self, node: ast.Call) -> None:
def check(num_args: int, param_name: str) -> None:
if len(node.args) > num_args:
arg = node.args[num_args]
- self.errors.append(
- B034(arg.lineno, arg.col_offset, vars=(func.attr, param_name))
- )
+ self.add_error("B034", arg, func.attr, param_name)
if func.attr in ("sub", "subn"):
check(3, "count")
@@ -1773,7 +1735,7 @@ def check_for_b909(self, node: ast.For) -> None:
for mutation in itertools.chain.from_iterable(
m for m in checker.mutations.values()
):
- self.errors.append(B909(mutation.lineno, mutation.col_offset))
+ self.add_error("B909", mutation)
def check_for_b910(self, node: ast.Call) -> None:
if (
@@ -1783,7 +1745,7 @@ def check_for_b910(self, node: ast.Call) -> None:
and isinstance(node.args[0], ast.Name)
and node.args[0].id == "int"
):
- self.errors.append(B910(node.lineno, node.col_offset))
+ self.add_error("B910", node)
def check_for_b911(self, node: ast.Call) -> None:
if (
@@ -1795,7 +1757,7 @@ def check_for_b911(self, node: ast.Call) -> None:
and node.func.value.id == "itertools"
)
) and not any(kw.arg == "strict" for kw in node.keywords):
- self.errors.append(B911(node.lineno, node.col_offset))
+ self.add_error("B911", node)
def compose_call_path(node):
@@ -2057,72 +2019,8 @@ def visit_Lambda(self, node) -> None:
self.names.pop(lambda_arg.arg, None)
-error = namedtuple("error", "lineno col message type vars")
-
-
-class Error:
- def __init__(self, message: str):
- self.message = message
-
- def __call__(self, lineno: int, col: int, vars: tuple[str, ...] = ()) -> error:
- return error(lineno, col, self.message, BugBearChecker, vars=vars)
-
-
-# note: bare except* is a syntax error, so B001 does not need to handle it
-B001 = Error(
- message=(
- "B001 Do not use bare `except:`, it also catches unexpected "
- "events like memory errors, interrupts, system exit, and so on. "
- "Prefer excepting specific exceptions If you're sure what you're "
- "doing, be explicit and write `except BaseException:`."
- )
-)
-
-B002 = Error(
- message=(
- "B002 Python does not support the unary prefix increment. Writing "
- "++n is equivalent to +(+(n)), which equals n. You meant n += 1."
- )
-)
-
-B003 = Error(
- message=(
- "B003 Assigning to `os.environ` doesn't clear the environment. "
- "Subprocesses are going to see outdated variables, in disagreement "
- "with the current process. Use `os.environ.clear()` or the `env=` "
- "argument to Popen."
- )
-)
-
-B004 = Error(
- message=(
- "B004 Using `hasattr(x, '__call__')` to test if `x` is callable "
- "is unreliable. If `x` implements custom `__getattr__` or its "
- "`__call__` is itself not callable, you might get misleading "
- "results. Use `callable(x)` for consistent results."
- )
-)
-
-B005 = Error(
- message=(
- "B005 Using .strip() with multi-character strings is misleading "
- "the reader. It looks like stripping a substring. Move your "
- "character set to a constant if this is deliberate. Use "
- ".replace(), .removeprefix(), .removesuffix(), or regular "
- "expressions to remove string fragments."
- )
-)
B005_METHODS = {"lstrip", "rstrip", "strip"}
-B006 = Error(
- message=(
- "B006 Do not use mutable data structures for argument defaults. They "
- "are created during function definition time. All calls to the function "
- "reuse this one instance of that data structure, persisting changes "
- "between them."
- )
-)
-
# Note: these are also used by B039
B006_MUTABLE_LITERALS = ("Dict", "List", "Set")
B006_MUTABLE_COMPREHENSIONS = ("ListComp", "DictComp", "SetComp")
@@ -2139,22 +2037,6 @@ def __call__(self, lineno: int, col: int, vars: tuple[str, ...] = ()) -> error:
"list",
"set",
}
-B007 = Error(
- message=(
- "B007 Loop control variable {!r} not used within the loop body. "
- "If this is intended, start the name with an underscore."
- )
-)
-B008 = Error(
- message=(
- "B008 Do not perform function calls in argument defaults. The call is "
- "performed only once at function definition time. All calls to your "
- "function will reuse the result of that definition-time function call. If "
- "this is intended, assign the function call to a module-level variable and "
- "use that variable as a default value."
- )
-)
-
# Note: these are also used by B039
B008_IMMUTABLE_CALLS = {
"tuple",
@@ -2169,43 +2051,6 @@ def __call__(self, lineno: int, col: int, vars: tuple[str, ...] = ()) -> error:
"itemgetter",
"methodcaller",
}
-B009 = Error(
- message=(
- "B009 Do not call getattr with a constant attribute value, "
- "it is not any safer than normal property access."
- )
-)
-B010 = Error(
- message=(
- "B010 Do not call setattr with a constant attribute value, "
- "it is not any safer than normal property access."
- )
-)
-B011 = Error(
- message=(
- "B011 Do not call assert False since python -O removes these calls. "
- "Instead callers should raise AssertionError()."
- )
-)
-B012 = Error(
- message=(
- "B012 return/continue/break inside finally blocks cause exceptions "
- "to be silenced. Exceptions should be silenced in except{0} blocks. Control "
- "statements can be moved outside the finally block."
- )
-)
-B013 = Error(
- message=(
- "B013 A length-one tuple literal is redundant. "
- "Write `except{1} {0}:` instead of `except{1} ({0},):`."
- )
-)
-B014 = Error(
- message=(
- "B014 Redundant exception types in `except{3} ({0}){1}:`. "
- "Write `except{3} {2}{1}:`, which catches exactly the same exceptions."
- )
-)
B014_REDUNDANT_EXCEPTIONS = {
"OSError": {
# All of these are actually aliases of OSError since Python 3.3
@@ -2220,237 +2065,336 @@ def __call__(self, lineno: int, col: int, vars: tuple[str, ...] = ()) -> error:
"binascii.Error",
},
}
-B015 = Error(
- message=(
- "B015 Result of comparison is not used. This line doesn't do "
- "anything. Did you intend to prepend it with assert?"
- )
-)
-B016 = Error(
- message=(
- "B016 Cannot raise a literal. Did you intend to return it or raise "
- "an Exception?"
- )
-)
-B017 = Error(
- message=(
- "B017 `assertRaises(Exception)` and `pytest.raises(Exception)` should "
- "be considered evil. They can lead to your test passing even if the "
- "code being tested is never executed due to a typo. Assert for a more "
- "specific exception (builtin or custom), or use `assertRaisesRegex` "
- "(if using `assertRaises`), or add the `match` keyword argument (if "
- "using `pytest.raises`), or use the context manager form with a target."
- )
-)
-B018 = Error(
- message=(
- "B018 Found useless {} expression. Consider either assigning it to a "
- "variable or removing it."
- )
-)
-B019 = Error(
- message=(
- "B019 Use of `functools.lru_cache` or `functools.cache` on methods "
- "can lead to memory leaks. The cache may retain instance references, "
- "preventing garbage collection."
- )
-)
B019_CACHES = {
"functools.cache",
"functools.lru_cache",
"cache",
"lru_cache",
}
-B020 = Error(
- message=(
- "B020 Found for loop that reassigns the iterable it is iterating "
- + "with each iterable value."
- )
-)
-B021 = Error(
- message=(
- "B021 f-string used as docstring. "
- "This will be interpreted by python as a joined string rather than a docstring."
- )
-)
-B022 = Error(
- message=(
- "B022 No arguments passed to `contextlib.suppress`. "
- "No exceptions will be suppressed and therefore this "
- "context manager is redundant."
- )
-)
-
-B023 = Error(message="B023 Function definition does not bind loop variable {!r}.")
-B024 = Error(
- message=(
- "B024 {} is an abstract base class, but none of the methods it defines are"
- " abstract. This is not necessarily an error, but you might have forgotten to"
- " add the @abstractmethod decorator, potentially in conjunction with"
- " @classmethod, @property and/or @staticmethod."
- )
-)
-B025 = Error(
- message=(
- "B025 Exception `{0}` has been caught multiple times. Only the first except{0}"
- " will be considered and all other except{0} catches can be safely removed."
- )
-)
-B026 = Error(
- message=(
- "B026 Star-arg unpacking after a keyword argument is strongly discouraged, "
- "because it only works when the keyword parameter is declared after all "
- "parameters supplied by the unpacked sequence, and this change of ordering can "
- "surprise and mislead readers."
- )
-)
-B027 = Error(
- message=(
- "B027 {} is an empty method in an abstract base class, but has no abstract"
- " decorator. Consider adding @abstractmethod."
- )
-)
-B028 = Error(
- message=(
- "B028 No explicit stacklevel argument found. The warn method from the"
- " warnings module uses a stacklevel of 1 by default. This will only show a"
- " stack trace for the line on which the warn method is called."
- " It is therefore recommended to use a stacklevel of 2 or"
- " greater to provide more information to the user."
- )
-)
-B029 = Error(
- message=(
- "B029 Using `except{0} ():` with an empty tuple does not handle/catch "
- "anything. Add exceptions to handle."
- )
-)
-
-B030 = Error(message="B030 Except handlers should only be names of exception classes")
-
-B031 = Error(
- message=(
- "B031 Using the generator returned from `itertools.groupby()` more than once"
- " will do nothing on the second usage. Save the result to a list, if the"
- " result is needed multiple times."
- )
-)
-
-B032 = Error(
- message=(
- "B032 Possible unintentional type annotation (using `:`). Did you mean to"
- " assign (using `=`)?"
- )
-)
-
-B033 = Error(
- message=(
- "B033 Set should not contain duplicate item {}. Duplicate items will be"
- " replaced with a single item at runtime."
- )
-)
-
-B034 = Error(
- message=(
- "B034 {} should pass `{}` and `flags` as keyword arguments to avoid confusion"
- " due to unintuitive argument positions."
- )
-)
-B035 = Error(message="B035 Static key in dict comprehension {!r}.")
-
-B036 = Error(
- message="B036 Don't except `BaseException` unless you plan to re-raise it."
-)
-
-B037 = Error(
- message="B037 Class `__init__` methods must not return or yield any values."
-)
-
-B039 = Error(
- message=(
- "B039 ContextVar with mutable literal or function call as default. "
- "This is only evaluated once, and all subsequent calls to `.get()` "
- "will return the same instance of the default."
- )
-)
-
-B040 = Error(
- message="B040 Exception with added note not used. Did you forget to raise it?"
-)
-
-B041 = Error(message=("B041 Repeated key-value pair in dictionary literal."))
-
-# Warnings disabled by default.
-B901 = Error(
- message=(
- "B901 Using `yield` together with `return x`. Use native "
- "`async def` coroutines or put a `# noqa` comment on this "
- "line if this was intentional."
- )
-)
-B902 = Error(
- message=(
- "B902 Invalid first argument {} used for {} method. Use the "
- "canonical first argument name in methods, i.e. {}."
- )
-)
B902_IMPLICIT_CLASSMETHODS = {"__new__", "__init_subclass__", "__class_getitem__"}
B902_SELF = ["self"] # it's a list because the first is preferred
B902_CLS = ["cls", "klass"] # ditto.
B902_METACLS = ["metacls", "metaclass", "typ", "mcs"] # ditto.
-B903 = Error(
- message=(
- "B903 Data class should either be immutable or use __slots__ to "
- "save memory. Use collections.namedtuple to generate an immutable "
- "class, or enumerate the attributes in a __slot__ declaration in "
- "the class to leave attributes mutable."
- )
-)
+error = namedtuple("error", "lineno col message type vars")
-B904 = Error(
- message=(
- "B904 Within an `except{0}` clause, raise exceptions with `raise ... from err` or"
- " `raise ... from None` to distinguish them from errors in exception handling. "
- " See https://docs.python.org/3/tutorial/errors.html#exception-chaining for"
- " details."
- )
-)
-B905 = Error(message="B905 `zip()` without an explicit `strict=` parameter.")
+class Error:
+ def __init__(self, message: str):
+ self.message = message
-B906 = Error(
- message=(
- "B906 `visit_` function with no further calls to a visit function, which might"
- " prevent the `ast` visitor from properly visiting all nodes."
- " Consider adding a call to `self.generic_visit(node)`."
- )
-)
+ def __call__(self, lineno: int, col: int, vars: tuple[object, ...] = ()) -> error:
+ return error(lineno, col, self.message, BugBearChecker, vars=vars)
-B907 = Error(
- message=(
- "B907 {!r} is manually surrounded by quotes, consider using the `!r` conversion"
- " flag."
- )
-)
-B908 = Error(
- message=(
- "B908 assertRaises-type context should not contain more than one top-level"
- " statement."
- )
-)
-B909 = Error(
- message=(
- "B909 editing a loop's mutable iterable often leads to unexpected results/bugs"
- )
-)
-B910 = Error(
- message="B910 Use Counter() instead of defaultdict(int) to avoid excessive memory use"
-)
-B911 = Error(
- message="B911 `itertools.batched()` without an explicit `strict=` parameter."
-)
-B950 = Error(message="B950 line too long ({} > {} characters)")
+
+error_codes = {
+ # note: bare except* is a syntax error, so B001 does not need to handle it
+ "B001": Error(
+ message=(
+ "B001 Do not use bare `except:`, it also catches unexpected "
+ "events like memory errors, interrupts, system exit, and so on. "
+ "Prefer excepting specific exceptions If you're sure what you're "
+ "doing, be explicit and write `except BaseException:`."
+ )
+ ),
+ "B002": Error(
+ message=(
+ "B002 Python does not support the unary prefix increment. Writing "
+ "++n is equivalent to +(+(n)), which equals n. You meant n += 1."
+ )
+ ),
+ "B003": Error(
+ message=(
+ "B003 Assigning to `os.environ` doesn't clear the environment. "
+ "Subprocesses are going to see outdated variables, in disagreement "
+ "with the current process. Use `os.environ.clear()` or the `env=` "
+ "argument to Popen."
+ )
+ ),
+ "B004": Error(
+ message=(
+ "B004 Using `hasattr(x, '__call__')` to test if `x` is callable "
+ "is unreliable. If `x` implements custom `__getattr__` or its "
+ "`__call__` is itself not callable, you might get misleading "
+ "results. Use `callable(x)` for consistent results."
+ )
+ ),
+ "B005": Error(
+ message=(
+ "B005 Using .strip() with multi-character strings is misleading "
+ "the reader. It looks like stripping a substring. Move your "
+ "character set to a constant if this is deliberate. Use "
+ ".replace(), .removeprefix(), .removesuffix(), or regular "
+ "expressions to remove string fragments."
+ )
+ ),
+ "B006": Error(
+ message=(
+ "B006 Do not use mutable data structures for argument defaults. They "
+ "are created during function definition time. All calls to the function "
+ "reuse this one instance of that data structure, persisting changes "
+ "between them."
+ )
+ ),
+ "B007": Error(
+ message=(
+ "B007 Loop control variable {!r} not used within the loop body. "
+ "If this is intended, start the name with an underscore."
+ )
+ ),
+ "B008": Error(
+ message=(
+ "B008 Do not perform function calls in argument defaults. The call is "
+ "performed only once at function definition time. All calls to your "
+ "function will reuse the result of that definition-time function call. If "
+ "this is intended, assign the function call to a module-level variable and "
+ "use that variable as a default value."
+ )
+ ),
+ "B009": Error(
+ message=(
+ "B009 Do not call getattr with a constant attribute value, "
+ "it is not any safer than normal property access."
+ )
+ ),
+ "B010": Error(
+ message=(
+ "B010 Do not call setattr with a constant attribute value, "
+ "it is not any safer than normal property access."
+ )
+ ),
+ "B011": Error(
+ message=(
+ "B011 Do not call assert False since python -O removes these calls. "
+ "Instead callers should raise AssertionError()."
+ )
+ ),
+ "B012": Error(
+ message=(
+ "B012 return/continue/break inside finally blocks cause exceptions "
+ "to be silenced. Exceptions should be silenced in except{0} blocks. Control "
+ "statements can be moved outside the finally block."
+ )
+ ),
+ "B013": Error(
+ message=(
+ "B013 A length-one tuple literal is redundant. "
+ "Write `except{1} {0}:` instead of `except{1} ({0},):`."
+ )
+ ),
+ "B014": Error(
+ message=(
+ "B014 Redundant exception types in `except{3} ({0}){1}:`. "
+ "Write `except{3} {2}{1}:`, which catches exactly the same exceptions."
+ )
+ ),
+ "B015": Error(
+ message=(
+ "B015 Result of comparison is not used. This line doesn't do "
+ "anything. Did you intend to prepend it with assert?"
+ )
+ ),
+ "B016": Error(
+ message=(
+ "B016 Cannot raise a literal. Did you intend to return it or raise "
+ "an Exception?"
+ )
+ ),
+ "B017": Error(
+ message=(
+ "B017 `assertRaises(Exception)` and `pytest.raises(Exception)` should "
+ "be considered evil. They can lead to your test passing even if the "
+ "code being tested is never executed due to a typo. Assert for a more "
+ "specific exception (builtin or custom), or use `assertRaisesRegex` "
+ "(if using `assertRaises`), or add the `match` keyword argument (if "
+ "using `pytest.raises`), or use the context manager form with a target."
+ )
+ ),
+ "B018": Error(
+ message=(
+ "B018 Found useless {} expression. Consider either assigning it to a "
+ "variable or removing it."
+ )
+ ),
+ "B019": Error(
+ message=(
+ "B019 Use of `functools.lru_cache` or `functools.cache` on methods "
+ "can lead to memory leaks. The cache may retain instance references, "
+ "preventing garbage collection."
+ )
+ ),
+ "B020": Error(
+ message=(
+ "B020 Found for loop that reassigns the iterable it is iterating "
+ + "with each iterable value."
+ )
+ ),
+ "B021": Error(
+ message=(
+ "B021 f-string used as docstring. "
+ "This will be interpreted by python as a joined string rather than a docstring."
+ )
+ ),
+ "B022": Error(
+ message=(
+ "B022 No arguments passed to `contextlib.suppress`. "
+ "No exceptions will be suppressed and therefore this "
+ "context manager is redundant."
+ )
+ ),
+ "B023": Error(message="B023 Function definition does not bind loop variable {!r}."),
+ "B024": Error(
+ message=(
+ "B024 {} is an abstract base class, but none of the methods it defines are"
+ " abstract. This is not necessarily an error, but you might have forgotten to"
+ " add the @abstractmethod decorator, potentially in conjunction with"
+ " @classmethod, @property and/or @staticmethod."
+ )
+ ),
+ "B025": Error(
+ message=(
+ "B025 Exception `{0}` has been caught multiple times. Only the first except{0}"
+ " will be considered and all other except{0} catches can be safely removed."
+ )
+ ),
+ "B026": Error(
+ message=(
+ "B026 Star-arg unpacking after a keyword argument is strongly discouraged, "
+ "because it only works when the keyword parameter is declared after all "
+ "parameters supplied by the unpacked sequence, and this change of ordering can "
+ "surprise and mislead readers."
+ )
+ ),
+ "B027": Error(
+ message=(
+ "B027 {} is an empty method in an abstract base class, but has no abstract"
+ " decorator. Consider adding @abstractmethod."
+ )
+ ),
+ "B028": Error(
+ message=(
+ "B028 No explicit stacklevel argument found. The warn method from the"
+ " warnings module uses a stacklevel of 1 by default. This will only show a"
+ " stack trace for the line on which the warn method is called."
+ " It is therefore recommended to use a stacklevel of 2 or"
+ " greater to provide more information to the user."
+ )
+ ),
+ "B029": Error(
+ message=(
+ "B029 Using `except{0} ():` with an empty tuple does not handle/catch "
+ "anything. Add exceptions to handle."
+ )
+ ),
+ "B030": Error(
+ message="B030 Except handlers should only be names of exception classes"
+ ),
+ "B031": Error(
+ message=(
+ "B031 Using the generator returned from `itertools.groupby()` more than once"
+ " will do nothing on the second usage. Save the result to a list, if the"
+ " result is needed multiple times."
+ )
+ ),
+ "B032": Error(
+ message=(
+ "B032 Possible unintentional type annotation (using `:`). Did you mean to"
+ " assign (using `=`)?"
+ )
+ ),
+ "B033": Error(
+ message=(
+ "B033 Set should not contain duplicate item {}. Duplicate items will be"
+ " replaced with a single item at runtime."
+ )
+ ),
+ "B034": Error(
+ message=(
+ "B034 {} should pass `{}` and `flags` as keyword arguments to avoid confusion"
+ " due to unintuitive argument positions."
+ )
+ ),
+ "B035": Error(message="B035 Static key in dict comprehension {!r}."),
+ "B036": Error(
+ message="B036 Don't except `BaseException` unless you plan to re-raise it."
+ ),
+ "B037": Error(
+ message="B037 Class `__init__` methods must not return or yield any values."
+ ),
+ "B039": Error(
+ message=(
+ "B039 ContextVar with mutable literal or function call as default. "
+ "This is only evaluated once, and all subsequent calls to `.get()` "
+ "will return the same instance of the default."
+ )
+ ),
+ "B040": Error(
+ message="B040 Exception with added note not used. Did you forget to raise it?"
+ ),
+ "B041": Error(message=("B041 Repeated key-value pair in dictionary literal.")),
+ # Warnings disabled by default.
+ "B901": Error(
+ message=(
+ "B901 Using `yield` together with `return x`. Use native "
+ "`async def` coroutines or put a `# noqa` comment on this "
+ "line if this was intentional."
+ )
+ ),
+ "B902": Error(
+ message=(
+ "B902 Invalid first argument {} used for {} method. Use the "
+ "canonical first argument name in methods, i.e. {}."
+ )
+ ),
+ "B903": Error(
+ message=(
+ "B903 Data class should either be immutable or use __slots__ to "
+ "save memory. Use collections.namedtuple to generate an immutable "
+ "class, or enumerate the attributes in a __slot__ declaration in "
+ "the class to leave attributes mutable."
+ )
+ ),
+ "B904": Error(
+ message=(
+ "B904 Within an `except{0}` clause, raise exceptions with `raise ... from err` or"
+ " `raise ... from None` to distinguish them from errors in exception handling. "
+ " See https://docs.python.org/3/tutorial/errors.html#exception-chaining for"
+ " details."
+ )
+ ),
+ "B905": Error(message="B905 `zip()` without an explicit `strict=` parameter."),
+ "B906": Error(
+ message=(
+ "B906 `visit_` function with no further calls to a visit function, which might"
+ " prevent the `ast` visitor from properly visiting all nodes."
+ " Consider adding a call to `self.generic_visit(node)`."
+ )
+ ),
+ "B907": Error(
+ message=(
+ "B907 {!r} is manually surrounded by quotes, consider using the `!r` conversion"
+ " flag."
+ )
+ ),
+ "B908": Error(
+ message=(
+ "B908 assertRaises-type context should not contain more than one top-level"
+ " statement."
+ )
+ ),
+ "B909": Error(
+ message=(
+ "B909 editing a loop's mutable iterable often leads to unexpected results/bugs"
+ )
+ ),
+ "B910": Error(
+ message="B910 Use Counter() instead of defaultdict(int) to avoid excessive memory use"
+ ),
+ "B911": Error(
+ message="B911 `itertools.batched()` without an explicit `strict=` parameter."
+ ),
+ "B950": Error(message="B950 line too long ({} > {} characters)"),
+}
disabled_by_default = [
diff --git a/tests/b001.py b/tests/b001.py
index dbd4407..1ac8525 100644
--- a/tests/b001.py
+++ b/tests/b001.py
@@ -1,11 +1,6 @@
-"""
-Should emit:
-B001 - on lines 8 and 40
-"""
-
try:
import something
-except:
+except: # B001: 0
# should be except ImportError:
import something_else as something
@@ -37,6 +32,6 @@
def func(**kwargs):
try:
is_debug = kwargs["debug"]
- except:
+ except: # B001: 4
# should be except KeyError:
return
diff --git a/tests/b002.py b/tests/b002.py
index 718946f..dd7d56f 100644
--- a/tests/b002.py
+++ b/tests/b002.py
@@ -1,8 +1,3 @@
-"""
-Should emit:
-B002 - on lines 15 and 20
-"""
-
def this_is_all_fine(n):
x = n + 1
y = 1 + n
@@ -11,9 +6,9 @@ def this_is_all_fine(n):
def this_is_buggy(n):
- x = ++n
+ x = ++n # B002: 8
return x
def this_is_buggy_too(n):
- return ++n
+ return ++n # B002: 11
diff --git a/tests/b003.py b/tests/b003.py
index 65bd137..b9f1b60 100644
--- a/tests/b003.py
+++ b/tests/b003.py
@@ -6,7 +6,7 @@
import os
from os import environ
-os.environ = {}
+os.environ = {} # B003: 0
environ = {} # that's fine, assigning a new meaning to the module-level name
diff --git a/tests/b004.py b/tests/b004.py
index 6a6e8c8..77fdc71 100644
--- a/tests/b004.py
+++ b/tests/b004.py
@@ -1,8 +1,8 @@
def this_is_a_bug():
o = object()
- if hasattr(o, "__call__"):
+ if hasattr(o, "__call__"): # B004: 7
print("Ooh, callable! Or is it?")
- if getattr(o, "__call__", False):
+ if getattr(o, "__call__", False): # B004: 7
print("Ooh, callable! Or is it?")
diff --git a/tests/b005.py b/tests/b005.py
index 1fb63f5..798a088 100644
--- a/tests/b005.py
+++ b/tests/b005.py
@@ -1,33 +1,33 @@
s = "qwe"
-s.strip(s) # no warning
-s.strip("we") # no warning
-s.strip(".facebook.com") # warning
-s.strip("e") # no warning
-s.strip("\n\t ") # no warning
-s.strip(r"\n\t ") # warning
-s.lstrip(s) # no warning
-s.lstrip("we") # no warning
-s.lstrip(".facebook.com") # warning
-s.lstrip("e") # no warning
-s.lstrip("\n\t ") # no warning
-s.lstrip(r"\n\t ") # warning
-s.rstrip(s) # no warning
-s.rstrip("we") # warning
-s.rstrip(".facebook.com") # warning
-s.rstrip("e") # no warning
-s.rstrip("\n\t ") # no warning
-s.rstrip(r"\n\t ") # warning
+s.strip(s)
+s.strip("we")
+s.strip(".facebook.com") # B005: 0
+s.strip("e")
+s.strip("\n\t ")
+s.strip(r"\n\t ") # B005: 0
+s.lstrip(s)
+s.lstrip("we")
+s.lstrip(".facebook.com") # B005: 0
+s.lstrip("e")
+s.lstrip("\n\t ")
+s.lstrip(r"\n\t ") # B005: 0
+s.rstrip(s)
+s.rstrip("we")
+s.rstrip(".facebook.com") # B005: 0
+s.rstrip("e")
+s.rstrip("\n\t ")
+s.rstrip(r"\n\t ") # B005: 0
from somewhere import other_type, strip
-strip("we") # no warning
-other_type().lstrip() # no warning
-other_type().rstrip(["a", "b", "c"]) # no warning
-other_type().strip("a", "b") # no warning
+strip("we")
+other_type().lstrip()
+other_type().rstrip(["a", "b", "c"])
+other_type().strip("a", "b")
import test, test2 # isort: skip
import test_as as test3
-test.strip("test") # no warning
-test2.strip("test") # no warning
-test3.strip("test") # no warning
+test.strip("test")
+test2.strip("test")
+test3.strip("test")
diff --git a/tests/b006_b008.py b/tests/b006_b008.py
index 4a4c302..71c673a 100644
--- a/tests/b006_b008.py
+++ b/tests/b006_b008.py
@@ -55,38 +55,38 @@ def kwonlyargs_immutable(*, value=()): ...
# Flag mutable literals/comprehensions
-def this_is_wrong(value=[1, 2, 3]): ...
+def this_is_wrong(value=[1, 2, 3]): ... # B006: 24
-def this_is_also_wrong(value={}): ...
+def this_is_also_wrong(value={}): ... # B006: 29
-def and_this(value=set()): ...
+def and_this(value=set()): ... # B006: 19
-def this_too(value=collections.OrderedDict()): ...
+def this_too(value=collections.OrderedDict()): ... # B006: 19
-async def async_this_too(value=collections.defaultdict()): ...
+async def async_this_too(value=collections.defaultdict()): ... # B006: 31
-def dont_forget_me(value=collections.deque()): ...
+def dont_forget_me(value=collections.deque()): ... # B006: 25
# N.B. we're also flagging the function call in the comprehension
-def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]):
+def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): # B006: 45 # B008: 60
pass
-def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}):
+def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): # B006: 45 # B008: 63
pass
-def set_comprehension_also_not_okay(default={i**2 for i in range(3)}):
+def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): # B006: 44 # B008: 59
pass
-def kwonlyargs_mutable(*, value=[]): ...
+def kwonlyargs_mutable(*, value=[]): ... # B006: 32
# Recommended approach for mutable defaults
@@ -97,14 +97,14 @@ def do_this_instead(value=None):
# B008
# Flag function calls as default args (including if they are part of a sub-expression)
-def in_fact_all_calls_are_wrong(value=time.time()): ...
+def in_fact_all_calls_are_wrong(value=time.time()): ... # B008: 38
-def f(when=dt.datetime.now() + dt.timedelta(days=7)):
+def f(when=dt.datetime.now() + dt.timedelta(days=7)): # B008: 11 # B008: 31
pass
-def can_even_catch_lambdas(a=(lambda x: x)()): ...
+def can_even_catch_lambdas(a=(lambda x: x)()): ... # B008: 29
# Recommended approach for function calls as default args
@@ -146,28 +146,28 @@ def float_infinity_literal(value=float("1e999")):
# But don't allow standard floats
-def float_int_is_wrong(value=float(3)):
+def float_int_is_wrong(value=float(3)): # B008: 29
pass
-def float_str_not_inf_or_nan_is_wrong(value=float("3.14")):
+def float_str_not_inf_or_nan_is_wrong(value=float("3.14")): # B008: 44
pass
# B006 and B008
# We should handle arbitrary nesting of these B008.
-def nested_combo(a=[float(3), dt.datetime.now()]):
+def nested_combo(a=[float(3), dt.datetime.now()]): # B006: 19 # B008: 20 # B008: 30
pass
# Don't flag nested B006 since we can't guarantee that
# it isn't made mutable by the outer operation.
-def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
+def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])): # B008: 21
pass
# B008-ception.
-def nested_b008(a=random.randint(0, dt.datetime.now().year)):
+def nested_b008(a=random.randint(0, dt.datetime.now().year)): # B008: 18 # B008: 36
pass
diff --git a/tests/b007.py b/tests/b007.py
index be623af..7c08b18 100644
--- a/tests/b007.py
+++ b/tests/b007.py
@@ -3,7 +3,7 @@
print(i) # name no longer defined on Python 3; no warning yet
-for i in range(10): # name not used within the loop; B007
+for i in range(10): # name not used within the loop; B007 # B007: 4, "i"
print(10)
print(i) # name no longer defined on Python 3; no warning yet
@@ -15,7 +15,7 @@
for i in range(10):
for j in range(10):
- for k in range(10): # k not used, i and j used transitively
+ for k in range(10): # k not used, i and j used transitively # B007: 12, "k"
print(i + j)
@@ -27,5 +27,5 @@ def strange_generator():
yield i, (j, (k, l))
-for i, (j, (k, l)) in strange_generator(): # i, k not used
+for i, (j, (k, l)) in strange_generator(): # i, k not used # B007: 4, "i" # B007: 12, "k"
print(j, l)
diff --git a/tests/b008_extended.py b/tests/b008_extended.py
index 484f68d..4f2c179 100644
--- a/tests/b008_extended.py
+++ b/tests/b008_extended.py
@@ -1,3 +1,4 @@
+# OPTIONS: extend_immutable_calls=["fastapi.Depends", "fastapi.Query"]
from typing import List
import fastapi
@@ -10,4 +11,5 @@ def this_is_okay_extended(db=fastapi.Depends(get_db)): ...
def this_is_okay_extended_second(data: List[str] = fastapi.Query(None)): ...
-def this_is_not_okay_relative_import_not_listed(data: List[str] = Query(None)): ...
+# not okay, relative import not listed
+def not_okay(data: List[str] = Query(None)): ... # B008: 31
diff --git a/tests/b009_b010.py b/tests/b009_b010.py
index e002b0f..1f18e41 100644
--- a/tests/b009_b010.py
+++ b/tests/b009_b010.py
@@ -14,9 +14,9 @@
getattr(foo, "except")
# Invalid usage
-getattr(foo, "bar")
-getattr(foo, "_123abc")
-getattr(foo, "abc123")
+getattr(foo, "bar") # B009: 0
+getattr(foo, "_123abc") # B009: 0
+getattr(foo, "abc123") # B009: 0
# Valid setattr usage
setattr(foo, bar, None)
@@ -25,9 +25,9 @@
setattr(foo, "except", None)
# Invalid usage
-setattr(foo, "bar", None)
-setattr(foo, "_123abc", None)
-setattr(foo, "abc123", None)
+setattr(foo, "bar", None) # B010: 0
+setattr(foo, "_123abc", None) # B010: 0
+setattr(foo, "abc123", None) # B010: 0
# Allow use of setattr within lambda expression
# since assignment is not valid in this context.
@@ -42,6 +42,6 @@ def __init__(self, has_setter):
# getattr is still flagged within lambda though
-c = lambda x: getattr(x, "some_attr")
+c = lambda x: getattr(x, "some_attr") # B009: 14
# should be replaced with
c = lambda x: x.some_attr
diff --git a/tests/b011.py b/tests/b011.py
index 3fa20eb..4d2703d 100644
--- a/tests/b011.py
+++ b/tests/b011.py
@@ -5,6 +5,6 @@
"""
assert 1 != 2
-assert False
+assert False # B011: 0, "i"
assert 1 != 2, "message"
-assert False, "message"
+assert False, "message" # B011: 0, "k"
diff --git a/tests/b012.py b/tests/b012.py
index c03ff4e..a1971af 100644
--- a/tests/b012.py
+++ b/tests/b012.py
@@ -2,7 +2,7 @@ def a():
try:
pass
finally:
- return # warning
+ return # warning # B012: 8, ""
def b():
@@ -10,7 +10,7 @@ def b():
pass
finally:
if 1 + 0 == 2 - 1:
- return # warning
+ return # warning # B012: 12, ""
def c():
@@ -18,7 +18,7 @@ def c():
pass
finally:
try:
- return # warning
+ return # warning # B012: 12, ""
except Exception:
pass
@@ -28,7 +28,7 @@ def d():
try:
pass
finally:
- return # warning
+ return # warning # B012: 12, ""
finally:
pass
@@ -41,7 +41,7 @@ def f():
try:
pass
finally:
- return # warning
+ return # warning # B012: 20, ""
finally:
pass
@@ -63,7 +63,7 @@ def i():
try:
pass
finally:
- break # warning
+ break # warning # B012: 12, ""
def j():
while True:
@@ -75,7 +75,7 @@ def h():
try:
pass
finally:
- continue # warning
+ continue # warning # B012: 12, ""
def j():
while True:
@@ -91,17 +91,17 @@ def k():
while True:
continue # no warning
while True:
- return # warning
+ return # warning # B012: 12, ""
while True:
try:
pass
finally:
- continue # warning
+ continue # warning # B012: 8, ""
while True:
try:
pass
finally:
- break # warning
+ break # warning # B012: 8, ""
diff --git a/tests/b012_py311.py b/tests/b012_py311.py
index 9e788fa..bb29f83 100644
--- a/tests/b012_py311.py
+++ b/tests/b012_py311.py
@@ -4,7 +4,7 @@ def a():
except* Exception:
pass
finally:
- return # warning
+ return # warning # B012: 8, "*"
def b():
@@ -14,7 +14,7 @@ def b():
pass
finally:
if 1 + 0 == 2 - 1:
- return # warning
+ return # warning # B012: 12, "*"
def c():
@@ -24,6 +24,6 @@ def c():
pass
finally:
try:
- return # warning
+ return # warning # B012: 12, "*"
except* Exception:
pass
diff --git a/tests/b013.py b/tests/b013.py
index 17f314f..9a7fabc 100644
--- a/tests/b013.py
+++ b/tests/b013.py
@@ -7,7 +7,7 @@
try:
pass
-except (ValueError,):
+except (ValueError,): # B013: 0, "ValueError", ""
# pointless use of tuple
pass
@@ -29,7 +29,7 @@
try:
pass
-except (re.error,):
+except (re.error,): # B013: 0, "re.error", ""
# pointless use of tuple with dotted attribute
pass
diff --git a/tests/b013_py311.py b/tests/b013_py311.py
index 27af470..52efaa4 100644
--- a/tests/b013_py311.py
+++ b/tests/b013_py311.py
@@ -7,7 +7,7 @@
try:
pass
-except* (ValueError,):
+except* (ValueError,): # B013: 0, "ValueError", "*"
# pointless use of tuple
pass
@@ -29,7 +29,7 @@
try:
pass
-except* (re.error,):
+except* (re.error,): # B013: 0, "re.error", "*"
# pointless use of tuple with dotted attribute
pass
diff --git a/tests/b014.py b/tests/b014.py
index 3cb4570..9244f93 100644
--- a/tests/b014.py
+++ b/tests/b014.py
@@ -8,13 +8,13 @@
try:
pass
-except (Exception, TypeError):
+except (Exception, TypeError): # B014: 0, "Exception, TypeError", "", "Exception", ""
# TypeError is a subclass of Exception, so it doesn't add anything
pass
try:
pass
-except (OSError, OSError) as err:
+except (OSError, OSError) as err: # B014: 0, "OSError, OSError", " as err", "OSError", ""
# Duplicate exception types are useless
pass
@@ -25,7 +25,7 @@ class MyError(Exception):
try:
pass
-except (MyError, MyError):
+except (MyError, MyError): # B014: 0, "MyError, MyError", "", "MyError", ""
# Detect duplicate non-builtin errors
pass
@@ -39,21 +39,21 @@ class MyError(Exception):
try:
pass
-except (MyError, BaseException) as e:
+except (MyError, BaseException) as e: # B014: 0, "MyError, BaseException", " as e", "BaseException", ""
# But we *can* assume that everything is a subclass of BaseException
raise e
try:
pass
-except (re.error, re.error):
+except (re.error, re.error): # B014: 0, "re.error, re.error", "", "re.error", ""
# Duplicate exception types as attributes
pass
try:
pass
-except (IOError, EnvironmentError, OSError):
+except (IOError, EnvironmentError, OSError): # B014: 0, "IOError, EnvironmentError, OSError", "", "OSError", ""
# Detect if a primary exception and any its aliases are present.
#
# Since Python 3.3, IOError, EnvironmentError, WindowsError, mmap.error,
@@ -71,6 +71,6 @@ class MyError(Exception):
try:
pass
-except (ValueError, binascii.Error):
+except (ValueError, binascii.Error): # B014: 0, "ValueError, binascii.Error", "", "ValueError", ""
# binascii.Error is a subclass of ValueError.
pass
diff --git a/tests/b014_py311.py b/tests/b014_py311.py
index 2979a51..60792d9 100644
--- a/tests/b014_py311.py
+++ b/tests/b014_py311.py
@@ -8,13 +8,13 @@
try:
pass
-except* (Exception, TypeError):
+except* (Exception, TypeError): # B014: 0, "Exception, TypeError", "", "Exception", "*"
# TypeError is a subclass of Exception, so it doesn't add anything
pass
try:
pass
-except* (OSError, OSError) as err:
+except* (OSError, OSError) as err: # B014: 0, "OSError, OSError", " as err", "OSError", "*"
# Duplicate exception types are useless
pass
@@ -25,7 +25,7 @@ class MyError(Exception):
try:
pass
-except* (MyError, MyError):
+except* (MyError, MyError): # B014: 0, "MyError, MyError", "", "MyError", "*"
# Detect duplicate non-builtin errors
pass
@@ -39,21 +39,21 @@ class MyError(Exception):
try:
pass
-except* (MyError, BaseException) as e:
+except* (MyError, BaseException) as e: # B014: 0, "MyError, BaseException", " as e", "BaseException", "*"
# But we *can* assume that everything is a subclass of BaseException
raise e
try:
pass
-except* (re.error, re.error):
+except* (re.error, re.error): # B014: 0, "re.error, re.error", "", "re.error", "*"
# Duplicate exception types as attributes
pass
try:
pass
-except* (IOError, EnvironmentError, OSError):
+except* (IOError, EnvironmentError, OSError): # B014: 0, "IOError, EnvironmentError, OSError", "", "OSError", "*"
# Detect if a primary exception and any its aliases are present.
#
# Since Python 3.3, IOError, EnvironmentError, WindowsError, mmap.error,
@@ -71,6 +71,6 @@ class MyError(Exception):
try:
pass
-except* (ValueError, binascii.Error):
+except* (ValueError, binascii.Error): # B014: 0, "ValueError, binascii.Error", "", "ValueError", "*"
# binascii.Error is a subclass of ValueError.
pass
diff --git a/tests/b015.py b/tests/b015.py
index 0351c5c..b6d7d05 100644
--- a/tests/b015.py
+++ b/tests/b015.py
@@ -5,11 +5,11 @@
assert 1 == 1
-1 == 1
+1 == 1 # B015: 0
assert 1 in (1, 2)
-1 in (1, 2)
+1 in (1, 2) # B015: 0
if 1 == 2:
@@ -19,11 +19,11 @@
def test():
assert 1 in (1, 2)
- 1 in (1, 2)
+ 1 in (1, 2) # B015: 4
data = [x for x in [1, 2, 3] if x in (1, 2)]
class TestClass:
- 1 == 1
+ 1 == 1 # B015: 4
diff --git a/tests/b016.py b/tests/b016.py
index f12f468..d022cb9 100644
--- a/tests/b016.py
+++ b/tests/b016.py
@@ -3,11 +3,11 @@
B016 - on lines 6, 7, 8, and 10
"""
-raise False
-raise 1
-raise "string"
+raise False # B016: 0
+raise 1 # B016: 0
+raise "string" # B016: 0
fstring = "fstring"
-raise f"fstring {fstring}"
+raise f"fstring {fstring}" # B016: 0
raise Exception(False)
raise Exception(1)
raise Exception("string")
diff --git a/tests/b017.py b/tests/b017.py
index 95f4e30..19b18b0 100644
--- a/tests/b017.py
+++ b/tests/b017.py
@@ -23,13 +23,13 @@ class Foo:
class Foobar(unittest.TestCase):
def evil_raises(self) -> None:
- with self.assertRaises(Exception):
+ with self.assertRaises(Exception): # B017: 8
raise Exception("Evil I say!")
- with self.assertRaises(Exception, msg="Generic exception"):
+ with self.assertRaises(Exception, msg="Generic exception"): # B017: 8
raise Exception("Evil I say!")
- with pytest.raises(Exception):
+ with pytest.raises(Exception): # B017: 8
raise Exception("Evil I say!")
- with raises(Exception):
+ with raises(Exception): # B017: 8
raise Exception("Evil I say!")
# These are evil as well but we are only testing inside a with statement
self.assertRaises(Exception, lambda x, y: x / y, 1, y=0)
@@ -70,9 +70,9 @@ def raises_with_absolute_reference(self):
Foo()
def raises_base_exception(self):
- with self.assertRaises(BaseException):
+ with self.assertRaises(BaseException): # B017: 8
Foo()
- with pytest.raises(BaseException):
+ with pytest.raises(BaseException): # B017: 8
Foo()
- with raises(BaseException):
+ with raises(BaseException): # B017: 8
Foo()
diff --git a/tests/b018_classes.py b/tests/b018_classes.py
index 8150a33..fcc41c6 100644
--- a/tests/b018_classes.py
+++ b/tests/b018_classes.py
@@ -13,23 +13,23 @@ class Foo2:
a = 2
"str" # Str (no raise)
f"{int}" # JoinedStr (no raise)
- 1j # Number (complex)
- 1 # Number (int)
- 1.0 # Number (float)
- b"foo" # Binary
- True # NameConstant (True)
- False # NameConstant (False)
- None # NameConstant (None)
- [1, 2] # list
- {1, 2} # set
- {"foo": "bar"} # dict
+ 1j # Number (complex) # B018: 4, "Constant"
+ 1 # Number (int) # B018: 4, "Constant"
+ 1.0 # Number (float) # B018: 4, "Constant"
+ b"foo" # Binary # B018: 4, "Constant"
+ True # NameConstant (True) # B018: 4, "Constant"
+ False # NameConstant (False) # B018: 4, "Constant"
+ None # NameConstant (None) # B018: 4, "Constant"
+ [1, 2] # list # B018: 4, "List"
+ {1, 2} # set # B018: 4, "Set"
+ {"foo": "bar"} # dict # B018: 4, "Dict"
class Foo3:
- 123
+ 123 # B018: 4, "Constant"
a = 2
"str"
- 1
- (1,) # bad
- (2, 3) # bad
+ 1 # B018: 4, "Constant"
+ (1,) # bad # B018: 4, "Tuple"
+ (2, 3) # bad # B018: 4, "Tuple"
t = (4, 5) # good
diff --git a/tests/b018_functions.py b/tests/b018_functions.py
index c3e5440..5dc1c02 100644
--- a/tests/b018_functions.py
+++ b/tests/b018_functions.py
@@ -12,23 +12,23 @@ def foo2():
a = 2
"str" # Str (no raise)
f"{int}" # JoinedStr (no raise)
- 1j # Number (complex)
- 1 # Number (int)
- 1.0 # Number (float)
- b"foo" # Binary
- True # NameConstant (True)
- False # NameConstant (False)
- None # NameConstant (None)
- [1, 2] # list
- {1, 2} # set
- {"foo": "bar"} # dict
+ 1j # Number (complex) # B018: 4, "Constant"
+ 1 # Number (int) # B018: 4, "Constant"
+ 1.0 # Number (float) # B018: 4, "Constant"
+ b"foo" # Binary # B018: 4, "Constant"
+ True # NameConstant (True) # B018: 4, "Constant"
+ False # NameConstant (False) # B018: 4, "Constant"
+ None # NameConstant (None) # B018: 4, "Constant"
+ [1, 2] # list # B018: 4, "List"
+ {1, 2} # set # B018: 4, "Set"
+ {"foo": "bar"} # dict # B018: 4, "Dict"
def foo3():
- 123
+ 123 # B018: 4, "Constant"
a = 2
"str"
- 3
- (1,) # bad
- (2, 3) # bad
+ 3 # B018: 4, "Constant"
+ (1,) # bad # B018: 4, "Tuple"
+ (2, 3) # bad # B018: 4, "Tuple"
t = (4, 5) # good
diff --git a/tests/b018_modules.py b/tests/b018_modules.py
index eb94008..d4d98e3 100644
--- a/tests/b018_modules.py
+++ b/tests/b018_modules.py
@@ -6,16 +6,16 @@
a = 2
"str" # Str (no raise)
f"{int}" # JoinedStr (no raise)
-1j # Number (complex)
-1 # Number (int)
-1.0 # Number (float)
-b"foo" # Binary
-True # NameConstant (True)
-False # NameConstant (False)
-None # NameConstant (None)
-[1, 2] # list
-{1, 2} # set
-{"foo": "bar"} # dict
-(1,) # bad
-(2, 3) # bad
+1j # Number (complex) # B018: 0, "Constant"
+1 # Number (int) # B018: 0, "Constant"
+1.0 # Number (float) # B018: 0, "Constant"
+b"foo" # Binary # B018: 0, "Constant"
+True # NameConstant (True) # B018: 0, "Constant"
+False # NameConstant (False) # B018: 0, "Constant"
+None # NameConstant (None) # B018: 0, "Constant"
+[1, 2] # list # B018: 0, "List"
+{1, 2} # set # B018: 0, "Set"
+{"foo": "bar"} # dict # B018: 0, "Dict"
+(1,) # bad # B018: 0, "Tuple"
+(2, 3) # bad # B018: 0, "Tuple"
t = (4, 5) # good
diff --git a/tests/b018_nested.py b/tests/b018_nested.py
index 3888d25..0d9171e 100644
--- a/tests/b018_nested.py
+++ b/tests/b018_nested.py
@@ -1,44 +1,44 @@
X = 1
-False # bad
+False # bad # B018: 0, "Constant"
def func(y):
a = y + 1
- 5.5 # bad
+ 5.5 # bad # B018: 4, "Constant"
return a
class TestClass:
GOOD = [1, 3]
- [5, 6] # bad
+ [5, 6] # bad # B018: 4, "List"
def method(self, xx, yy=5):
t = (xx,)
- (yy,) # bad
+ (yy,) # bad # B018: 8, "Tuple"
while 1:
i = 3
- 4 # bad
+ 4 # bad # B018: 12, "Constant"
for n in range(i):
j = 5
- 1.5 # bad
+ 1.5 # bad # B018: 16, "Constant"
if j < n:
u = {1, 2}
- {4, 5} # bad
+ {4, 5} # bad # B018: 20, "Set"
elif j == n:
u = {1, 2, 3}
- {4, 5, 6} # bad
+ {4, 5, 6} # bad # B018: 20, "Set"
else:
u = {2, 3}
- {4, 6} # bad
+ {4, 6} # bad # B018: 20, "Set"
try:
- 1j # bad
+ 1j # bad # B018: 24, "Constant"
r = 2j
except Exception:
r = 3j
- 5 # bad
+ 5 # bad # B018: 24, "Constant"
finally:
- 4j # bad
+ 4j # bad # B018: 24, "Constant"
r += 1
return u + t
diff --git a/tests/b019.py b/tests/b019.py
index 93042fc..445e445 100644
--- a/tests/b019.py
+++ b/tests/b019.py
@@ -58,26 +58,26 @@ def some_cached_property(self): ...
def some_other_cached_property(self): ...
# Remaining methods should emit B019
- @functools.cache
+ @functools.cache # B019: 5
def cached_method(self, y): ...
- @cache
+ @cache # B019: 5
def another_cached_method(self, y): ...
- @functools.cache()
+ @functools.cache() # B019: 5
def called_cached_method(self, y): ...
- @cache()
+ @cache() # B019: 5
def another_called_cached_method(self, y): ...
- @functools.lru_cache
+ @functools.lru_cache # B019: 5
def lru_cached_method(self, y): ...
- @lru_cache
+ @lru_cache # B019: 5
def another_lru_cached_method(self, y): ...
- @functools.lru_cache()
+ @functools.lru_cache() # B019: 5
def called_lru_cached_method(self, y): ...
- @lru_cache()
- def another_called_lru_cached_method(self, y): ...
+ @lru_cache() # B019: 5
+ def another_called_lru_cached_method(self, y): ...
\ No newline at end of file
diff --git a/tests/b020.py b/tests/b020.py
index 9bdc4b4..e6b39ea 100644
--- a/tests/b020.py
+++ b/tests/b020.py
@@ -5,7 +5,7 @@
items = [1, 2, 3]
-for items in items:
+for items in items: # B020: 4, "items"
print(items)
items = [1, 2, 3]
@@ -18,7 +18,7 @@
for key, value in values.items():
print(f"{key}, {value}")
-for key, values in values.items():
+for key, values in values.items(): # B020: 9, "values"
print(f"{key}, {values}")
# Variables defined in a comprehension are local in scope
@@ -29,11 +29,11 @@
for var in (var for var in range(10)):
print(var)
-for k, v in {k: v for k, v in zip(range(10), range(10, 20))}.items():
+for k, v in {k: v for k, v in zip(range(10), range(10, 20))}.items(): # B905: 30
print(k, v)
# However we still call out reassigning the iterable in the comprehension.
-for vars in [i for i in vars]:
+for vars in [i for i in vars]: # B020: 4, "vars"
print(vars)
for var in sorted(range(10), key=lambda var: var.real):
diff --git a/tests/b021.py b/tests/b021.py
index dd0bb63..dd9233a 100644
--- a/tests/b021.py
+++ b/tests/b021.py
@@ -11,7 +11,7 @@ def foo1():
def foo2():
- f"""hello {VARIABLE}!"""
+ f"""hello {VARIABLE}!""" # B021: 4
class bar1:
@@ -19,7 +19,7 @@ class bar1:
class bar2:
- f"""hello {VARIABLE}!"""
+ f"""hello {VARIABLE}!""" # B021: 4
def foo1():
@@ -27,7 +27,7 @@ def foo1():
def foo2():
- f"""hello {VARIABLE}!"""
+ f"""hello {VARIABLE}!""" # B021: 4
class bar1:
@@ -35,7 +35,7 @@ class bar1:
class bar2:
- f"""hello {VARIABLE}!"""
+ f"""hello {VARIABLE}!""" # B021: 4
def foo1():
@@ -43,7 +43,7 @@ def foo1():
def foo2():
- f"hello {VARIABLE}!"
+ f"hello {VARIABLE}!" # B021: 4
class bar1:
@@ -51,7 +51,7 @@ class bar1:
class bar2:
- f"hello {VARIABLE}!"
+ f"hello {VARIABLE}!" # B021: 4
def foo1():
@@ -59,7 +59,7 @@ def foo1():
def foo2():
- f"hello {VARIABLE}!"
+ f"hello {VARIABLE}!" # B021: 4
class bar1:
@@ -67,10 +67,10 @@ class bar1:
class bar2:
- f"hello {VARIABLE}!"
+ f"hello {VARIABLE}!" # B021: 4
def baz():
- f"""I'm probably a docstring: {VARIABLE}!"""
+ f"""I'm probably a docstring: {VARIABLE}!""" # B021: 4
print(f"""I'm a normal string""")
- f"""Don't detect me!"""
+ f"""Don't detect me!"""
\ No newline at end of file
diff --git a/tests/b022.py b/tests/b022.py
index 9d597ed..dbb45f5 100644
--- a/tests/b022.py
+++ b/tests/b022.py
@@ -5,7 +5,7 @@
import contextlib
-with contextlib.suppress():
+with contextlib.suppress(): # B022: 0
raise ValueError
with contextlib.suppress(ValueError):
diff --git a/tests/b023.py b/tests/b023.py
index bcbea9a..e4fff45 100644
--- a/tests/b023.py
+++ b/tests/b023.py
@@ -10,11 +10,11 @@
for x in range(3):
y = x + 1
# Subject to late-binding problems
- functions.append(lambda: x)
- functions.append(lambda: y) # not just the loop var
+ functions.append(lambda: x) # B023: 29, "x"
+ functions.append(lambda: y) # not just the loop var # B023: 29, "y"
def f_bad_1():
- return x
+ return x # B023: 15, "x"
# Actually OK
functions.append(lambda x: x * 2)
@@ -26,10 +26,10 @@ def f_ok_1(x):
def check_inside_functions_too():
- ls = [lambda: x for x in range(2)] # error
- st = {lambda: x for x in range(2)} # error
- gn = (lambda: x for x in range(2)) # error
- dt = {x: lambda: x for x in range(2)} # error
+ ls = [lambda: x for x in range(2)] # error # B023: 18, "x"
+ st = {lambda: x for x in range(2)} # error # B023: 18, "x"
+ gn = (lambda: x for x in range(2)) # error # B023: 18, "x"
+ dt = {x: lambda: x for x in range(2)} # error # B023: 21, "x"
async def pointless_async_iterable():
@@ -38,9 +38,9 @@ async def pointless_async_iterable():
async def container_for_problems():
async for x in pointless_async_iterable():
- functions.append(lambda: x) # error
+ functions.append(lambda: x) # error # B023: 33, "x"
- [lambda: x async for x in pointless_async_iterable()] # error
+ [lambda: x async for x in pointless_async_iterable()] # error # B023: 13, "x"
a = 10
@@ -48,10 +48,10 @@ async def container_for_problems():
while True:
a = a_ = a - 1
b += 1
- functions.append(lambda: a) # error
- functions.append(lambda: a_) # error
- functions.append(lambda: b) # error
- functions.append(lambda: c) # error, but not a name error due to late binding
+ functions.append(lambda: a) # error # B023: 29, "a"
+ functions.append(lambda: a_) # error # B023: 29, "a_"
+ functions.append(lambda: b) # error # B023: 29, "b"
+ functions.append(lambda: c) # error, but not a name error due to late binding # B023: 29, "c"
c: bool = a > 3
if not c:
break
@@ -59,14 +59,14 @@ async def container_for_problems():
# Nested loops should not duplicate reports
for j in range(2):
for k in range(3):
- lambda: j * k # error
+ lambda: j * k # error # B023: 16, "j" # B023: 20, "k"
for j, k, l in [(1, 2, 3)]:
def f():
j = None # OK because it's an assignment
- [l for k in range(2)] # error for l, not for k
+ [l for k in range(2)] # error for l, not for k # B023: 9, "l"
assert a and functions
@@ -111,11 +111,11 @@ def myfunc(x):
# argument or in a consumed `filter()` (even if a comprehension is better style)
for x in range(2):
# It's not a complete get-out-of-linting-free construct - these should fail:
- min([None, lambda: x], key=repr)
- sorted([None, lambda: x], key=repr)
- any(filter(bool, [None, lambda: x]))
- list(filter(bool, [None, lambda: x]))
- all(reduce(bool, [None, lambda: x]))
+ min([None, lambda: x], key=repr) # B023: 23, "x"
+ sorted([None, lambda: x], key=repr) # B023: 26, "x"
+ any(filter(bool, [None, lambda: x])) # B023: 36, "x"
+ list(filter(bool, [None, lambda: x])) # B023: 37, "x"
+ all(reduce(bool, [None, lambda: x])) # B023: 36, "x"
# But all these ones should be OK:
min(range(3), key=lambda y: x * y)
@@ -166,7 +166,7 @@ def iter_f(names):
return lambda: name if exists(name) else None
if foo(name):
- return [lambda: name] # known false alarm
+ return [lambda: name] # known false alarm # B023: 28, "name"
if False:
- return [lambda: i for i in range(3)] # error
+ return [lambda: i for i in range(3)] # error # B023: 28, "i"
\ No newline at end of file
diff --git a/tests/b024.py b/tests/b024.py
index 058de7a..4956bb5 100644
--- a/tests/b024.py
+++ b/tests/b024.py
@@ -14,7 +14,7 @@
"""
-class Base_1(ABC): # error
+class Base_1(ABC): # error # B024: 0, "Base_1"
def method(self):
foo()
@@ -49,13 +49,13 @@ def method(self):
foo()
-class Base_7(ABC): # error
+class Base_7(ABC): # error # B024: 0, "Base_7"
@notabstract
def method(self):
foo()
-class MetaBase_1(metaclass=ABCMeta): # error
+class MetaBase_1(metaclass=ABCMeta): # error # B024: 0, "MetaBase_1"
def method(self):
foo()
@@ -66,12 +66,12 @@ def method(self):
foo()
-class abc_Base_1(abc.ABC): # error
+class abc_Base_1(abc.ABC): # error # B024: 0, "abc_Base_1"
def method(self):
foo()
-class abc_Base_2(metaclass=abc.ABCMeta): # error
+class abc_Base_2(metaclass=abc.ABCMeta): # error # B024: 0, "abc_Base_2"
def method(self):
foo()
@@ -120,13 +120,13 @@ def method(self):
# *not* safe, see https://github.com/PyCQA/flake8-bugbear/issues/471
-class abc_assign_class_variable(ABC):
+class abc_assign_class_variable(ABC): # B024: 0, "abc_assign_class_variable"
foo = 2
def method(self):
foo()
-class abc_annassign_class_variable(ABC): # *not* safe, see #471
+class abc_annassign_class_variable(ABC): # *not* safe, see #471 # B024: 0, "abc_annassign_class_variable"
foo: int = 2
def method(self):
foo()
diff --git a/tests/b025.py b/tests/b025.py
index 085f82f..034b56f 100644
--- a/tests/b025.py
+++ b/tests/b025.py
@@ -12,14 +12,14 @@
finally:
a = 3
-try:
+try: # B025: 0, "ValueError"
a = 1
except ValueError:
a = 2
except ValueError:
a = 2
-try:
+try: # B025: 0, "pickle.PickleError"
a = 1
except pickle.PickleError:
a = 2
@@ -28,7 +28,7 @@
except pickle.PickleError:
a = 2
-try:
+try: # B025: 0, "TypeError" # B025: 0, "ValueError"
a = 1
except (ValueError, TypeError):
a = 2
diff --git a/tests/b025_py311.py b/tests/b025_py311.py
index d1d9914..e6713a3 100644
--- a/tests/b025_py311.py
+++ b/tests/b025_py311.py
@@ -12,14 +12,14 @@
finally:
a = 3
-try:
+try: # B025: 0, "ValueError"
a = 1
except* ValueError:
a = 2
except* ValueError:
a = 2
-try:
+try: # B025: 0, "pickle.PickleError"
a = 1
except* pickle.PickleError:
a = 2
@@ -28,7 +28,7 @@
except* pickle.PickleError:
a = 2
-try:
+try: # B025: 0, "TypeError" # B025: 0, "ValueError"
a = 1
except* (ValueError, TypeError):
a = 2
diff --git a/tests/b026.py b/tests/b026.py
index a4b7f8f..c72fa97 100644
--- a/tests/b026.py
+++ b/tests/b026.py
@@ -12,10 +12,10 @@ def foo(bar, baz, bam):
foo("bar", "baz", bam="bam")
foo("bar", baz="baz", bam="bam")
foo(bar="bar", baz="baz", bam="bam")
-foo(bam="bam", *["bar", "baz"])
-foo(bam="bam", *bar_baz)
-foo(baz="baz", bam="bam", *["bar"])
-foo(bar="bar", baz="baz", bam="bam", *[])
-foo(bam="bam", *["bar"], *["baz"])
-foo(*["bar"], bam="bam", *["baz"])
-foo.bar(bam="bam", *["bar"])
+foo(bam="bam", *["bar", "baz"]) # B026: 15
+foo(bam="bam", *bar_baz) # B026: 15
+foo(baz="baz", bam="bam", *["bar"]) # B026: 26
+foo(bar="bar", baz="baz", bam="bam", *[]) # B026: 37
+foo(bam="bam", *["bar"], *["baz"]) # B026: 15 # B026: 25
+foo(*["bar"], bam="bam", *["baz"]) # B026: 25
+foo.bar(bam="bam", *["bar"]) # B026: 19
diff --git a/tests/b027.py b/tests/b027.py
index 218184b..83e8638 100644
--- a/tests/b027.py
+++ b/tests/b027.py
@@ -10,17 +10,17 @@
class AbstractClass(ABC):
- def empty_1(self): # error
+ def empty_1(self): # error # B027: 4, "empty_1"
...
- def empty_2(self): # error
+ def empty_2(self): # error # B027: 4, "empty_2"
pass
- def empty_3(self): # error
+ def empty_3(self): # error # B027: 4, "empty_3"
"""docstring"""
...
- def empty_4(self): # error
+ def empty_4(self): # error # B027: 4, "empty_4"
"""multiple ellipsis/pass"""
...
pass
@@ -28,7 +28,7 @@ def empty_4(self): # error
pass
@notabstract
- def empty_5(self): # error
+ def empty_5(self): # error # B027: 4, "empty_5"
...
@abstractmethod
diff --git a/tests/b028.py b/tests/b028.py
index 1490e99..2c01919 100644
--- a/tests/b028.py
+++ b/tests/b028.py
@@ -5,8 +5,8 @@
B028 - on lines 8 and 9
"""
-warnings.warn("test", DeprecationWarning)
-warnings.warn("test", DeprecationWarning, source=None)
+warnings.warn("test", DeprecationWarning) # B028: 0
+warnings.warn("test", DeprecationWarning, source=None) # B028: 0
warnings.warn("test", DeprecationWarning, source=None, stacklevel=2)
warnings.warn("test", DeprecationWarning, stacklevel=1)
warnings.warn("test", DeprecationWarning, 1)
diff --git a/tests/b029.py b/tests/b029.py
index 3df98cb..779b186 100644
--- a/tests/b029.py
+++ b/tests/b029.py
@@ -5,10 +5,10 @@
try:
pass
-except ():
+except (): # B029: 0, ""
pass
try:
pass
-except () as e:
+except () as e: # B029: 0, ""
pass
diff --git a/tests/b029_py311.py b/tests/b029_py311.py
index b121436..081996f 100644
--- a/tests/b029_py311.py
+++ b/tests/b029_py311.py
@@ -5,10 +5,10 @@
try:
pass
-except* ():
+except* (): # B029: 0, "*"
pass
try:
pass
-except* () as e:
+except* () as e: # B029: 0, "*"
pass
diff --git a/tests/b030.py b/tests/b030.py
index f90b2cd..01077af 100644
--- a/tests/b030.py
+++ b/tests/b030.py
@@ -1,6 +1,6 @@
try:
pass
-except (ValueError, (RuntimeError, (KeyError, TypeError))): # error
+except (ValueError, (RuntimeError, (KeyError, TypeError))): # error # B030: 0
pass
try:
@@ -10,12 +10,12 @@
try:
pass
-except 1: # error
+except 1: # error # B030: 0
pass
try:
pass
-except (1, ValueError): # error
+except (1, ValueError): # error # B030: 0
pass
try:
diff --git a/tests/b030_py311.py b/tests/b030_py311.py
index beca578..9d78c4f 100644
--- a/tests/b030_py311.py
+++ b/tests/b030_py311.py
@@ -1,6 +1,6 @@
try:
pass
-except* (ValueError, (RuntimeError, (KeyError, TypeError))): # error
+except* (ValueError, (RuntimeError, (KeyError, TypeError))): # error # B030: 0
pass
try:
@@ -10,12 +10,12 @@
try:
pass
-except* 1: # error
+except* 1: # error # B030: 0
pass
try:
pass
-except* (1, ValueError): # error
+except* (1, ValueError): # error # B030: 0
pass
try:
diff --git a/tests/b031.py b/tests/b031.py
index 3f3ef57..6580076 100644
--- a/tests/b031.py
+++ b/tests/b031.py
@@ -28,11 +28,11 @@ def collect_shop_items(shopper, items):
# Group by shopping section
for _section, section_items in groupby(items, key=lambda p: p[1]):
for shopper in shoppers:
- collect_shop_items(shopper, section_items)
+ collect_shop_items(shopper, section_items) # B031: 36, "section_items"
for _section, section_items in groupby(items, key=lambda p: p[1]):
collect_shop_items("Jane", section_items)
- collect_shop_items("Joe", section_items)
+ collect_shop_items("Joe", section_items) # B031: 30, "section_items"
for _section, section_items in groupby(items, key=lambda p: p[1]):
@@ -41,7 +41,7 @@ def collect_shop_items(shopper, items):
for _section, section_items in itertools.groupby(items, key=lambda p: p[1]):
for shopper in shoppers:
- collect_shop_items(shopper, section_items)
+ collect_shop_items(shopper, section_items) # B031: 36, "section_items"
for group in groupby(items, key=lambda p: p[1]):
# This is bad, but not detected currently
diff --git a/tests/b032.py b/tests/b032.py
index fc1fa12..3e39294 100644
--- a/tests/b032.py
+++ b/tests/b032.py
@@ -6,17 +6,17 @@
# Flag these
dct = {"a": 1}
-dct["b"]: 2
-dct.b: 2
+dct["b"]: 2 # B032: 0
+dct.b: 2 # B032: 0
-dct["b"]: "test"
-dct.b: "test"
+dct["b"]: "test" # B032: 0
+dct.b: "test" # B032: 0
test = "test"
-dct["b"]: test
-dct["b"]: test.lower()
-dct.b: test
-dct.b: test.lower()
+dct["b"]: test # B032: 0
+dct["b"]: test.lower() # B032: 0
+dct.b: test # B032: 0
+dct.b: test.lower() # B032: 0
# Do not flag below
typed_dct: dict[str, int] = {"a": 1}
diff --git a/tests/b033.py b/tests/b033.py
index 8ce42c9..efab06a 100644
--- a/tests/b033.py
+++ b/tests/b033.py
@@ -3,19 +3,19 @@
B033 - on lines 6-12, 16, 18
"""
-test = {1, 2, 3, 3, 5}
-test = {"a", "b", "c", "c", "e"}
-test = {True, False, True}
-test = {None, True, None}
-test = {3, 3.0}
-test = {1, True}
-test = {0, False}
+test = {1, 2, 3, 3, 5} # B033: 17, "3"
+test = {"a", "b", "c", "c", "e"} # B033: 23, "'c'"
+test = {True, False, True} # B033: 21, "True"
+test = {None, True, None} # B033: 20, "None"
+test = {3, 3.0} # B033: 11, "3.0"
+test = {1, True} # B033: 11, "True"
+test = {0, False} # B033: 11, "False"
multi_line = {
"alongvalueeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
1,
- True,
+ True, # B033: 4, "True"
0,
- False,
+ False, # B033: 4, "False"
}
test = {1, 2, 3, 3.5, 5}
diff --git a/tests/b034.py b/tests/b034.py
index 8c20009..a2fc29e 100644
--- a/tests/b034.py
+++ b/tests/b034.py
@@ -2,15 +2,15 @@
from re import sub
# error
-re.sub("a", "b", "aaa", re.IGNORECASE)
-re.sub("a", "b", "aaa", 5)
-re.sub("a", "b", "aaa", 5, re.IGNORECASE)
-re.subn("a", "b", "aaa", re.IGNORECASE)
-re.subn("a", "b", "aaa", 5)
-re.subn("a", "b", "aaa", 5, re.IGNORECASE)
-re.split(" ", "a a a a", re.I)
-re.split(" ", "a a a a", 2)
-re.split(" ", "a a a a", 2, re.I)
+re.sub("a", "b", "aaa", re.IGNORECASE) # B034: 24, "sub", "count"
+re.sub("a", "b", "aaa", 5) # B034: 24, "sub", "count"
+re.sub("a", "b", "aaa", 5, re.IGNORECASE) # B034: 24, "sub", "count"
+re.subn("a", "b", "aaa", re.IGNORECASE) # B034: 25, "subn", "count"
+re.subn("a", "b", "aaa", 5) # B034: 25, "subn", "count"
+re.subn("a", "b", "aaa", 5, re.IGNORECASE) # B034: 25, "subn", "count"
+re.split(" ", "a a a a", re.I) # B034: 25, "split", "maxsplit"
+re.split(" ", "a a a a", 2) # B034: 25, "split", "maxsplit"
+re.split(" ", "a a a a", 2, re.I) # B034: 25, "split", "maxsplit"
# okay
re.sub("a", "b", "aaa")
diff --git a/tests/b035.py b/tests/b035.py
index dd41d66..dee810b 100644
--- a/tests/b035.py
+++ b/tests/b035.py
@@ -3,8 +3,8 @@
regular_nested_dict = {"a": 1, "nested": {"b": 2, "c": "three"}}
# bad - const key in dict comprehension
-bad_const_key_str = {"a": i for i in range(3)}
-bad_const_key_int = {1: i for i in range(3)}
+bad_const_key_str = {"a": i for i in range(3)} # B035: 21, "a"
+bad_const_key_int = {1: i for i in range(3)} # B035: 21, 1
# OK - const value in dict comp
const_val = {i: "a" for i in range(3)}
@@ -16,13 +16,13 @@
# nested
nested_bad_and_good = {
"good": {"a": 1, "b": 2},
- "bad": {"a": i for i in range(3)},
+ "bad": {"a": i for i in range(3)}, # B035: 12, "a"
}
CONST_KEY_VAR = "KEY"
# bad
-bad_const_key_var = {CONST_KEY_VAR: i for i in range(3)}
+bad_const_key_var = {CONST_KEY_VAR: i for i in range(3)} # B035: 21, "CONST_KEY_VAR"
# OK - variable from tuple
var_from_tuple = {k: v for k, v in {}.items()}
@@ -32,7 +32,7 @@
# bad - variabe not from generator
v3 = 1
-bad_var_not_from_nested_tuple = {v3: k for k, (v1, v2) in {"a": (1, 2)}.items()}
+bad_var_not_from_nested_tuple = {v3: k for k, (v1, v2) in {"a": (1, 2)}.items()} # B035: 33, "v3"
# OK - variable from named expression
var_from_named_expr = {
diff --git a/tests/b036.py b/tests/b036.py
index 933ee8f..43905e3 100644
--- a/tests/b036.py
+++ b/tests/b036.py
@@ -1,14 +1,14 @@
try:
pass
-except BaseException: # bad
+except BaseException: # bad # B036: 0
print("aaa")
pass
try:
pass
-except BaseException as ex: # bad
+except BaseException as ex: # bad # B036: 0
print(ex)
pass
@@ -17,7 +17,7 @@
pass
except ValueError:
raise
-except BaseException: # bad
+except BaseException: # bad # B036: 0
pass
@@ -30,7 +30,7 @@
try:
pass
-except BaseException as ex: # bad - raised something else
+except BaseException as ex: # bad - raised something else # B036: 0
print("aaa")
raise KeyError from ex
@@ -47,7 +47,7 @@
try:
pass
-except BaseException:
+except BaseException: # B036: 0
try: # nested try
pass
except ValueError:
@@ -55,5 +55,5 @@
try:
pass
-except BaseException:
+except BaseException: # B036: 0
raise a.b from None # bad (regression test for #449)
diff --git a/tests/b036_py311.py b/tests/b036_py311.py
index f9ca110..cd85dc0 100644
--- a/tests/b036_py311.py
+++ b/tests/b036_py311.py
@@ -1,14 +1,14 @@
try:
pass
-except* BaseException: # bad
+except* BaseException: # bad # B036: 0
print("aaa")
pass
try:
pass
-except* BaseException as ex: # bad
+except* BaseException as ex: # bad # B036: 0
print(ex)
pass
@@ -17,7 +17,7 @@
pass
except* ValueError:
raise
-except* BaseException: # bad
+except* BaseException: # bad # B036: 0
pass
@@ -30,7 +30,7 @@
try:
pass
-except* BaseException as ex: # bad - raised something else
+except* BaseException as ex: # bad - raised something else # B036: 0
print("aaa")
raise KeyError from ex
@@ -47,7 +47,7 @@
try:
pass
-except* BaseException:
+except* BaseException: # B036: 0
try: # nested try
pass
except* ValueError:
@@ -55,5 +55,5 @@
try:
pass
-except* BaseException:
+except* BaseException: # B036: 0
raise a.b from None # bad (regression test for #449)
diff --git a/tests/b037.py b/tests/b037.py
index e580764..dc80d90 100644
--- a/tests/b037.py
+++ b/tests/b037.py
@@ -1,18 +1,18 @@
class A:
def __init__(self) -> None:
- return 1 # bad
+ return 1 # bad # B037: 8
class B:
def __init__(self, x) -> None:
if x:
return # ok
else:
- return [] # bad
+ return [] # bad # B037: 12
class BNested:
def __init__(self) -> None:
- yield # bad
+ yield # bad # B037: 12
class C:
@@ -20,14 +20,14 @@ def func(self):
pass
def __init__(self, k="") -> None:
- yield from [] # bad
+ yield from [] # bad # B037: 8
class D(C):
def __init__(self, k="") -> None:
super().__init__(k)
- return None # bad
+ return None # bad # B037: 8
class E:
def __init__(self) -> None:
- yield "a"
+ yield "a" # B037: 8
diff --git a/tests/b039.py b/tests/b039.py
index 1ba7ce3..5acde64 100644
--- a/tests/b039.py
+++ b/tests/b039.py
@@ -2,11 +2,11 @@
import time
from contextvars import ContextVar
-ContextVar("cv", default=[]) # bad
-ContextVar("cv", default=list()) # bad
-ContextVar("cv", default=set()) # bad
-ContextVar("cv", default=time.time()) # bad (B008-like)
-contextvars.ContextVar("cv", default=[]) # bad
+ContextVar("cv", default=[]) # bad # B039: 25
+ContextVar("cv", default=list()) # bad # B039: 25
+ContextVar("cv", default=set()) # bad # B039: 25
+ContextVar("cv", default=time.time()) # bad (B008-like) # B039: 25
+contextvars.ContextVar("cv", default=[]) # bad # B039: 37
# good
diff --git a/tests/b040.py b/tests/b040.py
index 1f5d6ec..cade900 100644
--- a/tests/b040.py
+++ b/tests/b040.py
@@ -4,7 +4,7 @@ def arbitrary_fun(*args, **kwargs): ...
# classic case
try:
...
-except Exception as e:
+except Exception as e: # B040: 0
e.add_note("...") # error
try:
@@ -21,7 +21,7 @@ def arbitrary_fun(*args, **kwargs): ...
# other exception raised
try:
...
-except Exception as e:
+except Exception as e: # B040: 0
f = ValueError()
e.add_note("...") # error
raise f
@@ -80,7 +80,7 @@ def arbitrary_fun(*args, **kwargs): ...
# multiple ExceptHandlers
try:
...
-except ValueError as e:
+except ValueError as e: # B040: 0
e.add_note("") # error
except TypeError as e:
raise e
@@ -104,14 +104,14 @@ def arbitrary_fun(*args, **kwargs): ...
# special case: e is only used in the `add_note` call itself
try:
...
-except Exception as e: # error
+except Exception as e: # error # B040: 0
e.add_note(str(e))
e.add_note(str(e))
# check nesting
try:
...
-except Exception as e: # error
+except Exception as e: # error # B040: 0
e.add_note("")
try:
...
@@ -121,7 +121,7 @@ def arbitrary_fun(*args, **kwargs): ...
# questionable if this should error
try:
...
-except Exception as e:
+except Exception as e: # B040: 0
e.add_note("")
e = ValueError()
diff --git a/tests/b041.py b/tests/b041.py
index ae819f4..4ccf6a1 100644
--- a/tests/b041.py
+++ b/tests/b041.py
@@ -1,11 +1,11 @@
a = 1
-test = {'yes': 1, 'yes': 1}
-test = {'yes': 1, 'yes': 1, 'no': 2, 'no': 2}
-test = {'yes': 1, 'yes': 1, 'yes': 1}
-test = {1: 1, 1.0: 1}
-test = {True: 1, True: 1}
-test = {None: 1, None: 1}
-test = {a: a, a: a}
+test = {'yes': 1, 'yes': 1} # B041: 18
+test = {'yes': 1, 'yes': 1, 'no': 2, 'no': 2} # B041: 18 # B041: 37
+test = {'yes': 1, 'yes': 1, 'yes': 1} # B041: 18 # B041: 28
+test = {1: 1, 1.0: 1} # B041: 14
+test = {True: 1, True: 1} # B041: 17
+test = {None: 1, None: 1} # B041: 17
+test = {a: a, a: a} # B041: 14
# no error if either keys or values are different
test = {'yes': 1, 'yes': 2}
diff --git a/tests/b901.py b/tests/b901.py
index 276eaca..c40f472 100644
--- a/tests/b901.py
+++ b/tests/b901.py
@@ -5,7 +5,7 @@
def broken():
if True:
- return [1, 2, 3] # B901
+ return [1, 2, 3] # B901 # B901: 8
yield 3
yield 2
@@ -32,7 +32,7 @@ def not_broken3():
def broken2():
- return [3, 2, 1] # B901
+ return [3, 2, 1] # B901 # B901: 4
yield from not_broken()
@@ -79,19 +79,19 @@ def __await__(self):
def broken3():
if True:
- return [1, 2, 3] # B901
+ return [1, 2, 3] # B901 # B901: 8
else:
yield 3
def broken4() -> Iterable[str]:
yield "x"
- return ["x"] # B901
+ return ["x"] # B901 # B901: 4
def broken5() -> Generator[str]:
yield "x"
- return ["x"] # B901
+ return ["x"] # B901 # B901: 4
def not_broken10() -> Generator[str, int, float]:
diff --git a/tests/b902.py b/tests/b902.py
index f48b09f..ebc7454 100644
--- a/tests/b902.py
+++ b/tests/b902.py
@@ -19,25 +19,25 @@ def not_a_problem(arg1): ...
class Warnings:
- def __init__(i_am_special): ...
+ def __init__(i_am_special): ... # B902: 17, "'i_am_special'", "instance", "self"
- def almost_a_class_method(cls, arg1): ...
+ def almost_a_class_method(cls, arg1): ... # B902: 30, "'cls'", "instance", "self"
- def almost_a_static_method(): ...
+ def almost_a_static_method(): ... # B902: 4, "(none)", "instance", "self"
@classmethod
- def wat(self, i_like_confusing_people): ...
+ def wat(self, i_like_confusing_people): ... # B902: 12, "'self'", "class", "cls"
- def i_am_strange(*args, **kwargs):
+ def i_am_strange(*args, **kwargs): # B902: 22, "*args", "instance", "self"
self = args[0]
def defaults_anyone(self=None): ...
- def invalid_kwargs_only(**kwargs): ...
+ def invalid_kwargs_only(**kwargs): ... # B902: 30, "**kwargs", "instance", "self"
- def invalid_keyword_only(*, self): ...
+ def invalid_keyword_only(*, self): ... # B902: 32, "*, self", "instance", "self"
- async def async_invalid_keyword_only(*, self): ...
+ async def async_invalid_keyword_only(*, self): ... # B902: 44, "*, self", "instance", "self"
class Meta(type):
@@ -49,10 +49,10 @@ def __prepare__(metacls, name, bases):
class OtherMeta(type):
- def __init__(self, name, bases, d): ...
+ def __init__(self, name, bases, d): ... # B902: 17, "'self'", "metaclass instance", "cls"
@classmethod
- def __prepare__(cls, name, bases):
+ def __prepare__(cls, name, bases): # B902: 20, "'cls'", "metaclass class", "metacls"
return {}
@classmethod
diff --git a/tests/b902_extended.py b/tests/b902_extended.py
index b458f1f..87fa4e6 100644
--- a/tests/b902_extended.py
+++ b/tests/b902_extended.py
@@ -1,33 +1,33 @@
-# parameters: --classmethod-decorators=["mylibrary.classmethod", "validator"]
+# OPTIONS: classmethod_decorators=["mylibrary.makeclassmethod", "validator"], select=["B902"],
class Errors:
# correctly registered as classmethod
@validator
- def foo_validator(self) -> None: ...
+ def foo_validator(self) -> None: ... # B902: 22, "'self'", "class", "cls"
@other.validator
- def foo_other_validator(self) -> None: ...
+ def foo_other_validator(self) -> None: ... # B902: 28, "'self'", "class", "cls"
@foo.bar.validator
- def foo_foo_bar_validator(self) -> None: ...
+ def foo_foo_bar_validator(self) -> None: ... # B902: 30, "'self'", "class", "cls"
@validator.blah
- def foo_validator_blah(cls) -> None: ...
+ def foo_validator_blah(cls) -> None: ... # B902: 27, "'cls'", "instance", "self"
# specifying attribute in options is not valid
@mylibrary.makeclassmethod
- def foo2(cls) -> None: ...
+ def foo2(cls) -> None: ... # B902: 13, "'cls'", "instance", "self"
# specified attribute in options
@makeclassmethod
- def foo6(cls) -> None: ...
+ def foo6(cls) -> None: ... # B902: 13, "'cls'", "instance", "self"
# classmethod is default, but if not specified it's ignored
@classmethod
- def foo3(cls) -> None: ...
+ def foo3(cls) -> None: ... # B902: 13, "'cls'", "instance", "self"
# random unknown decorator
@aoeuaoeu
- def foo5(cls) -> None: ...
+ def foo5(cls) -> None: ... # B902: 13, "'cls'", "instance", "self"
class NoErrors:
@@ -56,32 +56,32 @@ def foo6(self) -> None: ...
class ErrorsMeta(type):
# correctly registered as classmethod
@validator
- def foo_validator(cls) -> None: ...
+ def foo_validator(cls) -> None: ... # B902: 22, "'cls'", "metaclass class", "metacls"
@other.validator
- def foo_other_validator(cls) -> None: ...
+ def foo_other_validator(cls) -> None: ... # B902: 28, "'cls'", "metaclass class", "metacls"
@foo.bar.validator
- def foo_foo_bar_validator(cls) -> None: ...
+ def foo_foo_bar_validator(cls) -> None: ... # B902: 30, "'cls'", "metaclass class", "metacls"
@validator.blah
- def foo_validator_blah(metacls) -> None: ...
+ def foo_validator_blah(metacls) -> None: ... # B902: 27, "'metacls'", "metaclass instance", "cls"
# specifying attribute in options is not valid
@mylibrary.makeclassmethod
- def foo2(metacls) -> None: ...
+ def foo2(metacls) -> None: ... # B902: 13, "'metacls'", "metaclass instance", "cls"
# specified attribute in options
@makeclassmethod
- def foo6(metacls) -> None: ...
+ def foo6(metacls) -> None: ... # B902: 13, "'metacls'", "metaclass instance", "cls"
# classmethod is default, but if not specified it's ignored
@classmethod
- def foo3(metacls) -> None: ...
+ def foo3(metacls) -> None: ... # B902: 13, "'metacls'", "metaclass instance", "cls"
# random unknown decorator
@aoeuaoeu
- def foo5(metacls) -> None: ...
+ def foo5(metacls) -> None: ... # B902: 13, "'metacls'", "metaclass instance", "cls"
class NoErrorsMeta(type):
diff --git a/tests/b902_py38.py b/tests/b902_py38.py
index 9d81e15..7e128d6 100644
--- a/tests/b902_py38.py
+++ b/tests/b902_py38.py
@@ -19,25 +19,25 @@ def not_a_problem(arg1, /): ...
class Warnings:
- def __init__(i_am_special, /): ...
+ def __init__(i_am_special, /): ... # B902: 17, "'i_am_special'", "instance", "self"
- def almost_a_class_method(cls, arg1, /): ...
+ def almost_a_class_method(cls, arg1, /): ... # B902: 30, "'cls'", "instance", "self"
- def almost_a_static_method(): ...
+ def almost_a_static_method(): ... # B902: 4, "(none)", "instance", "self"
@classmethod
- def wat(self, i_like_confusing_people, /): ...
+ def wat(self, i_like_confusing_people, /): ... # B902: 12, "'self'", "class", "cls"
- def i_am_strange(*args, **kwargs):
+ def i_am_strange(*args, **kwargs): # B902: 22, "*args", "instance", "self"
self = args[0]
def defaults_anyone(self=None, /): ...
- def invalid_kwargs_only(**kwargs): ...
+ def invalid_kwargs_only(**kwargs): ... # B902: 30, "**kwargs", "instance", "self"
- def invalid_keyword_only(*, self): ...
+ def invalid_keyword_only(*, self): ... # B902: 32, "*, self", "instance", "self"
- async def async_invalid_keyword_only(*, self): ...
+ async def async_invalid_keyword_only(*, self): ... # B902: 44, "*, self", "instance", "self"
class Meta(type):
@@ -49,10 +49,10 @@ def __prepare__(metacls, name, bases, /):
class OtherMeta(type):
- def __init__(self, name, bases, d, /): ...
+ def __init__(self, name, bases, d, /): ... # B902: 17, "'self'", "metaclass instance", "cls"
@classmethod
- def __prepare__(cls, name, bases, /):
+ def __prepare__(cls, name, bases, /): # B902: 20, "'cls'", "metaclass class", "metacls"
return {}
@classmethod
diff --git a/tests/b903.py b/tests/b903.py
index 78da444..7c62cfb 100644
--- a/tests/b903.py
+++ b/tests/b903.py
@@ -28,13 +28,13 @@ def __init__(self, foo, bar):
self.bar = bar
-class Warnings:
+class Warnings: # B903: 0
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
-class WarningsWithDocstring:
+class WarningsWithDocstring: # B903: 0
"""A docstring should not be an impediment to a warning"""
def __init__(self, foo, bar):
diff --git a/tests/b904.py b/tests/b904.py
index c410d10..5ef1f5d 100644
--- a/tests/b904.py
+++ b/tests/b904.py
@@ -7,13 +7,13 @@
raise ValueError
except ValueError:
if "abc":
- raise TypeError
- raise UserWarning
+ raise TypeError # B904: 8, ""
+ raise UserWarning # B904: 4, ""
except AssertionError:
raise # Bare `raise` should not be an error
except Exception as err:
assert err
- raise Exception("No cause here...")
+ raise Exception("No cause here...") # B904: 4, ""
except BaseException as base_err:
# Might use this instead of bare raise with the `.with_traceback()` method
raise base_err
@@ -52,4 +52,4 @@ def context_switch():
try:
raise ValueError
except ValueError:
- raise Exception
+ raise Exception # B904: 16, ""
diff --git a/tests/b904_py311.py b/tests/b904_py311.py
index 43cb4e9..ceda2c2 100644
--- a/tests/b904_py311.py
+++ b/tests/b904_py311.py
@@ -7,13 +7,13 @@
raise ValueError
except* ValueError:
if "abc":
- raise TypeError
- raise UserWarning
+ raise TypeError # B904: 8, "*"
+ raise UserWarning # B904: 4, "*"
except* AssertionError:
raise # Bare `raise` should not be an error
except* Exception as err:
assert err
- raise Exception("No cause here...")
+ raise Exception("No cause here...") # B904: 4, "*"
except* BaseException as base_err:
# Might use this instead of bare raise with the `.with_traceback()` method
raise base_err
@@ -52,4 +52,4 @@ def context_switch():
try:
raise ValueError
except* ValueError:
- raise Exception
+ raise Exception # B904: 16, "*"
diff --git a/tests/b905_py310.py b/tests/b905_py310.py
index 07f6d59..ae4694e 100644
--- a/tests/b905_py310.py
+++ b/tests/b905_py310.py
@@ -1,9 +1,9 @@
-zip()
-zip(range(3))
-zip("a", "b")
-zip("a", "b", *zip("c"))
-zip(zip("a"), strict=False)
-zip(zip("a", strict=True))
+zip() # B905: 0
+zip(range(3)) # B905: 0
+zip("a", "b") # B905: 0
+zip("a", "b", *zip("c")) # B905: 0 # B905: 15
+zip(zip("a"), strict=False) # B905: 4
+zip(zip("a", strict=True)) # B905: 0
zip(range(3), strict=True)
zip("a", "b", strict=False)
@@ -18,5 +18,5 @@
zip([1, 2, 3], itertools.repeat(1, None))
zip([1, 2, 3], itertools.repeat(1, times=None))
-zip([1, 2, 3], itertools.repeat(1, 1))
-zip([1, 2, 3], itertools.repeat(1, times=4))
+zip([1, 2, 3], itertools.repeat(1, 1)) # B905: 0
+zip([1, 2, 3], itertools.repeat(1, times=4)) # B905: 0
diff --git a/tests/b906.py b/tests/b906.py
index b96a00e..f5478a3 100644
--- a/tests/b906.py
+++ b/tests/b906.py
@@ -6,7 +6,7 @@
# error
-def visit_For(): ...
+def visit_For(): ... # B906: 0
# has call to visit function
diff --git a/tests/b907.py b/tests/b907.py
deleted file mode 100644
index adfc629..0000000
--- a/tests/b907.py
+++ /dev/null
@@ -1,114 +0,0 @@
-def foo():
- return "hello"
-
-
-var = var2 = "hello"
-
-# warnings
-f"begin '{var}' end"
-f"'{var}' end"
-f"begin '{var}'"
-
-f'begin "{var}" end'
-f'"{var}" end'
-f'begin "{var}"'
-
-f'a "{"hello"}" b'
-f'a "{foo()}" b'
-
-# fmt: off
-k = (f'"' # Error emitted here on }"'
-f'"{var:^}"'
-f'"{var:5<}"'
-
-# explicit string specifier
-f'"{var:s}"'
-
-# empty format string
-f'"{var:}"'
-
-# These all currently give warnings, but could be considered false alarms
-# multiple quote marks
-f'"""{var}"""'
-# str conversion specified
-f'"{var!s}"'
-# two variables fighting over the same quote mark
-f'"{var}"{var2}"' # currently gives warning on the first one
-
-
-# ***no warnings*** #
-
-# padding inside quotes
-f'"{var:5}"'
-
-# quote mark not immediately adjacent
-f'" {var} "'
-f'"{var} "'
-f'" {var}"'
-
-# mixed quote marks
-f"'{var}\""
-
-# repr specifier already given
-f'"{var!r}"'
-
-# two variables in a row with no quote mark inbetween
-f'"{var}{var}"'
-
-# don't crash on non-string constants
-f'5{var}"'
-f"\"{var}'"
-
-# sign option (only valid for number types)
-f'"{var:+}"'
-
-# integer presentation type specified
-f'"{var:b}"'
-f'"{var:x}"'
-
-# float presentation type
-f'"{var:e%}"'
-
-# alignment specifier invalid for strings
-f'"{var:=}"'
-
-# other types and combinations are tested in test_b907_format_specifier_permutations
-
-# don't attempt to parse complex format specs
-f'"{var:{var}}"'
-f'"{var:5{var}}"'
-
-# even if explicit string type (not implemented)
-f'"{var:{var}s}"'
diff --git a/tests/b907_py312.py b/tests/b907_py312.py
new file mode 100644
index 0000000..1c92627
--- /dev/null
+++ b/tests/b907_py312.py
@@ -0,0 +1,116 @@
+# column and lineno changes since 3.12
+# on <3.12 and columns are 0, and lineno is emitted from the first line if multiline
+def foo():
+ return "hello"
+
+
+var = var2 = "hello"
+
+# warnings
+f"begin '{var}' end" # B907: 9, "var"
+f"'{var}' end" # B907: 3, "var"
+f"begin '{var}'" # B907: 9, "var"
+
+f'begin "{var}" end' # B907: 9, "var"
+f'"{var}" end' # B907: 3, "var"
+f'begin "{var}"' # B907: 9, "var"
+
+f'a "{"hello"}" b' # B907: 5, "'hello'"
+f'a "{foo()}" b' # B907: 5, "foo()"
+
+# fmt: off
+k = (f'"' # Error emitted here on }"' # B907: 3, "var"
+f'"{var:^}"' # B907: 3, "var"
+f'"{var:5<}"' # B907: 3, "var"
+
+# explicit string specifier
+f'"{var:s}"' # B907: 3, "var"
+
+# empty format string
+f'"{var:}"' # B907: 3, "var"
+
+# These all currently give warnings, but could be considered false alarms
+# multiple quote marks
+f'"""{var}"""' # B907: 5, "var"
+# str conversion specified
+f'"{var!s}"' # B907: 3, "var"
+# two variables fighting over the same quote mark
+f'"{var}"{var2}"' # currently gives warning on the first one # B907: 3, "var"
+
+
+# ***no warnings*** #
+
+# padding inside quotes
+f'"{var:5}"'
+
+# quote mark not immediately adjacent
+f'" {var} "'
+f'"{var} "'
+f'" {var}"'
+
+# mixed quote marks
+f"'{var}\""
+
+# repr specifier already given
+f'"{var!r}"'
+
+# two variables in a row with no quote mark inbetween
+f'"{var}{var}"'
+
+# don't crash on non-string constants
+f'5{var}"'
+f"\"{var}'"
+
+# sign option (only valid for number types)
+f'"{var:+}"'
+
+# integer presentation type specified
+f'"{var:b}"'
+f'"{var:x}"'
+
+# float presentation type
+f'"{var:e%}"'
+
+# alignment specifier invalid for strings
+f'"{var:=}"'
+
+# other types and combinations are tested in test_b907_format_specifier_permutations
+
+# don't attempt to parse complex format specs
+f'"{var:{var}}"'
+f'"{var:5{var}}"'
+
+# even if explicit string type (not implemented)
+f'"{var:{var}s}"'
diff --git a/tests/b908.py b/tests/b908.py
index d89f97e..8acb138 100644
--- a/tests/b908.py
+++ b/tests/b908.py
@@ -4,7 +4,7 @@
import pytest
from pytest import raises, warns
-with pytest.raises(TypeError):
+with pytest.raises(TypeError): # B908: 0
a = 1 + "x"
b = "x" + 1
print(a, b)
@@ -12,19 +12,19 @@
class SomeTestCase(unittest.TestCase):
def test_func_raises(self):
- with self.assertRaises(TypeError):
+ with self.assertRaises(TypeError): # B908: 8
a = 1 + "x"
b = "x" + 1
print(a, b)
def test_func_raises_regex(self):
- with self.assertRaisesRegex(TypeError):
+ with self.assertRaisesRegex(TypeError): # B908: 8
a = 1 + "x"
b = "x" + 1
print(a, b)
def test_func_raises_regexp(self):
- with self.assertRaisesRegexp(TypeError):
+ with self.assertRaisesRegexp(TypeError): # B908: 8
a = 1 + "x"
b = "x" + 1
print(a, b)
@@ -34,15 +34,15 @@ def test_raises_correct(self):
print("1" + 1)
-with raises(Exception):
+with raises(Exception): # B017: 0 # B908: 0
"1" + 1
"2" + 2
-with pytest.warns(Warning):
+with pytest.warns(Warning): # B908: 0
print("print before warning")
warnings.warn("some warning", stacklevel=1)
-with warns(Warning):
+with warns(Warning): # B908: 0
print("print before warning")
warnings.warn("some warning", stacklevel=1)
@@ -53,5 +53,5 @@ def test_raises_correct(self):
with pytest.warns(Warning):
warnings.warn("some warning", stacklevel=1)
-with raises(Exception):
+with raises(Exception): # B017: 0
raise Exception("some exception")
diff --git a/tests/b909.py b/tests/b909.py
index 1601ee9..18e6489 100644
--- a/tests/b909.py
+++ b/tests/b909.py
@@ -9,20 +9,20 @@
some_other_list = [1, 2, 3]
for elem in some_list:
# errors
- some_list.remove(elem)
- del some_list[2]
- some_list.append(elem)
- some_list.sort()
- some_list.reverse()
- some_list.clear()
- some_list.extend([1, 2])
- some_list.insert(1, 1)
- some_list.pop(1)
- some_list.pop()
+ some_list.remove(elem) # B909: 4
+ del some_list[2] # B909: 4
+ some_list.append(elem) # B909: 4
+ some_list.sort() # B909: 4
+ some_list.reverse() # B909: 4
+ some_list.clear() # B909: 4
+ some_list.extend([1, 2]) # B909: 4
+ some_list.insert(1, 1) # B909: 4
+ some_list.pop(1) # B909: 4
+ some_list.pop() # B909: 4
# conditional break should error
if elem == 2:
- some_list.remove(elem)
+ some_list.remove(elem) # B909: 8
if elem == 3:
break
@@ -44,9 +44,9 @@
for elem in mydicts:
# errors
- mydicts.popitem()
- mydicts.setdefault('foo', 1)
- mydicts.update({'foo': 'bar'})
+ mydicts.popitem() # B909: 4
+ mydicts.setdefault('foo', 1) # B909: 4
+ mydicts.update({'foo': 'bar'}) # B909: 4
# no errors
elem.popitem()
@@ -59,12 +59,12 @@
for _ in myset:
# errors
- myset.update({4, 5})
- myset.intersection_update({4, 5})
- myset.difference_update({4, 5})
- myset.symmetric_difference_update({4, 5})
- myset.add(4)
- myset.discard(3)
+ myset.update({4, 5}) # B909: 4
+ myset.intersection_update({4, 5}) # B909: 4
+ myset.difference_update({4, 5}) # B909: 4
+ myset.symmetric_difference_update({4, 5}) # B909: 4
+ myset.add(4) # B909: 4
+ myset.discard(3) # B909: 4
# no errors
del myset
@@ -81,8 +81,8 @@ def __init__(self, ls):
a = A((1, 2, 3))
# ensure member accesses are handled
for elem in a.some_list:
- a.some_list.remove(elem)
- del a.some_list[2]
+ a.some_list.remove(elem) # B909: 4
+ del a.some_list[2] # B909: 4
# Augassign
@@ -90,19 +90,19 @@ def __init__(self, ls):
foo = [1, 2, 3]
bar = [4, 5, 6]
for _ in foo:
- foo *= 2
- foo += bar
- foo[1] = 9 #todo
- foo[1:2] = bar
- foo[1:2:3] = bar
+ foo *= 2 # B909: 4
+ foo += bar # B909: 4
+ foo[1] = 9 #todo # B909: 4
+ foo[1:2] = bar # B909: 4
+ foo[1:2:3] = bar # B909: 4
foo = {1,2,3}
bar = {4,5,6}
for _ in foo:
- foo |= bar
- foo &= bar
- foo -= bar
- foo ^= bar
+ foo |= bar # B909: 4
+ foo &= bar # B909: 4
+ foo -= bar # B909: 4
+ foo ^= bar # B909: 4
# more tests for unconditional breaks
@@ -122,7 +122,7 @@ def __init__(self, ls):
# should error (?)
for _ in foo:
- foo.remove(1)
+ foo.remove(1) # B909: 4
if bar:
bar.remove(1)
break
diff --git a/tests/b910.py b/tests/b910.py
index 0b98bc6..3ad0c51 100644
--- a/tests/b910.py
+++ b/tests/b910.py
@@ -1,8 +1,8 @@
from collections import defaultdict
-a = defaultdict(int)
+a = defaultdict(int) # B910: 4
b = defaultdict(float)
c = defaultdict(bool)
d = defaultdict(str)
e = defaultdict()
-f = defaultdict(int)
+f = defaultdict(int) # B910: 4
diff --git a/tests/b911_py313.py b/tests/b911_py313.py
index 61107c8..3287fa4 100644
--- a/tests/b911_py313.py
+++ b/tests/b911_py313.py
@@ -2,12 +2,12 @@
from itertools import batched
# Expect B911
-batched(range(3), 2)
-batched(range(3), n=2)
-batched(iterable=range(3), n=2)
-itertools.batched(range(3), 2)
-itertools.batched(range(3), n=2)
-itertools.batched(iterable=range(3), n=2)
+batched(range(3), 2) # B911: 0
+batched(range(3), n=2) # B911: 0
+batched(iterable=range(3), n=2) # B911: 0
+itertools.batched(range(3), 2) # B911: 0
+itertools.batched(range(3), n=2) # B911: 0
+itertools.batched(iterable=range(3), n=2) # B911: 0
# OK
batched(range(3), 2, strict=True)
diff --git a/tests/b950.py b/tests/b950.py
index d5b7e4e..50e54a5 100644
--- a/tests/b950.py
+++ b/tests/b950.py
@@ -4,21 +4,21 @@
"line is fine"
" line is fine "
" line is still fine "
-" line is no longer fine by any measures, yup"
+" line is no longer fine by any measures, yup" # B950: 113, 113, 79
"line is fine again"
# Ensure URL/path on it's own line is fine
"https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com"
-"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com"
+"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com" # B950: 125, 125, 79
# https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com
-# NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com
+# NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # B950: 125, 125, 79
#
#: Okay
# This
# almost_empty_line_too_long
# This
-# almost_empty_line_too_long
+# almost_empty_line_too_long # B950: 118, 118, 79
# Long empty line
"""
@@ -33,5 +33,5 @@
"https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # type: ignore[some-code]"
"https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # type: ignore[some-code] # noqa: F401"
"https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # noqa: F401 # type:ignore[some-code]"
-"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # noqa"
-"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # type: ignore"
+"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # noqa" # B950: 132, 132, 79
+"NOT OK: https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo.com # type: ignore" # B950: 140, 140, 79
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index 44426bf..b67fc99 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -1,858 +1,85 @@
import ast
import itertools
import os
+import re
import site
import subprocess
-import sys
import unittest
from argparse import Namespace
from pathlib import Path
+import pytest
+
from bugbear import (
- B001,
- B002,
- B003,
- B004,
- B005,
- B006,
- B007,
- B008,
- B009,
- B010,
- B011,
- B012,
- B013,
- B014,
- B015,
- B016,
- B017,
- B018,
- B019,
- B020,
- B021,
- B022,
- B023,
- B024,
- B025,
- B026,
- B027,
- B028,
- B029,
- B030,
- B031,
- B032,
- B033,
- B034,
- B035,
- B036,
- B037,
- B039,
- B040,
- B041,
- B901,
- B902,
- B903,
- B904,
- B905,
- B906,
- B907,
- B908,
- B909,
- B910,
- B911,
- B950,
BugBearChecker,
BugBearVisitor,
+ error,
+ error_codes,
+)
+
+test_files: list[tuple[str, Path]] = sorted(
+ (f.stem.upper(), f)
+ for f in (Path(__file__).parent).iterdir()
+ if re.fullmatch(r"b\d\d\d.*\.py", f.name)
)
+@pytest.mark.parametrize(("test", "path"), test_files, ids=[f[0] for f in test_files])
+def test_eval(
+ test: str,
+ path: Path,
+):
+ print(test, path)
+ content = path.read_text()
+ expected, options = _parse_eval_file(test, content)
+ assert expected
+ tuple_expected = [
+ (e.lineno, e.col, e.message.format(*e.vars), e.type) for e in expected
+ ]
+
+ bbc = BugBearChecker(filename=str(path), options=options)
+ errors = [e for e in bbc.run() if (test == "B950" or not e[2].startswith("B950"))]
+ errors.sort()
+ assert errors == tuple_expected
+
+
+def _parse_eval_file(test: str, content: str) -> tuple[list[error], Namespace | None]:
+
+ # error_class: Any = eval(error_code)
+ expected: list[error] = []
+ options: Namespace | None = None
+
+ for lineno, line in enumerate(content.split("\n"), start=1):
+ if line.startswith("# OPTIONS:"):
+ options = eval(f"Namespace({line[10:]})")
+
+ # skip commented out lines
+ if not line or (line[0] == "#" and test != "B950"):
+ continue
+
+ # skip lines that *don't* have a comment
+ if "#" not in line:
+ continue
+
+ # get text between `error:` and (end of line or another comment)
+ k = re.findall(r"(B\d\d\d):([^#]*)(?=#|$)", line)
+ for err_code, err_args in k:
+ # evaluate the arguments as if in a tuple
+ args = eval(f"({err_args},)")
+ assert args, "you must specify at least column"
+ col, *vars = args
+ assert isinstance(col, int), "column must be an int"
+ error_class = error_codes[err_code]
+ expected.append(error_class(lineno, col, vars=vars))
+ return expected, options
+
+
class BugbearTestCase(unittest.TestCase):
maxDiff = None
def errors(self, *errors):
return [BugBearChecker.adapt_error(e) for e in errors]
- def test_b001(self):
- filename = Path(__file__).absolute().parent / "b001.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B001(8, 0),
- B001(40, 4),
- )
- self.assertEqual(errors, expected)
-
- def test_b002(self):
- filename = Path(__file__).absolute().parent / "b002.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B002(14, 8), B002(19, 11)))
-
- def test_b003(self):
- filename = Path(__file__).absolute().parent / "b003.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B003(9, 0)))
-
- def test_b004(self):
- filename = Path(__file__).absolute().parent / "b004.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B004(3, 7), B004(5, 7)))
-
- def test_b005(self):
- filename = Path(__file__).absolute().parent / "b005.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B005(4, 0),
- B005(7, 0),
- B005(10, 0),
- B005(13, 0),
- B005(16, 0),
- B005(19, 0),
- ),
- )
-
- def test_b006_b008(self):
- filename = Path(__file__).absolute().parent / "b006_b008.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B006(58, 24),
- B006(61, 29),
- B006(64, 19),
- B006(67, 19),
- B006(70, 31),
- B006(73, 25),
- B006(77, 45),
- B008(77, 60),
- B006(81, 45),
- B008(81, 63),
- B006(85, 44),
- B008(85, 59),
- B006(89, 32),
- B008(100, 38),
- B008(103, 11),
- B008(103, 31),
- B008(107, 29),
- B008(149, 29),
- B008(153, 44),
- B006(159, 19),
- B008(159, 20),
- B008(159, 30),
- B008(165, 21),
- B008(170, 18),
- B008(170, 36),
- ),
- )
-
- def test_b007(self):
- filename = Path(__file__).absolute().parent / "b007.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B007(6, 4, vars=("i",)),
- B007(18, 12, vars=("k",)),
- B007(30, 4, vars=("i",)),
- B007(30, 12, vars=("k",)),
- ),
- )
-
- def test_b008_extended(self):
- filename = Path(__file__).absolute().parent / "b008_extended.py"
-
- mock_options = Namespace(
- extend_immutable_calls=["fastapi.Depends", "fastapi.Query"]
- )
-
- bbc = BugBearChecker(filename=str(filename), options=mock_options)
- errors = list(bbc.run())
-
- self.assertEqual(
- errors,
- self.errors(B008(13, 66)),
- )
-
- def test_b009_b010(self):
- filename = Path(__file__).absolute().parent / "b009_b010.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- all_errors = [
- B009(17, 0),
- B009(18, 0),
- B009(19, 0),
- B010(28, 0),
- B010(29, 0),
- B010(30, 0),
- B009(45, 14),
- ]
- self.assertEqual(errors, self.errors(*all_errors))
-
- def test_b011(self):
- filename = Path(__file__).absolute().parent / "b011.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors, self.errors(B011(8, 0, vars=("i",)), B011(10, 0, vars=("k",)))
- )
-
- def test_b012(self):
- filename = Path(__file__).absolute().parent / "b012.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- all_errors = [
- B012(5, 8, vars=("",)),
- B012(13, 12, vars=("",)),
- B012(21, 12, vars=("",)),
- B012(31, 12, vars=("",)),
- B012(44, 20, vars=("",)),
- B012(66, 12, vars=("",)),
- B012(78, 12, vars=("",)),
- B012(94, 12, vars=("",)),
- B012(101, 8, vars=("",)),
- B012(107, 8, vars=("",)),
- ]
- self.assertEqual(errors, self.errors(*all_errors))
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b012_py311(self):
- filename = Path(__file__).absolute().parent / "b012_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- all_errors = [
- B012(7, 8, vars=("*",)),
- B012(17, 12, vars=("*",)),
- B012(27, 12, vars=("*",)),
- ]
- self.assertEqual(errors, self.errors(*all_errors))
-
- def test_b013(self):
- filename = Path(__file__).absolute().parent / "b013.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B013(10, 0, vars=("ValueError", "")),
- B013(32, 0, vars=("re.error", "")),
- )
- self.assertEqual(errors, expected)
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b013_py311(self):
- filename = Path(__file__).absolute().parent / "b013_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B013(10, 0, vars=("ValueError", "*")),
- B013(32, 0, vars=("re.error", "*")),
- )
- self.assertEqual(errors, expected)
-
- def test_b014(self):
- filename = Path(__file__).absolute().parent / "b014.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B014(11, 0, vars=("Exception, TypeError", "", "Exception", "")),
- B014(17, 0, vars=("OSError, OSError", " as err", "OSError", "")),
- B014(28, 0, vars=("MyError, MyError", "", "MyError", "")),
- B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException", "")),
- B014(49, 0, vars=("re.error, re.error", "", "re.error", "")),
- B014(
- 56,
- 0,
- vars=("IOError, EnvironmentError, OSError", "", "OSError", ""),
- ),
- B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError", "")),
- )
- self.assertEqual(errors, expected)
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b014_py311(self):
- filename = Path(__file__).absolute().parent / "b014_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B014(11, 0, vars=("Exception, TypeError", "", "Exception", "*")),
- B014(17, 0, vars=("OSError, OSError", " as err", "OSError", "*")),
- B014(28, 0, vars=("MyError, MyError", "", "MyError", "*")),
- B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException", "*")),
- B014(49, 0, vars=("re.error, re.error", "", "re.error", "*")),
- B014(
- 56,
- 0,
- vars=("IOError, EnvironmentError, OSError", "", "OSError", "*"),
- ),
- B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError", "*")),
- )
- self.assertEqual(errors, expected)
-
- def test_b015(self):
- filename = Path(__file__).absolute().parent / "b015.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(B015(8, 0), B015(12, 0), B015(22, 4), B015(29, 4))
- self.assertEqual(errors, expected)
-
- def test_b016(self):
- filename = Path(__file__).absolute().parent / "b016.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(B016(6, 0), B016(7, 0), B016(8, 0), B016(10, 0))
- self.assertEqual(errors, expected)
-
- def test_b017(self):
- filename = Path(__file__).absolute().parent / "b017.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B017(26, 8),
- B017(28, 8),
- B017(30, 8),
- B017(32, 8),
- B017(73, 8),
- B017(75, 8),
- B017(77, 8),
- )
- self.assertEqual(errors, expected)
-
- def test_b018_functions(self):
- filename = Path(__file__).absolute().parent / "b018_functions.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
-
- expected = [
- B018(15, 4, vars=("Constant",)),
- B018(16, 4, vars=("Constant",)),
- B018(17, 4, vars=("Constant",)),
- B018(18, 4, vars=("Constant",)),
- B018(19, 4, vars=("Constant",)),
- B018(20, 4, vars=("Constant",)),
- B018(21, 4, vars=("Constant",)),
- B018(22, 4, vars=("List",)),
- B018(23, 4, vars=("Set",)),
- B018(24, 4, vars=("Dict",)),
- B018(28, 4, vars=("Constant",)),
- B018(31, 4, vars=("Constant",)),
- B018(32, 4, vars=("Tuple",)),
- B018(33, 4, vars=("Tuple",)),
- ]
-
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b018_classes(self):
- filename = Path(__file__).absolute().parent / "b018_classes.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
-
- expected = [
- B018(16, 4, vars=("Constant",)),
- B018(17, 4, vars=("Constant",)),
- B018(18, 4, vars=("Constant",)),
- B018(19, 4, vars=("Constant",)),
- B018(20, 4, vars=("Constant",)),
- B018(21, 4, vars=("Constant",)),
- B018(22, 4, vars=("Constant",)),
- B018(23, 4, vars=("List",)),
- B018(24, 4, vars=("Set",)),
- B018(25, 4, vars=("Dict",)),
- B018(29, 4, vars=("Constant",)),
- B018(32, 4, vars=("Constant",)),
- B018(33, 4, vars=("Tuple",)),
- B018(34, 4, vars=("Tuple",)),
- ]
-
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b018_modules(self):
- filename = Path(__file__).absolute().parent / "b018_modules.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
-
- expected = [
- B018(9, 0, vars=("Constant",)),
- B018(10, 0, vars=("Constant",)),
- B018(11, 0, vars=("Constant",)),
- B018(12, 0, vars=("Constant",)),
- B018(13, 0, vars=("Constant",)),
- B018(14, 0, vars=("Constant",)),
- B018(15, 0, vars=("Constant",)),
- B018(16, 0, vars=("List",)),
- B018(17, 0, vars=("Set",)),
- B018(18, 0, vars=("Dict",)),
- B018(19, 0, vars=("Tuple",)),
- B018(20, 0, vars=("Tuple",)),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b018_nested(self):
- filename = Path(__file__).absolute().parent / "b018_nested.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
-
- expected = [
- B018(3, 0, vars=("Constant",)),
- B018(8, 4, vars=("Constant",)),
- B018(14, 4, vars=("List",)),
- B018(18, 8, vars=("Tuple",)),
- B018(22, 12, vars=("Constant",)),
- B018(25, 16, vars=("Constant",)),
- B018(28, 20, vars=("Set",)),
- B018(31, 20, vars=("Set",)),
- B018(34, 20, vars=("Set",)),
- B018(36, 24, vars=("Constant",)),
- B018(40, 24, vars=("Constant",)),
- B018(42, 24, vars=("Constant",)),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b019(self):
- filename = Path(__file__).absolute().parent / "b019.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B019(61, 5),
- B019(64, 5),
- B019(67, 5),
- B019(70, 5),
- B019(73, 5),
- B019(76, 5),
- B019(79, 5),
- B019(82, 5),
- ),
- )
-
- def test_b020(self):
- filename = Path(__file__).absolute().parent / "b020.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = [e for e in bbc.run() if e[2][:4] == "B020"]
- self.assertEqual(
- errors,
- self.errors(
- B020(8, 4, vars=("items",)),
- B020(21, 9, vars=("values",)),
- B020(36, 4, vars=("vars",)),
- ),
- )
-
- def test_b021_classes(self):
- filename = Path(__file__).absolute().parent / "b021.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B021(14, 4),
- B021(22, 4),
- B021(30, 4),
- B021(38, 4),
- B021(46, 4),
- B021(54, 4),
- B021(62, 4),
- B021(70, 4),
- B021(74, 4),
- )
- self.assertEqual(errors, expected)
-
- def test_b022(self):
- filename = Path(__file__).absolute().parent / "b022.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B022(8, 0)))
-
- def test_b023(self):
- filename = Path(__file__).absolute().parent / "b023.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B023(13, 29, vars=("x",)),
- B023(14, 29, vars=("y",)),
- B023(17, 15, vars=("x",)),
- B023(29, 18, vars=("x",)),
- B023(30, 18, vars=("x",)),
- B023(31, 18, vars=("x",)),
- B023(32, 21, vars=("x",)),
- B023(41, 33, vars=("x",)),
- B023(43, 13, vars=("x",)),
- B023(51, 29, vars=("a",)),
- B023(52, 29, vars=("a_",)),
- B023(53, 29, vars=("b",)),
- B023(54, 29, vars=("c",)),
- B023(62, 16, vars=("j",)),
- B023(62, 20, vars=("k",)),
- B023(69, 9, vars=("l",)),
- B023(114, 23, vars=("x",)),
- B023(115, 26, vars=("x",)),
- B023(116, 36, vars=("x",)),
- B023(117, 37, vars=("x",)),
- B023(118, 36, vars=("x",)),
- B023(169, 28, vars=("name",)), # known false alarm
- B023(172, 28, vars=("i",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b024(self):
- filename = Path(__file__).absolute().parent / "b024.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B024(17, 0, vars=("Base_1",)),
- B024(52, 0, vars=("Base_7",)),
- B024(58, 0, vars=("MetaBase_1",)),
- B024(69, 0, vars=("abc_Base_1",)),
- B024(74, 0, vars=("abc_Base_2",)),
- B024(123, 0, vars=("abc_assign_class_variable",)),
- B024(129, 0, vars=("abc_annassign_class_variable",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b025(self):
- filename = Path(__file__).absolute().parent / "b025.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B025(15, 0, vars=("ValueError",)),
- B025(22, 0, vars=("pickle.PickleError",)),
- B025(31, 0, vars=("TypeError",)),
- B025(31, 0, vars=("ValueError",)),
- ),
- )
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b025_py311(self):
- filename = Path(__file__).absolute().parent / "b025_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B025(15, 0, vars=("ValueError",)),
- B025(22, 0, vars=("pickle.PickleError",)),
- B025(31, 0, vars=("TypeError",)),
- B025(31, 0, vars=("ValueError",)),
- ),
- )
-
- def test_b026(self):
- filename = Path(__file__).absolute().parent / "b026.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B026(15, 15),
- B026(16, 15),
- B026(17, 26),
- B026(18, 37),
- B026(19, 15),
- B026(19, 25),
- B026(20, 25),
- B026(21, 19),
- ),
- )
-
- def test_b027(self):
- filename = Path(__file__).absolute().parent / "b027.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B027(13, 4, vars=("empty_1",)),
- B027(16, 4, vars=("empty_2",)),
- B027(19, 4, vars=("empty_3",)),
- B027(23, 4, vars=("empty_4",)),
- B027(31, 4, vars=("empty_5",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b028(self):
- filename = Path(__file__).absolute().parent / "b028.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(B028(8, 0), B028(9, 0))
- self.assertEqual(errors, expected)
-
- def test_b029(self):
- filename = Path(__file__).absolute().parent / "b029.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B029(8, 0, vars=("",)),
- B029(13, 0, vars=("",)),
- )
- self.assertEqual(errors, expected)
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b029_py311(self):
- filename = Path(__file__).absolute().parent / "b029_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B029(8, 0, vars=("*",)),
- B029(13, 0, vars=("*",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b030(self):
- filename = Path(__file__).absolute().parent / "b030.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B030(3, 0),
- B030(13, 0),
- B030(18, 0),
- )
- self.assertEqual(errors, expected)
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b030_py311(self):
- filename = Path(__file__).absolute().parent / "b030_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B030(3, 0),
- B030(13, 0),
- B030(18, 0),
- )
- self.assertEqual(errors, expected)
-
- def test_b031(self):
- filename = Path(__file__).absolute().parent / "b031.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B031(31, 36, vars=("section_items",)),
- B031(35, 30, vars=("section_items",)),
- B031(44, 36, vars=("section_items",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b032(self):
- filename = Path(__file__).absolute().parent / "b032.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B032(9, 0),
- B032(10, 0),
- B032(12, 0),
- B032(13, 0),
- B032(16, 0),
- B032(17, 0),
- B032(18, 0),
- B032(19, 0),
- )
- self.assertEqual(errors, expected)
-
- def test_b033(self):
- filename = Path(__file__).absolute().parent / "b033.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B033(6, 17, vars=("3",)),
- B033(7, 23, vars=("'c'",)),
- B033(8, 21, vars=("True",)),
- B033(9, 20, vars=("None",)),
- B033(10, 11, vars=("3.0",)),
- B033(11, 11, vars=("True",)),
- B033(12, 11, vars=("False",)),
- B033(16, 4, vars=("True",)),
- B033(18, 4, vars=("False",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b034(self):
- filename = Path(__file__).absolute().parent / "b034.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B034(5, 24, vars=("sub", "count")),
- B034(6, 24, vars=("sub", "count")),
- B034(7, 24, vars=("sub", "count")),
- B034(8, 25, vars=("subn", "count")),
- B034(9, 25, vars=("subn", "count")),
- B034(10, 25, vars=("subn", "count")),
- B034(11, 25, vars=("split", "maxsplit")),
- B034(12, 25, vars=("split", "maxsplit")),
- B034(13, 25, vars=("split", "maxsplit")),
- )
- self.assertEqual(errors, expected)
-
- def test_b035(self):
- filename = Path(__file__).absolute().parent / "b035.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B035(6, 21, vars=("a",)),
- B035(7, 21, vars=(1,)),
- B035(19, 12, vars=("a",)),
- B035(25, 21, vars=("CONST_KEY_VAR",)),
- B035(35, 33, vars=("v3",)),
- )
- self.assertEqual(errors, expected)
-
- def test_b036(self) -> None:
- filename = Path(__file__).absolute().parent / "b036.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B036(4, 0),
- B036(11, 0),
- B036(20, 0),
- B036(33, 0),
- B036(50, 0),
- B036(58, 0),
- )
- self.assertEqual(errors, expected)
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b036_py311(self) -> None:
- filename = Path(__file__).absolute().parent / "b036_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B036(4, 0),
- B036(11, 0),
- B036(20, 0),
- B036(33, 0),
- B036(50, 0),
- B036(58, 0),
- )
- self.assertEqual(errors, expected)
-
- def test_b037(self) -> None:
- filename = Path(__file__).absolute().parent / "b037.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B037(4, 8),
- B037(11, 12),
- B037(15, 12),
- B037(23, 8),
- B037(29, 8),
- B037(33, 8),
- )
- self.assertEqual(errors, expected)
-
- def test_b039(self) -> None:
- filename = Path(__file__).absolute().parent / "b039.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B039(5, 25),
- B039(6, 25),
- B039(7, 25),
- B039(8, 25),
- B039(9, 37),
- )
- self.assertEqual(errors, expected)
-
- def test_b040(self) -> None:
- filename = Path(__file__).absolute().parent / "b040.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B040(7, 0),
- B040(24, 0),
- B040(83, 0),
- B040(107, 0),
- B040(114, 0),
- B040(124, 0),
- )
- self.assertEqual(errors, expected)
-
- def test_b041(self) -> None:
- filename = Path(__file__).absolute().parent / "b041.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B041(2, 18),
- B041(3, 18),
- B041(3, 37),
- B041(4, 18),
- B041(4, 28),
- B041(5, 14),
- B041(6, 17),
- B041(7, 17),
- B041(8, 14),
- )
- self.assertEqual(errors, expected)
-
- def test_b908(self):
- filename = Path(__file__).absolute().parent / "b908.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = self.errors(
- B908(7, 0),
- B908(15, 8),
- B908(21, 8),
- B908(27, 8),
- B017(37, 0),
- B908(37, 0),
- B908(41, 0),
- B908(45, 0),
- B017(56, 0),
- )
- self.assertEqual(errors, expected)
-
- def test_b907(self):
- filename = Path(__file__).absolute().parent / "b907.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- py39 = sys.version_info >= (3, 9)
- py312 = sys.version_info >= (3, 12)
-
- def on_py312(number):
- """F-string nodes have column numbers set to 0 on 60k permutations
# see format spec at
# https://docs.python.org/3/library/string.html#format-specification-mini-language
@@ -907,177 +134,22 @@ def test_b907_format_specifier_permutations(self):
visitor.errors == []
), f"b907 raised for {format_spec} that would look different with !r"
- def test_b901(self):
- filename = Path(__file__).absolute().parent / "b901.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(B901(8, 8), B901(35, 4), B901(82, 8), B901(89, 4), B901(94, 4)),
- )
-
- def test_b902(self):
- filename = Path(__file__).absolute().parent / "b902.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B902(22, 17, vars=("'i_am_special'", "instance", "self")),
- B902(24, 30, vars=("'cls'", "instance", "self")),
- B902(26, 4, vars=("(none)", "instance", "self")),
- B902(29, 12, vars=("'self'", "class", "cls")),
- B902(31, 22, vars=("*args", "instance", "self")),
- B902(36, 30, vars=("**kwargs", "instance", "self")),
- B902(38, 32, vars=("*, self", "instance", "self")),
- B902(40, 44, vars=("*, self", "instance", "self")),
- B902(52, 17, vars=("'self'", "metaclass instance", "cls")),
- B902(55, 20, vars=("'cls'", "metaclass class", "metacls")),
- ),
- )
-
- def test_b902_extended(self):
- filename = Path(__file__).absolute().parent / "b902_extended.py"
-
- mock_options = Namespace(
- classmethod_decorators=["mylibrary.makeclassmethod", "validator"],
- select=["B902"],
- )
- bbc = BugBearChecker(filename=str(filename), options=mock_options)
- errors = list(bbc.run())
-
- self.assertEqual(
- errors,
- self.errors(
- B902(5, 22, vars=("'self'", "class", "cls")),
- B902(8, 28, vars=("'self'", "class", "cls")),
- B902(11, 30, vars=("'self'", "class", "cls")),
- B902(14, 27, vars=("'cls'", "instance", "self")),
- B902(18, 13, vars=("'cls'", "instance", "self")),
- B902(22, 13, vars=("'cls'", "instance", "self")),
- B902(26, 13, vars=("'cls'", "instance", "self")),
- B902(30, 13, vars=("'cls'", "instance", "self")),
- # metaclass
- B902(59, 22, vars=("'cls'", "metaclass class", "metacls")),
- B902(62, 28, vars=("'cls'", "metaclass class", "metacls")),
- B902(65, 30, vars=("'cls'", "metaclass class", "metacls")),
- B902(68, 27, vars=("'metacls'", "metaclass instance", "cls")),
- B902(72, 13, vars=("'metacls'", "metaclass instance", "cls")),
- B902(76, 13, vars=("'metacls'", "metaclass instance", "cls")),
- B902(80, 13, vars=("'metacls'", "metaclass instance", "cls")),
- B902(84, 13, vars=("'metacls'", "metaclass instance", "cls")),
- ),
- )
-
- def test_b902_py38(self):
- filename = Path(__file__).absolute().parent / "b902_py38.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B902(22, 17, vars=("'i_am_special'", "instance", "self")),
- B902(24, 30, vars=("'cls'", "instance", "self")),
- B902(26, 4, vars=("(none)", "instance", "self")),
- B902(29, 12, vars=("'self'", "class", "cls")),
- B902(31, 22, vars=("*args", "instance", "self")),
- B902(36, 30, vars=("**kwargs", "instance", "self")),
- B902(38, 32, vars=("*, self", "instance", "self")),
- B902(40, 44, vars=("*, self", "instance", "self")),
- B902(52, 17, vars=("'self'", "metaclass instance", "cls")),
- B902(55, 20, vars=("'cls'", "metaclass class", "metacls")),
- ),
- )
-
- def test_b903(self):
- filename = Path(__file__).absolute().parent / "b903.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(errors, self.errors(B903(31, 0), B903(37, 0)))
-
- def test_b904(self):
- filename = Path(__file__).absolute().parent / "b904.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = [
- B904(10, 8, vars=("",)),
- B904(11, 4, vars=("",)),
- B904(16, 4, vars=("",)),
- B904(55, 16, vars=("",)),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- @unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
- def test_b904_py311(self):
- filename = Path(__file__).absolute().parent / "b904_py311.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = [
- B904(10, 8, vars=("*",)),
- B904(11, 4, vars=("*",)),
- B904(16, 4, vars=("*",)),
- B904(55, 16, vars=("*",)),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- @unittest.skipIf(sys.version_info < (3, 10), "requires 3.10+")
- def test_b905(self):
- filename = Path(__file__).absolute().parent / "b905_py310.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = [
- B905(1, 0),
- B905(2, 0),
- B905(3, 0),
- B905(4, 0),
- B905(4, 15),
- B905(5, 4),
- B905(6, 0),
- B905(21, 0),
- B905(22, 0),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b906(self):
- filename = Path(__file__).absolute().parent / "b906.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = [
- B906(9, 0),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b950(self):
- filename = Path(__file__).absolute().parent / "b950.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- self.assertEqual(
- errors,
- self.errors(
- B950(7, 92, vars=(92, 79)),
- B950(12, 103, vars=(103, 79)),
- B950(14, 103, vars=(103, 79)),
- B950(21, 97, vars=(97, 79)),
- B950(36, 104, vars=(104, 79)),
- B950(37, 104, vars=(104, 79)),
- ),
- )
-
def test_b9_select(self):
filename = Path(__file__).absolute().parent / "b950.py"
mock_options = Namespace(select=["B950"])
bbc = BugBearChecker(filename=str(filename), options=mock_options)
errors = list(bbc.run())
+ B950 = error_codes["B950"]
self.assertEqual(
errors,
self.errors(
- B950(7, 92, vars=(92, 79)),
- B950(12, 103, vars=(103, 79)),
- B950(14, 103, vars=(103, 79)),
- B950(21, 97, vars=(97, 79)),
- B950(36, 104, vars=(104, 79)),
- B950(37, 104, vars=(104, 79)),
+ B950(7, 113, vars=(113, 79)),
+ B950(12, 125, vars=(125, 79)),
+ B950(14, 125, vars=(125, 79)),
+ B950(21, 118, vars=(118, 79)),
+ B950(36, 132, vars=(132, 79)),
+ B950(37, 140, vars=(140, 79)),
),
)
@@ -1089,15 +161,16 @@ def test_b9_extend_select(self):
mock_options = Namespace(select=[], extend_select=["B950"])
bbc = BugBearChecker(filename=str(filename), options=mock_options)
errors = list(bbc.run())
+ B950 = error_codes["B950"]
self.assertEqual(
errors,
self.errors(
- B950(7, 92, vars=(92, 79)),
- B950(12, 103, vars=(103, 79)),
- B950(14, 103, vars=(103, 79)),
- B950(21, 97, vars=(97, 79)),
- B950(36, 104, vars=(104, 79)),
- B950(37, 104, vars=(104, 79)),
+ B950(7, 113, vars=(113, 79)),
+ B950(12, 125, vars=(125, 79)),
+ B950(14, 125, vars=(125, 79)),
+ B950(21, 118, vars=(118, 79)),
+ B950(36, 132, vars=(132, 79)),
+ B950(37, 140, vars=(140, 79)),
),
)
@@ -1133,73 +206,6 @@ def test_selfclean_test_bugbear(self):
self.assertEqual(proc.stdout, b"")
self.assertEqual(proc.stderr, b"")
- 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())
- expected = [
- B909(12, 4),
- B909(13, 4),
- B909(14, 4),
- B909(15, 4),
- B909(16, 4),
- B909(17, 4),
- B909(18, 4),
- B909(19, 4),
- B909(20, 4),
- B909(21, 4),
- B909(25, 8),
- B909(47, 4),
- B909(48, 4),
- B909(49, 4),
- B909(62, 4),
- B909(63, 4),
- B909(64, 4),
- B909(65, 4),
- B909(66, 4),
- B909(67, 4),
- B909(84, 4),
- B909(85, 4),
- B909(93, 4),
- B909(94, 4),
- B909(95, 4),
- B909(96, 4),
- B909(97, 4),
- B909(102, 4),
- B909(103, 4),
- B909(104, 4),
- B909(105, 4),
- B909(125, 4),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- def test_b910(self):
- filename = Path(__file__).absolute().parent / "b910.py"
- mock_options = Namespace(select=[], extend_select=["B910"])
- bbc = BugBearChecker(filename=str(filename), options=mock_options)
- errors = list(bbc.run())
- expected = [
- B910(3, 4),
- B910(8, 4),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
- @unittest.skipIf(sys.version_info < (3, 13), "requires 3.13+")
- def test_b911(self):
- filename = Path(__file__).absolute().parent / "b911_py313.py"
- bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
- expected = [
- B911(5, 0),
- B911(6, 0),
- B911(7, 0),
- B911(8, 0),
- B911(9, 0),
- B911(10, 0),
- ]
- self.assertEqual(errors, self.errors(*expected))
-
class TestFuzz(unittest.TestCase):
from hypothesis import HealthCheck, given, settings
diff --git a/tox.ini b/tox.ini
index 5a58747..7f0b7e7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,7 +10,7 @@ python =
3.11: py311,pep8_naming
3.12: py312
3.13: py313,mypy
- # blocked by https://github.com/PyO3/pyo3/issues/5107
+ # blocked by https://github.com/ijl/orjson/issues/569
# 3.14: py314
[testenv]
From 536c8b4f35e8dc9a09d433806800500e5228e578 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 22 May 2025 17:08:25 +0200
Subject: [PATCH 2/7] use pytest
---
tests/test_bugbear.py | 5 +----
tox.ini | 6 ++++--
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index b67fc99..4fac0fe 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -211,6 +211,7 @@ class TestFuzz(unittest.TestCase):
from hypothesis import HealthCheck, given, settings
from hypothesmith import from_grammar
+ @pytest.mark.filterwarnings("ignore::SyntaxWarning")
@settings(suppress_health_check=[HealthCheck.too_slow])
@given(from_grammar().map(ast.parse))
def test_does_not_crash_on_any_valid_code(self, syntax_tree):
@@ -250,7 +251,3 @@ def test_does_not_crash_on_call_in_except_statement(self):
"foo = lambda: IOError\ntry:\n ...\nexcept (foo(),):\n ...\n"
)
BugBearVisitor(filename="", lines=[]).visit(syntax_tree)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tox.ini b/tox.ini
index 7f0b7e7..9e2d842 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,8 +19,9 @@ deps =
coverage
hypothesis
hypothesmith
+ pytest
commands =
- coverage run tests/test_bugbear.py {posargs}
+ coverage run -m pytest tests/test_bugbear.py {posargs}
coverage report -m
[testenv:pep8_naming]
@@ -28,9 +29,10 @@ deps =
coverage
hypothesis
hypothesmith
+ pytest
pep8-naming
commands =
- coverage run tests/test_bugbear.py -k b902 {posargs}
+ coverage run -m pytest tests/test_bugbear.py -k b902 {posargs}
coverage report -m
[testenv:{py39-,py310-,py311-,py312-,py313-}mypy]
From 428d3b671f4c71c50c08e171e2f3a4058a4b9499 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 22 May 2025 17:15:59 +0200
Subject: [PATCH 3/7] fix lower python versions
---
tests/test_bugbear.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index 4fac0fe..1b31e9b 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -1,9 +1,12 @@
+from __future__ import annotations
+
import ast
import itertools
import os
import re
import site
import subprocess
+import sys
import unittest
from argparse import Namespace
from pathlib import Path
@@ -24,12 +27,23 @@
)
+def check_version(test: str) -> None:
+ python_version = re.search(r"(?<=_PY)\d*", test)
+ if python_version:
+ version_str = python_version.group()
+ major, minor = version_str[0], version_str[1:]
+ v_i = sys.version_info
+ if (v_i.major, v_i.minor) < (int(major), int(minor)):
+ pytest.skip(f"python version {v_i} smaller than {major}, {minor}")
+
+
@pytest.mark.parametrize(("test", "path"), test_files, ids=[f[0] for f in test_files])
def test_eval(
test: str,
path: Path,
):
- print(test, path)
+ check_version(test)
+
content = path.read_text()
expected, options = _parse_eval_file(test, content)
assert expected
From ebf48f7a1fd1a0dc222b3fad0eb398dc002f2d43 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 23 May 2025 12:23:51 +0200
Subject: [PATCH 4/7] add instructions to DEVELOPMENT.md, move eval files to
tests/eval_files
---
.pre-commit-config.yaml | 2 +-
DEVELOPMENT.md | 31 +++++++++++++++++++++---
pyproject.toml | 2 +-
tests/{ => eval_files}/b001.py | 0
tests/{ => eval_files}/b002.py | 0
tests/{ => eval_files}/b003.py | 0
tests/{ => eval_files}/b004.py | 0
tests/{ => eval_files}/b005.py | 0
tests/{ => eval_files}/b006_b008.py | 0
tests/{ => eval_files}/b007.py | 0
tests/{ => eval_files}/b008_extended.py | 0
tests/{ => eval_files}/b009_b010.py | 0
tests/{ => eval_files}/b011.py | 0
tests/{ => eval_files}/b012.py | 0
tests/{ => eval_files}/b012_py311.py | 0
tests/{ => eval_files}/b013.py | 0
tests/{ => eval_files}/b013_py311.py | 0
tests/{ => eval_files}/b014.py | 0
tests/{ => eval_files}/b014_py311.py | 0
tests/{ => eval_files}/b015.py | 0
tests/{ => eval_files}/b016.py | 0
tests/{ => eval_files}/b017.py | 0
tests/{ => eval_files}/b018_classes.py | 0
tests/{ => eval_files}/b018_functions.py | 0
tests/{ => eval_files}/b018_modules.py | 0
tests/{ => eval_files}/b018_nested.py | 0
tests/{ => eval_files}/b019.py | 0
tests/{ => eval_files}/b020.py | 0
tests/{ => eval_files}/b021.py | 0
tests/{ => eval_files}/b022.py | 0
tests/{ => eval_files}/b023.py | 0
tests/{ => eval_files}/b024.py | 0
tests/{ => eval_files}/b025.py | 0
tests/{ => eval_files}/b025_py311.py | 0
tests/{ => eval_files}/b026.py | 0
tests/{ => eval_files}/b027.py | 0
tests/{ => eval_files}/b028.py | 0
tests/{ => eval_files}/b029.py | 0
tests/{ => eval_files}/b029_py311.py | 0
tests/{ => eval_files}/b030.py | 0
tests/{ => eval_files}/b030_py311.py | 0
tests/{ => eval_files}/b031.py | 0
tests/{ => eval_files}/b032.py | 0
tests/{ => eval_files}/b033.py | 0
tests/{ => eval_files}/b034.py | 0
tests/{ => eval_files}/b035.py | 0
tests/{ => eval_files}/b036.py | 0
tests/{ => eval_files}/b036_py311.py | 0
tests/{ => eval_files}/b037.py | 0
tests/{ => eval_files}/b039.py | 0
tests/{ => eval_files}/b040.py | 0
tests/{ => eval_files}/b041.py | 0
tests/{ => eval_files}/b901.py | 0
tests/{ => eval_files}/b902.py | 0
tests/{ => eval_files}/b902_extended.py | 2 +-
tests/{ => eval_files}/b902_py38.py | 0
tests/{ => eval_files}/b903.py | 0
tests/{ => eval_files}/b904.py | 0
tests/{ => eval_files}/b904_py311.py | 0
tests/{ => eval_files}/b905_py310.py | 0
tests/{ => eval_files}/b906.py | 0
tests/{ => eval_files}/b907_py312.py | 0
tests/{ => eval_files}/b908.py | 0
tests/{ => eval_files}/b909.py | 0
tests/{ => eval_files}/b910.py | 0
tests/{ => eval_files}/b911_py313.py | 0
tests/{ => eval_files}/b950.py | 0
tests/test_bugbear.py | 17 +++++++++----
68 files changed, 43 insertions(+), 11 deletions(-)
rename tests/{ => eval_files}/b001.py (100%)
rename tests/{ => eval_files}/b002.py (100%)
rename tests/{ => eval_files}/b003.py (100%)
rename tests/{ => eval_files}/b004.py (100%)
rename tests/{ => eval_files}/b005.py (100%)
rename tests/{ => eval_files}/b006_b008.py (100%)
rename tests/{ => eval_files}/b007.py (100%)
rename tests/{ => eval_files}/b008_extended.py (100%)
rename tests/{ => eval_files}/b009_b010.py (100%)
rename tests/{ => eval_files}/b011.py (100%)
rename tests/{ => eval_files}/b012.py (100%)
rename tests/{ => eval_files}/b012_py311.py (100%)
rename tests/{ => eval_files}/b013.py (100%)
rename tests/{ => eval_files}/b013_py311.py (100%)
rename tests/{ => eval_files}/b014.py (100%)
rename tests/{ => eval_files}/b014_py311.py (100%)
rename tests/{ => eval_files}/b015.py (100%)
rename tests/{ => eval_files}/b016.py (100%)
rename tests/{ => eval_files}/b017.py (100%)
rename tests/{ => eval_files}/b018_classes.py (100%)
rename tests/{ => eval_files}/b018_functions.py (100%)
rename tests/{ => eval_files}/b018_modules.py (100%)
rename tests/{ => eval_files}/b018_nested.py (100%)
rename tests/{ => eval_files}/b019.py (100%)
rename tests/{ => eval_files}/b020.py (100%)
rename tests/{ => eval_files}/b021.py (100%)
rename tests/{ => eval_files}/b022.py (100%)
rename tests/{ => eval_files}/b023.py (100%)
rename tests/{ => eval_files}/b024.py (100%)
rename tests/{ => eval_files}/b025.py (100%)
rename tests/{ => eval_files}/b025_py311.py (100%)
rename tests/{ => eval_files}/b026.py (100%)
rename tests/{ => eval_files}/b027.py (100%)
rename tests/{ => eval_files}/b028.py (100%)
rename tests/{ => eval_files}/b029.py (100%)
rename tests/{ => eval_files}/b029_py311.py (100%)
rename tests/{ => eval_files}/b030.py (100%)
rename tests/{ => eval_files}/b030_py311.py (100%)
rename tests/{ => eval_files}/b031.py (100%)
rename tests/{ => eval_files}/b032.py (100%)
rename tests/{ => eval_files}/b033.py (100%)
rename tests/{ => eval_files}/b034.py (100%)
rename tests/{ => eval_files}/b035.py (100%)
rename tests/{ => eval_files}/b036.py (100%)
rename tests/{ => eval_files}/b036_py311.py (100%)
rename tests/{ => eval_files}/b037.py (100%)
rename tests/{ => eval_files}/b039.py (100%)
rename tests/{ => eval_files}/b040.py (100%)
rename tests/{ => eval_files}/b041.py (100%)
rename tests/{ => eval_files}/b901.py (100%)
rename tests/{ => eval_files}/b902.py (100%)
rename tests/{ => eval_files}/b902_extended.py (98%)
rename tests/{ => eval_files}/b902_py38.py (100%)
rename tests/{ => eval_files}/b903.py (100%)
rename tests/{ => eval_files}/b904.py (100%)
rename tests/{ => eval_files}/b904_py311.py (100%)
rename tests/{ => eval_files}/b905_py310.py (100%)
rename tests/{ => eval_files}/b906.py (100%)
rename tests/{ => eval_files}/b907_py312.py (100%)
rename tests/{ => eval_files}/b908.py (100%)
rename tests/{ => eval_files}/b909.py (100%)
rename tests/{ => eval_files}/b910.py (100%)
rename tests/{ => eval_files}/b911_py313.py (100%)
rename tests/{ => eval_files}/b950.py (100%)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index be47cc6..db4703b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -19,7 +19,7 @@ repos:
hooks:
- id: flake8
additional_dependencies: [flake8-bugbear]
- exclude: ^tests/b.*
+ exclude: ^tests/eval_files/.*
- repo: https://github.com/rstcheck/rstcheck
rev: v6.2.4
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 6ebde17..9d3c8a6 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -42,12 +42,37 @@ cd flake8-bugbear
/path/to/venv/bin/pip install -e '.[dev]'
```
+## Writing Tests
+
+flake8-bugbear has a test runner that will go through all files in `tests/eval_files/`, run them through the linter, and check that they emit the appropriate error messages.
+
+The expected errors are specified by adding comments on the line where the error is expected, using the format `# : [, ][, ][...]`. E.g.
+```python
+x = ++n # B002: 4
+try:
+ ...
+except* (ValueError,): # B013: 0, "ValueError", "*"
+ ...
+```
+The error code should be in the `error_codes` dict, and the other values are passed to `eval` so should be valid python objects.
+
+You can also specify options to be passed to `BugBearChecker` with an `# OPTIONS` comments
+```python
+# OPTIONS: extend_immutable_calls=["fastapi.Depends", "fastapi.Query"]
+# OPTIONS: classmethod_decorators=["mylibrary.makeclassmethod", "validator"], select=["B902"]
+```
+
+If you specify a python version somewhere in the file name with `_pyXX`, the file will be skipped on smaller versions. Otherwise the name has no impact on the test, and you can test multiple errors in the same file.
+
+The infrastructure is based on the test runner in https://github.com/python-trio/flake8-async which has some additional features that can be pulled into flake8-bugbear when desired.
+
+
## Running Tests
flake8-bugbear uses coverage to run standard unittest tests.
```console
-/path/to/venv/bin/coverage run tests/test_bugbear.py
+/path/to/venv/bin/coverage run -m pytest tests/test_bugbear.py
```
You can also use [tox](https://tox.wiki/en/latest/index.html) to test with multiple different python versions, emulating what the CI does.
@@ -55,10 +80,10 @@ You can also use [tox](https://tox.wiki/en/latest/index.html) to test with multi
```console
/path/to/venv/bin/tox
```
-will by default run all tests on python versions 3.8 through 3.12. If you only want to test a specific version you can specify the environment with `-e`
+will by default run all tests on python versions 3.9 through 3.13. If you only want to test a specific version you can specify the environment with `-e`
```console
-/path/to/venv/bin/tox -e py38
+/path/to/venv/bin/tox -e py313
```
## Running linter
diff --git a/pyproject.toml b/pyproject.toml
index b9cacd1..01a0bc6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -72,6 +72,6 @@ profile = "black"
[tool.black]
force-exclude = '''
(
- ^/tests\/b.*
+ ^/tests\/eval_files\/.*
)
'''
diff --git a/tests/b001.py b/tests/eval_files/b001.py
similarity index 100%
rename from tests/b001.py
rename to tests/eval_files/b001.py
diff --git a/tests/b002.py b/tests/eval_files/b002.py
similarity index 100%
rename from tests/b002.py
rename to tests/eval_files/b002.py
diff --git a/tests/b003.py b/tests/eval_files/b003.py
similarity index 100%
rename from tests/b003.py
rename to tests/eval_files/b003.py
diff --git a/tests/b004.py b/tests/eval_files/b004.py
similarity index 100%
rename from tests/b004.py
rename to tests/eval_files/b004.py
diff --git a/tests/b005.py b/tests/eval_files/b005.py
similarity index 100%
rename from tests/b005.py
rename to tests/eval_files/b005.py
diff --git a/tests/b006_b008.py b/tests/eval_files/b006_b008.py
similarity index 100%
rename from tests/b006_b008.py
rename to tests/eval_files/b006_b008.py
diff --git a/tests/b007.py b/tests/eval_files/b007.py
similarity index 100%
rename from tests/b007.py
rename to tests/eval_files/b007.py
diff --git a/tests/b008_extended.py b/tests/eval_files/b008_extended.py
similarity index 100%
rename from tests/b008_extended.py
rename to tests/eval_files/b008_extended.py
diff --git a/tests/b009_b010.py b/tests/eval_files/b009_b010.py
similarity index 100%
rename from tests/b009_b010.py
rename to tests/eval_files/b009_b010.py
diff --git a/tests/b011.py b/tests/eval_files/b011.py
similarity index 100%
rename from tests/b011.py
rename to tests/eval_files/b011.py
diff --git a/tests/b012.py b/tests/eval_files/b012.py
similarity index 100%
rename from tests/b012.py
rename to tests/eval_files/b012.py
diff --git a/tests/b012_py311.py b/tests/eval_files/b012_py311.py
similarity index 100%
rename from tests/b012_py311.py
rename to tests/eval_files/b012_py311.py
diff --git a/tests/b013.py b/tests/eval_files/b013.py
similarity index 100%
rename from tests/b013.py
rename to tests/eval_files/b013.py
diff --git a/tests/b013_py311.py b/tests/eval_files/b013_py311.py
similarity index 100%
rename from tests/b013_py311.py
rename to tests/eval_files/b013_py311.py
diff --git a/tests/b014.py b/tests/eval_files/b014.py
similarity index 100%
rename from tests/b014.py
rename to tests/eval_files/b014.py
diff --git a/tests/b014_py311.py b/tests/eval_files/b014_py311.py
similarity index 100%
rename from tests/b014_py311.py
rename to tests/eval_files/b014_py311.py
diff --git a/tests/b015.py b/tests/eval_files/b015.py
similarity index 100%
rename from tests/b015.py
rename to tests/eval_files/b015.py
diff --git a/tests/b016.py b/tests/eval_files/b016.py
similarity index 100%
rename from tests/b016.py
rename to tests/eval_files/b016.py
diff --git a/tests/b017.py b/tests/eval_files/b017.py
similarity index 100%
rename from tests/b017.py
rename to tests/eval_files/b017.py
diff --git a/tests/b018_classes.py b/tests/eval_files/b018_classes.py
similarity index 100%
rename from tests/b018_classes.py
rename to tests/eval_files/b018_classes.py
diff --git a/tests/b018_functions.py b/tests/eval_files/b018_functions.py
similarity index 100%
rename from tests/b018_functions.py
rename to tests/eval_files/b018_functions.py
diff --git a/tests/b018_modules.py b/tests/eval_files/b018_modules.py
similarity index 100%
rename from tests/b018_modules.py
rename to tests/eval_files/b018_modules.py
diff --git a/tests/b018_nested.py b/tests/eval_files/b018_nested.py
similarity index 100%
rename from tests/b018_nested.py
rename to tests/eval_files/b018_nested.py
diff --git a/tests/b019.py b/tests/eval_files/b019.py
similarity index 100%
rename from tests/b019.py
rename to tests/eval_files/b019.py
diff --git a/tests/b020.py b/tests/eval_files/b020.py
similarity index 100%
rename from tests/b020.py
rename to tests/eval_files/b020.py
diff --git a/tests/b021.py b/tests/eval_files/b021.py
similarity index 100%
rename from tests/b021.py
rename to tests/eval_files/b021.py
diff --git a/tests/b022.py b/tests/eval_files/b022.py
similarity index 100%
rename from tests/b022.py
rename to tests/eval_files/b022.py
diff --git a/tests/b023.py b/tests/eval_files/b023.py
similarity index 100%
rename from tests/b023.py
rename to tests/eval_files/b023.py
diff --git a/tests/b024.py b/tests/eval_files/b024.py
similarity index 100%
rename from tests/b024.py
rename to tests/eval_files/b024.py
diff --git a/tests/b025.py b/tests/eval_files/b025.py
similarity index 100%
rename from tests/b025.py
rename to tests/eval_files/b025.py
diff --git a/tests/b025_py311.py b/tests/eval_files/b025_py311.py
similarity index 100%
rename from tests/b025_py311.py
rename to tests/eval_files/b025_py311.py
diff --git a/tests/b026.py b/tests/eval_files/b026.py
similarity index 100%
rename from tests/b026.py
rename to tests/eval_files/b026.py
diff --git a/tests/b027.py b/tests/eval_files/b027.py
similarity index 100%
rename from tests/b027.py
rename to tests/eval_files/b027.py
diff --git a/tests/b028.py b/tests/eval_files/b028.py
similarity index 100%
rename from tests/b028.py
rename to tests/eval_files/b028.py
diff --git a/tests/b029.py b/tests/eval_files/b029.py
similarity index 100%
rename from tests/b029.py
rename to tests/eval_files/b029.py
diff --git a/tests/b029_py311.py b/tests/eval_files/b029_py311.py
similarity index 100%
rename from tests/b029_py311.py
rename to tests/eval_files/b029_py311.py
diff --git a/tests/b030.py b/tests/eval_files/b030.py
similarity index 100%
rename from tests/b030.py
rename to tests/eval_files/b030.py
diff --git a/tests/b030_py311.py b/tests/eval_files/b030_py311.py
similarity index 100%
rename from tests/b030_py311.py
rename to tests/eval_files/b030_py311.py
diff --git a/tests/b031.py b/tests/eval_files/b031.py
similarity index 100%
rename from tests/b031.py
rename to tests/eval_files/b031.py
diff --git a/tests/b032.py b/tests/eval_files/b032.py
similarity index 100%
rename from tests/b032.py
rename to tests/eval_files/b032.py
diff --git a/tests/b033.py b/tests/eval_files/b033.py
similarity index 100%
rename from tests/b033.py
rename to tests/eval_files/b033.py
diff --git a/tests/b034.py b/tests/eval_files/b034.py
similarity index 100%
rename from tests/b034.py
rename to tests/eval_files/b034.py
diff --git a/tests/b035.py b/tests/eval_files/b035.py
similarity index 100%
rename from tests/b035.py
rename to tests/eval_files/b035.py
diff --git a/tests/b036.py b/tests/eval_files/b036.py
similarity index 100%
rename from tests/b036.py
rename to tests/eval_files/b036.py
diff --git a/tests/b036_py311.py b/tests/eval_files/b036_py311.py
similarity index 100%
rename from tests/b036_py311.py
rename to tests/eval_files/b036_py311.py
diff --git a/tests/b037.py b/tests/eval_files/b037.py
similarity index 100%
rename from tests/b037.py
rename to tests/eval_files/b037.py
diff --git a/tests/b039.py b/tests/eval_files/b039.py
similarity index 100%
rename from tests/b039.py
rename to tests/eval_files/b039.py
diff --git a/tests/b040.py b/tests/eval_files/b040.py
similarity index 100%
rename from tests/b040.py
rename to tests/eval_files/b040.py
diff --git a/tests/b041.py b/tests/eval_files/b041.py
similarity index 100%
rename from tests/b041.py
rename to tests/eval_files/b041.py
diff --git a/tests/b901.py b/tests/eval_files/b901.py
similarity index 100%
rename from tests/b901.py
rename to tests/eval_files/b901.py
diff --git a/tests/b902.py b/tests/eval_files/b902.py
similarity index 100%
rename from tests/b902.py
rename to tests/eval_files/b902.py
diff --git a/tests/b902_extended.py b/tests/eval_files/b902_extended.py
similarity index 98%
rename from tests/b902_extended.py
rename to tests/eval_files/b902_extended.py
index 87fa4e6..9e1e9e4 100644
--- a/tests/b902_extended.py
+++ b/tests/eval_files/b902_extended.py
@@ -1,4 +1,4 @@
-# OPTIONS: classmethod_decorators=["mylibrary.makeclassmethod", "validator"], select=["B902"],
+# OPTIONS: classmethod_decorators=["mylibrary.makeclassmethod", "validator"], select=["B902"]
class Errors:
# correctly registered as classmethod
@validator
diff --git a/tests/b902_py38.py b/tests/eval_files/b902_py38.py
similarity index 100%
rename from tests/b902_py38.py
rename to tests/eval_files/b902_py38.py
diff --git a/tests/b903.py b/tests/eval_files/b903.py
similarity index 100%
rename from tests/b903.py
rename to tests/eval_files/b903.py
diff --git a/tests/b904.py b/tests/eval_files/b904.py
similarity index 100%
rename from tests/b904.py
rename to tests/eval_files/b904.py
diff --git a/tests/b904_py311.py b/tests/eval_files/b904_py311.py
similarity index 100%
rename from tests/b904_py311.py
rename to tests/eval_files/b904_py311.py
diff --git a/tests/b905_py310.py b/tests/eval_files/b905_py310.py
similarity index 100%
rename from tests/b905_py310.py
rename to tests/eval_files/b905_py310.py
diff --git a/tests/b906.py b/tests/eval_files/b906.py
similarity index 100%
rename from tests/b906.py
rename to tests/eval_files/b906.py
diff --git a/tests/b907_py312.py b/tests/eval_files/b907_py312.py
similarity index 100%
rename from tests/b907_py312.py
rename to tests/eval_files/b907_py312.py
diff --git a/tests/b908.py b/tests/eval_files/b908.py
similarity index 100%
rename from tests/b908.py
rename to tests/eval_files/b908.py
diff --git a/tests/b909.py b/tests/eval_files/b909.py
similarity index 100%
rename from tests/b909.py
rename to tests/eval_files/b909.py
diff --git a/tests/b910.py b/tests/eval_files/b910.py
similarity index 100%
rename from tests/b910.py
rename to tests/eval_files/b910.py
diff --git a/tests/b911_py313.py b/tests/eval_files/b911_py313.py
similarity index 100%
rename from tests/b911_py313.py
rename to tests/eval_files/b911_py313.py
diff --git a/tests/b950.py b/tests/eval_files/b950.py
similarity index 100%
rename from tests/b950.py
rename to tests/eval_files/b950.py
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index 1b31e9b..80b6921 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -20,10 +20,12 @@
error_codes,
)
+EVAL_FILES_DIR = Path(__file__).parent / "eval_files"
+
test_files: list[tuple[str, Path]] = sorted(
(f.stem.upper(), f)
- for f in (Path(__file__).parent).iterdir()
- if re.fullmatch(r"b\d\d\d.*\.py", f.name)
+ for f in EVAL_FILES_DIR.iterdir()
+ if re.fullmatch(r".*.py", f.name)
)
@@ -65,6 +67,11 @@ def _parse_eval_file(test: str, content: str) -> tuple[list[error], Namespace |
for lineno, line in enumerate(content.split("\n"), start=1):
if line.startswith("# OPTIONS:"):
+ assert options is None, (
+ "Multiple '# OPTIONS' found in file. You can specify multiple options"
+ " in the same line, but if you want something more readable you may"
+ " want to upgrade the logic in this function."
+ )
options = eval(f"Namespace({line[10:]})")
# skip commented out lines
@@ -149,7 +156,7 @@ def test_b907_format_specifier_permutations(self):
), f"b907 raised for {format_spec} that would look different with !r"
def test_b9_select(self):
- filename = Path(__file__).absolute().parent / "b950.py"
+ filename = EVAL_FILES_DIR / "b950.py"
mock_options = Namespace(select=["B950"])
bbc = BugBearChecker(filename=str(filename), options=mock_options)
@@ -168,7 +175,7 @@ def test_b9_select(self):
)
def test_b9_extend_select(self):
- filename = Path(__file__).absolute().parent / "b950.py"
+ filename = EVAL_FILES_DIR / "b950.py"
# select is always going to have a value, usually the default codes, but can
# also be empty
@@ -189,7 +196,7 @@ def test_b9_extend_select(self):
)
def test_b9_flake8_next_default_options(self):
- filename = Path(__file__).absolute().parent / "b950.py"
+ filename = EVAL_FILES_DIR / "b950.py"
# in flake8 next, unset select / extend_select will be `None` to
# signify the default values
From e30efda052ce5337c8134b0cb41ebd9df157a034 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 23 May 2025 12:24:39 +0200
Subject: [PATCH 5/7] remove old commented-out code
---
tests/test_bugbear.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index 80b6921..b01f5bd 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -60,8 +60,6 @@ def test_eval(
def _parse_eval_file(test: str, content: str) -> tuple[list[error], Namespace | None]:
-
- # error_class: Any = eval(error_code)
expected: list[error] = []
options: Namespace | None = None
From 8aa22ab299271a3509e61211ad0cb1ec3a868a8c Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 23 May 2025 18:04:56 +0200
Subject: [PATCH 6/7] clarified error message
---
DEVELOPMENT.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 9d3c8a6..4e05c6f 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -46,7 +46,7 @@ cd flake8-bugbear
flake8-bugbear has a test runner that will go through all files in `tests/eval_files/`, run them through the linter, and check that they emit the appropriate error messages.
-The expected errors are specified by adding comments on the line where the error is expected, using the format `# : [, ][, ][...]`. E.g.
+The expected errors are specified by adding comments on the line where the error is expected. The format consists of the error code, followed by a comma-separated list of the `col_offset` as well as `vars` that are used when `str.format`ing the full error message.
```python
x = ++n # B002: 4
try:
@@ -54,7 +54,7 @@ try:
except* (ValueError,): # B013: 0, "ValueError", "*"
...
```
-The error code should be in the `error_codes` dict, and the other values are passed to `eval` so should be valid python objects.
+The error code should be in the `error_codes` dict, and the other values are `eval`'d as if in a `tuple` and should be valid python objects. (I.e. remember to quote strings)
You can also specify options to be passed to `BugBearChecker` with an `# OPTIONS` comments
```python
From f7d5123f7aaefe27b9a1e8bbdf76b26157dfc336 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 23 May 2025 18:09:59 +0200
Subject: [PATCH 7/7] fix comment (it applied to flake8-async, but this
implementation does not allow # error: ...)
---
tests/test_bugbear.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py
index b01f5bd..04835cf 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -80,7 +80,7 @@ def _parse_eval_file(test: str, content: str) -> tuple[list[error], Namespace |
if "#" not in line:
continue
- # get text between `error:` and (end of line or another comment)
+ # get text between `B\d\d\d:` and (end of line or another comment)
k = re.findall(r"(B\d\d\d):([^#]*)(?=#|$)", line)
for err_code, err_args in k:
# evaluate the arguments as if in a tuple