Georg Brandl avatar Georg Brandl committed 65c6859

Initial import.

Comments (0)

Files changed (16)

+\.svn/
+
+Copyright (c) 2005 Divmod, Inc., http://www.divmod.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+0.4.0 (2009-11-25):
+  - Fix reporting for certain SyntaxErrors which lack line number
+    information.
+  - Check for syntax errors more rigorously.
+  - Support checking names used with the class decorator syntax in versions
+    of Python which have it.
+  - Detect local variables which are bound but never used.
+  - Handle permission errors when trying to read source files.
+  - Handle problems with the encoding of source files.
+  - Support importing dotted names so as not to incorrectly report them as
+    redefined unused names.
+  - Support all forms of the with statement.
+  - Consider static `__all__` definitions and avoid reporting unused names
+    if the names are listed there.
+  - Fix incorrect checking of class names with respect to the names of their
+    bases in the class statement.
+  - Support the `__path__` global in `__init__.py`.
+
+0.3.0 (2009-01-30):
+  - Display more informative SyntaxError messages.
+  - Don't hang flymake with unmatched triple quotes (only report a single
+    line of source for a multiline syntax error).
+  - Recognize __builtins__ as a defined name.
+  - Improve pyflakes support for python versions 2.3-2.5
+  - Support for if-else expressions and with statements.
+  - Warn instead of error on non-existant file paths.
+  - Check for __future__ imports after other statements.
+  - Add reporting for some types of import shadowing.
+  - Improve reporting of unbound locals
+#!/usr/bin/python
+
+from pyflakes.scripts.pyflakes import main
+main()

pyflakes/__init__.py

+
+__version__ = '0.4.0'

pyflakes/checker.py

+# -*- test-case-name: pyflakes -*-
+# (c) 2005-2008 Divmod, Inc.
+# See LICENSE file for details
+
+import __builtin__
+import os.path
+from compiler import ast
+
+from pyflakes import messages
+
+
+
+class Binding(object):
+    """
+    Represents the binding of a value to a name.
+
+    The checker uses this to keep track of which names have been bound and
+    which names have not. See L{Assignment} for a special type of binding that
+    is checked with stricter rules.
+
+    @ivar used: pair of (L{Scope}, line-number) indicating the scope and
+                line number that this binding was last used
+    """
+
+    def __init__(self, name, source):
+        self.name = name
+        self.source = source
+        self.used = False
+
+
+    def __str__(self):
+        return self.name
+
+
+    def __repr__(self):
+        return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
+                                                        self.name,
+                                                        self.source.lineno,
+                                                        id(self))
+
+
+
+class UnBinding(Binding):
+    '''Created by the 'del' operator.'''
+
+
+
+class Importation(Binding):
+    """
+    A binding created by an import statement.
+
+    @ivar fullName: The complete name given to the import statement,
+        possibly including multiple dotted components.
+    @type fullName: C{str}
+    """
+    def __init__(self, name, source):
+        self.fullName = name
+        name = name.split('.')[0]
+        super(Importation, self).__init__(name, source)
+
+
+
+class Argument(Binding):
+    """
+    Represents binding a name as an argument.
+    """
+
+
+
+class Assignment(Binding):
+    """
+    Represents binding a name with an explicit assignment.
+
+    The checker will raise warnings for any Assignment that isn't used. Also,
+    the checker does not consider assignments in tuple/list unpacking to be
+    Assignments, rather it treats them as simple Bindings.
+    """
+
+
+
+class FunctionDefinition(Binding):
+    pass
+
+
+
+class ExportBinding(Binding):
+    """
+    A binding created by an C{__all__} assignment.  If the names in the list
+    can be determined statically, they will be treated as names for export and
+    additional checking applied to them.
+
+    The only C{__all__} assignment that can be recognized is one which takes
+    the value of a literal list containing literal strings.  For example::
+
+        __all__ = ["foo", "bar"]
+
+    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.nodes:
+                if isinstance(node, ast.Const):
+                    names.append(node.value)
+        return names
+
+
+
+class Scope(dict):
+    importStarred = False       # set to True when import * is found
+
+
+    def __repr__(self):
+        return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
+
+
+    def __init__(self):
+        super(Scope, self).__init__()
+
+
+
+class ClassScope(Scope):
+    pass
+
+
+
+class FunctionScope(Scope):
+    """
+    I represent a name scope for a function.
+
+    @ivar globals: Names declared 'global' in this function.
+    """
+    def __init__(self):
+        super(FunctionScope, self).__init__()
+        self.globals = {}
+
+
+
+class ModuleScope(Scope):
+    pass
+
+
+# Globally defined names which are not attributes of the __builtin__ module.
+_MAGIC_GLOBALS = ['__file__', '__builtins__']
+
+
+
+class Checker(object):
+    """
+    I check the cleanliness and sanity of Python code.
+
+    @ivar _deferredFunctions: Tracking list used by L{deferFunction}.  Elements
+        of the list are two-tuples.  The first element is the callable passed
+        to L{deferFunction}.  The second element is a copy of the scope stack
+        at the time L{deferFunction} was called.
+
+    @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
+        callables which are deferred assignment checks.
+    """
+
+    nodeDepth = 0
+    traceTree = False
+
+    def __init__(self, tree, filename='(none)'):
+        self._deferredFunctions = []
+        self._deferredAssignments = []
+        self.dead_scopes = []
+        self.messages = []
+        self.filename = filename
+        self.scopeStack = [ModuleScope()]
+        self.futuresAllowed = True
+        self.handleChildren(tree)
+        self._runDeferred(self._deferredFunctions)
+        # Set _deferredFunctions to None so that deferFunction will fail
+        # noisily if called after we've run through the deferred functions.
+        self._deferredFunctions = None
+        self._runDeferred(self._deferredAssignments)
+        # Set _deferredAssignments to None so that deferAssignment will fail
+        # noisly if called after we've run through the deferred assignments.
+        self._deferredAssignments = None
+        del self.scopeStack[1:]
+        self.popScope()
+        self.check_dead_scopes()
+
+
+    def deferFunction(self, callable):
+        '''
+        Schedule a function handler to be called just before completion.
+
+        This is used for handling function bodies, which must be deferred
+        because code later in the file might modify the global scope. When
+        `callable` is called, the scope at the time this is called will be
+        restored, however it will contain any new bindings added to it.
+        '''
+        self._deferredFunctions.append((callable, self.scopeStack[:]))
+
+
+    def deferAssignment(self, callable):
+        """
+        Schedule an assignment handler to be called just after deferred
+        function handlers.
+        """
+        self._deferredAssignments.append((callable, self.scopeStack[:]))
+
+
+    def _runDeferred(self, deferred):
+        """
+        Run the callables in C{deferred} using their associated scope stack.
+        """
+        for handler, scope in deferred:
+            self.scopeStack = scope
+            handler()
+
+
+    def scope(self):
+        return self.scopeStack[-1]
+    scope = property(scope)
+
+    def popScope(self):
+        self.dead_scopes.append(self.scopeStack.pop())
+
+
+    def check_dead_scopes(self):
+        """
+        Look at scopes which have been fully examined and report names in them
+        which were imported but unused.
+        """
+        for scope in self.dead_scopes:
+            export = isinstance(scope.get('__all__'), ExportBinding)
+            if export:
+                all = scope['__all__'].names()
+                if os.path.split(self.filename)[1] != '__init__.py':
+                    # Look for possible mistakes in the export list
+                    undefined = set(all) - set(scope)
+                    for name in undefined:
+                        self.report(
+                            messages.UndefinedExport,
+                            scope['__all__'].source.lineno,
+                            name)
+            else:
+                all = []
+
+            # Look for imported names that aren't used.
+            for importation in scope.itervalues():
+                if isinstance(importation, Importation):
+                    if not importation.used and importation.name not in all:
+                        self.report(
+                            messages.UnusedImport,
+                            importation.source.lineno,
+                            importation.name)
+
+
+    def pushFunctionScope(self):
+        self.scopeStack.append(FunctionScope())
+
+    def pushClassScope(self):
+        self.scopeStack.append(ClassScope())
+
+    def report(self, messageClass, *args, **kwargs):
+        self.messages.append(messageClass(self.filename, *args, **kwargs))
+
+    def handleChildren(self, tree):
+        for node in tree.getChildNodes():
+            self.handleNode(node, tree)
+
+    def handleNode(self, node, parent):
+        node.parent = parent
+        if self.traceTree:
+            print '  ' * self.nodeDepth + node.__class__.__name__
+        self.nodeDepth += 1
+        nodeType = node.__class__.__name__.upper()
+        if nodeType not in ('STMT', 'FROM'):
+            self.futuresAllowed = False
+        try:
+            handler = getattr(self, nodeType)
+            handler(node)
+        finally:
+            self.nodeDepth -= 1
+        if self.traceTree:
+            print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
+
+    def ignore(self, node):
+        pass
+
+    STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \
+    ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = \
+    RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \
+    SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \
+    RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \
+    FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \
+    AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = \
+    IFEXP = handleChildren
+
+    CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore
+
+    def addBinding(self, lineno, value, reportRedef=True):
+        '''Called when a binding is altered.
+
+        - `lineno` is the line of 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.
+        '''
+        if (isinstance(self.scope.get(value.name), FunctionDefinition)
+                    and isinstance(value, FunctionDefinition)):
+            self.report(messages.RedefinedFunction,
+                        lineno, value.name, self.scope[value.name].source.lineno)
+
+        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):
+
+                    self.report(messages.RedefinedWhileUnused,
+                                lineno, value.name, scope[value.name].source.lineno)
+
+        if isinstance(value, UnBinding):
+            try:
+                del self.scope[value.name]
+            except KeyError:
+                self.report(messages.UndefinedName, lineno, value.name)
+        else:
+            self.scope[value.name] = value
+
+
+    def WITH(self, node):
+        """
+        Handle C{with} by checking the target of the statement (which can be an
+        identifier, a list or tuple of targets, an attribute, etc) for
+        undefined names and defining any it adds to the scope and by continuing
+        to process the suite within the statement.
+        """
+        # Check the "foo" part of a "with foo as bar" statement.  Do this no
+        # matter what, since there's always a "foo" part.
+        self.handleNode(node.expr, node)
+
+        if node.vars is not None:
+            self.handleNode(node.vars, node)
+
+        self.handleChildren(node.body)
+
+
+    def GLOBAL(self, node):
+        """
+        Keep track of globals declarations.
+        """
+        if isinstance(self.scope, FunctionScope):
+            self.scope.globals.update(dict.fromkeys(node.names))
+
+    def LISTCOMP(self, node):
+        for qual in node.quals:
+            self.handleNode(qual, node)
+        self.handleNode(node.expr, node)
+
+    GENEXPRINNER = LISTCOMP
+
+    def FOR(self, node):
+        """
+        Process bindings for loop variables.
+        """
+        vars = []
+        def collectLoopVars(n):
+            if hasattr(n, 'name'):
+                vars.append(n.name)
+            else:
+                for c in n.getChildNodes():
+                    collectLoopVars(c)
+
+        collectLoopVars(node.assign)
+        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.lineno, varn, self.scope[varn].source.lineno)
+
+        self.handleChildren(node)
+
+    def NAME(self, node):
+        """
+        Locate the name in locals / function / globals scopes.
+        """
+        # try local scope
+        importStarred = self.scope.importStarred
+        try:
+            self.scope[node.name].used = (self.scope, node.lineno)
+        except KeyError:
+            pass
+        else:
+            return
+
+        # try enclosing function scopes
+
+        for scope in self.scopeStack[-2:0:-1]:
+            importStarred = importStarred or scope.importStarred
+            if not isinstance(scope, FunctionScope):
+                continue
+            try:
+                scope[node.name].used = (self.scope, node.lineno)
+            except KeyError:
+                pass
+            else:
+                return
+
+        # try global scope
+
+        importStarred = importStarred or self.scopeStack[0].importStarred
+        try:
+            self.scopeStack[0][node.name].used = (self.scope, node.lineno)
+        except KeyError:
+            if ((not hasattr(__builtin__, node.name))
+                    and node.name not in _MAGIC_GLOBALS
+                    and not importStarred):
+                if (os.path.basename(self.filename) == '__init__.py' and
+                    node.name == '__path__'):
+                    # the special name __path__ is valid only in packages
+                    pass
+                else:
+                    self.report(messages.UndefinedName, node.lineno, node.name)
+
+
+    def FUNCTION(self, node):
+        if getattr(node, "decorators", None) is not None:
+            self.handleChildren(node.decorators)
+        self.addBinding(node.lineno, FunctionDefinition(node.name, node))
+        self.LAMBDA(node)
+
+    def LAMBDA(self, node):
+        for default in node.defaults:
+            self.handleNode(default, node)
+
+        def runFunction():
+            args = []
+
+            def addArgs(arglist):
+                for arg in arglist:
+                    if isinstance(arg, tuple):
+                        addArgs(arg)
+                    else:
+                        if arg in args:
+                            self.report(messages.DuplicateArgument, node.lineno, arg)
+                        args.append(arg)
+
+            self.pushFunctionScope()
+            addArgs(node.argnames)
+            for name in args:
+                self.addBinding(node.lineno, Argument(name, node), reportRedef=False)
+            self.handleNode(node.code, node)
+            def checkUnusedAssignments():
+                """
+                Check to see if any assignments have not been used.
+                """
+                for name, binding in self.scope.iteritems():
+                    if (not binding.used and not name in self.scope.globals
+                        and isinstance(binding, Assignment)):
+                        self.report(messages.UnusedVariable,
+                                    binding.source.lineno, name)
+            self.deferAssignment(checkUnusedAssignments)
+            self.popScope()
+
+        self.deferFunction(runFunction)
+
+
+    def CLASS(self, node):
+        """
+        Check names used in a class definition, including its decorators, base
+        classes, and the body of its definition.  Additionally, add its name to
+        the current scope.
+        """
+        if getattr(node, "decorators", None) is not None:
+            self.handleChildren(node.decorators)
+        for baseNode in node.bases:
+            self.handleNode(baseNode, node)
+        self.addBinding(node.lineno, Binding(node.name, node))
+        self.pushClassScope()
+        self.handleChildren(node.code)
+        self.popScope()
+
+
+    def ASSNAME(self, node):
+        if node.flags == 'OP_DELETE':
+            if isinstance(self.scope, FunctionScope) and node.name in self.scope.globals:
+                del self.scope.globals[node.name]
+            else:
+                self.addBinding(node.lineno, UnBinding(node.name, node))
+        else:
+            # if the name hasn't already been defined in the current scope
+            if isinstance(self.scope, FunctionScope) and node.name not in self.scope:
+                # for each function or module scope above us
+                for scope in self.scopeStack[:-1]:
+                    if not isinstance(scope, (FunctionScope, ModuleScope)):
+                        continue
+                    # if the name was defined in that scope, and the name has
+                    # been accessed already in the current scope, and hasn't
+                    # been declared global
+                    if (node.name in scope
+                            and scope[node.name].used
+                            and scope[node.name].used[0] is self.scope
+                            and node.name not in self.scope.globals):
+                        # then it's probably a mistake
+                        self.report(messages.UndefinedLocal,
+                                    scope[node.name].used[1],
+                                    node.name,
+                                    scope[node.name].source.lineno)
+                        break
+
+            if isinstance(node.parent,
+                          (ast.For, ast.ListCompFor, ast.GenExprFor,
+                           ast.AssTuple, ast.AssList)):
+                binding = Binding(node.name, node)
+            elif (node.name == '__all__' and
+                  isinstance(self.scope, ModuleScope) and
+                  isinstance(node.parent, ast.Assign)):
+                binding = ExportBinding(node.name, node.parent.expr)
+            else:
+                binding = Assignment(node.name, node)
+            if node.name in self.scope:
+                binding.used = self.scope[node.name].used
+            self.addBinding(node.lineno, binding)
+
+    def ASSIGN(self, node):
+        self.handleNode(node.expr, node)
+        for subnode in node.nodes[::-1]:
+            self.handleNode(subnode, node)
+
+    def IMPORT(self, node):
+        for name, alias in node.names:
+            name = alias or name
+            importation = Importation(name, node)
+            self.addBinding(node.lineno, importation)
+
+    def FROM(self, node):
+        if node.modname == '__future__':
+            if not self.futuresAllowed:
+                self.report(messages.LateFutureImport, node.lineno, [n[0] for n in node.names])
+        else:
+            self.futuresAllowed = False
+
+        for name, alias in node.names:
+            if name == '*':
+                self.scope.importStarred = True
+                self.report(messages.ImportStarUsed, node.lineno, node.modname)
+                continue
+            name = alias or name
+            importation = Importation(name, node)
+            if node.modname == '__future__':
+                importation.used = (self.scope, node.lineno)
+            self.addBinding(node.lineno, importation)

pyflakes/messages.py

+# (c) 2005 Divmod, Inc.  See LICENSE file for details
+
+class Message(object):
+    message = ''
+    message_args = ()
+    def __init__(self, filename, lineno):
+        self.filename = filename
+        self.lineno = lineno
+    def __str__(self):
+        return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
+
+
+class UnusedImport(Message):
+    message = '%r imported but unused'
+    def __init__(self, filename, lineno, name):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name,)
+
+
+class RedefinedWhileUnused(Message):
+    message = 'redefinition of unused %r from line %r'
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
+class ImportShadowedByLoopVar(Message):
+    message = 'import %r from line %r shadowed by loop variable'
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
+class ImportStarUsed(Message):
+    message = "'from %s import *' used; unable to detect undefined names"
+    def __init__(self, filename, lineno, modname):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (modname,)
+
+
+class UndefinedName(Message):
+    message = 'undefined name %r'
+    def __init__(self, filename, lineno, name):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name,)
+
+
+
+class UndefinedExport(Message):
+    message = 'undefined name %r in __all__'
+    def __init__(self, filename, lineno, name):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name,)
+
+
+
+class UndefinedLocal(Message):
+    message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
+class DuplicateArgument(Message):
+    message = 'duplicate argument %r in function definition'
+    def __init__(self, filename, lineno, name):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name,)
+
+
+class RedefinedFunction(Message):
+    message = 'redefinition of function %r from line %r'
+    def __init__(self, filename, lineno, name, orig_lineno):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (name, orig_lineno)
+
+
+class LateFutureImport(Message):
+    message = 'future import(s) %r after other statements'
+    def __init__(self, filename, lineno, names):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (names,)
+
+
+class UnusedVariable(Message):
+    """
+    Indicates that a variable has been explicity assigned to but not actually
+    used.
+    """
+
+    message = 'local variable %r is assigned to but never used'
+    def __init__(self, filename, lineno, names):
+        Message.__init__(self, filename, lineno)
+        self.message_args = (names,)
Add a comment to this file

pyflakes/scripts/__init__.py

Empty file added.

pyflakes/scripts/pyflakes.py

+
+"""
+Implementation of the command-line I{pyflakes} tool.
+"""
+
+import compiler, sys
+import os
+
+checker = __import__('pyflakes.checker').checker
+
+def check(codeString, filename):
+    """
+    Check the Python source given by C{codeString} for flakes.
+
+    @param codeString: The Python source to check.
+    @type codeString: C{str}
+
+    @param filename: The name of the file the source came from, used to report
+        errors.
+    @type filename: C{str}
+
+    @return: The number of warnings emitted.
+    @rtype: C{int}
+    """
+    # Since compiler.parse does not reliably report syntax errors, use the
+    # built in compiler first to detect those.
+    try:
+        try:
+            compile(codeString, filename, "exec")
+        except MemoryError:
+            # Python 2.4 will raise MemoryError if the source can't be
+            # decoded.
+            if sys.version_info[:2] == (2, 4):
+                raise SyntaxError(None)
+            raise
+    except (SyntaxError, IndentationError), value:
+        msg = value.args[0]
+
+        (lineno, offset, text) = value.lineno, value.offset, value.text
+
+        # If there's an encoding problem with the file, the text is None.
+        if text is None:
+            # Avoid using msg, since for the only known case, it contains a
+            # bogus message that claims the encoding the file declared was
+            # unknown.
+            print >> sys.stderr, "%s: problem decoding source" % (filename, )
+        else:
+            line = text.splitlines()[-1]
+
+            if offset is not None:
+                offset = offset - (len(text) - len(line))
+
+            print >> sys.stderr, '%s:%d: %s' % (filename, lineno, msg)
+            print >> sys.stderr, line
+
+            if offset is not None:
+                print >> sys.stderr, " " * offset, "^"
+
+        return 1
+    else:
+        # Okay, it's syntactically valid.  Now parse it into an ast and check
+        # it.
+        tree = compiler.parse(codeString)
+        w = checker.Checker(tree, filename)
+        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+        for warning in w.messages:
+            print warning
+        return len(w.messages)
+
+
+def checkPath(filename):
+    """
+    Check the given path, printing out any warnings detected.
+
+    @return: the number of warnings printed
+    """
+    try:
+        return check(file(filename, 'U').read() + '\n', filename)
+    except IOError, msg:
+        print >> sys.stderr, "%s: %s" % (filename, msg.args[1])
+        return 1
+
+
+def main():
+    warnings = 0
+    args = sys.argv[1:]
+    if args:
+        for arg in args:
+            if os.path.isdir(arg):
+                for dirpath, dirnames, filenames in os.walk(arg):
+                    for filename in filenames:
+                        if filename.endswith('.py'):
+                            warnings += checkPath(os.path.join(dirpath, filename))
+            else:
+                warnings += checkPath(arg)
+    else:
+        warnings += check(sys.stdin.read(), '<stdin>')
+
+    raise SystemExit(warnings > 0)
Add a comment to this file

pyflakes/test/__init__.py

Empty file added.

pyflakes/test/harness.py

+
+import textwrap, compiler
+
+from twisted.trial import unittest
+
+from pyflakes import checker
+
+
+class Test(unittest.TestCase):
+
+    def flakes(self, input, *expectedOutputs, **kw):
+        w = checker.Checker(compiler.parse(textwrap.dedent(input)), **kw)
+        outputs = [type(o) for o in w.messages]
+        expectedOutputs = list(expectedOutputs)
+        outputs.sort()
+        expectedOutputs.sort()
+        self.assert_(outputs == expectedOutputs, '''\
+for input:
+%s
+expected outputs:
+%s
+but got:
+%s''' % (input, repr(expectedOutputs), '\n'.join([str(o) for o in w.messages])))
+        return w

pyflakes/test/test_imports.py

+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+class Test(harness.Test):
+
+    def test_unusedImport(self):
+        self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
+        self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
+
+    def test_aliasedImport(self):
+        self.flakes('import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport)
+        self.flakes('from moo import fu as FU, bar as FU', m.RedefinedWhileUnused, m.UnusedImport)
+
+    def test_usedImport(self):
+        self.flakes('import fu; print fu')
+        self.flakes('from baz import fu; print fu')
+
+    def test_redefinedWhileUnused(self):
+        self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
+        self.flakes('import fu; del fu', m.RedefinedWhileUnused)
+        self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
+        self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
+
+    def test_redefinedByFunction(self):
+        self.flakes('''
+        import fu
+        def fu():
+            pass
+        ''', m.RedefinedWhileUnused)
+
+    def test_redefinedInNestedFunction(self):
+        """
+        Test that shadowing a global name with a nested function definition
+        generates a warning.
+        """
+        self.flakes('''
+        import fu
+        def bar():
+            def baz():
+                def fu():
+                    pass
+        ''', m.RedefinedWhileUnused, m.UnusedImport)
+
+    def test_redefinedByClass(self):
+        self.flakes('''
+        import fu
+        class fu:
+            pass
+        ''', m.RedefinedWhileUnused)
+
+
+    def test_redefinedBySubclass(self):
+        """
+        If an imported name is redefined by a class statement which also uses
+        that name in the bases list, no warning is emitted.
+        """
+        self.flakes('''
+        from fu import bar
+        class bar(bar):
+            pass
+        ''')
+
+
+    def test_redefinedInClass(self):
+        """
+        Test that shadowing a global with a class attribute does not produce a
+        warning.
+        """
+        self.flakes('''
+        import fu
+        class bar:
+            fu = 1
+        print fu
+        ''')
+
+    def test_usedInFunction(self):
+        self.flakes('''
+        import fu
+        def fun():
+            print fu
+        ''')
+
+    def test_shadowedByParameter(self):
+        self.flakes('''
+        import fu
+        def fun(fu):
+            print fu
+        ''', m.UnusedImport)
+
+        self.flakes('''
+        import fu
+        def fun(fu):
+            print fu
+        print fu
+        ''')
+
+    def test_newAssignment(self):
+        self.flakes('fu = None')
+
+    def test_usedInGetattr(self):
+        self.flakes('import fu; fu.bar.baz')
+        self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
+
+    def test_usedInSlice(self):
+        self.flakes('import fu; print fu.bar[1:]')
+
+    def test_usedInIfBody(self):
+        self.flakes('''
+        import fu
+        if True: print fu
+        ''')
+
+    def test_usedInIfConditional(self):
+        self.flakes('''
+        import fu
+        if fu: pass
+        ''')
+
+    def test_usedInElifConditional(self):
+        self.flakes('''
+        import fu
+        if False: pass
+        elif fu: pass
+        ''')
+
+    def test_usedInElse(self):
+        self.flakes('''
+        import fu
+        if False: pass
+        else: print fu
+        ''')
+
+    def test_usedInCall(self):
+        self.flakes('import fu; fu.bar()')
+
+    def test_usedInClass(self):
+        self.flakes('''
+        import fu
+        class bar:
+            bar = fu
+        ''')
+
+    def test_usedInClassBase(self):
+        self.flakes('''
+        import fu
+        class bar(object, fu.baz):
+            pass
+        ''')
+
+    def test_notUsedInNestedScope(self):
+        self.flakes('''
+        import fu
+        def bleh():
+            pass
+        print fu
+        ''')
+
+    def test_usedInFor(self):
+        self.flakes('''
+        import fu
+        for bar in range(9):
+            print fu
+        ''')
+
+    def test_usedInForElse(self):
+        self.flakes('''
+        import fu
+        for bar in range(10):
+            pass
+        else:
+            print fu
+        ''')
+
+    def test_redefinedByFor(self):
+        self.flakes('''
+        import fu
+        for fu in range(2):
+            pass
+        ''', m.RedefinedWhileUnused)
+
+    def test_shadowedByFor(self):
+        """
+        Test that shadowing a global name with a for loop variable generates a
+        warning.
+        """
+        self.flakes('''
+        import fu
+        fu.bar()
+        for fu in ():
+            pass
+        ''', m.ImportShadowedByLoopVar)
+
+    def test_shadowedByForDeep(self):
+        """
+        Test that shadowing a global name with a for loop variable nested in a
+        tuple unpack generates a warning.
+        """
+        self.flakes('''
+        import fu
+        fu.bar()
+        for (x, y, z, (a, b, c, (fu,))) in ():
+            pass
+        ''', m.ImportShadowedByLoopVar)
+
+    def test_usedInReturn(self):
+        self.flakes('''
+        import fu
+        def fun():
+            return fu
+        ''')
+
+    def test_usedInOperators(self):
+        self.flakes('import fu; 3 + fu.bar')
+        self.flakes('import fu; 3 % fu.bar')
+        self.flakes('import fu; 3 - fu.bar')
+        self.flakes('import fu; 3 * fu.bar')
+        self.flakes('import fu; 3 ** fu.bar')
+        self.flakes('import fu; 3 / fu.bar')
+        self.flakes('import fu; 3 // fu.bar')
+        self.flakes('import fu; -fu.bar')
+        self.flakes('import fu; ~fu.bar')
+        self.flakes('import fu; 1 == fu.bar')
+        self.flakes('import fu; 1 | fu.bar')
+        self.flakes('import fu; 1 & fu.bar')
+        self.flakes('import fu; 1 ^ fu.bar')
+        self.flakes('import fu; 1 >> fu.bar')
+        self.flakes('import fu; 1 << fu.bar')
+
+    def test_usedInAssert(self):
+        self.flakes('import fu; assert fu.bar')
+
+    def test_usedInSubscript(self):
+        self.flakes('import fu; fu.bar[1]')
+
+    def test_usedInLogic(self):
+        self.flakes('import fu; fu and False')
+        self.flakes('import fu; fu or False')
+        self.flakes('import fu; not fu.bar')
+
+    def test_usedInList(self):
+        self.flakes('import fu; [fu]')
+
+    def test_usedInTuple(self):
+        self.flakes('import fu; (fu,)')
+
+    def test_usedInTry(self):
+        self.flakes('''
+        import fu
+        try: fu
+        except: pass
+        ''')
+
+    def test_usedInExcept(self):
+        self.flakes('''
+        import fu
+        try: fu
+        except: pass
+        ''')
+
+    def test_redefinedByExcept(self):
+        self.flakes('''
+        import fu
+        try: pass
+        except Exception, fu: pass
+        ''', m.RedefinedWhileUnused)
+
+    def test_usedInRaise(self):
+        self.flakes('''
+        import fu
+        raise fu.bar
+        ''')
+
+    def test_usedInYield(self):
+        self.flakes('''
+        import fu
+        def gen():
+            yield fu
+        ''')
+
+    def test_usedInDict(self):
+        self.flakes('import fu; {fu:None}')
+        self.flakes('import fu; {1:fu}')
+
+    def test_usedInParameterDefault(self):
+        self.flakes('''
+        import fu
+        def f(bar=fu):
+            pass
+        ''')
+
+    def test_usedInAttributeAssign(self):
+        self.flakes('import fu; fu.bar = 1')
+
+    def test_usedInKeywordArg(self):
+        self.flakes('import fu; fu.bar(stuff=fu)')
+
+    def test_usedInAssignment(self):
+        self.flakes('import fu; bar=fu')
+        self.flakes('import fu; n=0; n+=fu')
+
+    def test_usedInListComp(self):
+        self.flakes('import fu; [fu for _ in range(1)]')
+        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)
+
+
+    def test_usedInTryFinally(self):
+        self.flakes('''
+        import fu
+        try: pass
+        finally: fu
+        ''')
+
+        self.flakes('''
+        import fu
+        try: fu
+        finally: pass
+        ''')
+
+    def test_usedInWhile(self):
+        self.flakes('''
+        import fu
+        while 0:
+            fu
+        ''')
+
+        self.flakes('''
+        import fu
+        while fu: pass
+        ''')
+
+    def test_usedInGlobal(self):
+        self.flakes('''
+        import fu
+        def f(): global fu
+        ''', m.UnusedImport)
+
+    def test_usedInBackquote(self):
+        self.flakes('import fu; `fu`')
+
+    def test_usedInExec(self):
+        self.flakes('import fu; exec "print 1" in fu.bar')
+
+    def test_usedInLambda(self):
+        self.flakes('import fu; lambda: fu')
+
+    def test_shadowedByLambda(self):
+        self.flakes('import fu; lambda fu: fu', m.UnusedImport)
+
+    def test_usedInSliceObj(self):
+        self.flakes('import fu; "meow"[::fu]')
+
+    def test_unusedInNestedScope(self):
+        self.flakes('''
+        def bar():
+            import fu
+        fu
+        ''', m.UnusedImport, m.UndefinedName)
+
+    def test_methodsDontUseClassScope(self):
+        self.flakes('''
+        class bar:
+            import fu
+            def fun(self):
+                fu
+        ''', m.UnusedImport, m.UndefinedName)
+
+    def test_nestedFunctionsNestScope(self):
+        self.flakes('''
+        def a():
+            def b():
+                fu
+            import fu
+        ''')
+
+    def test_nestedClassAndFunctionScope(self):
+        self.flakes('''
+        def a():
+            import fu
+            class b:
+                def c(self):
+                    print fu
+        ''')
+
+    def test_importStar(self):
+        self.flakes('from fu import *', m.ImportStarUsed)
+
+
+    def test_packageImport(self):
+        """
+        If a dotted name is imported and used, no warning is reported.
+        """
+        self.flakes('''
+        import fu.bar
+        fu.bar
+        ''')
+
+
+    def test_unusedPackageImport(self):
+        """
+        If a dotted name is imported and not used, an unused import warning is
+        reported.
+        """
+        self.flakes('import fu.bar', m.UnusedImport)
+
+
+    def test_duplicateSubmoduleImport(self):
+        """
+        If a submodule of a package is imported twice, an unused import warning
+        and a redefined while unused warning are reported.
+        """
+        self.flakes('''
+        import fu.bar, fu.bar
+        fu.bar
+        ''', m.RedefinedWhileUnused)
+        self.flakes('''
+        import fu.bar
+        import fu.bar
+        fu.bar
+        ''', m.RedefinedWhileUnused)
+
+
+    def test_differentSubmoduleImport(self):
+        """
+        If two different submodules of a package are imported, no duplicate
+        import warning is reported for the package.
+        """
+        self.flakes('''
+        import fu.bar, fu.baz
+        fu.bar, fu.baz
+        ''')
+        self.flakes('''
+        import fu.bar
+        import fu.baz
+        fu.bar, fu.baz
+        ''')
+
+    def test_assignRHSFirst(self):
+        self.flakes('import fu; fu = fu')
+        self.flakes('import fu; fu, bar = fu')
+        self.flakes('import fu; [fu, bar] = fu')
+        self.flakes('import fu; fu += fu')
+
+    def test_tryingMultipleImports(self):
+        self.flakes('''
+        try:
+            import fu
+        except ImportError:
+            import bar as fu
+        ''')
+    test_tryingMultipleImports.todo = ''
+
+    def test_nonGlobalDoesNotRedefine(self):
+        self.flakes('''
+        import fu
+        def a():
+            fu = 3
+            return fu
+        fu
+        ''')
+
+    def test_functionsRunLater(self):
+        self.flakes('''
+        def a():
+            fu
+        import fu
+        ''')
+
+    def test_functionNamesAreBoundNow(self):
+        self.flakes('''
+        import fu
+        def fu():
+            fu
+        fu
+        ''', m.RedefinedWhileUnused)
+
+    def test_ignoreNonImportRedefinitions(self):
+        self.flakes('a = 1; a = 2')
+
+    def test_importingForImportError(self):
+        self.flakes('''
+        try:
+            import fu
+        except ImportError:
+            pass
+        ''')
+    test_importingForImportError.todo = ''
+
+    def test_importedInClass(self):
+        '''Imports in class scope can be used through self'''
+        self.flakes('''
+        class c:
+            import i
+            def __init__(self):
+                self.i
+        ''')
+    test_importedInClass.todo = 'requires evaluating attribute access'
+
+    def test_futureImport(self):
+        '''__future__ is special'''
+        self.flakes('from __future__ import division')
+
+    def test_futureImportFirst(self):
+        """
+        __future__ imports must come before anything else.
+        """
+        self.flakes('''
+        x = 5
+        from __future__ import division
+        ''', m.LateFutureImport)
+
+
+
+class TestSpecialAll(harness.Test):
+    """
+    Tests for suppression of unused import warnings by C{__all__}.
+    """
+    def test_ignoredInFunction(self):
+        """
+        An C{__all__} definition does not suppress unused import warnings in a
+        function scope.
+        """
+        self.flakes('''
+        def foo():
+            import bar
+            __all__ = ["bar"]
+        ''', m.UnusedImport, m.UnusedVariable)
+
+
+    def test_ignoredInClass(self):
+        """
+        An C{__all__} definition does not suppress unused import warnings in a
+        class scope.
+        """
+        self.flakes('''
+        class foo:
+            import bar
+            __all__ = ["bar"]
+        ''', m.UnusedImport)
+
+
+    def test_warningSuppressed(self):
+        """
+        If a name is imported and unused but is named in C{__all__}, no warning
+        is reported.
+        """
+        self.flakes('''
+        import foo
+        __all__ = ["foo"]
+        ''')
+
+
+    def test_unrecognizable(self):
+        """
+        If C{__all__} is defined in a way that can't be recognized statically,
+        it is ignored.
+        """
+        self.flakes('''
+        import foo
+        __all__ = ["f" + "oo"]
+        ''', m.UnusedImport)
+        self.flakes('''
+        import foo
+        __all__ = [] + ["foo"]
+        ''', m.UnusedImport)
+
+
+    def test_unboundExported(self):
+        """
+        If C{__all__} includes a name which is not bound, a warning is emitted.
+        """
+        self.flakes('''
+        __all__ = ["foo"]
+        ''', m.UndefinedExport)
+
+        # Skip this in __init__.py though, since the rules there are a little
+        # different.
+        for filename in ["foo/__init__.py", "__init__.py"]:
+            self.flakes('''
+            __all__ = ["foo"]
+            ''', filename=filename)
+
+
+
+class Python24Tests(harness.Test):
+    """
+    Tests for checking of syntax which is valid in Python 2.4 and newer.
+    """
+    if version_info < (2, 4):
+        skip = "Python 2.4 required for generator expression and decorator tests."
+
+
+    def test_usedInGenExp(self):
+        """
+        Using a global in a generator expression results in no warnings.
+        """
+        self.flakes('import fu; (fu for _ in range(1))')
+        self.flakes('import fu; (1 for _ in range(1) if fu)')
+
+
+    def test_redefinedByGenExp(self):
+        """
+        Re-using a global name as the loop variable for a generator
+        expression results in a redefinition warning.
+        """
+        self.flakes('import fu; (1 for fu in range(1))', m.RedefinedWhileUnused)
+
+
+    def test_usedAsDecorator(self):
+        """
+        Using a global name in a decorator statement results in no warnings,
+        but using an undefined name in a decorator statement results in an
+        undefined name warning.
+        """
+        self.flakes('''
+        from interior import decorate
+        @decorate
+        def f():
+            return "hello"
+        ''')
+
+        self.flakes('''
+        from interior import decorate
+        @decorate('value')
+        def f():
+            return "hello"
+        ''')
+
+        self.flakes('''
+        @decorate
+        def f():
+            return "hello"
+        ''', m.UndefinedName)
+
+
+class Python26Tests(harness.Test):
+    """
+    Tests for checking of syntax which is valid in PYthon 2.6 and newer.
+    """
+    if version_info < (2, 6):
+        skip = "Python 2.6 required for class decorator tests."
+
+
+    def test_usedAsClassDecorator(self):
+        """
+        Using an imported name as a class decorator results in no warnings,
+        but using an undefined name as a class decorator results in an
+        undefined name warning.
+        """
+        self.flakes('''
+        from interior import decorate
+        @decorate
+        class foo:
+            pass
+        ''')
+
+        self.flakes('''
+        from interior import decorate
+        @decorate("foo")
+        class bar:
+            pass
+        ''')
+
+        self.flakes('''
+        @decorate
+        class foo:
+            pass
+        ''', m.UndefinedName)

pyflakes/test/test_other.py

+# (c) 2005-2008 Divmod, Inc.
+# See LICENSE file for details
+
+"""
+Tests for various Pyflakes behavior.
+"""
+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+
+class Test(harness.Test):
+
+    def test_duplicateArgs(self):
+        self.flakes('def fu(bar, bar): pass', m.DuplicateArgument)
+
+    def test_localReferencedBeforeAssignment(self):
+        self.flakes('''
+        a = 1
+        def f():
+            a; a=1
+        f()
+        ''', m.UndefinedName)
+    test_localReferencedBeforeAssignment.todo = 'this requires finding all assignments in the function body first'
+
+    def test_redefinedFunction(self):
+        """
+        Test that shadowing a function definition with another one raises a
+        warning.
+        """
+        self.flakes('''
+        def a(): pass
+        def a(): pass
+        ''', m.RedefinedFunction)
+
+    def test_redefinedClassFunction(self):
+        """
+        Test that shadowing a function definition in a class suite with another
+        one raises a warning.
+        """
+        self.flakes('''
+        class A:
+            def a(): pass
+            def a(): pass
+        ''', m.RedefinedFunction)
+
+    def test_functionDecorator(self):
+        """
+        Test that shadowing a function definition with a decorated version of
+        that function does not raise a warning.
+        """
+        self.flakes('''
+        from somewhere import somedecorator
+
+        def a(): pass
+        a = somedecorator(a)
+        ''')
+
+    def test_classFunctionDecorator(self):
+        """
+        Test that shadowing a function definition in a class suite with a
+        decorated version of that function does not raise a warning.
+        """
+        self.flakes('''
+        class A:
+            def a(): pass
+            a = classmethod(a)
+        ''')
+
+    def test_unaryPlus(self):
+        '''Don't die on unary +'''
+        self.flakes('+1')
+
+
+    def test_undefinedBaseClass(self):
+        """
+        If a name in the base list of a class definition is undefined, a
+        warning is emitted.
+        """
+        self.flakes('''
+        class foo(foo):
+            pass
+        ''', m.UndefinedName)
+
+
+
+class TestUnusedAssignment(harness.Test):
+    """
+    Tests for warning about unused assignments.
+    """
+
+    def test_unusedVariable(self):
+        """
+        Warn when a variable in a function is assigned a value that's never
+        used.
+        """
+        self.flakes('''
+        def a():
+            b = 1
+        ''', m.UnusedVariable)
+
+
+    def test_assignToGlobal(self):
+        """
+        Assigning to a global and then not using that global is perfectly
+        acceptable. Do not mistake it for an unused local variable.
+        """
+        self.flakes('''
+        b = 0
+        def a():
+            global b
+            b = 1
+        ''')
+
+
+    def test_assignToMember(self):
+        """
+        Assigning to a member of another object and then not using that member
+        variable is perfectly acceptable. Do not mistake it for an unused
+        local variable.
+        """
+        # XXX: Adding this test didn't generate a failure. Maybe not
+        # necessary?
+        self.flakes('''
+        class b:
+            pass
+        def a():
+            b.foo = 1
+        ''')
+
+
+    def test_assignInForLoop(self):
+        """
+        Don't warn when a variable in a for loop is assigned to but not used.
+        """
+        self.flakes('''
+        def f():
+            for i in range(10):
+                pass
+        ''')
+
+
+    def test_assignInListComprehension(self):
+        """
+        Don't warn when a variable in a list comprehension is assigned to but
+        not used.
+        """
+        self.flakes('''
+        def f():
+            [None for i in range(10)]
+        ''')
+
+
+    def test_generatorExpression(self):
+        """
+        Don't warn when a variable in a generator expression is assigned to but not used.
+        """
+        self.flakes('''
+        def f():
+            (None for i in range(10))
+        ''')
+
+
+    def test_assignmentInsideLoop(self):
+        """
+        Don't warn when a variable assignment occurs lexically after its use.
+        """
+        self.flakes('''
+        def f():
+            x = None
+            for i in range(10):
+                if i > 2:
+                    return x
+                x = i * 2
+        ''')
+
+
+    def test_tupleUnpacking(self):
+        """
+        Don't warn when a variable included in tuple unpacking is unused. It's
+        very common for variables in a tuple unpacking assignment to be unused
+        in good Python code, so warning will only create false positives.
+        """
+        self.flakes('''
+        def f():
+            (x, y) = 1, 2
+        ''')
+
+
+    def test_listUnpacking(self):
+        """
+        Don't warn when a variable included in list unpacking is unused.
+        """
+        self.flakes('''
+        def f():
+            [x, y] = [1, 2]
+        ''')
+
+
+    def test_closedOver(self):
+        """
+        Don't warn when the assignment is used in an inner function.
+        """
+        self.flakes('''
+        def barMaker():
+            foo = 5
+            def bar():
+                return foo
+            return bar
+        ''')
+
+
+    def test_doubleClosedOver(self):
+        """
+        Don't warn when the assignment is used in an inner function, even if
+        that inner function itself is in an inner function.
+        """
+        self.flakes('''
+        def barMaker():
+            foo = 5
+            def bar():
+                def baz():
+                    return foo
+            return bar
+        ''')
+
+
+
+class Python25Test(harness.Test):
+    """
+    Tests for checking of syntax only available in Python 2.5 and newer.
+    """
+    if version_info < (2, 5):
+        skip = "Python 2.5 required for if-else and with tests"
+
+    def test_ifexp(self):
+        """
+        Test C{foo if bar else baz} statements.
+        """
+        self.flakes("a = 'moo' if True else 'oink'")
+        self.flakes("a = foo if True else 'oink'", m.UndefinedName)
+        self.flakes("a = 'moo' if True else bar", m.UndefinedName)
+
+
+    def test_withStatementNoNames(self):
+        """
+        No warnings are emitted for using inside or after a nameless C{with}
+        statement a name defined beforehand.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        bar = None
+        with open("foo"):
+            bar
+        bar
+        ''')
+
+    def test_withStatementSingleName(self):
+        """
+        No warnings are emitted for using a name defined by a C{with} statement
+        within the suite or afterwards.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with open('foo') as bar:
+            bar
+        bar
+        ''')
+
+
+    def test_withStatementAttributeName(self):
+        """
+        No warnings are emitted for using an attribute as the target of a
+        C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        import foo
+        with open('foo') as foo.bar:
+            pass
+        ''')
+
+
+    def test_withStatementSubscript(self):
+        """
+        No warnings are emitted for using a subscript as the target of a
+        C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        import foo
+        with open('foo') as foo[0]:
+            pass
+        ''')
+
+
+    def test_withStatementSubscriptUndefined(self):
+        """
+        An undefined name warning is emitted if the subscript used as the
+        target of a C{with} statement is not defined.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        import foo
+        with open('foo') as foo[bar]:
+            pass
+        ''', m.UndefinedName)
+
+
+    def test_withStatementTupleNames(self):
+        """
+        No warnings are emitted for using any of the tuple of names defined by
+        a C{with} statement within the suite or afterwards.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with open('foo') as (bar, baz):
+            bar, baz
+        bar, baz
+        ''')
+
+
+    def test_withStatementListNames(self):
+        """
+        No warnings are emitted for using any of the list of names defined by a
+        C{with} statement within the suite or afterwards.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with open('foo') as [bar, baz]:
+            bar, baz
+        bar, baz
+        ''')
+
+
+    def test_withStatementComplicatedTarget(self):
+        """
+        If the target of a C{with} statement uses any or all of the valid forms
+        for that part of the grammar (See
+        U{http://docs.python.org/reference/compound_stmts.html#the-with-statement}),
+        the names involved are checked both for definedness and any bindings
+        created are respected in the suite of the statement and afterwards.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        c = d = e = g = h = i = None
+        with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
+            a, b, c, d, e, g, h, i
+        a, b, c, d, e, g, h, i
+        ''')
+
+
+    def test_withStatementSingleNameUndefined(self):
+        """
+        An undefined name warning is emitted if the name first defined by a
+        C{with} statement is used before the C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        bar
+        with open('foo') as bar:
+            pass
+        ''', m.UndefinedName)
+
+
+    def test_withStatementTupleNamesUndefined(self):
+        """
+        An undefined name warning is emitted if a name first defined by a the
+        tuple-unpacking form of the C{with} statement is used before the
+        C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        baz
+        with open('foo') as (bar, baz):
+            pass
+        ''', m.UndefinedName)
+
+
+    def test_withStatementSingleNameRedefined(self):
+        """
+        A redefined name warning is emitted if a name bound by an import is
+        rebound by the name defined by a C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        import bar
+        with open('foo') as bar:
+            pass
+        ''', m.RedefinedWhileUnused)
+
+
+    def test_withStatementTupleNamesRedefined(self):
+        """
+        A redefined name warning is emitted if a name bound by an import is
+        rebound by one of the names defined by the tuple-unpacking form of a
+        C{with} statement.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        import bar
+        with open('foo') as (bar, baz):
+            pass
+        ''', m.RedefinedWhileUnused)
+
+
+    def test_withStatementUndefinedInside(self):
+        """
+        An undefined name warning is emitted if a name is used inside the
+        body of a C{with} statement without first being bound.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with open('foo') as bar:
+            baz
+        ''', m.UndefinedName)
+
+
+    def test_withStatementNameDefinedInBody(self):
+        """
+        A name defined in the body of a C{with} statement can be used after
+        the body ends without warning.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with open('foo') as bar:
+            baz = 10
+        baz
+        ''')
+
+
+    def test_withStatementUndefinedInExpression(self):
+        """
+        An undefined name warning is emitted if a name in the I{test}
+        expression of a C{with} statement is undefined.
+        """
+        self.flakes('''
+        from __future__ import with_statement
+        with bar as baz:
+            pass
+        ''', m.UndefinedName)
+
+        self.flakes('''
+        from __future__ import with_statement
+        with bar as bar:
+            pass
+        ''', m.UndefinedName)

pyflakes/test/test_script.py

+
+"""
+Tests for L{pyflakes.scripts.pyflakes}.
+"""
+
+import sys
+from StringIO import StringIO
+
+from twisted.python.filepath import FilePath
+from twisted.trial.unittest import TestCase
+
+from pyflakes.scripts.pyflakes import checkPath
+
+def withStderrTo(stderr, f):
+    """
+    Call C{f} with C{sys.stderr} redirected to C{stderr}.
+    """
+    (outer, sys.stderr) = (sys.stderr, stderr)
+    try:
+        return f()
+    finally:
+        sys.stderr = outer
+
+
+
+class CheckTests(TestCase):
+    """
+    Tests for L{check} and L{checkPath} which check a file for flakes.
+    """
+    def test_missingTrailingNewline(self):
+        """
+        Source which doesn't end with a newline shouldn't cause any
+        exception to be raised nor an error indicator to be returned by
+        L{check}.
+        """
+        fName = self.mktemp()
+        FilePath(fName).setContent("def foo():\n\tpass\n\t")
+        self.assertFalse(checkPath(fName))
+
+
+    def test_checkPathNonExisting(self):
+        """
+        L{checkPath} handles non-existing files.
+        """
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath('extremo'))
+        self.assertEquals(err.getvalue(), 'extremo: No such file or directory\n')
+        self.assertEquals(count, 1)
+
+
+    def test_multilineSyntaxError(self):
+        """
+        Source which includes a syntax error which results in the raised
+        L{SyntaxError.text} containing multiple lines of source are reported
+        with only the last line of that source.
+        """
+        source = """\
+def foo():
+    '''
+
+def bar():
+    pass
+
+def baz():
+    '''quux'''
+"""
+
+        # Sanity check - SyntaxError.text should be multiple lines, if it
+        # isn't, something this test was unprepared for has happened.
+        def evaluate(source):
+            exec source
+        exc = self.assertRaises(SyntaxError, evaluate, source)
+        self.assertTrue(exc.text.count('\n') > 1)
+
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent(source)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEqual(count, 1)
+
+        self.assertEqual(
+            err.getvalue(),
+            """\
+%s:8: invalid syntax
+    '''quux'''
+           ^
+""" % (sourcePath.path,))
+
+
+    def test_eofSyntaxError(self):
+        """
+        The error reported for source files which end prematurely causing a
+        syntax error reflects the cause for the syntax error.
+        """
+        source = "def foo("
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent(source)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEqual(count, 1)
+        self.assertEqual(
+            err.getvalue(),
+            """\
+%s:1: unexpected EOF while parsing
+def foo(
+         ^
+""" % (sourcePath.path,))
+
+
+    def test_nonDefaultFollowsDefaultSyntaxError(self):
+        """
+        Source which has a non-default argument following a default argument
+        should include the line number of the syntax error.  However these
+        exceptions do not include an offset.
+        """
+        source = """\
+def foo(bar=baz, bax):
+    pass
+"""
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent(source)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEqual(count, 1)
+        self.assertEqual(
+            err.getvalue(),
+            """\
+%s:1: non-default argument follows default argument
+def foo(bar=baz, bax):
+""" % (sourcePath.path,))
+
+
+    def test_nonKeywordAfterKeywordSyntaxError(self):
+        """
+        Source which has a non-keyword argument after a keyword argument should
+        include the line number of the syntax error.  However these exceptions
+        do not include an offset.
+        """
+        source = """\
+foo(bar=baz, bax)
+"""
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent(source)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEqual(count, 1)
+        self.assertEqual(
+            err.getvalue(),
+            """\
+%s:1: non-keyword arg after keyword arg
+foo(bar=baz, bax)
+""" % (sourcePath.path,))
+
+
+    def test_permissionDenied(self):
+        """
+        If the a source file is not readable, this is reported on standard
+        error.
+        """
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent('')
+        sourcePath.chmod(0)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEquals(count, 1)
+        self.assertEquals(
+            err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,))
+
+
+    def test_misencodedFile(self):
+        """
+        If a source file contains bytes which cannot be decoded, this is
+        reported on stderr.
+        """
+        source = u"""\
+# coding: ascii
+x = "\N{SNOWMAN}"
+""".encode('utf-8')
+        sourcePath = FilePath(self.mktemp())
+        sourcePath.setContent(source)
+        err = StringIO()
+        count = withStderrTo(err, lambda: checkPath(sourcePath.path))
+        self.assertEquals(count, 1)
+        self.assertEquals(
+            err.getvalue(), "%s: problem decoding source\n" % (sourcePath.path,))

pyflakes/test/test_undefined_names.py

+
+from sys import version_info
+
+from pyflakes import messages as m
+from pyflakes.test import harness
+
+
+class Test(harness.Test):
+    def test_undefined(self):
+        self.flakes('bar', m.UndefinedName)
+
+    def test_definedInListComp(self):
+        self.flakes('[a for a in range(10) if a]')
+
+
+    def test_functionsNeedGlobalScope(self):
+        self.flakes('''
+        class a:
+            def b():
+                fu
+        fu = 1
+        ''')
+
+    def test_builtins(self):
+        self.flakes('range(10)')
+
+
+    def test_magicGlobalsFile(self):
+        """
+        Use of the C{__file__} magic global should not emit an undefined name
+        warning.
+        """
+        self.flakes('__file__')
+
+
+    def test_magicGlobalsBuiltins(self):
+        """
+        Use of the C{__builtins__} magic global should not emit an undefined
+        name warning.
+        """
+        self.flakes('__builtins__')
+
+
+    def test_magicGlobalsName(self):
+        """
+        Use of the C{__name__} magic global should not emit an undefined name
+        warning.
+        """
+        self.flakes('__name__')
+
+
+    def test_magicGlobalsPath(self):
+        """
+        Use of the C{__path__} magic global should not emit an undefined name
+        warning, if you refer to it from a file called __init__.py.
+        """
+        self.flakes('__path__', m.UndefinedName)