Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ limitations make it difficult.

**B027**: Empty method in abstract base class, but has no abstract decorator. Consider adding @abstractmethod.

**BO28**: No explicit stacklevel keyword 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.

Opinionated warnings
~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -320,6 +324,7 @@ Future
``ast.Str`` nodes are all deprecated, but may still be used by some codebases in
order to maintain backwards compatibility with Python 3.7.
* B016: Warn when raising f-strings.
* Add B028: Check for an explicit stacklevel keyword argument on the warn method from the warnings module.

23.1.20
~~~~~~~~~
Expand Down
20 changes: 20 additions & 0 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ def visit_Call(self, node):
self.check_for_b026(node)

self.check_for_b905(node)
self.check_for_b028(node)
self.generic_visit(node)

def visit_Module(self, node):
Expand Down Expand Up @@ -1146,6 +1147,16 @@ def myunparse(node: ast.AST) -> str: # pragma: no cover
# if no pre-mark or variable detected, reset state
current_mark = variable = None

def check_for_b028(self, node):
if (
isinstance(node.func, ast.Attribute)
and node.func.attr == "warn"
and isinstance(node.func.value, ast.Name)
and node.func.value.id == "warnings"
and not any(kw.arg == "stacklevel" for kw in node.keywords)
):
self.errors.append(B028(node.lineno, node.col_offset))


def compose_call_path(node):
if isinstance(node, ast.Attribute):
Expand Down Expand Up @@ -1510,6 +1521,15 @@ def visit_Lambda(self, node):
" decorator. Consider adding @abstractmethod."
)
)
B028 = Error(
message=(
"B028 No explicit stacklevel keyword 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."
)
)

# Warnings disabled by default.
B901 = Error(
Expand Down
11 changes: 11 additions & 0 deletions tests/b028.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import warnings

"""
Should emit:
B028 - on lines 8 and 9
"""

warnings.warn(DeprecationWarning("test"))
warnings.warn(DeprecationWarning("test"), source=None)
warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2)
warnings.warn(DeprecationWarning("test"), stacklevel=1)
8 changes: 8 additions & 0 deletions tests/test_bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
B025,
B026,
B027,
B028,
B901,
B902,
B903,
Expand Down Expand Up @@ -430,6 +431,13 @@ def test_b027(self):
)
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)

@unittest.skipIf(sys.version_info < (3, 8), "not implemented for <3.8")
def test_b907(self):
filename = Path(__file__).absolute().parent / "b907.py"
Expand Down