Add pyflakes.py and run it in make check. Update the source code to fix
[platform/upstream/gobject-introspection.git] / misc / pyflakes.py
1 # -*- test-case-name: pyflakes -*-
2 # (c) 2005-2008 Divmod, Inc.
3 # See LICENSE file for details
4
5 import __builtin__
6 import compiler
7 import sys
8 import os
9
10 from compiler import ast
11
12
13 class Message(object):
14     message = ''
15     message_args = ()
16     def __init__(self, filename, lineno):
17         self.filename = filename
18         self.lineno = lineno
19     def __str__(self):
20         return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
21
22
23 class UnusedImport(Message):
24     message = '%r imported but unused'
25     def __init__(self, filename, lineno, name):
26         Message.__init__(self, filename, lineno)
27         self.message_args = (name,)
28
29
30 class RedefinedWhileUnused(Message):
31     message = 'redefinition of unused %r from line %r'
32     def __init__(self, filename, lineno, name, orig_lineno):
33         Message.__init__(self, filename, lineno)
34         self.message_args = (name, orig_lineno)
35
36
37 class ImportShadowedByLoopVar(Message):
38     message = 'import %r from line %r shadowed by loop variable'
39     def __init__(self, filename, lineno, name, orig_lineno):
40         Message.__init__(self, filename, lineno)
41         self.message_args = (name, orig_lineno)
42
43
44 class ImportStarUsed(Message):
45     message = "'from %s import *' used; unable to detect undefined names"
46     def __init__(self, filename, lineno, modname):
47         Message.__init__(self, filename, lineno)
48         self.message_args = (modname,)
49
50
51 class UndefinedName(Message):
52     message = 'undefined name %r'
53     def __init__(self, filename, lineno, name):
54         Message.__init__(self, filename, lineno)
55         self.message_args = (name,)
56
57
58 class UndefinedLocal(Message):
59     message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment"
60     def __init__(self, filename, lineno, name, orig_lineno):
61         Message.__init__(self, filename, lineno)
62         self.message_args = (name, orig_lineno)
63
64
65 class DuplicateArgument(Message):
66     message = 'duplicate argument %r in function definition'
67     def __init__(self, filename, lineno, name):
68         Message.__init__(self, filename, lineno)
69         self.message_args = (name,)
70
71
72 class RedefinedFunction(Message):
73     message = 'redefinition of function %r from line %r'
74     def __init__(self, filename, lineno, name, orig_lineno):
75         Message.__init__(self, filename, lineno)
76         self.message_args = (name, orig_lineno)
77
78
79 class LateFutureImport(Message):
80     message = 'future import(s) %r after other statements'
81     def __init__(self, filename, lineno, names):
82         Message.__init__(self, filename, lineno)
83         self.message_args = (names,)
84
85
86 class Binding(object):
87     """
88     @ivar used: pair of (L{Scope}, line-number) indicating the scope and
89                 line number that this binding was last used
90     """
91     def __init__(self, name, source):
92         self.name = name
93         self.source = source
94         self.used = False
95
96     def __str__(self):
97         return self.name
98
99     def __repr__(self):
100         return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
101                                                         self.name,
102                                                         self.source.lineno,
103                                                         id(self))
104
105 class UnBinding(Binding):
106     '''Created by the 'del' operator.'''
107
108 class Importation(Binding):
109     def __init__(self, name, source):
110         name = name.split('.')[0]
111         super(Importation, self).__init__(name, source)
112
113 class Assignment(Binding):
114     pass
115
116 class FunctionDefinition(Binding):
117     pass
118
119
120 class Scope(dict):
121     importStarred = False       # set to True when import * is found
122
123     def __repr__(self):
124         return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
125
126     def __init__(self):
127         super(Scope, self).__init__()
128
129 class ClassScope(Scope):
130     pass
131
132
133
134 class FunctionScope(Scope):
135     """
136     I represent a name scope for a function.
137
138     @ivar globals: Names declared 'global' in this function.
139     """
140     def __init__(self):
141         super(FunctionScope, self).__init__()
142         self.globals = {}
143
144
145
146 class ModuleScope(Scope):
147     pass
148
149
150 class Checker(object):
151     nodeDepth = 0
152     traceTree = False
153
154     def __init__(self, tree, filename='(none)'):
155         self.deferred = []
156         self.dead_scopes = []
157         self.messages = []
158         self.filename = filename
159         self.scopeStack = [ModuleScope()]
160         self.futuresAllowed = True
161
162         self.handleChildren(tree)
163         for handler, scope in self.deferred:
164             self.scopeStack = scope
165             handler()
166         del self.scopeStack[1:]
167         self.popScope()
168         self.check_dead_scopes()
169
170     def defer(self, callable):
171         '''Schedule something to be called after just before completion.
172
173         This is used for handling function bodies, which must be deferred
174         because code later in the file might modify the global scope. When
175         `callable` is called, the scope at the time this is called will be
176         restored, however it will contain any new bindings added to it.
177         '''
178         self.deferred.append( (callable, self.scopeStack[:]) )
179
180     def scope(self):
181         return self.scopeStack[-1]
182     scope = property(scope)
183
184     def popScope(self):
185         self.dead_scopes.append(self.scopeStack.pop())
186
187     def check_dead_scopes(self):
188         for scope in self.dead_scopes:
189             for importation in scope.itervalues():
190                 if isinstance(importation, Importation) and not importation.used:
191                     self.report(UnusedImport, importation.source.lineno, importation.name)
192
193     def pushFunctionScope(self):
194         self.scopeStack.append(FunctionScope())
195
196     def pushClassScope(self):
197         self.scopeStack.append(ClassScope())
198
199     def report(self, messageClass, *args, **kwargs):
200         self.messages.append(messageClass(self.filename, *args, **kwargs))
201
202     def handleChildren(self, tree):
203         for node in tree.getChildNodes():
204             self.handleNode(node)
205
206     def handleNode(self, node):
207         if self.traceTree:
208             print '  ' * self.nodeDepth + node.__class__.__name__
209         self.nodeDepth += 1
210         nodeType = node.__class__.__name__.upper()
211         if nodeType not in ('STMT', 'FROM'):
212             self.futuresAllowed = False
213         try:
214             handler = getattr(self, nodeType)
215             handler(node)
216         finally:
217             self.nodeDepth -= 1
218         if self.traceTree:
219             print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
220
221     def ignore(self, node):
222         pass
223
224     STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \
225     ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = \
226     RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \
227     SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \
228     RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \
229     FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \
230     AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = \
231     IFEXP = handleChildren
232
233     CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore
234
235     def addBinding(self, lineno, value, reportRedef=True):
236         '''Called when a binding is altered.
237
238         - `lineno` is the line of the statement responsible for the change
239         - `value` is the optional new value, a Binding instance, associated
240           with the binding; if None, the binding is deleted if it exists.
241         - if `reportRedef` is True (default), rebinding while unused will be
242           reported.
243         '''
244         if (isinstance(self.scope.get(value.name), FunctionDefinition)
245                     and isinstance(value, FunctionDefinition)):
246             self.report(RedefinedFunction,
247                         lineno, value.name, self.scope[value.name].source.lineno)
248
249         if not isinstance(self.scope, ClassScope):
250             for scope in self.scopeStack[::-1]:
251                 if (isinstance(scope.get(value.name), Importation)
252                         and not scope[value.name].used
253                         and reportRedef):
254
255                     self.report(RedefinedWhileUnused,
256                                 lineno, value.name, scope[value.name].source.lineno)
257
258         if isinstance(value, UnBinding):
259             try:
260                 del self.scope[value.name]
261             except KeyError:
262                 self.report(UndefinedName, lineno, value.name)
263         else:
264             self.scope[value.name] = value
265
266
267     def WITH(self, node):
268         """
269         Handle C{with} by adding bindings for the name or tuple of names it
270         puts into scope and by continuing to process the suite within the
271         statement.
272         """
273         # for "with foo as bar", there is no AssName node for "bar".
274         # Instead, there is a Name node. If the "as" expression assigns to
275         # a tuple, it will instead be a AssTuple node of Name nodes.
276         #
277         # Of course these are assignments, not references, so we have to
278         # handle them as a special case here.
279
280         self.handleNode(node.expr)
281
282         if isinstance(node.vars, ast.AssTuple):
283             varNodes = node.vars.nodes
284         elif node.vars is not None:
285             varNodes = [node.vars]
286         else:
287             varNodes = []
288
289         for varNode in varNodes:
290             self.addBinding(varNode.lineno, Assignment(varNode.name, varNode))
291
292         self.handleChildren(node.body)
293
294
295     def GLOBAL(self, node):
296         """
297         Keep track of globals declarations.
298         """
299         if isinstance(self.scope, FunctionScope):
300             self.scope.globals.update(dict.fromkeys(node.names))
301
302     def LISTCOMP(self, node):
303         for qual in node.quals:
304             self.handleNode(qual)
305         self.handleNode(node.expr)
306
307     GENEXPRINNER = LISTCOMP
308
309     def FOR(self, node):
310         """
311         Process bindings for loop variables.
312         """
313         vars = []
314         def collectLoopVars(n):
315             if hasattr(n, 'name'):
316                 vars.append(n.name)
317             else:
318                 for c in n.getChildNodes():
319                     collectLoopVars(c)
320
321         collectLoopVars(node.assign)
322         for varn in vars:
323             if (isinstance(self.scope.get(varn), Importation)
324                     # unused ones will get an unused import warning
325                     and self.scope[varn].used):
326                 self.report(ImportShadowedByLoopVar,
327                             node.lineno, varn, self.scope[varn].source.lineno)
328
329         self.handleChildren(node)
330
331     def NAME(self, node):
332         """
333         Locate the name in locals / function / globals scopes.
334         """
335         # try local scope
336         importStarred = self.scope.importStarred
337         try:
338             self.scope[node.name].used = (self.scope, node.lineno)
339         except KeyError:
340             pass
341         else:
342             return
343
344         # try enclosing function scopes
345
346         for scope in self.scopeStack[-2:0:-1]:
347             importStarred = importStarred or scope.importStarred
348             if not isinstance(scope, FunctionScope):
349                 continue
350             try:
351                 scope[node.name].used = (self.scope, node.lineno)
352             except KeyError:
353                 pass
354             else:
355                 return
356
357         # try global scope
358
359         importStarred = importStarred or self.scopeStack[0].importStarred
360         try:
361             self.scopeStack[0][node.name].used = (self.scope, node.lineno)
362         except KeyError:
363             if ((not hasattr(__builtin__, node.name))
364                     and node.name not in ['__file__']
365                     and not importStarred):
366                 self.report(UndefinedName, node.lineno, node.name)
367
368
369     def FUNCTION(self, node):
370         if getattr(node, "decorators", None) is not None:
371             self.handleChildren(node.decorators)
372         self.addBinding(node.lineno, FunctionDefinition(node.name, node))
373         self.LAMBDA(node)
374
375     def LAMBDA(self, node):
376         for default in node.defaults:
377             self.handleNode(default)
378
379         def runFunction():
380             args = []
381
382             def addArgs(arglist):
383                 for arg in arglist:
384                     if isinstance(arg, tuple):
385                         addArgs(arg)
386                     else:
387                         if arg in args:
388                             self.report(DuplicateArgument, node.lineno, arg)
389                         args.append(arg)
390
391             self.pushFunctionScope()
392             addArgs(node.argnames)
393             for name in args:
394                 self.addBinding(node.lineno, Assignment(name, node), reportRedef=False)
395             self.handleNode(node.code)
396             self.popScope()
397
398         self.defer(runFunction)
399
400     def CLASS(self, node):
401         self.addBinding(node.lineno, Assignment(node.name, node))
402         for baseNode in node.bases:
403             self.handleNode(baseNode)
404         self.pushClassScope()
405         self.handleChildren(node.code)
406         self.popScope()
407
408     def ASSNAME(self, node):
409         if node.flags == 'OP_DELETE':
410             if isinstance(self.scope, FunctionScope) and node.name in self.scope.globals:
411                 del self.scope.globals[node.name]
412             else:
413                 self.addBinding(node.lineno, UnBinding(node.name, node))
414         else:
415             # if the name hasn't already been defined in the current scope
416             if isinstance(self.scope, FunctionScope) and node.name not in self.scope:
417                 # for each function or module scope above us
418                 for scope in self.scopeStack[:-1]:
419                     if not isinstance(scope, (FunctionScope, ModuleScope)):
420                         continue
421                     # if the name was defined in that scope, and the name has
422                     # been accessed already in the current scope, and hasn't
423                     # been declared global
424                     if (node.name in scope
425                             and scope[node.name].used
426                             and scope[node.name].used[0] is self.scope
427                             and node.name not in self.scope.globals):
428                         # then it's probably a mistake
429                         self.report(UndefinedLocal,
430                                     scope[node.name].used[1],
431                                     node.name,
432                                     scope[node.name].source.lineno)
433                         break
434
435             self.addBinding(node.lineno, Assignment(node.name, node))
436
437     def ASSIGN(self, node):
438         self.handleNode(node.expr)
439         for subnode in node.nodes[::-1]:
440             self.handleNode(subnode)
441
442     def IMPORT(self, node):
443         for name, alias in node.names:
444             name = alias or name
445             importation = Importation(name, node)
446             self.addBinding(node.lineno, importation)
447
448     def FROM(self, node):
449         if node.modname == '__future__':
450             if not self.futuresAllowed:
451                 self.report(LateFutureImport, node.lineno, [n[0] for n in node.names])
452         else:
453             self.futuresAllowed = False
454
455         for name, alias in node.names:
456             if name == '*':
457                 self.scope.importStarred = True
458                 self.report(ImportStarUsed, node.lineno, node.modname)
459                 continue
460             name = alias or name
461             importation = Importation(name, node)
462             if node.modname == '__future__':
463                 importation.used = (self.scope, node.lineno)
464             self.addBinding(node.lineno, importation)
465
466 def check(codeString, filename):
467     try:
468         tree = compiler.parse(codeString)
469     except (SyntaxError, IndentationError):
470         value = sys.exc_info()[1]
471         try:
472             (lineno, offset, line) = value[1][1:]
473         except IndexError:
474             print >> sys.stderr, 'could not compile %r' % (filename,)
475             return 1
476         if line.endswith("\n"):
477             line = line[:-1]
478         print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno)
479         print >> sys.stderr, line
480         print >> sys.stderr, " " * (offset-2), "^"
481         return 1
482     else:
483         w = Checker(tree, filename)
484         w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
485         for warning in w.messages:
486             print warning
487         return len(w.messages)
488
489
490 def checkPath(filename):
491     if os.path.exists(filename):
492         return check(file(filename, 'U').read(), filename)
493
494
495 def main(args):
496     warnings = 0
497     if args:
498         for arg in args:
499             if os.path.isdir(arg):
500                 for dirpath, dirnames, filenames in os.walk(arg):
501                     for filename in filenames:
502                         if filename.endswith('.py'):
503                             warnings += checkPath(
504                                 os.path.join(dirpath, filename))
505             else:
506                 warnings += checkPath(arg)
507     else:
508         warnings += check(sys.stdin.read(), '<stdin>')
509
510     return warnings > 0
511
512 if __name__ == '__main__':
513     sys.exit(main(sys.argv[1:]))