diff --git a/pyflakes/checker.py b/pyflakes/checker.py index fe201462..5115675a 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -453,6 +453,17 @@ def getParent(self, node): if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): return node + def has_parent_type(self, node, cls): + """Check if node has any parent of type cls.""" + while node: + try: + node = self.getParent(node) + except AttributeError: + return False + if isinstance(node, cls): + return True + return False + def getCommonAncestor(self, lnode, rnode, stop): if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and hasattr(rnode, 'parent')): @@ -746,7 +757,7 @@ def ignore(self, node): # "stmt" type nodes DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \ - ASYNCWITH = ASYNCWITHITEM = RAISE = TRYFINALLY = EXEC = \ + ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \ EXPR = ASSIGN = handleChildren PASS = ignore @@ -1071,6 +1082,19 @@ def IMPORTFROM(self, node): importation = Importation(name, node) self.addBinding(node, importation) + def RAISE(self, node): + try: + next(iter_child_nodes(node)) + has_children = True + except StopIteration: + has_children = False + + if not has_children: + if not self.has_parent_type(node, ast.ExceptHandler): + self.report(messages.ReraiseOutsideExcept, node) + + self.handleChildren(node) + def TRY(self, node): handler_names = [] # List the exception handlers diff --git a/pyflakes/messages.py b/pyflakes/messages.py index 05db5bfa..ecaf7184 100644 --- a/pyflakes/messages.py +++ b/pyflakes/messages.py @@ -189,6 +189,13 @@ class ContinueInFinally(Message): message = '\'continue\' not supported inside \'finally\' clause' +class ReraiseOutsideExcept(Message): + """ + A bare raise used outside an except clause. + """ + message = '\'raise\' not supported outside \'except\' clause' + + class DefaultExceptNotLast(Message): """ Indicates an except: block as not the last exception handler. diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py index 0ac96a32..1008a57b 100644 --- a/pyflakes/test/test_other.py +++ b/pyflakes/test/test_other.py @@ -885,6 +885,69 @@ def test_defaultExceptNotLast(self): pass ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast) + def test_raise_name(self): + self.flakes(''' + raise Exception + ''') + + def test_raise_object(self): + self.flakes(''' + raise Exception() + ''') + + def test_raise_object_in_try(self): + self.flakes(''' + try: + raise Exception() + except Exception: + pass + ''') + + def test_reraise_in_except(self): + self.flakes(''' + try: + int('x') + except ValueError: + raise + ''') + + def test_reraise_in_except_with_if(self): + self.flakes(''' + try: + int('x') + except ValueError as e: + if True: + raise + ''') + + def test_reraise_in_try(self): + self.flakes(''' + try: + raise + except Exception: + pass + ''', m.ReraiseOutsideExcept) + + def test_reraise_in_finally(self): + self.flakes(''' + try: + int('x') + except ValueError: + pass + finally: + raise + ''', m.ReraiseOutsideExcept) + + def test_reraise_in_else(self): + self.flakes(''' + try: + int('x') + except ValueError: + pass + else: + raise + ''', m.ReraiseOutsideExcept) + @skipIf(version_info < (3,), "Python 3 only") def test_starredAssignmentNoError(self): """