Skip to content

Commit 9e14a8c

Browse files
B904: ensure the raise is in the same context with the except (#191)
* B904: ensure the raise is in the same context with the except * not len() >= 2 to len() < 2 Co-authored-by: Cooper Lees <[email protected]> * add tests about multi-level contexts * Update bugbear.py Change the self.context to self.contexts ... Co-authored-by: Cooper Lees <[email protected]>
1 parent c452048 commit 9e14a8c

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

bugbear.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@
1515
__version__ = "21.9.2"
1616

1717
LOG = logging.getLogger("flake8.bugbear")
18+
CONTEXTFUL_NODES = (
19+
ast.Module,
20+
ast.ClassDef,
21+
ast.AsyncFunctionDef,
22+
ast.FunctionDef,
23+
ast.Lambda,
24+
ast.ListComp,
25+
ast.SetComp,
26+
ast.DictComp,
27+
ast.GeneratorExp,
28+
)
29+
30+
Context = namedtuple("Context", ["node", "stack"])
1831

1932

2033
@attr.s(hash=False)
@@ -168,6 +181,7 @@ class BugBearVisitor(ast.NodeVisitor):
168181
node_window = attr.ib(default=attr.Factory(list))
169182
errors = attr.ib(default=attr.Factory(list))
170183
futures = attr.ib(default=attr.Factory(set))
184+
contexts = attr.ib(default=attr.Factory(list))
171185

172186
NODE_WINDOW_SIZE = 4
173187

@@ -178,13 +192,30 @@ def __getattr__(self, name):
178192
print(name)
179193
return self.__getattribute__(name)
180194

195+
@property
196+
def node_stack(self):
197+
if len(self.contexts) == 0:
198+
return []
199+
200+
context, stack = self.contexts[-1]
201+
return stack
202+
181203
def visit(self, node):
204+
is_contextful = isinstance(node, CONTEXTFUL_NODES)
205+
206+
if is_contextful:
207+
context = Context(node, [])
208+
self.contexts.append(context)
209+
182210
self.node_stack.append(node)
183211
self.node_window.append(node)
184212
self.node_window = self.node_window[-self.NODE_WINDOW_SIZE :]
185213
super().visit(node)
186214
self.node_stack.pop()
187215

216+
if is_contextful:
217+
self.contexts.pop()
218+
188219
def visit_ExceptHandler(self, node):
189220
if node.type is None:
190221
self.errors.append(
@@ -500,9 +531,12 @@ def check_for_b901(self, node):
500531
break
501532

502533
def check_for_b902(self, node):
503-
if not isinstance(self.node_stack[-2], ast.ClassDef):
534+
if len(self.contexts) < 2 or not isinstance(
535+
self.contexts[-2].node, ast.ClassDef
536+
):
504537
return
505538

539+
cls = self.contexts[-2].node
506540
decorators = NameFinder()
507541
decorators.visit(node.decorator_list)
508542

@@ -511,7 +545,7 @@ def check_for_b902(self, node):
511545
# `cls`?
512546
return
513547

514-
bases = {b.id for b in self.node_stack[-2].bases if isinstance(b, ast.Name)}
548+
bases = {b.id for b in cls.bases if isinstance(b, ast.Name)}
515549
if "type" in bases:
516550
if (
517551
"classmethod" in decorators.names

tests/b904.py

+31
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,34 @@
1919
raise base_err
2020
finally:
2121
raise Exception("Nothing to chain from, so no warning here")
22+
23+
try:
24+
raise ValueError
25+
except ValueError:
26+
# should not emit, since we are not raising something
27+
def proxy():
28+
raise NameError
29+
30+
try:
31+
from preferred_library import Thing
32+
except ImportError:
33+
try:
34+
from fallback_library import Thing
35+
except ImportError:
36+
class Thing:
37+
def __getattr__(self, name):
38+
# same as the case above, should not emit.
39+
raise AttributeError
40+
41+
42+
try:
43+
from preferred_library import Thing
44+
except ImportError:
45+
try:
46+
from fallback_library import Thing
47+
except ImportError:
48+
def context_switch():
49+
try:
50+
raise ValueError
51+
except ValueError:
52+
raise Exception

tests/test_bugbear.py

+1
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ def test_b904(self):
310310
B904(10, 8),
311311
B904(11, 4),
312312
B904(16, 4),
313+
B904(52, 16),
313314
]
314315
self.assertEqual(errors, self.errors(*expected))
315316

0 commit comments

Comments
 (0)