Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py

branch
BgService
changeset 3159
02cb2adb4868
parent 2571
e6bb19eb87ea
child 3177
5af61402d74d
diff -r 14721e0f3b5b -r 02cb2adb4868 Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py	Tue Dec 31 18:03:31 2013 +0100
@@ -0,0 +1,711 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+# Original (c) 2005-2010 Divmod, Inc.
+#
+# This module is based on pyflakes for Python2 but was modified to
+# work with eric5
+
+import os.path
+try:
+    import builtins
+    PY2 = False
+except ImportError:
+    import __builtin__ as builtins      #__IGNORE_WARNING__
+    PY2 = True
+
+try:
+    import ast
+    iter_child_nodes = ast.iter_child_nodes
+except (ImportError, AttributeError):   # Python 2.5
+    import _ast as ast
+
+    def iter_child_nodes(node, astcls=ast.AST):
+        """
+        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, astcls):
+                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 . 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.
+    """
+
+    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.
+    """
+    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 Definition(Binding):
+    """
+    A binding that defines a function or a class.
+    """
+
+
+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(Definition):
+    pass
+
+
+class ClassDefinition(Definition):
+    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.elts:
+                if isinstance(node, ast.Str):
+                    names.append(node.s)
+        return names
+
+
+class Scope(dict):
+    importStarred = False       # set to True when import * is found
+    usesLocals = False
+
+    def __repr__(self):
+        return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
+
+
+class ClassScope(Scope):
+    pass
+
+
+class FunctionScope(Scope):
+    """
+    I represent a name scope for a function.
+    """
+    def __init__(self):
+        super(FunctionScope, self).__init__()
+        self.globals = {}
+
+
+class ModuleScope(Scope):
+    pass
+
+
+# Globally defined names which are not attributes of the builtins module, or
+# are only present on some platforms.
+_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
+
+
+def getNodeName(node):
+    # Returns node.id, or node.name, or None
+    if hasattr(node, 'id'):     # One of the many nodes with an id
+        return node.id
+    if hasattr(node, 'name'):   # a ExceptHandler node
+        return node.name
+
+
+class Checker(object):
+    """
+    I check the cleanliness and sanity of Python code.
+    """
+
+    nodeDepth = 0
+    traceTree = False
+    builtIns = set(dir(builtins)) | set(_MAGIC_GLOBALS)
+
+    def __init__(self, tree, filename='(none)', builtins=None):
+        self._deferredFunctions = []
+        self._deferredAssignments = []
+        self.deadScopes = []
+        self.messages = []
+        self.filename = filename
+        if builtins:
+            self.builtIns = self.builtIns.union(builtins)
+        self.scopeStack = [ModuleScope()]
+        self.futuresAllowed = True
+        self.root = tree
+        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
+        # noisily if called after we've run through the deferred assignments.
+        self._deferredAssignments = None
+        del self.scopeStack[1:]
+        self.popScope()
+        self.checkDeadScopes()
+
+    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()
+
+    @property
+    def scope(self):
+        return self.scopeStack[-1]
+
+    def popScope(self):
+        self.deadScopes.append(self.scopeStack.pop())
+
+    def checkDeadScopes(self):
+        """
+        Look at scopes which have been fully examined and report names in them
+        which were imported but unused.
+        """
+        for scope in self.deadScopes:
+            export = isinstance(scope.get('__all__'), ExportBinding)
+            if export:
+                all = 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)
+                    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 hasParent(self, node, kind):
+        while hasattr(node, 'parent'):
+            node = node.parent
+            if isinstance(node, kind):
+                return True
+
+    def getCommonAncestor(self, lnode, rnode, stop=None):
+        if not stop:
+            stop = self.root
+        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):
+            return self.getCommonAncestor(lnode.parent, rnode, stop)
+        if (rnode.level > lnode.level):
+            return self.getCommonAncestor(lnode, rnode.parent, stop)
+        return self.getCommonAncestor(lnode.parent, rnode.parent, stop)
+
+    def descendantOf(self, node, ancestors, stop=None):
+        for a in ancestors:
+            if self.getCommonAncestor(node, a, stop) not in (stop, None):
+                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):
+                    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):
+        """
+        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
+                    self.report(messages.RedefinedWhileUnused,
+                                node.lineno, value.name, existing.source.lineno)
+
+        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))):
+                self.report(messages.RedefinedInListComp,
+                            node.lineno, value.name, existing.source.lineno)
+
+        if isinstance(value, UnBinding):
+            try:
+                del self.scope[value.name]
+            except KeyError:
+                self.report(messages.UndefinedName, node.lineno, value.name)
+        elif (isinstance(existing, Definition)
+              and not existing.used
+              and not self.differentForks(node, existing.source)):
+            self.report(messages.RedefinedWhileUnused,
+                        node.lineno, value.name, existing.source.lineno)
+        else:
+            self.scope[value.name] = value
+
+    def handleNodeLoad(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        # try local scope
+        importStarred = self.scope.importStarred
+        try:
+            self.scope[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[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][name].used = (self.scope, node.lineno)
+        except KeyError:
+            if not importStarred and name not in self.builtIns:
+                if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'):
+                    # the special name __path__ is valid only in packages
+                    pass
+                else:
+                    self.report(messages.UndefinedName, node.lineno, name)
+
+    def handleNodeStore(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        # if the name hasn't already been defined in the current scope
+        if isinstance(self.scope, FunctionScope) and 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 (name in scope and scope[name].used and scope[name].used[0] is self.scope
+                        and name not in self.scope.globals):
+                    # then it's probably a mistake
+                    self.report(messages.UndefinedLocal,
+                                scope[name].used[1], name, scope[name].source.lineno)
+                    break
+
+        parent = getattr(node, 'parent', None)
+        if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)):
+            binding = Binding(name, node)
+        elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope):
+            binding = ExportBinding(name, parent.value)
+        else:
+            binding = Assignment(name, node)
+        if name in self.scope:
+            binding.used = self.scope[name].used
+        self.addBinding(node, binding)
+
+    def handleNodeDelete(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
+            del self.scope.globals[name]
+        else:
+            self.addBinding(node, UnBinding(name, node))
+
+    def handleChildren(self, tree):
+        for node in iter_child_nodes(tree):
+            self.handleNode(node, tree)
+
+    def isDocstring(self, node):
+        """
+        Determine if the given node is a docstring, as long as it is at the
+        correct place in the node tree.
+        """
+        return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and
+                                             isinstance(node.value, ast.Str))
+
+    def handleNode(self, node, parent):
+        if node is None:
+            return
+        node.parent = parent
+        if self.traceTree:
+            print('  ' * self.nodeDepth + node.__class__.__name__)
+        self.nodeDepth += 1
+        if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or
+                                        self.isDocstring(node)):
+            self.futuresAllowed = False
+        nodeType = node.__class__.__name__.upper()
+        node.level = self.nodeDepth
+        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" type nodes
+    RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \
+        TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren
+
+    CONTINUE = BREAK = PASS = ignore
+
+    # "expr" type nodes
+    BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \
+        COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \
+        STARRED = handleChildren
+
+    NUM = STR = BYTES = ELLIPSIS = ignore
+
+    # "slice" type nodes
+    SLICE = EXTSLICE = INDEX = handleChildren
+
+    # expression contexts are node instances too, though being constants
+    LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
+
+    # same for operators
+    AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
+        BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
+        EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
+
+    # additional node types
+    COMPREHENSION = KEYWORD = handleChildren
+
+    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):
+        # handle generators before element
+        for gen in node.generators:
+            self.handleNode(gen, node)
+        self.handleNode(node.elt, node)
+
+    GENERATOREXP = SETCOMP = LISTCOMP
+
+    def DICTCOMP(self, node):
+        for gen in node.generators:
+            self.handleNode(gen, node)
+        self.handleNode(node.key, node)
+        self.handleNode(node.value, node)
+
+    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.lineno, varn, self.scope[varn].source.lineno)
+
+        self.handleChildren(node)
+
+    def NAME(self, node):
+        """
+        Handle occurrence of Name (which can be a load/store/delete access.)
+        """
+        if node.id == 'locals' and isinstance(node.parent, ast.Call):
+            # we are doing locals() call in current scope
+            self.scope.usesLocals = True
+        # Locate the name in locals / function / globals scopes.
+        if isinstance(node.ctx, (ast.Load, ast.AugLoad)):
+            self.handleNodeLoad(node)
+        elif isinstance(node.ctx, (ast.Store, ast.AugStore)):
+            self.handleNodeStore(node)
+        elif isinstance(node.ctx, ast.Del):
+            self.handleNodeDelete(node)
+        else:
+            # must be a Param context -- this only happens for names in function
+            # arguments, but these aren't dispatched through here
+            raise RuntimeError("Got impossible expression context: %r" % (node.ctx,))
+
+    def FUNCTIONDEF(self, node):
+        if not hasattr(node, 'decorator_list'):   # Python 2.5
+            node.decorator_list = node.decorators
+        for deco in node.decorator_list:
+            self.handleNode(deco, node)
+        self.addBinding(node, FunctionDefinition(node.name, node))
+        self.LAMBDA(node)
+
+    def LAMBDA(self, node):
+        args = []
+
+        if PY2:
+            def addArgs(arglist):
+                for arg in arglist:
+                    if isinstance(arg, ast.Tuple):
+                        addArgs(arg.elts)
+                    else:
+                        if arg.id in args:
+                            self.report(messages.DuplicateArgument,
+                                        node.lineno, 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.lineno, 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)
+            defaults = node.args.defaults + node.args.kw_defaults
+
+        # vararg/kwarg identifiers are not Name nodes
+        for wildcard in (node.args.vararg, node.args.kwarg):
+            if not wildcard:
+                continue
+            if wildcard in args:
+                self.report(messages.DuplicateArgument, node.lineno, wildcard)
+            args.append(wildcard)
+        for default in defaults:
+            self.handleNode(default, node)
+
+        def runFunction():
+
+            self.pushFunctionScope()
+            for name in args:
+                self.addBinding(node, Argument(name, node), reportRedef=False)
+            if isinstance(node.body, list):
+                # case for FunctionDefs
+                for stmt in node.body:
+                    self.handleNode(stmt, node)
+            else:
+                # case for Lambdas
+                self.handleNode(node.body, node)
+
+            def checkUnusedAssignments():
+                """
+                Check to see if any assignments have not been used.
+                """
+                for name, binding in self.scope.items():
+                    if (not binding.used and name not in self.scope.globals
+                            and not self.scope.usesLocals
+                            and isinstance(binding, Assignment)):
+                        self.report(messages.UnusedVariable,
+                                    binding.source.lineno, name)
+            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.
+        """
+        # no class decorator in Python 2.5
+        for deco in getattr(node, 'decorator_list', ''):
+            self.handleNode(deco, node)
+        for baseNode in node.bases:
+            self.handleNode(baseNode, node)
+        if not PY2:
+            for keywordNode in node.keywords:
+                self.handleNode(keywordNode, node)
+        self.pushClassScope()
+        for stmt in node.body:
+            self.handleNode(stmt, node)
+        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)
+        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, 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, importation)
+
+    def EXCEPTHANDLER(self, node):
+        # 3.x: in addition to handling children, we must handle the name of
+        # the exception, which is not a Name node, but a simple string.
+        if isinstance(node.name, str):
+            self.handleNodeStore(node)
+        self.handleChildren(node)

eric ide

mercurial