From: Johan Dahlin Date: Thu, 14 Aug 2008 11:23:26 +0000 (+0000) Subject: Add pyflakes.py and run it in make check. Update the source code to fix X-Git-Tag: GOBJECT_INTROSPECTION_0_5_0~197 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2f24c447fbe50c83a60deb523108b62805c2714f;p=platform%2Fupstream%2Fgobject-introspection.git Add pyflakes.py and run it in make check. Update the source code to fix 2008-08-14 Johan Dahlin * 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 --- diff --git a/ChangeLog b/ChangeLog index c1aac6d..d24c507 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2008-08-14 Johan Dahlin + * 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. + +2008-08-14 Johan Dahlin + * giscanner/ast.py: * giscanner/girparser.py: * giscanner/glibast.py: diff --git a/giscanner/girwriter.py b/giscanner/girwriter.py index 54f169f..eda3d49 100644 --- a/giscanner/girwriter.py +++ b/giscanner/girwriter.py @@ -73,7 +73,7 @@ class GIRWriter(XMLWriter): def _write_alias(self, alias): attrs = [('name', alias.name), ('target', alias.target)] if alias.ctype is not None: - attrs.append(('c:type', ntype.ctype)) + attrs.append(('c:type', alias.ctype)) self.write_tag('alias', attrs) def _write_function(self, func, tag_name='function'): diff --git a/giscanner/glibast.py b/giscanner/glibast.py index 4ef8c78..14c0c03 100644 --- a/giscanner/glibast.py +++ b/giscanner/glibast.py @@ -22,9 +22,8 @@ from .ast import Class, Enum, Interface, Member, Node, Property, Struct from .ast import ( type_names, TYPE_STRING, TYPE_INT8, TYPE_UINT8, TYPE_INT16, TYPE_UINT16, - TYPE_INT32, TYPE_UINT32, TYPE_INT32, TYPE_UINT32, TYPE_LONG, - TYPE_ULONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, TYPE_BOOLEAN, - TYPE_ANY, TYPE_SIZE, TYPE_SSIZE) + TYPE_UINT32, TYPE_INT32, TYPE_LONG, TYPE_ULONG, TYPE_FLOAT, + TYPE_DOUBLE, TYPE_BOOLEAN, TYPE_ANY, TYPE_SIZE, TYPE_SSIZE) # Glib type names type_names['gchararray'] = TYPE_STRING diff --git a/giscanner/glibtransformer.py b/giscanner/glibtransformer.py index 38c6f4e..564e2d9 100644 --- a/giscanner/glibtransformer.py +++ b/giscanner/glibtransformer.py @@ -19,14 +19,11 @@ # import ctypes -import os -import sys from . import cgobject from .odict import odict from .ast import (Callback, Enum, Function, Member, Namespace, Parameter, - Property, Return, Sequence, Struct, Field, Type, Alias, - type_name_from_ctype) + Property, Return, Struct, Type, Alias, type_name_from_ctype) from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags, GLibInterface, GLibObject, GLibSignal) from .utils import extract_libtool, to_underscores diff --git a/giscanner/transformer.py b/giscanner/transformer.py index 7904eeb..666d770 100644 --- a/giscanner/transformer.py +++ b/giscanner/transformer.py @@ -18,21 +18,17 @@ # 02110-1301, USA. # -import os -import sys - from giscanner.ast import (Callback, Enum, Function, Namespace, Member, Parameter, Return, Sequence, Struct, Field, Type, Alias, type_name_from_ctype) -from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags, - GLibInterface, GLibObject, GLibSignal) +from .glibast import GLibBoxed, GLibInterface, GLibObject from giscanner.sourcescanner import ( - SourceSymbol, ctype_name, symbol_type_name, CTYPE_POINTER, - CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, - CTYPE_TYPEDEF, CTYPE_VOID, CTYPE_BASIC_TYPE, CTYPE_ENUM, - CTYPE_FUNCTION, CTYPE_STRUCT, CSYMBOL_TYPE_FUNCTION, - CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT, CSYMBOL_TYPE_ENUM, - CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT, CSYMBOL_TYPE_MEMBER) + SourceSymbol, ctype_name, CTYPE_POINTER, + CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF, + CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT, + CSYMBOL_TYPE_FUNCTION, CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT, + CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT, + CSYMBOL_TYPE_MEMBER) from .utils import strip_common_prefix @@ -86,8 +82,7 @@ class Transformer(object): for node in parser.get_nodes(): if hasattr(node, 'ctype'): self._ctype_names[node.ctype] = (nsname, node) - if isinstance(node, GLibBoxed) or isinstance(node, GLibInterface) \ - or isinstance(node, GLibObject): + if isinstance(node, (GLibBoxed, GLibInterface, GLibObject)): self._type_names[node.type_name] = (nsname, node) elif isinstance(node, Alias): self._alias_names[node.name] = (nsname, node) diff --git a/misc/pyflakes.py b/misc/pyflakes.py new file mode 100644 index 0000000..fc56402 --- /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(), '') + + return warnings > 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tests/Makefile.am b/tests/Makefile.am index f903af0..5ddcc52 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,4 +20,7 @@ EXTRA_DIST = \ TESTS = #roundtrips.sh check-local: - find $(top_srcdir)/giscanner -name \*.py | sort | uniq | xargs $(PYTHON) $(top_srcdir)/misc/pep8.py --repeat + @echo Running PEP8 on Python sources + @find $(top_srcdir)/giscanner -name \*.py | sort | uniq | xargs $(PYTHON) $(top_srcdir)/misc/pep8.py --repeat + @echo Running Pyflakes on Python sources + @find $(top_srcdir)/giscanner -name \*.py | sort | uniq | xargs $(PYTHON) $(top_srcdir)/misc/pyflakes.py