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):
16 def __init__(self, filename, lineno):
17 self.filename = filename
20 return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args)
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,)
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)
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)
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,)
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,)
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)
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,)
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)
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,)
86 class Binding(object):
88 @ivar used: pair of (L{Scope}, line-number) indicating the scope and
89 line number that this binding was last used
91 def __init__(self, name, source):
100 return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
105 class UnBinding(Binding):
106 '''Created by the 'del' operator.'''
108 class Importation(Binding):
109 def __init__(self, name, source):
110 name = name.split('.')[0]
111 super(Importation, self).__init__(name, source)
113 class Assignment(Binding):
116 class FunctionDefinition(Binding):
121 importStarred = False # set to True when import * is found
124 return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
127 super(Scope, self).__init__()
129 class ClassScope(Scope):
134 class FunctionScope(Scope):
136 I represent a name scope for a function.
138 @ivar globals: Names declared 'global' in this function.
141 super(FunctionScope, self).__init__()
146 class ModuleScope(Scope):
150 class Checker(object):
154 def __init__(self, tree, filename='(none)'):
156 self.dead_scopes = []
158 self.filename = filename
159 self.scopeStack = [ModuleScope()]
160 self.futuresAllowed = True
162 self.handleChildren(tree)
163 for handler, scope in self.deferred:
164 self.scopeStack = scope
166 del self.scopeStack[1:]
168 self.check_dead_scopes()
170 def defer(self, callable):
171 '''Schedule something to be called after just before completion.
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.
178 self.deferred.append( (callable, self.scopeStack[:]) )
181 return self.scopeStack[-1]
182 scope = property(scope)
185 self.dead_scopes.append(self.scopeStack.pop())
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)
193 def pushFunctionScope(self):
194 self.scopeStack.append(FunctionScope())
196 def pushClassScope(self):
197 self.scopeStack.append(ClassScope())
199 def report(self, messageClass, *args, **kwargs):
200 self.messages.append(messageClass(self.filename, *args, **kwargs))
202 def handleChildren(self, tree):
203 for node in tree.getChildNodes():
204 self.handleNode(node)
206 def handleNode(self, node):
208 print ' ' * self.nodeDepth + node.__class__.__name__
210 nodeType = node.__class__.__name__.upper()
211 if nodeType not in ('STMT', 'FROM'):
212 self.futuresAllowed = False
214 handler = getattr(self, nodeType)
219 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
221 def ignore(self, node):
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
233 CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore
235 def addBinding(self, lineno, value, reportRedef=True):
236 '''Called when a binding is altered.
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
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)
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
255 self.report(RedefinedWhileUnused,
256 lineno, value.name, scope[value.name].source.lineno)
258 if isinstance(value, UnBinding):
260 del self.scope[value.name]
262 self.report(UndefinedName, lineno, value.name)
264 self.scope[value.name] = value
267 def WITH(self, node):
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
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.
277 # Of course these are assignments, not references, so we have to
278 # handle them as a special case here.
280 self.handleNode(node.expr)
282 if isinstance(node.vars, ast.AssTuple):
283 varNodes = node.vars.nodes
284 elif node.vars is not None:
285 varNodes = [node.vars]
289 for varNode in varNodes:
290 self.addBinding(varNode.lineno, Assignment(varNode.name, varNode))
292 self.handleChildren(node.body)
295 def GLOBAL(self, node):
297 Keep track of globals declarations.
299 if isinstance(self.scope, FunctionScope):
300 self.scope.globals.update(dict.fromkeys(node.names))
302 def LISTCOMP(self, node):
303 for qual in node.quals:
304 self.handleNode(qual)
305 self.handleNode(node.expr)
307 GENEXPRINNER = LISTCOMP
311 Process bindings for loop variables.
314 def collectLoopVars(n):
315 if hasattr(n, 'name'):
318 for c in n.getChildNodes():
321 collectLoopVars(node.assign)
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)
329 self.handleChildren(node)
331 def NAME(self, node):
333 Locate the name in locals / function / globals scopes.
336 importStarred = self.scope.importStarred
338 self.scope[node.name].used = (self.scope, node.lineno)
344 # try enclosing function scopes
346 for scope in self.scopeStack[-2:0:-1]:
347 importStarred = importStarred or scope.importStarred
348 if not isinstance(scope, FunctionScope):
351 scope[node.name].used = (self.scope, node.lineno)
359 importStarred = importStarred or self.scopeStack[0].importStarred
361 self.scopeStack[0][node.name].used = (self.scope, node.lineno)
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)
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))
375 def LAMBDA(self, node):
376 for default in node.defaults:
377 self.handleNode(default)
382 def addArgs(arglist):
384 if isinstance(arg, tuple):
388 self.report(DuplicateArgument, node.lineno, arg)
391 self.pushFunctionScope()
392 addArgs(node.argnames)
394 self.addBinding(node.lineno, Assignment(name, node), reportRedef=False)
395 self.handleNode(node.code)
398 self.defer(runFunction)
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)
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]
413 self.addBinding(node.lineno, UnBinding(node.name, node))
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)):
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],
432 scope[node.name].source.lineno)
435 self.addBinding(node.lineno, Assignment(node.name, node))
437 def ASSIGN(self, node):
438 self.handleNode(node.expr)
439 for subnode in node.nodes[::-1]:
440 self.handleNode(subnode)
442 def IMPORT(self, node):
443 for name, alias in node.names:
445 importation = Importation(name, node)
446 self.addBinding(node.lineno, importation)
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])
453 self.futuresAllowed = False
455 for name, alias in node.names:
457 self.scope.importStarred = True
458 self.report(ImportStarUsed, node.lineno, node.modname)
461 importation = Importation(name, node)
462 if node.modname == '__future__':
463 importation.used = (self.scope, node.lineno)
464 self.addBinding(node.lineno, importation)
466 def check(codeString, filename):
468 tree = compiler.parse(codeString)
469 except (SyntaxError, IndentationError):
470 value = sys.exc_info()[1]
472 (lineno, offset, line) = value[1][1:]
474 print >> sys.stderr, 'could not compile %r' % (filename,)
476 if line.endswith("\n"):
478 print >> sys.stderr, '%s:%d: could not compile' % (filename, lineno)
479 print >> sys.stderr, line
480 print >> sys.stderr, " " * (offset-2), "^"
483 w = Checker(tree, filename)
484 w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
485 for warning in w.messages:
487 return len(w.messages)
490 def checkPath(filename):
491 if os.path.exists(filename):
492 return check(file(filename, 'U').read(), filename)
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))
506 warnings += checkPath(arg)
508 warnings += check(sys.stdin.read(), '<stdin>')
512 if __name__ == '__main__':
513 sys.exit(main(sys.argv[1:]))