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