diff --git a/README.rst b/README.rst index 5e38d85..be9d42d 100644 --- a/README.rst +++ b/README.rst @@ -141,6 +141,12 @@ garbage collection. **B021**: f-string used as docstring. This will be interpreted by python as a joined string rather than a docstring. +**B022**: No arguments passed to `contextlib.suppress`. +No exceptions will be suppressed and therefore this context manager is redundant. +N.B. this rule currently does not flag `suppress` calls to avoid potential false +positives due to similarly named user-defined functions. + + Opinionated warnings ~~~~~~~~~~~~~~~~~~~~ diff --git a/bugbear.py b/bugbear.py index 031a2fa..ee75fdf 100644 --- a/bugbear.py +++ b/bugbear.py @@ -387,6 +387,7 @@ def visit_Raise(self, node): def visit_With(self, node): self.check_for_b017(node) + self.check_for_b022(node) self.generic_visit(node) def compose_call_path(self, node): @@ -739,6 +740,20 @@ def check_for_b021(self, node): B021(node.body[0].value.lineno, node.body[0].value.col_offset) ) + def check_for_b022(self, node): + item = node.items[0] + item_context = item.context_expr + if ( + hasattr(item_context, "func") + and hasattr(item_context.func, "value") + and hasattr(item_context.func.value, "id") + and item_context.func.value.id == "contextlib" + and hasattr(item_context.func, "attr") + and item_context.func.attr == "suppress" + and len(item_context.args) == 0 + ): + self.errors.append(B022(node.lineno, node.col_offset)) + @attr.s class NameFinder(ast.NodeVisitor): @@ -965,6 +980,13 @@ def visit(self, node): "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." + ) +) # Warnings disabled by default. B901 = Error( diff --git a/tests/b022.py b/tests/b022.py new file mode 100644 index 0000000..9d597ed --- /dev/null +++ b/tests/b022.py @@ -0,0 +1,19 @@ +""" +Should emit: +B022 - on lines 8 +""" + +import contextlib + +with contextlib.suppress(): + raise ValueError + +with contextlib.suppress(ValueError): + raise ValueError + +exceptions_to_suppress = [] +if True: + exceptions_to_suppress.append(ValueError) + +with contextlib.suppress(*exceptions_to_suppress): + raise ValueError diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index 517c7ca..6639f17 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -32,6 +32,7 @@ B019, B020, B021, + B022, B901, B902, B903, @@ -303,6 +304,12 @@ def test_b021_classes(self): ) 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_b901(self): filename = Path(__file__).absolute().parent / "b901.py" bbc = BugBearChecker(filename=str(filename))