diff -Nru pyflakes-0.7.3/AUTHORS pyflakes-0.8.1/AUTHORS --- pyflakes-0.7.3/AUTHORS 2013-04-21 10:51:56.000000000 +0000 +++ pyflakes-0.8.1/AUTHORS 2014-03-22 12:31:07.000000000 +0000 @@ -2,6 +2,7 @@ Contributors ------------ +* Phil Frost - Former Divmod Team * Moe Aboulkheir - Former Divmod Team * Jean-Paul Calderone - Former Divmod Team * Glyph Lefkowitz - Former Divmod Team diff -Nru pyflakes-0.7.3/debian/changelog pyflakes-0.8.1/debian/changelog --- pyflakes-0.7.3/debian/changelog 2013-08-30 10:57:33.000000000 +0000 +++ pyflakes-0.8.1/debian/changelog 2014-07-24 02:18:06.000000000 +0000 @@ -1,8 +1,31 @@ -pyflakes (0.7.3-1~ubuntu12.04.1~ppa1) precise; urgency=low +pyflakes (0.8.1-1~ubuntu12.04.1~ppa1) precise; urgency=medium * No-change backport to precise - -- Chris Johnston Fri, 30 Aug 2013 06:57:33 -0400 + -- Chris Johnston Wed, 23 Jul 2014 22:18:06 -0400 + +pyflakes (0.8.1-1) unstable; urgency=medium + + * Team upload. + * New upstream release. + + -- Barry Warsaw Mon, 31 Mar 2014 13:58:49 -0400 + +pyflakes (0.8-1) unstable; urgency=medium + + * Team upload. + * New upstream release. + * d/patches/patch 0.7.3-17-gecd6eca.patch: Removed, applied upstream. + * d/control: Bump Standards-Version with no other changes needed. + + -- Barry Warsaw Fri, 28 Mar 2014 11:27:36 -0400 + +pyflakes (0.7.3-2) unstable; urgency=medium + + * Cherry-pick 17 upstream commits since 0.7.3, to add python 3.4 + support. + + -- Dimitri John Ledkov Mon, 20 Jan 2014 23:00:22 +0000 pyflakes (0.7.3-1) unstable; urgency=low diff -Nru pyflakes-0.7.3/debian/control pyflakes-0.8.1/debian/control --- pyflakes-0.7.3/debian/control 2013-08-04 22:18:29.000000000 +0000 +++ pyflakes-0.8.1/debian/control 2014-03-28 15:16:44.000000000 +0000 @@ -4,7 +4,7 @@ Maintainer: Python Applications Packaging Team Uploaders: Tristan Seligmann , Varun Hiremath , - Dmitrijs Ledkovs , + Dimitri John Ledkov , Build-Depends: debhelper (>= 9), dh-python, python-all, @@ -15,7 +15,7 @@ python3-setuptools X-Python-Version: >= 2.6 X-Python3-Version: >= 3.2 -Standards-Version: 3.9.4 +Standards-Version: 3.9.5 Homepage: https://launchpad.net/pyflakes Vcs-Svn: svn://anonscm.debian.org/python-apps/packages/pyflakes/trunk/ Vcs-Browser: http://anonscm.debian.org/viewvc/python-apps/packages/pyflakes/trunk/ diff -Nru pyflakes-0.7.3/LICENSE pyflakes-0.8.1/LICENSE --- pyflakes-0.7.3/LICENSE 2013-01-28 23:26:57.000000000 +0000 +++ pyflakes-0.8.1/LICENSE 2014-01-17 08:50:59.000000000 +0000 @@ -1,5 +1,5 @@ Copyright 2005-2011 Divmod, Inc. -Copyright 2013 Florent Xicluna +Copyright 2013-2014 Florent Xicluna Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff -Nru pyflakes-0.7.3/NEWS.txt pyflakes-0.8.1/NEWS.txt --- pyflakes-0.7.3/NEWS.txt 2013-07-02 15:48:09.000000000 +0000 +++ pyflakes-0.8.1/NEWS.txt 2014-03-30 18:46:51.000000000 +0000 @@ -1,3 +1,29 @@ +0.8.1 (2014-03-30): + - Detect the declared encoding in Python 3. + - Do not report redefinition of import in a local scope, if the + global name is used elsewhere in the module. + - Catch undefined variable in loop generator when it is also used as + loop variable. + - Report undefined name for `(a, b) = (1, 2)` but not for the general + unpacking `(a, b) = func()`. + - Correctly detect when an imported module is used in default arguments + of a method, when the method and the module use the same name. + - Distribute a universal wheel file. + +0.8.0 (2014-03-22): + - Adapt for the AST in Python 3.4. + - Fix caret position on SyntaxError. + - Fix crash on Python 2.x with some doctest SyntaxError. + - Add tox.ini. + - The `PYFLAKES_NODOCTEST` environment variable has been replaced with the + `PYFLAKES_DOCTEST` environment variable (with the opposite meaning). + Doctest checking is now disabled by default; set the environment variable + to enable it. + - Correctly parse incremental `__all__ += [...]`. + - Catch return with arguments inside a generator (Python <= 3.2). + - Do not complain about `_` in doctests. + - Drop deprecated methods `pushFunctionScope` and `pushClassScope`. + 0.7.3 (2013-07-02): - Do not report undefined name for generator expression and dict or set comprehension at class level. diff -Nru pyflakes-0.7.3/PKG-INFO pyflakes-0.8.1/PKG-INFO --- pyflakes-0.7.3/PKG-INFO 2013-07-02 16:04:27.000000000 +0000 +++ pyflakes-0.8.1/PKG-INFO 2014-03-30 18:53:07.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyflakes -Version: 0.7.3 +Version: 0.8.1 Summary: passive checker of Python programs Home-page: https://launchpad.net/pyflakes Author: Florent Xicluna @@ -17,7 +17,7 @@ modules with side effects. It's also much faster. It is `available on PyPI `_ - and it supports all active versions of Python from 2.5 to 3.3. + and it supports all active versions of Python from 2.5 to 3.4. Installation @@ -39,6 +39,10 @@ :target: https://travis-ci.org/pyflakes/pyflakes :alt: Build status + .. image:: https://pypip.in/wheel/pyflakes/badge.png + :target: https://pypi.python.org/pypi/pyflakes + :alt: Wheel Status + Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Environment :: Console diff -Nru pyflakes-0.7.3/pyflakes/api.py pyflakes-0.8.1/pyflakes/api.py --- pyflakes-0.7.3/pyflakes/api.py 2013-04-23 05:36:53.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/api.py 2014-03-29 11:07:31.000000000 +0000 @@ -74,8 +74,10 @@ if reporter is None: reporter = modReporter._makeDefaultReporter() try: - with open(filename, 'U') as f: - codestr = f.read() + '\n' + with open(filename, 'rb') as f: + codestr = f.read() + if sys.version_info < (2, 7): + codestr += '\n' # Work around for Python <= 2.6 except UnicodeError: reporter.unexpectedError(filename, 'problem decoding source') return 1 @@ -122,7 +124,7 @@ def main(prog=None): parser = OptionParser(prog=prog, version=__version__) - __, args = parser.parse_args() + (__, args) = parser.parse_args() reporter = modReporter._makeDefaultReporter() if args: warnings = checkRecursive(args, reporter) diff -Nru pyflakes-0.7.3/pyflakes/checker.py pyflakes-0.8.1/pyflakes/checker.py --- pyflakes-0.7.3/pyflakes/checker.py 2013-06-18 23:34:23.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/checker.py 2014-03-30 17:43:56.000000000 +0000 @@ -7,16 +7,14 @@ import doctest import os import sys -try: - builtin_vars = dir(__import__('builtins')) - PY2 = False -except ImportError: - builtin_vars = dir(__import__('__builtin__')) - PY2 = True + +PY2 = sys.version_info < (3, 0) +PY32 = sys.version_info < (3, 3) # Python 2.5 to 3.2 +PY33 = sys.version_info < (3, 4) # Python 2.5 to 3.3 +builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) try: import ast - iter_child_nodes = ast.iter_child_nodes except ImportError: # Python 2.5 import _ast as ast @@ -25,26 +23,6 @@ ast.ClassDef.decorator_list = () ast.FunctionDef.decorator_list = property(lambda s: s.decorators) - def iter_child_nodes(node): - """ - Yield all direct child nodes of *node*, that is, all fields that - are nodes and all items of fields that are lists of nodes. - """ - for name in node._fields: - field = getattr(node, name, None) - if isinstance(field, ast.AST): - yield field - elif isinstance(field, list): - for item in field: - yield item -# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) -if hasattr(ast, 'Try'): - ast_TryExcept = ast.Try - ast_TryFinally = () -else: - ast_TryExcept = ast.TryExcept - ast_TryFinally = ast.TryFinally - from pyflakes import messages @@ -56,6 +34,55 @@ def getNodeType(node_class): return node_class.__name__.upper() +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if PY32: + def getAlternatives(n): + if isinstance(n, (ast.If, ast.TryFinally)): + return [n.body] + if isinstance(n, ast.TryExcept): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] +else: + def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + if isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] + + +class _FieldsOrder(dict): + """Fix order of AST node fields.""" + + def _get_fields(self, node_class): + # handle iter before target, and generators before element + fields = node_class._fields + if 'iter' in fields: + key_first = 'iter'.find + elif 'generators' in fields: + key_first = 'generators'.find + else: + key_first = 'value'.find + return tuple(sorted(fields, key=key_first, reverse=True)) + + def __missing__(self, node_class): + self[node_class] = fields = self._get_fields(node_class) + return fields + + +def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + """ + for name in _fields_order[node.__class__]: + if name == omit: + continue + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + yield item + class Binding(object): """ @@ -83,8 +110,17 @@ self.source.lineno, id(self)) + def redefines(self, other): + return isinstance(other, Definition) and self.name == other.name + + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + -class Importation(Binding): +class Importation(Definition): """ A binding created by an import statement. @@ -92,11 +128,18 @@ possibly including multiple dotted components. @type fullName: C{str} """ + def __init__(self, name, source): self.fullName = name + self.redefined = [] name = name.split('.')[0] super(Importation, self).__init__(name, source) + def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return isinstance(other, Definition) and self.name == other.name + class Argument(Binding): """ @@ -104,12 +147,6 @@ """ -class Definition(Binding): - """ - A binding that defines a function or a class. - """ - - class Assignment(Binding): """ Represents binding a name with an explicit assignment. @@ -142,16 +179,17 @@ Names which are imported and not otherwise used but appear in the value of C{__all__} will not have an unused import warning reported for them. """ - def names(self): - """ - Return a list of the names referenced by this binding. - """ - names = [] - if isinstance(self.source, ast.List): - for node in self.source.elts: + + def __init__(self, name, source, scope): + if '__all__' in scope and isinstance(source, ast.AugAssign): + self.names = list(scope['__all__'].names) + else: + self.names = [] + if isinstance(source.value, (ast.List, ast.Tuple)): + for node in source.value.elts: if isinstance(node, ast.Str): - names.append(node.s) - return names + self.names.append(node.s) + super(ExportBinding, self).__init__(name, source) class Scope(dict): @@ -180,6 +218,8 @@ super(FunctionScope, self).__init__() # Simplify: manage the special locals as globals self.globals = self.alwaysUsed.copy() + self.returnValue = None # First non-empty return + self.isGenerator = False # Detect a generator def unusedAssignments(self): """ @@ -229,7 +269,6 @@ nodeDepth = 0 offset = None traceTree = False - withDoctest = ('PYFLAKES_NODOCTEST' not in os.environ) builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') @@ -237,7 +276,8 @@ builtIns.update(_customBuiltIns.split(',')) del _customBuiltIns - def __init__(self, tree, filename='(none)', builtins=None): + def __init__(self, tree, filename='(none)', builtins=None, + withDoctest='PYFLAKES_DOCTEST' in os.environ): self._nodeHandlers = {} self._deferredFunctions = [] self._deferredAssignments = [] @@ -246,6 +286,7 @@ self.filename = filename if builtins: self.builtIns = self.builtIns.union(builtins) + self.withDoctest = withDoctest self.scopeStack = [ModuleScope()] self.exceptHandlers = [()] self.futuresAllowed = True @@ -303,126 +344,111 @@ which were imported but unused. """ for scope in self.deadScopes: - export = isinstance(scope.get('__all__'), ExportBinding) - if export: - all = scope['__all__'].names() + if isinstance(scope.get('__all__'), ExportBinding): + all_names = set(scope['__all__'].names) if not scope.importStarred and \ os.path.basename(self.filename) != '__init__.py': # Look for possible mistakes in the export list - undefined = set(all) - set(scope) + undefined = all_names.difference(scope) for name in undefined: self.report(messages.UndefinedExport, scope['__all__'].source, name) else: - all = [] + all_names = [] # Look for imported names that aren't used. - for importation in scope.values(): - if isinstance(importation, Importation): - if not importation.used and importation.name not in all: - self.report(messages.UnusedImport, - importation.source, importation.name) + for value in scope.values(): + if isinstance(value, Importation): + used = value.used or value.name in all_names + if not used: + messg = messages.UnusedImport + self.report(messg, value.source, value.name) + for node in value.redefined: + if isinstance(self.getParent(node), ast.For): + messg = messages.ImportShadowedByLoopVar + elif used: + continue + else: + messg = messages.RedefinedWhileUnused + self.report(messg, node, value.name, value.source) def pushScope(self, scopeClass=FunctionScope): self.scopeStack.append(scopeClass()) - def pushFunctionScope(self): # XXX Deprecated - self.pushScope(FunctionScope) - - def pushClassScope(self): # XXX Deprecated - self.pushScope(ClassScope) - def report(self, messageClass, *args, **kwargs): self.messages.append(messageClass(self.filename, *args, **kwargs)) - def hasParent(self, node, kind): - while hasattr(node, 'parent'): + def getParent(self, node): + # Lookup the first parent which is not Tuple, List or Starred + while True: node = node.parent - if isinstance(node, kind): - return True + if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): + return node - def getCommonAncestor(self, lnode, rnode, stop=None): - if not stop: - stop = self.root + def getCommonAncestor(self, lnode, rnode, stop): + if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and + hasattr(rnode, 'parent')): + return None if lnode is rnode: return lnode - if stop in (lnode, rnode): - return stop - if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'): - return - if (lnode.level > rnode.level): + if (lnode.depth > rnode.depth): return self.getCommonAncestor(lnode.parent, rnode, stop) - if (rnode.level > lnode.level): + if (lnode.depth < rnode.depth): return self.getCommonAncestor(lnode, rnode.parent, stop) return self.getCommonAncestor(lnode.parent, rnode.parent, stop) - def descendantOf(self, node, ancestors, stop=None): + def descendantOf(self, node, ancestors, stop): for a in ancestors: - if self.getCommonAncestor(node, a, stop) not in (stop, None): + if self.getCommonAncestor(node, a, stop): return True return False - def onFork(self, parent, lnode, rnode, items): - return (self.descendantOf(lnode, items, parent) ^ - self.descendantOf(rnode, items, parent)) - def differentForks(self, lnode, rnode): """True, if lnode and rnode are located on different forks of IF/TRY""" - ancestor = self.getCommonAncestor(lnode, rnode) - if isinstance(ancestor, ast.If): - for fork in (ancestor.body, ancestor.orelse): - if self.onFork(ancestor, lnode, rnode, fork): - return True - elif isinstance(ancestor, ast_TryExcept): - body = ancestor.body + ancestor.orelse - for fork in [body] + [[hdl] for hdl in ancestor.handlers]: - if self.onFork(ancestor, lnode, rnode, fork): + ancestor = self.getCommonAncestor(lnode, rnode, self.root) + parts = getAlternatives(ancestor) + if parts: + for items in parts: + if self.descendantOf(lnode, items, ancestor) ^ \ + self.descendantOf(rnode, items, ancestor): return True - elif isinstance(ancestor, ast_TryFinally): - if self.onFork(ancestor, lnode, rnode, ancestor.body): - return True return False - def addBinding(self, node, value, reportRedef=True): + def addBinding(self, node, value): """ Called when a binding is altered. - `node` is the statement responsible for the change - - `value` is the optional new value, a Binding instance, associated - with the binding; if None, the binding is deleted if it exists. - - if `reportRedef` is True (default), rebinding while unused will be - reported. - """ - redefinedWhileUnused = False - if not isinstance(self.scope, ClassScope): - for scope in self.scopeStack[::-1]: - existing = scope.get(value.name) - if (isinstance(existing, Importation) - and not existing.used - and (not isinstance(value, Importation) or - value.fullName == existing.fullName) - and reportRedef - and not self.differentForks(node, existing.source)): - redefinedWhileUnused = True + - `value` is the new value, a Binding instance + """ + # assert value.source in (node, node.parent): + for scope in self.scopeStack[::-1]: + if value.name in scope: + break + existing = scope.get(value.name) + + if existing and not self.differentForks(node, existing.source): + + parent_stmt = self.getParent(value.source) + if isinstance(existing, Importation) and isinstance(parent_stmt, ast.For): + self.report(messages.ImportShadowedByLoopVar, + node, value.name, existing.source) + + elif scope is self.scope: + if (isinstance(parent_stmt, ast.comprehension) and + not isinstance(self.getParent(existing.source), + (ast.For, ast.comprehension))): + self.report(messages.RedefinedInListComp, + node, value.name, existing.source) + elif not existing.used and value.redefines(existing): self.report(messages.RedefinedWhileUnused, node, value.name, existing.source) - existing = self.scope.get(value.name) - if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp): - if (existing and reportRedef - and not self.hasParent(existing.source, (ast.For, ast.ListComp)) - and not self.differentForks(node, existing.source)): - self.report(messages.RedefinedInListComp, - node, value.name, existing.source) + elif isinstance(existing, Importation) and value.redefines(existing): + existing.redefined.append(node) - if (isinstance(existing, Definition) - and not existing.used - and not self.differentForks(node, existing.source)): - self.report(messages.RedefinedWhileUnused, - node, value.name, existing.source) - else: - self.scope[value.name] = value + self.scope[value.name] = value def getNodeHandler(self, node_class): try: @@ -491,12 +517,13 @@ scope[name].used[1], name, scope[name].source) break - parent = getattr(node, 'parent', None) - if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): + parent_stmt = self.getParent(node) + if isinstance(parent_stmt, (ast.For, ast.comprehension)) or ( + parent_stmt != node.parent and + not self.isLiteralTupleUnpacking(parent_stmt)): binding = Binding(name, node) - elif (parent is not None and name == '__all__' and - isinstance(self.scope, ModuleScope)): - binding = ExportBinding(name, parent.value) + elif name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, node.parent, self.scope) else: binding = Assignment(name, node) if name in self.scope: @@ -515,10 +542,17 @@ except KeyError: self.report(messages.UndefinedName, node, name) - def handleChildren(self, tree): - for node in iter_child_nodes(tree): + def handleChildren(self, tree, omit=None): + for node in iter_child_nodes(tree, omit=omit): self.handleNode(node, tree) + def isLiteralTupleUnpacking(self, node): + if isinstance(node, ast.Assign): + for child in node.targets + [node.value]: + if not hasattr(child, 'elts'): + return False + return True + def isDocstring(self, node): """ Determine if the given node is a docstring, as long as it is at the @@ -548,7 +582,7 @@ self.isDocstring(node)): self.futuresAllowed = False self.nodeDepth += 1 - node.level = self.nodeDepth + node.depth = self.nodeDepth node.parent = parent try: handler = self.getNodeHandler(node.__class__) @@ -562,44 +596,49 @@ def handleDoctests(self, node): try: - docstring, node_lineno = self.getDocstring(node.body[0]) - if not docstring: - return - examples = self._getDoctestExamples(docstring) + (docstring, node_lineno) = self.getDocstring(node.body[0]) + examples = docstring and self._getDoctestExamples(docstring) except (ValueError, IndexError): # e.g. line 6 of the docstring for has inconsistent # leading whitespace: ... return + if not examples: + return node_offset = self.offset or (0, 0) self.pushScope() + underscore_in_builtins = '_' in self.builtIns + if not underscore_in_builtins: + self.builtIns.add('_') for example in examples: try: tree = compile(example.source, "", "exec", ast.PyCF_ONLY_AST) except SyntaxError: e = sys.exc_info()[1] position = (node_lineno + example.lineno + e.lineno, - example.indent + 4 + e.offset) + example.indent + 4 + (e.offset or 0)) self.report(messages.DoctestSyntaxError, node, position) else: self.offset = (node_offset[0] + node_lineno + example.lineno, node_offset[1] + example.indent + 4) self.handleChildren(tree) self.offset = node_offset + if not underscore_in_builtins: + self.builtIns.remove('_') self.popScope() def ignore(self, node): pass # "stmt" type nodes - RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ - TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren + DELETE = PRINT = FOR = WHILE = IF = WITH = WITHITEM = RAISE = \ + TRYFINALLY = ASSERT = EXEC = EXPR = ASSIGN = handleChildren CONTINUE = BREAK = PASS = ignore # "expr" type nodes - BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \ + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = \ COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \ - STARRED = handleChildren + STARRED = NAMECONSTANT = handleChildren NUM = STR = BYTES = ELLIPSIS = ignore @@ -615,7 +654,7 @@ EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore # additional node types - COMPREHENSION = KEYWORD = handleChildren + LISTCOMP = COMPREHENSION = KEYWORD = handleChildren def GLOBAL(self, node): """ @@ -626,54 +665,12 @@ NONLOCAL = GLOBAL - def LISTCOMP(self, node): - # handle generators before element - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.elt, node) - def GENERATOREXP(self, node): self.pushScope(GeneratorScope) - # handle generators before element - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.elt, node) - self.popScope() - - SETCOMP = GENERATOREXP - - def DICTCOMP(self, node): - self.pushScope(GeneratorScope) - for gen in node.generators: - self.handleNode(gen, node) - self.handleNode(node.key, node) - self.handleNode(node.value, node) + self.handleChildren(node) self.popScope() - def FOR(self, node): - """ - Process bindings for loop variables. - """ - vars = [] - - def collectLoopVars(n): - if isinstance(n, ast.Name): - vars.append(n.id) - elif isinstance(n, ast.expr_context): - return - else: - for c in iter_child_nodes(n): - collectLoopVars(c) - - collectLoopVars(node.target) - for varn in vars: - if (isinstance(self.scope.get(varn), Importation) - # unused ones will get an unused import warning - and self.scope[varn].used): - self.report(messages.ImportShadowedByLoopVar, - node, varn, self.scope[varn].source) - - self.handleChildren(node) + DICTCOMP = SETCOMP = GENERATOREXP def NAME(self, node): """ @@ -695,16 +692,28 @@ # arguments, but these aren't dispatched through here raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) + def RETURN(self, node): + if node.value and not self.scope.returnValue: + self.scope.returnValue = node.value + self.handleNode(node.value, node) + + def YIELD(self, node): + self.scope.isGenerator = True + self.handleNode(node.value, node) + + YIELDFROM = YIELD + def FUNCTIONDEF(self, node): for deco in node.decorator_list: self.handleNode(deco, node) - self.addBinding(node, FunctionDefinition(node.name, node)) self.LAMBDA(node) + self.addBinding(node, FunctionDefinition(node.name, node)) if self.withDoctest: self.deferFunction(lambda: self.handleDoctests(node)) def LAMBDA(self, node): args = [] + annotations = [] if PY2: def addArgs(arglist): @@ -712,40 +721,47 @@ if isinstance(arg, ast.Tuple): addArgs(arg.elts) else: - if arg.id in args: - self.report(messages.DuplicateArgument, - node, arg.id) args.append(arg.id) addArgs(node.args.args) defaults = node.args.defaults else: for arg in node.args.args + node.args.kwonlyargs: - if arg.arg in args: - self.report(messages.DuplicateArgument, - node, arg.arg) args.append(arg.arg) - self.handleNode(arg.annotation, node) - if hasattr(node, 'returns'): # Only for FunctionDefs - for annotation in (node.args.varargannotation, - node.args.kwargannotation, node.returns): - self.handleNode(annotation, node) + annotations.append(arg.annotation) defaults = node.args.defaults + node.args.kw_defaults - # vararg/kwarg identifiers are not Name nodes - for wildcard in (node.args.vararg, node.args.kwarg): + # Only for Python3 FunctionDefs + is_py3_func = hasattr(node, 'returns') + + for arg_name in ('vararg', 'kwarg'): + wildcard = getattr(node.args, arg_name) if not wildcard: continue - if wildcard in args: - self.report(messages.DuplicateArgument, node, wildcard) - args.append(wildcard) - for default in defaults: - self.handleNode(default, node) + args.append(wildcard if PY33 else wildcard.arg) + if is_py3_func: + if PY33: # Python 2.5 to 3.3 + argannotation = arg_name + 'annotation' + annotations.append(getattr(node.args, argannotation)) + else: # Python >= 3.4 + annotations.append(wildcard.annotation) + + if is_py3_func: + annotations.append(node.returns) + + if len(set(args)) < len(args): + for (idx, arg) in enumerate(args): + if arg in args[:idx]: + self.report(messages.DuplicateArgument, node, arg) + + for child in annotations + defaults: + if child: + self.handleNode(child, node) def runFunction(): self.pushScope() for name in args: - self.addBinding(node, Argument(name, node), reportRedef=False) + self.addBinding(node, Argument(name, node)) if isinstance(node.body, list): # case for FunctionDefs for stmt in node.body: @@ -761,6 +777,17 @@ for name, binding in self.scope.unusedAssignments(): self.report(messages.UnusedVariable, binding.source, name) self.deferAssignment(checkUnusedAssignments) + + if PY32: + def checkReturnWithArgumentInsideGenerator(): + """ + Check to see if there is any return statement with + arguments but the function is a generator. + """ + if self.scope.isGenerator and self.scope.returnValue: + self.report(messages.ReturnWithArgsInsideGenerator, + self.scope.returnValue) + self.deferAssignment(checkReturnWithArgumentInsideGenerator) self.popScope() self.deferFunction(runFunction) @@ -786,11 +813,6 @@ self.popScope() self.addBinding(node, ClassDefinition(node.name, node)) - def ASSIGN(self, node): - self.handleNode(node.value, node) - for target in node.targets: - self.handleNode(target, node) - def AUGASSIGN(self, node): self.handleNodeLoad(node.target) self.handleNode(node.value, node) @@ -836,9 +858,7 @@ self.handleNode(child, node) self.exceptHandlers.pop() # Process the other nodes: "except:", "else:", "finally:" - for child in iter_child_nodes(node): - if child not in node.body: - self.handleNode(child, node) + self.handleChildren(node, omit='body') TRYEXCEPT = TRY diff -Nru pyflakes-0.7.3/pyflakes/__init__.py pyflakes-0.8.1/pyflakes/__init__.py --- pyflakes-0.7.3/pyflakes/__init__.py 2013-07-02 15:36:58.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/__init__.py 2014-03-30 14:56:10.000000000 +0000 @@ -1,2 +1,2 @@ -__version__ = '0.7.3' +__version__ = '0.8.1' diff -Nru pyflakes-0.7.3/pyflakes/messages.py pyflakes-0.8.1/pyflakes/messages.py --- pyflakes-0.7.3/pyflakes/messages.py 2013-04-24 12:10:46.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/messages.py 2014-03-22 12:31:07.000000000 +0000 @@ -126,3 +126,10 @@ def __init__(self, filename, loc, names): Message.__init__(self, filename, loc) self.message_args = (names,) + + +class ReturnWithArgsInsideGenerator(Message): + """ + Indicates a return statement with arguments inside a generator. + """ + message = '\'return\' with argument inside generator' diff -Nru pyflakes-0.7.3/pyflakes/reporter.py pyflakes-0.8.1/pyflakes/reporter.py --- pyflakes-0.7.3/pyflakes/reporter.py 2013-04-23 05:36:53.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/reporter.py 2014-03-22 18:26:16.000000000 +0000 @@ -2,6 +2,7 @@ Provide the Reporter class. """ +import re import sys @@ -45,7 +46,7 @@ @ptype msg: C{unicode} @param lineno: The line number where the syntax error occurred. @ptype lineno: C{int} - @param offset: The column on which the syntax error occurred. + @param offset: The column on which the syntax error occurred, or None. @ptype offset: C{int} @param text: The source code containing the syntax error. @ptype text: C{unicode} @@ -53,11 +54,15 @@ line = text.splitlines()[-1] if offset is not None: offset = offset - (len(text) - len(line)) - self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) + self._stderr.write('%s:%d:%d: %s\n' % + (filename, lineno, offset + 1, msg)) + else: + self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg)) self._stderr.write(line) self._stderr.write('\n') if offset is not None: - self._stderr.write(" " * (offset + 1) + "^\n") + self._stderr.write(re.sub(r'\S', ' ', line[:offset]) + + "^\n") def flake(self, message): """ diff -Nru pyflakes-0.7.3/pyflakes/test/harness.py pyflakes-0.8.1/pyflakes/test/harness.py --- pyflakes-0.7.3/pyflakes/test/harness.py 2013-06-19 13:15:15.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/harness.py 2014-03-22 12:31:07.000000000 +0000 @@ -18,9 +18,11 @@ class TestCase(unittest.TestCase): + withDoctest = False + def flakes(self, input, *expectedOutputs, **kw): tree = compile(textwrap.dedent(input), "", "exec", PyCF_ONLY_AST) - w = checker.Checker(tree, **kw) + w = checker.Checker(tree, withDoctest=self.withDoctest, **kw) outputs = [type(o) for o in w.messages] expectedOutputs = list(expectedOutputs) outputs.sort(key=lambda t: t.__name__) diff -Nru pyflakes-0.7.3/pyflakes/test/test_api.py pyflakes-0.8.1/pyflakes/test/test_api.py --- pyflakes-0.7.3/pyflakes/test/test_api.py 2013-06-19 11:59:49.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_api.py 2014-03-29 11:11:03.000000000 +0000 @@ -15,7 +15,7 @@ checkRecursive, iterSourceCode, ) -from pyflakes.test.harness import TestCase, skipIf +from pyflakes.test.harness import TestCase if sys.version_info < (3,): from cStringIO import StringIO @@ -159,11 +159,11 @@ """ err = StringIO() reporter = Reporter(None, err) - reporter.syntaxError('foo.py', 'a problem', 3, 4, 'bad line of source') + reporter.syntaxError('foo.py', 'a problem', 3, 7, 'bad line of source') self.assertEqual( - ("foo.py:3: a problem\n" + ("foo.py:3:8: a problem\n" "bad line of source\n" - " ^\n"), + " ^\n"), err.getvalue()) def test_syntaxErrorNoOffset(self): @@ -192,12 +192,12 @@ 'more bad lines of source', ] reporter = Reporter(None, err) - reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 5, + reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7, '\n'.join(lines)) self.assertEqual( - ("foo.py:3: a problem\n" + + ("foo.py:3:7: a problem\n" + lines[-1] + "\n" + - " ^\n"), + " ^\n"), err.getvalue()) def test_unexpectedError(self): @@ -320,9 +320,9 @@ self.assertHasErrors( sourcePath, ["""\ -%s:8: invalid syntax +%s:8:11: invalid syntax '''quux''' - ^ + ^ """ % (sourcePath,)]) def test_eofSyntaxError(self): @@ -334,9 +334,23 @@ self.assertHasErrors( sourcePath, ["""\ -%s:1: unexpected EOF while parsing +%s:1:9: unexpected EOF while parsing def foo( - ^ + ^ +""" % (sourcePath,)]) + + def test_eofSyntaxErrorWithTab(self): + """ + The error reported for source files which end prematurely causing a + syntax error reflects the cause for the syntax error. + """ + sourcePath = self.makeTempFile("if True:\n\tfoo =") + self.assertHasErrors( + sourcePath, + ["""\ +%s:2:7: invalid syntax +\tfoo = +\t ^ """ % (sourcePath,)]) def test_nonDefaultFollowsDefaultSyntaxError(self): @@ -350,13 +364,14 @@ pass """ sourcePath = self.makeTempFile(source) - last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + column = '8:' if sys.version_info >= (3, 2) else '' self.assertHasErrors( sourcePath, ["""\ -%s:1: non-default argument follows default argument +%s:1:%s non-default argument follows default argument def foo(bar=baz, bax): -%s""" % (sourcePath, last_line)]) +%s""" % (sourcePath, column, last_line)]) def test_nonKeywordAfterKeywordSyntaxError(self): """ @@ -368,13 +383,14 @@ foo(bar=baz, bax) """ sourcePath = self.makeTempFile(source) - last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + last_line = ' ^\n' if sys.version_info >= (3, 2) else '' + column = '13:' if sys.version_info >= (3, 2) else '' self.assertHasErrors( sourcePath, ["""\ -%s:1: non-keyword arg after keyword arg +%s:1:%s non-keyword arg after keyword arg foo(bar=baz, bax) -%s""" % (sourcePath, last_line)]) +%s""" % (sourcePath, column, last_line)]) def test_invalidEscape(self): """ @@ -386,11 +402,11 @@ if ver < (3,): decoding_error = "%s: problem decoding source\n" % (sourcePath,) else: - last_line = ' ^\n' if ver >= (3, 2) else '' + last_line = ' ^\n' if ver >= (3, 2) else '' # Column has been "fixed" since 3.2.4 and 3.3.1 col = 1 if ver >= (3, 3, 1) or ((3, 2, 4) <= ver < (3, 3)) else 2 decoding_error = """\ -%s:1: (unicode error) 'unicodeescape' codec can't decode bytes \ +%s:1:7: (unicode error) 'unicodeescape' codec can't decode bytes \ in position 0-%d: truncated \\xXX escape foo = '\\xyz' %s""" % (sourcePath, col, last_line) @@ -421,7 +437,18 @@ self.assertEqual( errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))]) - @skipIf(sys.version_info >= (3,), "not relevant") + def test_encodedFileUTF8(self): + """ + If source file declares the correct encoding, no error is reported. + """ + SNOWMAN = unichr(0x2603) + source = ("""\ +# coding: utf-8 +x = "%s" +""" % SNOWMAN).encode('utf-8') + sourcePath = self.makeTempFile(source) + self.assertHasErrors(sourcePath, []) + def test_misencodedFileUTF8(self): """ If a source file contains bytes which cannot be decoded, this is diff -Nru pyflakes-0.7.3/pyflakes/test/test_doctests.py pyflakes-0.8.1/pyflakes/test/test_doctests.py --- pyflakes-0.7.3/pyflakes/test/test_doctests.py 2013-06-19 11:45:15.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_doctests.py 2014-03-30 01:32:53.000000000 +0000 @@ -4,10 +4,12 @@ from pyflakes.test.test_other import Test as TestOther from pyflakes.test.test_imports import Test as TestImports from pyflakes.test.test_undefined_names import Test as TestUndefinedNames -from pyflakes.test.harness import skip +from pyflakes.test.harness import TestCase, skip -class Test(TestOther, TestImports, TestUndefinedNames): +class _DoctestMixin(object): + + withDoctest = True def doctestify(self, input): lines = [] @@ -33,37 +35,15 @@ return doctestificator % "\n ".join(lines) def flakes(self, input, *args, **kw): - return super(Test, self).flakes(self.doctestify(input), - *args, **kw) + return super(_DoctestMixin, self).flakes(self.doctestify(input), *args, **kw) - def test_doubleNestingReportsClosestName(self): - """ - Lines in doctest are a bit different so we can't use the test - from TestUndefinedNames - """ - exc = super(Test, self).flakes(''' - def doctest_stuff(): - """ - >>> def a(): - ... x = 1 - ... def b(): - ... x = 2 # line 7 in the file - ... def c(): - ... x - ... x = 3 - ... return x - ... return x - ... return x - """ - ''', m.UndefinedLocal).messages[0] - self.assertEqual(exc.message_args, ('x', 7)) +class Test(TestCase): - def test_futureImport(self): - """XXX This test can't work in a doctest""" + withDoctest = True def test_importBeforeDoctest(self): - super(Test, self).flakes(""" + self.flakes(""" import foo def doctest_stuff(): @@ -74,7 +54,7 @@ @skip("todo") def test_importBeforeAndInDoctest(self): - super(Test, self).flakes(''' + self.flakes(''' import foo def doctest_stuff(): @@ -87,7 +67,7 @@ ''', m.Redefined) def test_importInDoctestAndAfter(self): - super(Test, self).flakes(''' + self.flakes(''' def doctest_stuff(): """ >>> import foo @@ -99,7 +79,7 @@ ''') def test_offsetInDoctests(self): - exc = super(Test, self).flakes(''' + exc = self.flakes(''' def doctest_stuff(): """ @@ -111,7 +91,7 @@ self.assertEqual(exc.col, 12) def test_offsetInLambdasInDoctests(self): - exc = super(Test, self).flakes(''' + exc = self.flakes(''' def doctest_stuff(): """ @@ -123,7 +103,7 @@ self.assertEqual(exc.col, 20) def test_offsetAfterDoctests(self): - exc = super(Test, self).flakes(''' + exc = self.flakes(''' def doctest_stuff(): """ @@ -137,7 +117,7 @@ self.assertEqual(exc.col, 0) def test_syntaxErrorInDoctest(self): - exceptions = super(Test, self).flakes( + exceptions = self.flakes( ''' def doctest_stuff(): """ @@ -160,7 +140,7 @@ self.assertEqual(exc.col, 18) def test_indentationErrorInDoctest(self): - exc = super(Test, self).flakes(''' + exc = self.flakes(''' def doctest_stuff(): """ >>> if True: @@ -171,7 +151,7 @@ self.assertEqual(exc.col, 16) def test_offsetWithMultiLineArgs(self): - (exc1, exc2) = super(Test, self).flakes( + (exc1, exc2) = self.flakes( ''' def doctest_stuff(arg1, arg2, @@ -189,7 +169,7 @@ self.assertEqual(exc2.col, 12) def test_doctestCanReferToFunction(self): - super(Test, self).flakes(""" + self.flakes(""" def foo(): ''' >>> foo @@ -197,7 +177,7 @@ """) def test_doctestCanReferToClass(self): - super(Test, self).flakes(""" + self.flakes(""" class Foo(): ''' >>> Foo @@ -207,3 +187,70 @@ >>> Foo ''' """) + + def test_noOffsetSyntaxErrorInDoctest(self): + exceptions = self.flakes( + ''' + def buildurl(base, *args, **kwargs): + """ + >>> buildurl('/blah.php', ('a', '&'), ('b', '=') + '/blah.php?a=%26&b=%3D' + >>> buildurl('/blah.php', a='&', 'b'='=') + '/blah.php?b=%3D&a=%26' + """ + pass + ''', + m.DoctestSyntaxError, + m.DoctestSyntaxError).messages + exc = exceptions[0] + self.assertEqual(exc.lineno, 4) + exc = exceptions[1] + self.assertEqual(exc.lineno, 6) + + def test_singleUnderscoreInDoctest(self): + self.flakes(''' + def func(): + """A docstring + + >>> func() + 1 + >>> _ + 1 + """ + return 1 + ''') + + +class TestOther(_DoctestMixin, TestOther): + pass + + +class TestImports(_DoctestMixin, TestImports): + + def test_futureImport(self): + """XXX This test can't work in a doctest""" + + def test_futureImportUsed(self): + """XXX This test can't work in a doctest""" + + +class TestUndefinedNames(_DoctestMixin, TestUndefinedNames): + + def test_doubleNestingReportsClosestName(self): + """ + Lines in doctest are a bit different so we can't use the test + from TestUndefinedNames + """ + exc = self.flakes(''' + def a(): + x = 1 + def b(): + x = 2 # line 7 in the file + def c(): + x + x = 3 + return x + return x + return x + ''', m.UndefinedLocal).messages[0] + self.assertEqual(exc.message_args, ('x', 7)) diff -Nru pyflakes-0.7.3/pyflakes/test/test_imports.py pyflakes-0.8.1/pyflakes/test/test_imports.py --- pyflakes-0.7.3/pyflakes/test/test_imports.py 2013-06-19 11:47:22.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_imports.py 2014-03-30 02:47:13.000000000 +0000 @@ -172,6 +172,39 @@ pass ''', m.RedefinedWhileUnused, m.UnusedImport) + def test_redefinedInNestedFunctionTwice(self): + """ + Test that shadowing a global name with a nested function definition + generates a warning. + """ + self.flakes(''' + import fu + def bar(): + import fu + def baz(): + def fu(): + pass + ''', m.RedefinedWhileUnused, m.RedefinedWhileUnused, + m.UnusedImport, m.UnusedImport) + + def test_redefinedButUsedLater(self): + """ + Test that a global import which is redefined locally, + but used later in another scope does not generate a warning. + """ + self.flakes(''' + import unittest, transport + + class GetTransportTestCase(unittest.TestCase): + def test_get_transport(self): + transport = 'transport' + self.assertIsNotNone(transport) + + class TestTransportMethodArgs(unittest.TestCase): + def test_send_defaults(self): + transport.Transport() + ''') + def test_redefinedByClass(self): self.flakes(''' import fu @@ -214,7 +247,7 @@ import fu def fun(fu): print(fu) - ''', m.UnusedImport) + ''', m.UnusedImport, m.RedefinedWhileUnused) self.flakes(''' import fu @@ -305,7 +338,7 @@ import fu for fu in range(2): pass - ''', m.RedefinedWhileUnused) + ''', m.ImportShadowedByLoopVar) def test_shadowedByFor(self): """ @@ -330,6 +363,13 @@ for (x, y, z, (a, b, c, (fu,))) in (): pass ''', m.ImportShadowedByLoopVar) + # Same with a list instead of a tuple + self.flakes(''' + import fu + fu.bar() + for [x, y, z, (a, b, c, (fu,))] in (): + pass + ''', m.ImportShadowedByLoopVar) def test_usedInReturn(self): self.flakes(''' @@ -433,7 +473,8 @@ self.flakes('import fu; [1 for _ in range(1) if fu]') def test_redefinedByListComp(self): - self.flakes('import fu; [1 for fu in range(1)]', m.RedefinedWhileUnused) + self.flakes('import fu; [1 for fu in range(1)]', + m.RedefinedInListComp) def test_usedInTryFinally(self): self.flakes(''' @@ -481,7 +522,9 @@ self.flakes('import fu; lambda: fu') def test_shadowedByLambda(self): - self.flakes('import fu; lambda fu: fu', m.UnusedImport) + self.flakes('import fu; lambda fu: fu', + m.UnusedImport, m.RedefinedWhileUnused) + self.flakes('import fu; lambda fu: fu\nfu()') def test_usedInSliceObj(self): self.flakes('import fu; "meow"[::fu]') @@ -628,6 +671,18 @@ self.i ''') + def test_importUsedInMethodDefinition(self): + """ + Method named 'foo' with default args referring to module named 'foo'. + """ + self.flakes(''' + import foo + + class Thing(object): + def foo(self, parser=foo.parse_foo): + pass + ''') + def test_futureImport(self): """__future__ is special.""" self.flakes('from __future__ import division') @@ -650,6 +705,15 @@ bar ''', m.LateFutureImport) + def test_futureImportUsed(self): + """__future__ is special, but names are injected in the namespace.""" + self.flakes(''' + from __future__ import division + from __future__ import print_function + + assert print_function is not division + ''') + class TestSpecialAll(TestCase): """ @@ -686,6 +750,23 @@ import foo __all__ = ["foo"] ''') + self.flakes(''' + import foo + __all__ = ("foo",) + ''') + + def test_augmentedAssignment(self): + """ + The C{__all__} variable is defined incrementally. + """ + self.flakes(''' + import a + import c + __all__ = ['a'] + __all__ += ['b'] + if 1 < 3: + __all__ += ['c', 'd'] + ''', m.UndefinedExport, m.UndefinedExport) def test_unrecognizable(self): """ diff -Nru pyflakes-0.7.3/pyflakes/test/test_other.py pyflakes-0.8.1/pyflakes/test/test_other.py --- pyflakes-0.7.3/pyflakes/test/test_other.py 2013-07-02 14:41:45.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_other.py 2014-03-30 02:07:29.000000000 +0000 @@ -13,14 +13,13 @@ def test_duplicateArgs(self): self.flakes('def fu(bar, bar): pass', m.DuplicateArgument) - @skip("todo: this requires finding all assignments in the function body first") def test_localReferencedBeforeAssignment(self): self.flakes(''' a = 1 def f(): a; a=1 f() - ''', m.UndefinedName) + ''', m.UndefinedLocal, m.UnusedVariable) def test_redefinedInListComp(self): """ @@ -592,18 +591,40 @@ in good Python code, so warning will only create false positives. """ self.flakes(''' + def f(tup): + (x, y) = tup + ''') + self.flakes(''' def f(): (x, y) = 1, 2 + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + if x > 1: + print(coords) ''') + self.flakes(''' + def f(): + (x, y) = coords = 1, 2 + ''', m.UnusedVariable) + self.flakes(''' + def f(): + coords = (x, y) = 1, 2 + ''', m.UnusedVariable) def test_listUnpacking(self): """ Don't warn when a variable included in list unpacking is unused. """ self.flakes(''' + def f(tup): + [x, y] = tup + ''') + self.flakes(''' def f(): [x, y] = [1, 2] - ''') + ''', m.UnusedVariable, m.UnusedVariable) def test_closedOver(self): """ diff -Nru pyflakes-0.7.3/pyflakes/test/test_return_with_arguments_inside_generator.py pyflakes-0.8.1/pyflakes/test/test_return_with_arguments_inside_generator.py --- pyflakes-0.7.3/pyflakes/test/test_return_with_arguments_inside_generator.py 1970-01-01 00:00:00.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_return_with_arguments_inside_generator.py 2014-03-22 12:31:07.000000000 +0000 @@ -0,0 +1,34 @@ + +from sys import version_info + +from pyflakes import messages as m +from pyflakes.test.harness import TestCase, skipIf + + +class Test(TestCase): + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_return(self): + self.flakes(''' + class a: + def b(): + for x in a.c: + if x: + yield x + return a + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnNone(self): + self.flakes(''' + def a(): + yield 12 + return None + ''', m.ReturnWithArgsInsideGenerator) + + @skipIf(version_info >= (3, 3), 'new in Python 3.3') + def test_returnYieldExpression(self): + self.flakes(''' + def a(): + b = yield a + return b + ''', m.ReturnWithArgsInsideGenerator) diff -Nru pyflakes-0.7.3/pyflakes/test/test_undefined_names.py pyflakes-0.8.1/pyflakes/test/test_undefined_names.py --- pyflakes-0.7.3/pyflakes/test/test_undefined_names.py 2013-07-02 15:39:12.000000000 +0000 +++ pyflakes-0.8.1/pyflakes/test/test_undefined_names.py 2014-03-30 16:59:23.000000000 +0000 @@ -280,6 +280,42 @@ ''') @skipIf(version_info < (3,), 'new in Python 3') + def test_usedAsStarUnpack(self): + """ + Star names in unpack are used if RHS is not a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = range(10) + ''') + self.flakes(''' + def f(): + (*a, b) = range(10) + ''') + self.flakes(''' + def f(): + [a, *b, c] = range(10) + ''') + + @skipIf(version_info < (3,), 'new in Python 3') + def test_unusedAsStarUnpack(self): + """ + Star names in unpack are unused if RHS is a tuple/list literal. + """ + self.flakes(''' + def f(): + a, *b = any, all, 4, 2, 'un' + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + (*a, b) = [bool, int, float, complex] + ''', m.UnusedVariable, m.UnusedVariable) + self.flakes(''' + def f(): + [a, *b, c] = 9, 8, 7, 6, 5, 4 + ''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable) + + @skipIf(version_info < (3,), 'new in Python 3') def test_keywordOnlyArgs(self): """Keyword-only arg names are defined.""" self.flakes(''' @@ -384,6 +420,21 @@ Y = {x:x for x in T} ''') + def test_undefinedInLoop(self): + """ + The loop variable is defined after the expression is computed. + """ + self.flakes(''' + for i in range(i): + print(i) + ''', m.UndefinedName) + self.flakes(''' + [42 for i in range(i)] + ''', m.UndefinedName) + self.flakes(''' + (42 for i in range(i)) + ''', m.UndefinedName) + class NameTests(TestCase): """ diff -Nru pyflakes-0.7.3/pyflakes.egg-info/PKG-INFO pyflakes-0.8.1/pyflakes.egg-info/PKG-INFO --- pyflakes-0.7.3/pyflakes.egg-info/PKG-INFO 2013-07-02 16:04:27.000000000 +0000 +++ pyflakes-0.8.1/pyflakes.egg-info/PKG-INFO 2014-03-30 18:53:07.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pyflakes -Version: 0.7.3 +Version: 0.8.1 Summary: passive checker of Python programs Home-page: https://launchpad.net/pyflakes Author: Florent Xicluna @@ -17,7 +17,7 @@ modules with side effects. It's also much faster. It is `available on PyPI `_ - and it supports all active versions of Python from 2.5 to 3.3. + and it supports all active versions of Python from 2.5 to 3.4. Installation @@ -39,6 +39,10 @@ :target: https://travis-ci.org/pyflakes/pyflakes :alt: Build status + .. image:: https://pypip.in/wheel/pyflakes/badge.png + :target: https://pypi.python.org/pypi/pyflakes + :alt: Wheel Status + Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Environment :: Console diff -Nru pyflakes-0.7.3/pyflakes.egg-info/SOURCES.txt pyflakes-0.8.1/pyflakes.egg-info/SOURCES.txt --- pyflakes-0.7.3/pyflakes.egg-info/SOURCES.txt 2013-07-02 16:04:27.000000000 +0000 +++ pyflakes-0.8.1/pyflakes.egg-info/SOURCES.txt 2014-03-30 18:53:07.000000000 +0000 @@ -3,6 +3,7 @@ MANIFEST.in NEWS.txt README.rst +setup.cfg setup.py bin/pyflakes pyflakes/__init__.py @@ -24,4 +25,5 @@ pyflakes/test/test_doctests.py pyflakes/test/test_imports.py pyflakes/test/test_other.py +pyflakes/test/test_return_with_arguments_inside_generator.py pyflakes/test/test_undefined_names.py \ No newline at end of file diff -Nru pyflakes-0.7.3/README.rst pyflakes-0.8.1/README.rst --- pyflakes-0.7.3/README.rst 2013-04-21 10:51:56.000000000 +0000 +++ pyflakes-0.8.1/README.rst 2014-03-30 18:30:36.000000000 +0000 @@ -9,7 +9,7 @@ modules with side effects. It's also much faster. It is `available on PyPI `_ -and it supports all active versions of Python from 2.5 to 3.3. +and it supports all active versions of Python from 2.5 to 3.4. Installation @@ -30,3 +30,7 @@ .. image:: https://api.travis-ci.org/pyflakes/pyflakes.png :target: https://travis-ci.org/pyflakes/pyflakes :alt: Build status + +.. image:: https://pypip.in/wheel/pyflakes/badge.png + :target: https://pypi.python.org/pypi/pyflakes + :alt: Wheel Status diff -Nru pyflakes-0.7.3/setup.cfg pyflakes-0.8.1/setup.cfg --- pyflakes-0.7.3/setup.cfg 2013-07-02 16:04:27.000000000 +0000 +++ pyflakes-0.8.1/setup.cfg 2014-03-30 18:53:07.000000000 +0000 @@ -1,3 +1,6 @@ +[wheel] +universal = 1 + [egg_info] tag_build = tag_date = 0