diff --git a/bugbear.py b/bugbear.py index 2398c4a..16815a2 100644 --- a/bugbear.py +++ b/bugbear.py @@ -15,6 +15,19 @@ __version__ = "21.9.2" LOG = logging.getLogger("flake8.bugbear") +CONTEXTFUL_NODES = ( + ast.Module, + ast.ClassDef, + ast.AsyncFunctionDef, + ast.FunctionDef, + ast.Lambda, + ast.ListComp, + ast.SetComp, + ast.DictComp, + ast.GeneratorExp, +) + +Context = namedtuple("Context", ["node", "stack"]) @attr.s(hash=False) @@ -168,6 +181,7 @@ class BugBearVisitor(ast.NodeVisitor): node_window = attr.ib(default=attr.Factory(list)) errors = attr.ib(default=attr.Factory(list)) futures = attr.ib(default=attr.Factory(set)) + contexts = attr.ib(default=attr.Factory(list)) NODE_WINDOW_SIZE = 4 @@ -178,13 +192,30 @@ def __getattr__(self, name): print(name) return self.__getattribute__(name) + @property + def node_stack(self): + if len(self.contexts) == 0: + return [] + + context, stack = self.contexts[-1] + return stack + def visit(self, node): + is_contextful = isinstance(node, CONTEXTFUL_NODES) + + if is_contextful: + context = Context(node, []) + self.contexts.append(context) + self.node_stack.append(node) self.node_window.append(node) self.node_window = self.node_window[-self.NODE_WINDOW_SIZE :] super().visit(node) self.node_stack.pop() + if is_contextful: + self.contexts.pop() + def visit_ExceptHandler(self, node): if node.type is None: self.errors.append( @@ -500,9 +531,12 @@ def check_for_b901(self, node): break def check_for_b902(self, node): - if not isinstance(self.node_stack[-2], ast.ClassDef): + if len(self.contexts) < 2 or not isinstance( + self.contexts[-2].node, ast.ClassDef + ): return + cls = self.contexts[-2].node decorators = NameFinder() decorators.visit(node.decorator_list) @@ -511,7 +545,7 @@ def check_for_b902(self, node): # `cls`? return - bases = {b.id for b in self.node_stack[-2].bases if isinstance(b, ast.Name)} + bases = {b.id for b in cls.bases if isinstance(b, ast.Name)} if "type" in bases: if ( "classmethod" in decorators.names diff --git a/tests/b904.py b/tests/b904.py index 104fda8..44d1659 100644 --- a/tests/b904.py +++ b/tests/b904.py @@ -19,3 +19,34 @@ raise base_err finally: raise Exception("Nothing to chain from, so no warning here") + +try: + raise ValueError +except ValueError: + # should not emit, since we are not raising something + def proxy(): + raise NameError + +try: + from preferred_library import Thing +except ImportError: + try: + from fallback_library import Thing + except ImportError: + class Thing: + def __getattr__(self, name): + # same as the case above, should not emit. + raise AttributeError + + +try: + from preferred_library import Thing +except ImportError: + try: + from fallback_library import Thing + except ImportError: + def context_switch(): + try: + raise ValueError + except ValueError: + raise Exception diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index 262b48c..14f43bc 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -310,6 +310,7 @@ def test_b904(self): B904(10, 8), B904(11, 4), B904(16, 4), + B904(52, 16), ] self.assertEqual(errors, self.errors(*expected))