1 # -*- test-case-name: pyflakes -*-
2 # (c) 2005-2008 Divmod, Inc.
3 # See LICENSE file for details
10 from compiler import ast
13 class Message(object):
17 def __init__(self, filename, lineno):
18 self.filename = filename
22 return '%s:%s: %s' % (self.filename,
24 self.message % self.message_args)
27 class UnusedImport(Message):
28 message = '%r imported but unused'
30 def __init__(self, filename, lineno, name):
31 Message.__init__(self, filename, lineno)
32 self.message_args = (name, )
35 class RedefinedWhileUnused(Message):
36 message = 'redefinition of unused %r from line %r'
38 def __init__(self, filename, lineno, name, orig_lineno):
39 Message.__init__(self, filename, lineno)
40 self.message_args = (name, orig_lineno)
43 class ImportShadowedByLoopVar(Message):
44 message = 'import %r from line %r shadowed by loop variable'
46 def __init__(self, filename, lineno, name, orig_lineno):
47 Message.__init__(self, filename, lineno)
48 self.message_args = (name, orig_lineno)
51 class ImportStarUsed(Message):
52 message = "'from %s import *' used; unable to detect undefined names"
54 def __init__(self, filename, lineno, modname):
55 Message.__init__(self, filename, lineno)
56 self.message_args = (modname, )
59 class UndefinedName(Message):
60 message = 'undefined name %r'
62 def __init__(self, filename, lineno, name):
63 Message.__init__(self, filename, lineno)
64 self.message_args = (name, )
67 class UndefinedLocal(Message):
68 message = ("local variable %r (defined in enclosing scope on line %r) "
69 "referenced before assignment")
71 def __init__(self, filename, lineno, name, orig_lineno):
72 Message.__init__(self, filename, lineno)
73 self.message_args = (name, orig_lineno)
76 class DuplicateArgument(Message):
77 message = 'duplicate argument %r in function definition'
79 def __init__(self, filename, lineno, name):
80 Message.__init__(self, filename, lineno)
81 self.message_args = (name, )
84 class RedefinedFunction(Message):
85 message = 'redefinition of function %r from line %r'
87 def __init__(self, filename, lineno, name, orig_lineno):
88 Message.__init__(self, filename, lineno)
89 self.message_args = (name, orig_lineno)
92 class LateFutureImport(Message):
93 message = 'future import(s) %r after other statements'
95 def __init__(self, filename, lineno, names):
96 Message.__init__(self, filename, lineno)
97 self.message_args = (names, )
100 class Binding(object):
102 @ivar used: pair of (L{Scope}, line-number) indicating the scope and
103 line number that this binding was last used
106 def __init__(self, name, source):
115 return '<%s object %r from line %r at 0x%x>' % (
116 self.__class__.__name__,
122 class UnBinding(Binding):
123 '''Created by the 'del' operator.'''
126 class Importation(Binding):
128 def __init__(self, name, source):
129 name = name.split('.')[0]
130 super(Importation, self).__init__(name, source)
133 class Assignment(Binding):
137 class FunctionDefinition(Binding):
142 importStarred = False # set to True when import * is found
145 return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self),
149 super(Scope, self).__init__()
152 class ClassScope(Scope):
156 class FunctionScope(Scope):
158 I represent a name scope for a function.
160 @ivar globals: Names declared 'global' in this function.
164 super(FunctionScope, self).__init__()
168 class ModuleScope(Scope):
172 class Checker(object):
176 def __init__(self, tree, filename='(none)'):
178 self.dead_scopes = []
180 self.filename = filename
181 self.scopeStack = [ModuleScope()]
182 self.futuresAllowed = True
184 self.handleChildren(tree)
185 for handler, scope in self.deferred:
186 self.scopeStack = scope
188 del self.scopeStack[1:]
190 self.check_dead_scopes()
192 def defer(self, callable):
193 '''Schedule something to be called after just before completion.
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.
200 self.deferred.append((callable, self.scopeStack[:]))
203 return self.scopeStack[-1]
204 scope = property(scope)
207 self.dead_scopes.append(self.scopeStack.pop())
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)
217 def pushFunctionScope(self):
218 self.scopeStack.append(FunctionScope())
220 def pushClassScope(self):
221 self.scopeStack.append(ClassScope())
223 def report(self, messageClass, *args, **kwargs):
224 self.messages.append(messageClass(self.filename, *args, **kwargs))
226 def handleChildren(self, tree):
227 for node in tree.getChildNodes():
228 self.handleNode(node)
230 def handleNode(self, node):
232 print ' ' * self.nodeDepth + node.__class__.__name__
234 nodeType = node.__class__.__name__.upper()
235 if nodeType not in ('STMT', 'FROM'):
236 self.futuresAllowed = False
238 handler = getattr(self, nodeType)
243 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
245 def ignore(self, node):
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
257 CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore
259 def addBinding(self, lineno, value, reportRedef=True):
260 '''Called when a binding is altered.
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
268 if (isinstance(self.scope.get(value.name), FunctionDefinition)
269 and isinstance(value, FunctionDefinition)):
270 self.report(RedefinedFunction,
272 self.scope[value.name].source.lineno)
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
280 self.report(RedefinedWhileUnused,
282 scope[value.name].source.lineno)
284 if isinstance(value, UnBinding):
286 del self.scope[value.name]
288 self.report(UndefinedName, lineno, value.name)
290 self.scope[value.name] = value
292 def WITH(self, node):
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
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.
302 # Of course these are assignments, not references, so we have to
303 # handle them as a special case here.
305 self.handleNode(node.expr)
307 if isinstance(node.vars, ast.AssTuple):
308 varNodes = node.vars.nodes
309 elif node.vars is not None:
310 varNodes = [node.vars]
314 for varNode in varNodes:
315 self.addBinding(varNode.lineno, Assignment(varNode.name, varNode))
317 self.handleChildren(node.body)
319 def GLOBAL(self, node):
321 Keep track of globals declarations.
323 if isinstance(self.scope, FunctionScope):
324 self.scope.globals.update(dict.fromkeys(node.names))
326 def LISTCOMP(self, node):
327 for qual in node.quals:
328 self.handleNode(qual)
329 self.handleNode(node.expr)
331 GENEXPRINNER = LISTCOMP
335 Process bindings for loop variables.
339 def collectLoopVars(n):
340 if hasattr(n, 'name'):
343 for c in n.getChildNodes():
346 collectLoopVars(node.assign)
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)
354 self.handleChildren(node)
356 def NAME(self, node):
358 Locate the name in locals / function / globals scopes.
361 importStarred = self.scope.importStarred
363 self.scope[node.name].used = (self.scope, node.lineno)
369 # try enclosing function scopes
371 for scope in self.scopeStack[-2:0:-1]:
372 importStarred = importStarred or scope.importStarred
373 if not isinstance(scope, FunctionScope):
376 scope[node.name].used = (self.scope, node.lineno)
384 importStarred = importStarred or self.scopeStack[0].importStarred
386 self.scopeStack[0][node.name].used = (self.scope, node.lineno)
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)
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))
399 def LAMBDA(self, node):
400 for default in node.defaults:
401 self.handleNode(default)
406 def addArgs(arglist):
408 if isinstance(arg, tuple):
412 self.report(DuplicateArgument, node.lineno, arg)
415 self.pushFunctionScope()
416 addArgs(node.argnames)
418 self.addBinding(node.lineno, Assignment(name, node),
420 self.handleNode(node.code)
423 self.defer(runFunction)
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)
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]
439 self.addBinding(node.lineno, UnBinding(node.name, node))
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)):
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],
459 scope[node.name].source.lineno)
462 self.addBinding(node.lineno, Assignment(node.name, node))
464 def ASSIGN(self, node):
465 self.handleNode(node.expr)
466 for subnode in node.nodes[::-1]:
467 self.handleNode(subnode)
469 def IMPORT(self, node):
470 for name, alias in node.names:
472 importation = Importation(name, node)
473 self.addBinding(node.lineno, importation)
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])
481 self.futuresAllowed = False
483 for name, alias in node.names:
485 self.scope.importStarred = True
486 self.report(ImportStarUsed, node.lineno, node.modname)
489 importation = Importation(name, node)
490 if node.modname == '__future__':
491 importation.used = (self.scope, node.lineno)
492 self.addBinding(node.lineno, importation)
495 def check(codeString, filename):
497 tree = compiler.parse(codeString)
498 except (SyntaxError, IndentationError):
499 value = sys.exc_info()[1]
501 (lineno, offset, line) = value[1][1:]
503 print >> sys.stderr, 'could not compile %r' % (filename, )
505 if line.endswith("\n"):
507 print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno)
508 print >> sys.stderr, line
509 print >> sys.stderr, " " * (offset-2), "^"
512 w = Checker(tree, filename)
513 w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
514 for warning in w.messages:
516 return len(w.messages)
519 def checkPath(filename):
520 if os.path.exists(filename):
521 return check(file(filename, 'U').read(), filename)
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))
535 warnings += checkPath(arg)
537 warnings += check(sys.stdin.read(), '<stdin>')
541 if __name__ == '__main__':
542 sys.exit(main(sys.argv[1:]))