Utilities/py3flakes/checker.py

changeset 88
3701923bccf2
child 125
064cfadcf15c
diff -r 4cc5c8d1184d -r 3701923bccf2 Utilities/py3flakes/checker.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utilities/py3flakes/checker.py	Sat Jan 30 18:37:18 2010 +0000
@@ -0,0 +1,692 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+# Original (c) 2005-2008 Divmod, Inc.
+#
+# This module is based on pyflakes for Python2 but was heavily hacked to
+# work with Python3
+
+import builtins
+import os.path
+import ast
+
+from . 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 Assignment for a special type of binding that
+    is checked with stricter rules.
+    """
+    def __init__(self, name, source):
+        self.name = name
+        self.source = source
+        self.used = False
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return '<{0} object {1!r} from line {2!r} at 0x{3:x}>'.format(
+            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.
+    """
+    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):
+    """
+    Represents a function definition.
+    """
+    pass
+
+class ExportBinding(Binding):
+    """
+    A binding created by an __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 __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
+    __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:
+                if isinstance(node, (ast.Str, ast.Bytes)):
+                    names.append(node.s)
+                elif isinstance(node, ast.Num):
+                    names.append(node.n)
+        return names
+
+class Scope(dict):
+    """
+    Class defining the scope base class.
+    """
+    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):
+    """
+    Class representing a name scope for a class.
+    """
+    pass
+
+class FunctionScope(Scope):
+    """
+    Class representing a name scope for a function.
+    """
+    def __init__(self):
+        super(FunctionScope, self).__init__()
+        self.globals = {}
+
+class ModuleScope(Scope):
+    """
+    Class representing a name scope for a module.
+    """
+    pass
+
+# Globally defined names which are not attributes of the builtins module.
+_MAGIC_GLOBALS = ['__file__', '__builtins__']
+
+class Checker(object):
+    """
+    Class to check the cleanliness and sanity of Python code.
+    """
+    nodeDepth = 0
+    traceTree = False
+
+    def __init__(self, module, filename = '(none)'):
+        """
+        Constructor
+        
+        @param module parsed module tree or module source code
+        @param filename name of the module file (string)
+        """
+        self._deferredFunctions = []
+        self._deferredAssignments = []
+        self.dead_scopes = []
+        self.messages = []
+        self.filename = filename
+        self.scopeStack = [ModuleScope()]
+        self.futuresAllowed = True
+        
+        if isinstance(module, str):
+            module = ast.parse(module, filename, "exec")
+        self.handleBody(module)
+        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 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.values():
+                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 handleBody(self, tree):
+        for node in tree.body:
+            self.handleNode(node, tree)
+
+    def handleNode(self, node, parent):
+        if node:
+            node.parent = parent
+            if self.traceTree:
+                print('  ' * self.nodeDepth + node.__class__.__name__)
+            self.nodeDepth += 1
+            nodeType = node.__class__.__name__.upper()
+            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
+    
+    # ast nodes to be ignored
+    PASS = CONTINUE = BREAK = ELLIPSIS = NUM = STR = BYTES = \
+    ATTRIBUTES = AND = OR = ADD = SUB = MULT = DIV = \
+    MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = \
+    INVERT = NOT = UADD = USUB = INVERT = NOT = UADD = USUB = ignore
+
+    def addBinding(self, lineno, value, reportRedef = True):
+        '''Called when a binding is altered.
+
+        @param lineno line of the statement responsible for the change (integer)
+        @param value the optional new value, a Binding instance, associated
+            with the binding; if None, the binding is deleted if it exists
+        @param reportRedef flag indicating if rebinding while unused will be
+            reported (boolean)
+        '''
+        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
+    
+    ############################################################
+    ## individual handler methods below
+    ############################################################
+    
+    def LIST(self, node):
+        for elt in node.elts:
+            self.handleNode(elt, node)
+    
+    SET = TUPLE = LIST
+    
+    def DICT(self, node):
+        for key in node.keys:
+            self.handleNode(key, node)
+        for val in node.values:
+            self.handleNode(val, node)
+    
+    def WITH(self, node):
+        """
+        Handle 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.context_expr, node)
+
+        if node.optional_vars is not None:
+            self.handleNode(node.optional_vars, node)
+
+        self.handleBody(node)
+
+    def GLOBAL(self, node):
+        """
+        Keep track of globals declarations.
+        """
+        if isinstance(self.scope, FunctionScope):
+            self.scope.globals.update(dict.fromkeys(node.names))
+    
+    NONLOCAL = GLOBAL
+
+    def LISTCOMP(self, node):
+        for generator in node.generators:
+            self.handleNode(generator, node)
+        self.handleNode(node.elt, node)
+    
+    SETCOMP = GENERATOREXP = LISTCOMP
+    
+    def DICTCOMP(self, node):
+        for generator in node.generators:
+            self.handleNode(generator, node)
+        self.handleNode(node.key, node)
+        self.handleNode(node.value, node)
+    
+    def COMPREHENSION(self, node):
+        node.target.parent = node
+        self.handleAssignName(node.target)
+        self.handleNode(node.iter, node)
+        for elt in node.ifs:
+            self.handleNode(elt, node)
+    
+    def FOR(self, node):
+        """
+        Process bindings for loop variables.
+        """
+        vars = []
+        def collectLoopVars(n):
+            if isinstance(n, ast.Name):
+                vars.append(n.id)
+
+        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.lineno, varn, self.scope[varn].source.lineno)
+        
+        node.target.parent = node
+        self.handleAssignName(node.target)
+        self.handleNode(node.iter, node)
+        self.handleBody(node)
+
+    def NAME(self, node):
+        """
+        Locate the name in locals / function / globals scopes.
+        """
+        # try local scope
+        importStarred = self.scope.importStarred
+        try:
+            self.scope[node.id].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.id].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.id].used = (self.scope, node.lineno)
+        except KeyError:
+            if ((not hasattr(builtins, node.id))
+                    and node.id not in _MAGIC_GLOBALS
+                    and not importStarred):
+                if (os.path.basename(self.filename) == '__init__.py' and
+                    node.id == '__path__'):
+                    # the special name __path__ is valid only in packages
+                    pass
+                else:
+                    self.report(messages.UndefinedName, node.lineno, node.id)
+
+    def FUNCTIONDEF(self, node):
+        if getattr(node, "decorator_list", None) is not None:
+            for decorator in node.decorator_list:
+                self.handleNode(decorator, node)
+        self.addBinding(node.lineno, FunctionDefinition(node.name, node))
+        self.LAMBDA(node)
+
+    def LAMBDA(self, node):
+        for default in node.args.defaults + node.args.kw_defaults:
+            self.handleNode(default, node)
+
+        def runFunction():
+            args = []
+
+            def addArgs(arglist):
+                for arg in arglist:
+                    if isinstance(arg.arg, tuple):
+                        addArgs(arg.arg)
+                    else:
+                        if arg.arg in args:
+                            self.report(messages.DuplicateArgument, node.lineno, arg.arg)
+                        args.append(arg.arg)
+            
+            def checkUnusedAssignments():
+                """
+                Check to see if any assignments have not been used.
+                """
+                for name, binding in self.scope.items():
+                    if (not binding.used and not name in self.scope.globals
+                        and isinstance(binding, Assignment)):
+                        self.report(messages.UnusedVariable,
+                                    binding.source.lineno, name)
+
+            self.pushFunctionScope()
+            addArgs(node.args.args)
+            addArgs(node.args.kwonlyargs)
+            for name in args:
+                self.addBinding(node.lineno, Argument(name, node), reportRedef=False)
+            if node.args.vararg:
+                self.addBinding(node.lineno, Argument(node.args.vararg, node), 
+                                reportRedef=False)
+            if node.args.kwarg:
+                self.addBinding(node.lineno, Argument(node.args.kwarg, node), 
+                                reportRedef=False)
+            if isinstance(node.body, list):
+                self.handleBody(node)
+            else:
+                self.handleNode(node.body, node)
+            self.deferAssignment(checkUnusedAssignments)
+            self.popScope()
+
+        self.deferFunction(runFunction)
+
+    def CLASSDEF(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, "decorator_list", None) is not None:
+            for decorator in node.decorator_list:
+                self.handleNode(decorator, node)
+        for baseNode in node.bases:
+            self.handleNode(baseNode, node)
+        self.addBinding(node.lineno, Binding(node.name, node))
+        self.pushClassScope()
+        self.handleBody(node)
+        self.popScope()
+
+    def handleAssignName(self, node):
+        # special handling for ast.Subscript and ast.Starred
+        if isinstance(node, (ast.Subscript, ast.Starred)):
+            node.value.parent = node
+            self.handleAssignName(node.value)
+            if isinstance(node, ast.Subscript):
+                if isinstance(node.slice, ast.Slice):
+                    self.handleNode(node.slice.lower, node)
+                    self.handleNode(node.slice.upper, node)
+                else:
+                    self.handleNode(node.slice.value, node)
+            return
+        
+        # if the name hasn't already been defined in the current scope
+        if isinstance(node, ast.Tuple):
+            for elt in node.elts:
+                elt.parent = node
+                self.handleAssignName(elt)
+            return
+        
+        if isinstance(node, ast.Attribute):
+            self.handleNode(node.value, node)
+            return
+        
+        if isinstance(self.scope, FunctionScope) and node.id 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.id in scope
+                        and scope[node.id].used
+                        and scope[node.id].used[0] is self.scope
+                        and node.id not in self.scope.globals):
+                    # then it's probably a mistake
+                    self.report(messages.UndefinedLocal,
+                                scope[node.id].used[1],
+                                node.id,
+                                scope[node.id].source.lineno)
+                    break
+
+        if isinstance(node.parent,
+                      (ast.For, ast.ListComp, ast.GeneratorExp,
+                       ast.Tuple, ast.List)):
+            binding = Binding(node.id, node)
+        elif (node.id == '__all__' and
+              isinstance(self.scope, ModuleScope) and
+              isinstance(node.parent, ast.Assign)):
+            binding = ExportBinding(node.id, node.parent.value)
+        else:
+            binding = Assignment(node.id, node)
+        if node.id in self.scope:
+            binding.used = self.scope[node.id].used
+        self.addBinding(node.lineno, binding)
+
+    def ASSIGN(self, node):
+        self.handleNode(node.value, node)
+        for subnode in node.targets[::-1]:
+            subnode.parent = node
+            if isinstance(subnode, ast.Attribute):
+                self.handleNode(subnode.value, subnode)
+            else:
+                self.handleAssignName(subnode)
+    
+    def AUGASSIGN(self, node):
+        self.handleNode(node.value, node)
+        self.handleNode(node.target, node)
+    
+    def IMPORT(self, node):
+        for alias in node.names:
+            name = alias.asname or alias.name
+            importation = Importation(name, node)
+            self.addBinding(node.lineno, importation)
+
+    def IMPORTFROM(self, node):
+        if node.module == '__future__':
+            if not self.futuresAllowed:
+                self.report(messages.LateFutureImport, node.lineno, 
+                            [n.name for n in node.names])
+        else:
+            self.futuresAllowed = False
+
+        for alias in node.names:
+            if alias.name == '*':
+                self.scope.importStarred = True
+                self.report(messages.ImportStarUsed, node.lineno, node.module)
+                continue
+            name = alias.asname or alias.name
+            importation = Importation(name, node)
+            if node.module == '__future__':
+                importation.used = (self.scope, node.lineno)
+            self.addBinding(node.lineno, importation)
+    
+    def CALL(self, node):
+        self.handleNode(node.func, node)
+        for arg in node.args:
+            self.handleNode(arg, node)
+        for kw in node.keywords:
+            self.handleNode(kw, node)
+        node.starargs and self.handleNode(node.starargs, node)
+        node.kwargs and self.handleNode(node.kwargs, node)
+    
+    def KEYWORD(self, node):
+        self.handleNode(node.value, node)
+    
+    def BOOLOP(self, node):
+        for val in node.values:
+            self.handleNode(val, node)
+    
+    def BINOP(self, node):
+        self.handleNode(node.left, node)
+        self.handleNode(node.right, node)
+    
+    def UNARYOP(self, node):
+        self.handleNode(node.operand, node)
+    
+    def RETURN(self, node):
+        node.value and self.handleNode(node.value, node)
+    
+    def DELETE(self, node):
+        for tgt in node.targets:
+            self.handleNode(tgt, node)
+    
+    def EXPR(self, node):
+        self.handleNode(node.value, node)
+    
+    def ATTRIBUTE(self, node):
+        self.handleNode(node.value, node)
+    
+    def IF(self, node):
+        self.handleNode(node.test, node)
+        self.handleBody(node)
+        for stmt in node.orelse:
+            self.handleNode(stmt, node)
+    
+    WHILE = IF
+    
+    def RAISE(self, node):
+        node.exc and self.handleNode(node.exc, node)
+        node.cause and self.handleNode(node.cause, node)
+    
+    def TRYEXCEPT(self, node):
+        self.handleBody(node)
+        for handler in node.handlers:
+            self.handleNode(handler, node)
+        for stmt in node.orelse:
+            self.handleNode(stmt, node)
+    
+    def TRYFINALLY(self, node):
+        self.handleBody(node)
+        for stmt in node.finalbody:
+            self.handleNode(stmt, node)
+    
+    def EXCEPTHANDLER(self, node):
+        node.type and self.handleNode(node.type, node)
+        if node.name:
+            node.id = node.name
+            self.handleAssignName(node)
+        self.handleBody(node)
+    
+    def ASSERT(self, node):
+        self.handleNode(node.test, node)
+        node.msg and self.handleNode(node.msg, node)
+    
+    def COMPARE(self, node):
+        self.handleNode(node.left, node)
+        for comparator in node.comparators:
+            self.handleNode(comparator, node)
+    
+    def YIELD(self, node):
+        node.value and self.handleNode(node.value, node)
+    
+    def SUBSCRIPT(self, node):
+        self.handleNode(node.value, node)
+        self.handleNode(node.slice, node)
+    
+    def SLICE(self, node):
+        node.lower and self.handleNode(node.lower, node)
+        node.upper and self.handleNode(node.upper, node)
+        node.step and self.handleNode(node.step, node)
+    
+    def EXTSLICE(self, node):
+        for slice in node.dims:
+            self.handleNode(slice, node)
+    
+    def INDEX(self, node):
+        self.handleNode(node.value, node)
+    
+    def IFEXP(self, node):
+        self.handleNode(node.test, node)
+        self.handleNode(node.body, node)
+        self.handleNode(node.orelse, node)
+    
+    def STARRED(self, node):
+        self.handleNode(node.value, node)

eric ide

mercurial