--- a/UtilitiesPython2/py2flakes/checker.py Thu Jan 12 18:47:58 2012 +0100 +++ b/UtilitiesPython2/py2flakes/checker.py Thu Jan 12 18:54:22 2012 +0100 @@ -2,18 +2,37 @@ # Copyright (c) 2011 - 2012 Detlev Offenbach <detlev@die-offenbachs.de> # -# Original (c) 2005-2008 Divmod, Inc. +# Original (c) 2005-2010 Divmod, Inc. # # This module is based on pyflakes for Python2 but was heavily hacked to # work within eric5 import __builtin__ import os.path -import compiler -from compiler import ast +import _ast from py2flakes import messages +# utility function to iterate over an AST node's children, adapted +# from Python 2.6's standard ast module +try: + import ast + iter_child_nodes = ast.iter_child_nodes +except (ImportError, AttributeError): + 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 + + class Binding(object): """ Represents the binding of a value to a name. @@ -90,10 +109,10 @@ 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) + 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): @@ -103,7 +122,8 @@ 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)) + return '<%s at 0x%x %s>' % ( + self.__class__.__name__, id(self), dict.__repr__(self)) def __init__(self): super(Scope, self).__init__() @@ -154,7 +174,7 @@ self.futuresAllowed = True if isinstance(module, str): - module = compiler.parse(module) + module = ast.parse(module, filename, "exec") self.handleChildren(module) self._runDeferred(self._deferredFunctions) # Set _deferredFunctions to None so that deferFunction will fail @@ -240,17 +260,27 @@ self.messages.append(messageClass(self.filename, *args, **kwargs)) def handleChildren(self, tree): - for node in tree.getChildNodes(): + 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): 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() - if nodeType not in ('STMT', 'FROM'): - self.futuresAllowed = False try: handler = getattr(self, nodeType) handler(node) @@ -262,16 +292,31 @@ 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 + # "stmt" type nodes + RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ + TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren + + CONTINUE = BREAK = PASS = ignore + + # "expr" type nodes + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ + CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren + + NUM = STR = ELLIPSIS = ignore - CONST = PASS = CONTINUE = BREAK = 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 = EXCEPTHANDLER = KEYWORD = handleChildren def addBinding(self, lineno, value, reportRedef=True): ''' @@ -292,9 +337,10 @@ 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 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) @@ -307,23 +353,6 @@ else: self.scope[value.name] = value - 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.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. @@ -332,11 +361,19 @@ 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) + # handle generators before element + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.elt, node) + + GENERATOREXP = SETCOMP = LISTCOMP - GENEXPRINNER = LISTCOMP + # dictionary comprehensions; introduced in Python 2.7 + 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): """ @@ -344,13 +381,15 @@ """ vars = [] def collectLoopVars(n): - if hasattr(n, 'name'): - vars.append(n.name) + if isinstance(n, _ast.Name): + vars.append(n.id) + elif isinstance(n, _ast.expr_context): + return else: - for c in n.getChildNodes(): + for c in iter_child_nodes(n): collectLoopVars(c) - collectLoopVars(node.assign) + collectLoopVars(node.target) for varn in vars: if (isinstance(self.scope.get(varn), Importation) # unused ones will get an unused import warning @@ -364,53 +403,101 @@ """ 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 + if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)): + # try local scope + importStarred = self.scope.importStarred try: - scope[node.name].used = (self.scope, node.lineno) + self.scope[node.id].used = (self.scope, node.lineno) except KeyError: pass else: return - # try global scope + # try enclosing function scopes - 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 + 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: - self.report(messages.UndefinedName, node.lineno, node.name) + return - def FUNCTION(self, node): - if getattr(node, "decorators", None) is not None: - self.handleChildren(node.decorators) + # 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(__builtin__, 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) + elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)): + # if the name hasn't already been defined in the current scope + 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.comprehension, _ast.Tuple, _ast.List)): + binding = Binding(node.id, node) + elif (node.id == '__all__' and + isinstance(self.scope, ModuleScope)): + 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) + elif isinstance(node.ctx, _ast.Del): + if isinstance(self.scope, FunctionScope) and \ + node.id in self.scope.globals: + del self.scope.globals[node.id] + else: + self.addBinding(node.lineno, UnBinding(node.id, 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 hasattr(node, 'decorators'): + for deco in node.decorators: + self.handleNode(deco, node) + else: + for deco in node.decorator_list: + self.handleNode(deco, node) self.addBinding(node.lineno, FunctionDefinition(node.name, node)) self.LAMBDA(node) def LAMBDA(self, node): - for default in node.defaults: + for default in node.args.defaults: self.handleNode(default, node) def runFunction(): @@ -418,18 +505,30 @@ def addArgs(arglist): for arg in arglist: - if isinstance(arg, tuple): - addArgs(arg) + if isinstance(arg, _ast.Tuple): + addArgs(arg.elts) else: - if arg in args: - self.report(messages.DuplicateArgument, node.lineno, arg) - args.append(arg) + if arg.id in args: + self.report(messages.DuplicateArgument, node.lineno, arg.id) + args.append(arg.id) self.pushFunctionScope() - addArgs(node.argnames) + addArgs(node.args.args) + # vararg/kwarg identifiers are not Name nodes + if node.args.vararg: + args.append(node.args.vararg) + if node.args.kwarg: + args.append(node.args.kwarg) for name in args: self.addBinding(node.lineno, Argument(name, node), reportRedef=False) - self.handleNode(node.code, node) + 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. @@ -444,90 +543,59 @@ self.deferFunction(runFunction) - - def CLASS(self, node): + 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, "decorators", None) is not None: - self.handleChildren(node.decorators) + # decorator_list is present as of Python 2.6 + for deco in getattr(node, 'decorator_list', []): + self.handleNode(deco, node) for baseNode in node.bases: self.handleNode(baseNode, node) - self.addBinding(node.lineno, Binding(node.name, node)) self.pushClassScope() - self.handleChildren(node.code) + for stmt in node.body: + self.handleNode(stmt, node) 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) + self.addBinding(node.lineno, Binding(node.name, node)) def ASSIGN(self, node): - self.handleNode(node.expr, node) - for subnode in node.nodes[::-1]: - self.handleNode(subnode, node) + self.handleNode(node.value, node) + for target in node.targets: + self.handleNode(target, node) + + def AUGASSIGN(self, node): + # AugAssign is awkward: must set the context explicitly and visit twice, + # once with AugLoad context, once with AugStore context + node.target.ctx = _ast.AugLoad() + self.handleNode(node.target, node) + self.handleNode(node.value, node) + node.target.ctx = _ast.AugStore() + self.handleNode(node.target, node) def IMPORT(self, node): - for name, alias in node.names: - name = alias or name + for alias in node.names: + name = alias.asname or alias.name importation = Importation(name, node) self.addBinding(node.lineno, importation) - def FROM(self, node): - if node.modname == '__future__': + def IMPORTFROM(self, node): + if node.module == '__future__': if not self.futuresAllowed: - self.report(messages.LateFutureImport, node.lineno, [n[0] for n in node.names]) + self.report(messages.LateFutureImport, node.lineno, + [n.name for n in node.names]) else: self.futuresAllowed = False - for name, alias in node.names: - if name == '*': + for alias in node.names: + if alias.name == '*': self.scope.importStarred = True - self.report(messages.ImportStarUsed, node.lineno, node.modname) + self.report(messages.ImportStarUsed, node.lineno, node.module) continue - name = alias or name + name = alias.asname or alias.name importation = Importation(name, node) - if node.modname == '__future__': + if node.module == '__future__': importation.used = (self.scope, node.lineno) self.addBinding(node.lineno, importation)