summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyflakes/checker.py65
-rw-r--r--pyflakes/messages.py55
-rw-r--r--pyflakes/test/test_other.py662
3 files changed, 772 insertions, 10 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 7a51328..3b35ca6 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -658,11 +658,11 @@ class Checker(object):
ASYNCWITH = ASYNCWITHITEM = RAISE = TRYFINALLY = ASSERT = EXEC = \
EXPR = ASSIGN = handleChildren
- CONTINUE = BREAK = PASS = ignore
+ PASS = ignore
# "expr" type nodes
BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = \
- COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \
+ COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = \
STARRED = NAMECONSTANT = handleChildren
NUM = STR = BYTES = ELLIPSIS = ignore
@@ -748,8 +748,33 @@ class Checker(object):
# arguments, but these aren't dispatched through here
raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
+ def CONTINUE(self, node):
+ # Walk the tree up until we see a loop (OK), a function or class
+ # definition (not OK), for 'continue', a finally block (not OK), or
+ # the top module scope (not OK)
+ n = node
+ while hasattr(n, 'parent'):
+ n, n_child = n.parent, n
+ if isinstance(n, (ast.While, ast.For)):
+ # Doesn't apply unless it's in the loop itself
+ if n_child not in n.orelse:
+ return
+ if isinstance(n, (ast.FunctionDef, ast.ClassDef)):
+ break
+ # Handle Try/TryFinally difference in Python < and >= 3.3
+ if hasattr(n, 'finalbody') and isinstance(node, ast.Continue):
+ if n_child in n.finalbody:
+ self.report(messages.ContinueInFinally, node)
+ return
+ if isinstance(node, ast.Continue):
+ self.report(messages.ContinueOutsideLoop, node)
+ else: # ast.Break
+ self.report(messages.BreakOutsideLoop, node)
+
+ BREAK = CONTINUE
+
def RETURN(self, node):
- if isinstance(self.scope, ClassScope):
+ if isinstance(self.scope, (ClassScope, ModuleScope)):
self.report(messages.ReturnOutsideFunction, node)
return
@@ -762,6 +787,10 @@ class Checker(object):
self.handleNode(node.value, node)
def YIELD(self, node):
+ if isinstance(self.scope, (ClassScope, ModuleScope)):
+ self.report(messages.YieldOutsideFunction, node)
+ return
+
self.scope.isGenerator = True
self.handleNode(node.value, node)
@@ -886,6 +915,31 @@ class Checker(object):
self.handleNode(node.value, node)
self.handleNode(node.target, node)
+ def TUPLE(self, node):
+ if not PY2 and isinstance(node.ctx, ast.Store):
+ # Python 3 advanced tuple unpacking: a, *b, c = d.
+ # Only one starred expression is allowed, and no more than 1<<8
+ # assignments are allowed before a stared expression. There is
+ # also a limit of 1<<24 expressions after the starred expression,
+ # which is impossible to test due to memory restrictions, but we
+ # add it here anyway
+ has_starred = False
+ star_loc = -1
+ for i, n in enumerate(node.elts):
+ if isinstance(n, ast.Starred):
+ if has_starred:
+ self.report(messages.TwoStarredExpressions, node)
+ # The SyntaxError doesn't distinguish two from more
+ # than two.
+ break
+ has_starred = True
+ star_loc = i
+ if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24:
+ self.report(messages.TooManyExpressionsInStarredAssignment, node)
+ self.handleChildren(node)
+
+ LIST = TUPLE
+
def IMPORT(self, node):
for alias in node.names:
name = alias.asname or alias.name
@@ -914,12 +968,15 @@ class Checker(object):
def TRY(self, node):
handler_names = []
# List the exception handlers
- for handler in node.handlers:
+ for i, handler in enumerate(node.handlers):
if isinstance(handler.type, ast.Tuple):
for exc_type in handler.type.elts:
handler_names.append(getNodeName(exc_type))
elif handler.type:
handler_names.append(getNodeName(handler.type))
+
+ if handler.type is None and i < len(node.handlers) - 1:
+ self.report(messages.DefaultExceptNotLast, handler)
# Memorize the except handlers and process the body
self.exceptHandlers.append(handler_names)
for child in node.body:
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
index 40e0cfe..27167e0 100644
--- a/pyflakes/messages.py
+++ b/pyflakes/messages.py
@@ -101,11 +101,11 @@ class DuplicateArgument(Message):
class LateFutureImport(Message):
- message = 'future import(s) %r after other statements'
+ message = 'from __future__ imports must occur at the beginning of the file'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
- self.message_args = (names,)
+ self.message_args = ()
class UnusedVariable(Message):
@@ -132,3 +132,54 @@ class ReturnOutsideFunction(Message):
Indicates a return statement outside of a function/method.
"""
message = '\'return\' outside function'
+
+
+class YieldOutsideFunction(Message):
+ """
+ Indicates a yield or yield from statement outside of a function/method.
+ """
+ message = '\'yield\' outside function'
+
+
+# For whatever reason, Python gives different error messages for these two. We
+# match the Python error message exactly.
+class ContinueOutsideLoop(Message):
+ """
+ Indicates a continue statement outside of a while or for loop.
+ """
+ message = '\'continue\' not properly in loop'
+
+
+class BreakOutsideLoop(Message):
+ """
+ Indicates a break statement outside of a while or for loop.
+ """
+ message = '\'break\' outside loop'
+
+
+class ContinueInFinally(Message):
+ """
+ Indicates a continue statement in a finally block in a while or for loop.
+ """
+ message = '\'continue\' not supported inside \'finally\' clause'
+
+
+class DefaultExceptNotLast(Message):
+ """
+ Indicates an except: block as not the last exception handler.
+ """
+ message = 'default \'except:\' must be last'
+
+
+class TwoStarredExpressions(Message):
+ """
+ Two or more starred expressions in an assignment (a, *b, *c = d).
+ """
+ message = 'two starred expressions in assignment'
+
+
+class TooManyExpressionsInStarredAssignment(Message):
+ """
+ Too many expressions in an assignment with star-unpacking
+ """
+ message = 'too many expressions in star-unpacking assignment'
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index 3167a1d..1891c15 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -353,6 +353,664 @@ class Test(TestCase):
return
''', m.ReturnOutsideFunction)
+ def test_moduleWithReturn(self):
+ """
+ If a return is used at the module level, a warning is emitted.
+ """
+ self.flakes('''
+ return
+ ''', m.ReturnOutsideFunction)
+
+ def test_classWithYield(self):
+ """
+ If a yield is used inside a class, a warning is emitted.
+ """
+ self.flakes('''
+ class Foo(object):
+ yield
+ ''', m.YieldOutsideFunction)
+
+ def test_moduleWithYield(self):
+ """
+ If a yield is used at the module level, a warning is emitted.
+ """
+ self.flakes('''
+ yield
+ ''', m.YieldOutsideFunction)
+
+ @skipIf(version_info < (3, 3), "Python >= 3.3 only")
+ def test_classWithYieldFrom(self):
+ """
+ If a yield from is used inside a class, a warning is emitted.
+ """
+ self.flakes('''
+ class Foo(object):
+ yield from range(10)
+ ''', m.YieldOutsideFunction)
+
+ @skipIf(version_info < (3, 3), "Python >= 3.3 only")
+ def test_moduleWithYieldFrom(self):
+ """
+ If a yield from is used at the module level, a warning is emitted.
+ """
+ self.flakes('''
+ yield from range(10)
+ ''', m.YieldOutsideFunction)
+
+ def test_continueOutsideLoop(self):
+ self.flakes('''
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ self.flakes('''
+ def f():
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ self.flakes('''
+ while True:
+ pass
+ else:
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ self.flakes('''
+ while True:
+ pass
+ else:
+ if 1:
+ if 2:
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ self.flakes('''
+ while True:
+ def f():
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ self.flakes('''
+ while True:
+ class A:
+ continue
+ ''', m.ContinueOutsideLoop)
+
+ def test_continueInsideLoop(self):
+ self.flakes('''
+ while True:
+ continue
+ ''')
+
+ self.flakes('''
+ for i in range(10):
+ continue
+ ''')
+
+ self.flakes('''
+ while True:
+ if 1:
+ continue
+ ''')
+
+ self.flakes('''
+ for i in range(10):
+ if 1:
+ continue
+ ''')
+
+ self.flakes('''
+ while True:
+ while True:
+ pass
+ else:
+ continue
+ else:
+ pass
+ ''')
+
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ while True:
+ continue
+ ''')
+
+ def test_continueInFinally(self):
+ # 'continue' inside 'finally' is a special syntax error
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ continue
+ ''', m.ContinueInFinally)
+
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ if 1:
+ if 2:
+ continue
+ ''', m.ContinueInFinally)
+
+ # Even when not in a loop, this is the error Python gives
+ self.flakes('''
+ try:
+ pass
+ finally:
+ continue
+ ''', m.ContinueInFinally)
+
+ def test_breakOutsideLoop(self):
+ self.flakes('''
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ def f():
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ while True:
+ pass
+ else:
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ while True:
+ pass
+ else:
+ if 1:
+ if 2:
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ while True:
+ def f():
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ while True:
+ class A:
+ break
+ ''', m.BreakOutsideLoop)
+
+ self.flakes('''
+ try:
+ pass
+ finally:
+ break
+ ''', m.BreakOutsideLoop)
+
+ def test_breakInsideLoop(self):
+ self.flakes('''
+ while True:
+ break
+ ''')
+
+ self.flakes('''
+ for i in range(10):
+ break
+ ''')
+
+ self.flakes('''
+ while True:
+ if 1:
+ break
+ ''')
+
+ self.flakes('''
+ for i in range(10):
+ if 1:
+ break
+ ''')
+
+ self.flakes('''
+ while True:
+ while True:
+ pass
+ else:
+ break
+ else:
+ pass
+ ''')
+
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ while True:
+ break
+ ''')
+
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ break
+ ''')
+
+ self.flakes('''
+ while True:
+ try:
+ pass
+ finally:
+ if 1:
+ if 2:
+ break
+ ''')
+
+ def test_defaultExceptLast(self):
+ """
+ A default except block should be last.
+
+ YES:
+
+ try:
+ ...
+ except Exception:
+ ...
+ except:
+ ...
+
+ NO:
+
+ try:
+ ...
+ except:
+ ...
+ except Exception:
+ ...
+ """
+ self.flakes('''
+ try:
+ pass
+ except ValueError:
+ pass
+ ''')
+
+ self.flakes('''
+ try:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ ''')
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ ''')
+
+ self.flakes('''
+ try:
+ pass
+ except ValueError:
+ pass
+ else:
+ pass
+ ''')
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ else:
+ pass
+ ''')
+
+ self.flakes('''
+ try:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ else:
+ pass
+ ''')
+
+ def test_defaultExceptNotLast(self):
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ else:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except:
+ pass
+ else:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ else:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ else:
+ pass
+ ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ else:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except:
+ pass
+ else:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ else:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast)
+
+ self.flakes('''
+ try:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ except:
+ pass
+ except ValueError:
+ pass
+ else:
+ pass
+ finally:
+ pass
+ ''', m.DefaultExceptNotLast, m.DefaultExceptNotLast)
+
+ @skipIf(version_info < (3,), "Python 3 only")
+ def test_starredAssignmentNoError(self):
+ """
+ Python 3 extended iterable unpacking
+ """
+ self.flakes('''
+ a, *b = range(10)
+ ''')
+
+ self.flakes('''
+ *a, b = range(10)
+ ''')
+
+ self.flakes('''
+ a, *b, c = range(10)
+ ''')
+
+ self.flakes('''
+ (a, *b) = range(10)
+ ''')
+
+ self.flakes('''
+ (*a, b) = range(10)
+ ''')
+
+ self.flakes('''
+ (a, *b, c) = range(10)
+ ''')
+
+ self.flakes('''
+ [a, *b] = range(10)
+ ''')
+
+ self.flakes('''
+ [*a, b] = range(10)
+ ''')
+
+ self.flakes('''
+ [a, *b, c] = range(10)
+ ''')
+
+ # Taken from test_unpack_ex.py in the cPython source
+ s = ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
+ ", *rest = range(1<<8)"
+ self.flakes(s)
+
+ s = "(" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
+ ", *rest) = range(1<<8)"
+ self.flakes(s)
+
+ s = "[" + ", ".join("a%d" % i for i in range(1 << 8 - 1)) + \
+ ", *rest] = range(1<<8)"
+ self.flakes(s)
+
+ @skipIf(version_info < (3, ), "Python 3 only")
+ def test_starredAssignmentErrors(self):
+ """
+ SyntaxErrors (not encoded in the ast) surrounding Python 3 extended
+ iterable unpacking
+ """
+ # Taken from test_unpack_ex.py in the cPython source
+ s = ", ".join("a%d" % i for i in range(1 << 8)) + \
+ ", *rest = range(1<<8 + 1)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ s = "(" + ", ".join("a%d" % i for i in range(1 << 8)) + \
+ ", *rest) = range(1<<8 + 1)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ s = "[" + ", ".join("a%d" % i for i in range(1 << 8)) + \
+ ", *rest] = range(1<<8 + 1)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ s = ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
+ ", *rest = range(1<<8 + 2)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ s = "(" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
+ ", *rest) = range(1<<8 + 2)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ s = "[" + ", ".join("a%d" % i for i in range(1 << 8 + 1)) + \
+ ", *rest] = range(1<<8 + 2)"
+ self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ # No way we can actually test this!
+ # s = "*rest, " + ", ".join("a%d" % i for i in range(1<<24)) + \
+ # ", *rest = range(1<<24 + 1)"
+ # self.flakes(s, m.TooManyExpressionsInStarredAssignment)
+
+ self.flakes('''
+ a, *b, *c = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ a, *b, c, *d = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ *a, *b, *c = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ (a, *b, *c) = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ (a, *b, c, *d) = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ (*a, *b, *c) = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ [a, *b, *c] = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ [a, *b, c, *d] = range(10)
+ ''', m.TwoStarredExpressions)
+
+ self.flakes('''
+ [*a, *b, *c] = range(10)
+ ''', m.TwoStarredExpressions)
+
@skip("todo: Too hard to make this warn but other cases stay silent")
def test_doubleAssignment(self):
"""
@@ -985,10 +1643,6 @@ class TestUnusedAssignment(TestCase):
yield from foo()
''', m.UndefinedName)
- def test_returnOnly(self):
- """Do not crash on lone "return"."""
- self.flakes('return 2')
-
@skipIf(version_info < (3, 6), 'new in Python 3.6')
def test_f_string(self):
"""Test PEP 498 f-strings are treated as a usage."""