diff options
author | Johan Dahlin <johan@gnome.org> | 2008-08-14 11:23:26 +0000 |
---|---|---|
committer | Johan Dahlin <johan@src.gnome.org> | 2008-08-14 11:23:26 +0000 |
commit | 2f24c447fbe50c83a60deb523108b62805c2714f (patch) | |
tree | 9e648a8726fc6e3ec3a01b6d1fcbf6a432a9d23a /misc | |
parent | 4a235bd178226ed010c7290321b1df6d2e6cdc85 (diff) | |
download | gobject-introspection-2f24c447fbe50c83a60deb523108b62805c2714f.tar.gz |
Add pyflakes.py and run it in make check. Update the source code to fix
2008-08-14 Johan Dahlin <johan@gnome.org>
* giscanner/girwriter.py:
* giscanner/glibast.py:
* giscanner/glibtransformer.py:
* giscanner/transformer.py:
* misc/pyflakes.py:
* tests/Makefile.am:
Add pyflakes.py and run it in make check.
Update the source code to fix the errors reported by
pyflakes.
svn path=/trunk/; revision=370
Diffstat (limited to 'misc')
-rw-r--r-- | misc/pyflakes.py | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/misc/pyflakes.py b/misc/pyflakes.py new file mode 100644 index 00000000..fc564025 --- /dev/null +++ b/misc/pyflakes.py @@ -0,0 +1,513 @@ +# -*- test-case-name: pyflakes -*- +# (c) 2005-2008 Divmod, Inc. +# See LICENSE file for details + +import __builtin__ +import compiler +import sys +import os + +from compiler import ast + + +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 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 Binding(object): + """ + @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): + def __init__(self, name, source): + name = name.split('.')[0] + super(Importation, self).__init__(name, source) + +class Assignment(Binding): + pass + +class FunctionDefinition(Binding): + pass + + +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 + + +class Checker(object): + nodeDepth = 0 + traceTree = False + + def __init__(self, tree, filename='(none)'): + self.deferred = [] + self.dead_scopes = [] + self.messages = [] + self.filename = filename + self.scopeStack = [ModuleScope()] + self.futuresAllowed = True + + self.handleChildren(tree) + for handler, scope in self.deferred: + self.scopeStack = scope + handler() + del self.scopeStack[1:] + self.popScope() + self.check_dead_scopes() + + def defer(self, callable): + '''Schedule something to be called after 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.deferred.append( (callable, self.scopeStack[:]) ) + + 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): + for scope in self.dead_scopes: + for importation in scope.itervalues(): + if isinstance(importation, Importation) and not importation.used: + self.report(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) + + def handleNode(self, node): + 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(RedefinedFunction, + lineno, value.name, self.scope[value.name].source.lineno) + + if not isinstance(self.scope, ClassScope): + for scope in self.scopeStack[::-1]: + if (isinstance(scope.get(value.name), Importation) + and not scope[value.name].used + and reportRedef): + + self.report(RedefinedWhileUnused, + lineno, value.name, scope[value.name].source.lineno) + + if isinstance(value, UnBinding): + try: + del self.scope[value.name] + except KeyError: + self.report(UndefinedName, lineno, value.name) + else: + self.scope[value.name] = value + + + def WITH(self, node): + """ + Handle C{with} by adding bindings for the name or tuple of names it + puts into scope and by continuing to process the suite within the + statement. + """ + # for "with foo as bar", there is no AssName node for "bar". + # Instead, there is a Name node. If the "as" expression assigns to + # a tuple, it will instead be a AssTuple node of Name nodes. + # + # Of course these are assignments, not references, so we have to + # handle them as a special case here. + + self.handleNode(node.expr) + + if isinstance(node.vars, ast.AssTuple): + varNodes = node.vars.nodes + elif node.vars is not None: + varNodes = [node.vars] + else: + varNodes = [] + + for varNode in varNodes: + self.addBinding(varNode.lineno, Assignment(varNode.name, varNode)) + + 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) + self.handleNode(node.expr) + + 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(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 ['__file__'] + and not importStarred): + self.report(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) + + def runFunction(): + args = [] + + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, tuple): + addArgs(arg) + else: + if arg in args: + self.report(DuplicateArgument, node.lineno, arg) + args.append(arg) + + self.pushFunctionScope() + addArgs(node.argnames) + for name in args: + self.addBinding(node.lineno, Assignment(name, node), reportRedef=False) + self.handleNode(node.code) + self.popScope() + + self.defer(runFunction) + + def CLASS(self, node): + self.addBinding(node.lineno, Assignment(node.name, node)) + for baseNode in node.bases: + self.handleNode(baseNode) + 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(UndefinedLocal, + scope[node.name].used[1], + node.name, + scope[node.name].source.lineno) + break + + self.addBinding(node.lineno, Assignment(node.name, node)) + + def ASSIGN(self, node): + self.handleNode(node.expr) + for subnode in node.nodes[::-1]: + self.handleNode(subnode) + + 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(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(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) + +def check(codeString, filename): + try: + tree = compiler.parse(codeString) + except (SyntaxError, IndentationError): + value = sys.exc_info()[1] + try: + (lineno, offset, line) = value[1][1:] + except IndexError: + print >> sys.stderr, 'could not compile %r' % (filename,) + return 1 + if line.endswith("\n"): + line = line[:-1] + print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno) + print >> sys.stderr, line + print >> sys.stderr, " " * (offset-2), "^" + return 1 + else: + w = 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): + if os.path.exists(filename): + return check(file(filename, 'U').read(), filename) + + +def main(args): + warnings = 0 + 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>') + + return warnings > 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) |