1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2013 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 # Original (c) 2005-2010 Divmod, Inc. |
|
6 # |
|
7 # This module is based on pyflakes for Python2 but was heavily hacked to |
|
8 # work within eric5 |
|
9 |
|
10 import __builtin__ |
|
11 import os.path |
|
12 import _ast |
|
13 |
|
14 from py2flakes import messages |
|
15 |
|
16 |
|
17 # utility function to iterate over an AST node's children, adapted |
|
18 # from Python 2.6's standard ast module |
|
19 try: |
|
20 import ast |
|
21 iter_child_nodes = ast.iter_child_nodes |
|
22 except (ImportError, AttributeError): |
|
23 def iter_child_nodes(node, astcls=_ast.AST): |
|
24 """ |
|
25 Yield all direct child nodes of *node*, that is, all fields that are nodes |
|
26 and all items of fields that are lists of nodes. |
|
27 """ |
|
28 for name in node._fields: |
|
29 field = getattr(node, name, None) |
|
30 if isinstance(field, astcls): |
|
31 yield field |
|
32 elif isinstance(field, list): |
|
33 for item in field: |
|
34 yield item |
|
35 |
|
36 |
|
37 class Binding(object): |
|
38 """ |
|
39 Represents the binding of a value to a name. |
|
40 |
|
41 The checker uses this to keep track of which names have been bound and |
|
42 which names have not. See L{Assignment} for a special type of binding that |
|
43 is checked with stricter rules. |
|
44 """ |
|
45 def __init__(self, name, source): |
|
46 self.name = name |
|
47 self.source = source |
|
48 self.used = False |
|
49 |
|
50 def __str__(self): |
|
51 return self.name |
|
52 |
|
53 def __repr__(self): |
|
54 return '<%s object %r from line %r at 0x%x>' % ( |
|
55 self.__class__.__name__, |
|
56 self.name, |
|
57 self.source.lineno, |
|
58 id(self)) |
|
59 |
|
60 |
|
61 class UnBinding(Binding): |
|
62 ''' |
|
63 Created by the 'del' operator. |
|
64 ''' |
|
65 |
|
66 |
|
67 class Importation(Binding): |
|
68 """ |
|
69 A binding created by an import statement. |
|
70 """ |
|
71 def __init__(self, name, source): |
|
72 self.fullName = name |
|
73 name = name.split('.')[0] |
|
74 super(Importation, self).__init__(name, source) |
|
75 |
|
76 |
|
77 class Argument(Binding): |
|
78 """ |
|
79 Represents binding a name as an argument. |
|
80 """ |
|
81 |
|
82 |
|
83 class Assignment(Binding): |
|
84 """ |
|
85 Represents binding a name with an explicit assignment. |
|
86 |
|
87 The checker will raise warnings for any Assignment that isn't used. Also, |
|
88 the checker does not consider assignments in tuple/list unpacking to be |
|
89 Assignments, rather it treats them as simple Bindings. |
|
90 """ |
|
91 |
|
92 |
|
93 class FunctionDefinition(Binding): |
|
94 """ |
|
95 Represents a function definition. |
|
96 """ |
|
97 pass |
|
98 |
|
99 |
|
100 class ExportBinding(Binding): |
|
101 """ |
|
102 A binding created by an __all__ assignment. If the names in the list |
|
103 can be determined statically, they will be treated as names for export and |
|
104 additional checking applied to them. |
|
105 |
|
106 The only __all__ assignment that can be recognized is one which takes |
|
107 the value of a literal list containing literal strings. For example:: |
|
108 |
|
109 __all__ = ["foo", "bar"] |
|
110 |
|
111 Names which are imported and not otherwise used but appear in the value of |
|
112 __all__ will not have an unused import warning reported for them. |
|
113 """ |
|
114 def names(self): |
|
115 """ |
|
116 Return a list of the names referenced by this binding. |
|
117 """ |
|
118 names = [] |
|
119 if isinstance(self.source, _ast.List): |
|
120 for node in self.source.elts: |
|
121 if isinstance(node, _ast.Str): |
|
122 names.append(node.s) |
|
123 return names |
|
124 |
|
125 |
|
126 class Scope(dict): |
|
127 """ |
|
128 Class defining the scope base class. |
|
129 """ |
|
130 importStarred = False # set to True when import * is found |
|
131 |
|
132 def __repr__(self): |
|
133 return '<%s at 0x%x %s>' % ( |
|
134 self.__class__.__name__, id(self), dict.__repr__(self)) |
|
135 |
|
136 def __init__(self): |
|
137 super(Scope, self).__init__() |
|
138 |
|
139 |
|
140 class ClassScope(Scope): |
|
141 """ |
|
142 Class representing a name scope for a class. |
|
143 """ |
|
144 pass |
|
145 |
|
146 |
|
147 class FunctionScope(Scope): |
|
148 """ |
|
149 Class representing a name scope for a function. |
|
150 """ |
|
151 def __init__(self): |
|
152 super(FunctionScope, self).__init__() |
|
153 self.globals = {} |
|
154 |
|
155 |
|
156 class ModuleScope(Scope): |
|
157 """ |
|
158 Class representing a name scope for a module. |
|
159 """ |
|
160 pass |
|
161 |
|
162 # Globally defined names which are not attributes of the __builtin__ module. |
|
163 _MAGIC_GLOBALS = ['__file__', '__builtins__'] |
|
164 |
|
165 |
|
166 class Checker(object): |
|
167 """ |
|
168 Class to check the cleanliness and sanity of Python code. |
|
169 """ |
|
170 nodeDepth = 0 |
|
171 traceTree = False |
|
172 |
|
173 def __init__(self, module, filename='(none)'): |
|
174 """ |
|
175 Constructor |
|
176 |
|
177 @param module parsed module tree or module source code |
|
178 @param filename name of the module file (string) |
|
179 """ |
|
180 self._deferredFunctions = [] |
|
181 self._deferredAssignments = [] |
|
182 self.dead_scopes = [] |
|
183 self.messages = [] |
|
184 self.filename = filename |
|
185 self.scopeStack = [ModuleScope()] |
|
186 self.futuresAllowed = True |
|
187 |
|
188 if isinstance(module, str): |
|
189 module = ast.parse(module, filename, "exec") |
|
190 self.handleChildren(module) |
|
191 self._runDeferred(self._deferredFunctions) |
|
192 # Set _deferredFunctions to None so that deferFunction will fail |
|
193 # noisily if called after we've run through the deferred functions. |
|
194 self._deferredFunctions = None |
|
195 self._runDeferred(self._deferredAssignments) |
|
196 # Set _deferredAssignments to None so that deferAssignment will fail |
|
197 # noisly if called after we've run through the deferred assignments. |
|
198 self._deferredAssignments = None |
|
199 del self.scopeStack[1:] |
|
200 self.popScope() |
|
201 self.check_dead_scopes() |
|
202 |
|
203 def deferFunction(self, callable): |
|
204 ''' |
|
205 Schedule a function handler to be called just before completion. |
|
206 |
|
207 This is used for handling function bodies, which must be deferred |
|
208 because code later in the file might modify the global scope. When |
|
209 `callable` is called, the scope at the time this is called will be |
|
210 restored, however it will contain any new bindings added to it. |
|
211 ''' |
|
212 self._deferredFunctions.append((callable, self.scopeStack[:])) |
|
213 |
|
214 def deferAssignment(self, callable): |
|
215 """ |
|
216 Schedule an assignment handler to be called just after deferred |
|
217 function handlers. |
|
218 """ |
|
219 self._deferredAssignments.append((callable, self.scopeStack[:])) |
|
220 |
|
221 def _runDeferred(self, deferred): |
|
222 """ |
|
223 Run the callables in deferred using their associated scope stack. |
|
224 """ |
|
225 for handler, scope in deferred: |
|
226 self.scopeStack = scope |
|
227 handler() |
|
228 |
|
229 def scope(self): |
|
230 return self.scopeStack[-1] |
|
231 scope = property(scope) |
|
232 |
|
233 def popScope(self): |
|
234 self.dead_scopes.append(self.scopeStack.pop()) |
|
235 |
|
236 def check_dead_scopes(self): |
|
237 """ |
|
238 Look at scopes which have been fully examined and report names in them |
|
239 which were imported but unused. |
|
240 """ |
|
241 for scope in self.dead_scopes: |
|
242 export = isinstance(scope.get('__all__'), ExportBinding) |
|
243 if export: |
|
244 all = scope['__all__'].names() |
|
245 if os.path.split(self.filename)[1] != '__init__.py': |
|
246 # Look for possible mistakes in the export list |
|
247 undefined = set(all) - set(scope) |
|
248 for name in undefined: |
|
249 self.report( |
|
250 messages.UndefinedExport, |
|
251 scope['__all__'].source.lineno, |
|
252 name) |
|
253 else: |
|
254 all = [] |
|
255 |
|
256 # Look for imported names that aren't used. |
|
257 for importation in scope.itervalues(): |
|
258 if isinstance(importation, Importation): |
|
259 if not importation.used and importation.name not in all: |
|
260 self.report( |
|
261 messages.UnusedImport, |
|
262 importation.source.lineno, |
|
263 importation.name) |
|
264 |
|
265 def pushFunctionScope(self): |
|
266 self.scopeStack.append(FunctionScope()) |
|
267 |
|
268 def pushClassScope(self): |
|
269 self.scopeStack.append(ClassScope()) |
|
270 |
|
271 def report(self, messageClass, *args, **kwargs): |
|
272 self.messages.append(messageClass(self.filename, *args, **kwargs)) |
|
273 |
|
274 def handleChildren(self, tree): |
|
275 for node in iter_child_nodes(tree): |
|
276 self.handleNode(node, tree) |
|
277 |
|
278 def isDocstring(self, node): |
|
279 """ |
|
280 Determine if the given node is a docstring, as long as it is at the |
|
281 correct place in the node tree. |
|
282 """ |
|
283 return isinstance(node, _ast.Str) or \ |
|
284 (isinstance(node, _ast.Expr) and |
|
285 isinstance(node.value, _ast.Str)) |
|
286 |
|
287 def handleNode(self, node, parent): |
|
288 if node: |
|
289 node.parent = parent |
|
290 if self.traceTree: |
|
291 print ' ' * self.nodeDepth + node.__class__.__name__ |
|
292 self.nodeDepth += 1 |
|
293 if self.futuresAllowed and not \ |
|
294 (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)): |
|
295 self.futuresAllowed = False |
|
296 nodeType = node.__class__.__name__.upper() |
|
297 try: |
|
298 handler = getattr(self, nodeType) |
|
299 handler(node) |
|
300 except AttributeError: |
|
301 print nodeType, "not supported yet. Please report this." |
|
302 finally: |
|
303 self.nodeDepth -= 1 |
|
304 if self.traceTree: |
|
305 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ |
|
306 |
|
307 def ignore(self, node): |
|
308 pass |
|
309 |
|
310 # "stmt" type nodes |
|
311 RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ |
|
312 TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren |
|
313 |
|
314 CONTINUE = BREAK = PASS = ignore |
|
315 |
|
316 # "expr" type nodes |
|
317 BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ |
|
318 CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren |
|
319 |
|
320 NUM = STR = ELLIPSIS = ignore |
|
321 |
|
322 # "slice" type nodes |
|
323 SLICE = EXTSLICE = INDEX = handleChildren |
|
324 |
|
325 # expression contexts are node instances too, though being constants |
|
326 LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore |
|
327 |
|
328 # same for operators |
|
329 AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ |
|
330 BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ |
|
331 EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore |
|
332 |
|
333 # additional node types |
|
334 COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren |
|
335 |
|
336 def addBinding(self, lineno, value, reportRedef=True): |
|
337 ''' |
|
338 Called when a binding is altered. |
|
339 |
|
340 @param lineno line of the statement responsible for the change (integer) |
|
341 @param value the optional new value, a Binding instance, associated |
|
342 with the binding; if None, the binding is deleted if it exists |
|
343 @param reportRedef flag indicating if rebinding while unused will be |
|
344 reported (boolean) |
|
345 ''' |
|
346 if (isinstance(self.scope.get(value.name), FunctionDefinition) |
|
347 and isinstance(value, FunctionDefinition)): |
|
348 self.report(messages.RedefinedFunction, |
|
349 lineno, value.name, self.scope[value.name].source.lineno) |
|
350 |
|
351 if not isinstance(self.scope, ClassScope): |
|
352 for scope in self.scopeStack[::-1]: |
|
353 existing = scope.get(value.name) |
|
354 if (isinstance(existing, Importation) |
|
355 and not existing.used |
|
356 and (not isinstance(value, Importation) or |
|
357 value.fullName == existing.fullName) |
|
358 and reportRedef): |
|
359 |
|
360 self.report(messages.RedefinedWhileUnused, |
|
361 lineno, value.name, scope[value.name].source.lineno) |
|
362 |
|
363 if isinstance(value, UnBinding): |
|
364 try: |
|
365 del self.scope[value.name] |
|
366 except KeyError: |
|
367 self.report(messages.UndefinedName, lineno, value.name) |
|
368 else: |
|
369 self.scope[value.name] = value |
|
370 |
|
371 def GLOBAL(self, node): |
|
372 """ |
|
373 Keep track of globals declarations. |
|
374 """ |
|
375 if isinstance(self.scope, FunctionScope): |
|
376 self.scope.globals.update(dict.fromkeys(node.names)) |
|
377 |
|
378 def LISTCOMP(self, node): |
|
379 # handle generators before element |
|
380 for gen in node.generators: |
|
381 self.handleNode(gen, node) |
|
382 self.handleNode(node.elt, node) |
|
383 |
|
384 GENERATOREXP = SETCOMP = LISTCOMP |
|
385 |
|
386 # dictionary comprehensions; introduced in Python 2.7 |
|
387 def DICTCOMP(self, node): |
|
388 for gen in node.generators: |
|
389 self.handleNode(gen, node) |
|
390 self.handleNode(node.key, node) |
|
391 self.handleNode(node.value, node) |
|
392 |
|
393 def FOR(self, node): |
|
394 """ |
|
395 Process bindings for loop variables. |
|
396 """ |
|
397 vars = [] |
|
398 |
|
399 def collectLoopVars(n): |
|
400 if isinstance(n, _ast.Name): |
|
401 vars.append(n.id) |
|
402 elif isinstance(n, _ast.expr_context): |
|
403 return |
|
404 else: |
|
405 for c in iter_child_nodes(n): |
|
406 collectLoopVars(c) |
|
407 |
|
408 collectLoopVars(node.target) |
|
409 for varn in vars: |
|
410 if (isinstance(self.scope.get(varn), Importation) |
|
411 # unused ones will get an unused import warning |
|
412 and self.scope[varn].used): |
|
413 self.report(messages.ImportShadowedByLoopVar, |
|
414 node.lineno, varn, self.scope[varn].source.lineno) |
|
415 |
|
416 self.handleChildren(node) |
|
417 |
|
418 def NAME(self, node): |
|
419 """ |
|
420 Locate the name in locals / function / globals scopes. |
|
421 """ |
|
422 if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)): |
|
423 # try local scope |
|
424 importStarred = self.scope.importStarred |
|
425 try: |
|
426 self.scope[node.id].used = (self.scope, node.lineno) |
|
427 except KeyError: |
|
428 pass |
|
429 else: |
|
430 return |
|
431 |
|
432 # try enclosing function scopes |
|
433 |
|
434 for scope in self.scopeStack[-2:0:-1]: |
|
435 importStarred = importStarred or scope.importStarred |
|
436 if not isinstance(scope, FunctionScope): |
|
437 continue |
|
438 try: |
|
439 scope[node.id].used = (self.scope, node.lineno) |
|
440 except KeyError: |
|
441 pass |
|
442 else: |
|
443 return |
|
444 |
|
445 # try global scope |
|
446 |
|
447 importStarred = importStarred or self.scopeStack[0].importStarred |
|
448 try: |
|
449 self.scopeStack[0][node.id].used = (self.scope, node.lineno) |
|
450 except KeyError: |
|
451 if ((not hasattr(__builtin__, node.id)) |
|
452 and node.id not in _MAGIC_GLOBALS |
|
453 and not importStarred): |
|
454 if (os.path.basename(self.filename) == '__init__.py' and |
|
455 node.id == '__path__'): |
|
456 # the special name __path__ is valid only in packages |
|
457 pass |
|
458 else: |
|
459 self.report(messages.UndefinedName, node.lineno, node.id) |
|
460 elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)): |
|
461 # if the name hasn't already been defined in the current scope |
|
462 if isinstance(self.scope, FunctionScope) and node.id not in self.scope: |
|
463 # for each function or module scope above us |
|
464 for scope in self.scopeStack[:-1]: |
|
465 if not isinstance(scope, (FunctionScope, ModuleScope)): |
|
466 continue |
|
467 # if the name was defined in that scope, and the name has |
|
468 # been accessed already in the current scope, and hasn't |
|
469 # been declared global |
|
470 if (node.id in scope |
|
471 and scope[node.id].used |
|
472 and scope[node.id].used[0] is self.scope |
|
473 and node.id not in self.scope.globals): |
|
474 # then it's probably a mistake |
|
475 self.report(messages.UndefinedLocal, |
|
476 scope[node.id].used[1], |
|
477 node.id, |
|
478 scope[node.id].source.lineno) |
|
479 break |
|
480 |
|
481 if isinstance(node.parent, |
|
482 (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)): |
|
483 binding = Binding(node.id, node) |
|
484 elif (node.id == '__all__' and |
|
485 isinstance(self.scope, ModuleScope)): |
|
486 binding = ExportBinding(node.id, node.parent.value) |
|
487 else: |
|
488 binding = Assignment(node.id, node) |
|
489 if node.id in self.scope: |
|
490 binding.used = self.scope[node.id].used |
|
491 self.addBinding(node.lineno, binding) |
|
492 elif isinstance(node.ctx, _ast.Del): |
|
493 if isinstance(self.scope, FunctionScope) and \ |
|
494 node.id in self.scope.globals: |
|
495 del self.scope.globals[node.id] |
|
496 else: |
|
497 self.addBinding(node.lineno, UnBinding(node.id, node)) |
|
498 else: |
|
499 # must be a Param context -- this only happens for names in function |
|
500 # arguments, but these aren't dispatched through here |
|
501 raise RuntimeError( |
|
502 "Got impossible expression context: %r" % (node.ctx,)) |
|
503 |
|
504 def FUNCTIONDEF(self, node): |
|
505 if hasattr(node, 'decorators'): |
|
506 for deco in node.decorators: |
|
507 self.handleNode(deco, node) |
|
508 else: |
|
509 for deco in node.decorator_list: |
|
510 self.handleNode(deco, node) |
|
511 self.addBinding(node.lineno, FunctionDefinition(node.name, node)) |
|
512 self.LAMBDA(node) |
|
513 |
|
514 def LAMBDA(self, node): |
|
515 for default in node.args.defaults: |
|
516 self.handleNode(default, node) |
|
517 |
|
518 def runFunction(): |
|
519 args = [] |
|
520 |
|
521 def addArgs(arglist): |
|
522 for arg in arglist: |
|
523 if isinstance(arg, _ast.Tuple): |
|
524 addArgs(arg.elts) |
|
525 else: |
|
526 if arg.id in args: |
|
527 self.report(messages.DuplicateArgument, node.lineno, arg.id) |
|
528 args.append(arg.id) |
|
529 |
|
530 self.pushFunctionScope() |
|
531 addArgs(node.args.args) |
|
532 # vararg/kwarg identifiers are not Name nodes |
|
533 if node.args.vararg: |
|
534 args.append(node.args.vararg) |
|
535 if node.args.kwarg: |
|
536 args.append(node.args.kwarg) |
|
537 for name in args: |
|
538 self.addBinding(node.lineno, Argument(name, node), reportRedef=False) |
|
539 if isinstance(node.body, list): |
|
540 # case for FunctionDefs |
|
541 for stmt in node.body: |
|
542 self.handleNode(stmt, node) |
|
543 else: |
|
544 # case for Lambdas |
|
545 self.handleNode(node.body, node) |
|
546 |
|
547 def checkUnusedAssignments(): |
|
548 """ |
|
549 Check to see if any assignments have not been used. |
|
550 """ |
|
551 for name, binding in self.scope.iteritems(): |
|
552 if (not binding.used and not name in self.scope.globals |
|
553 and isinstance(binding, Assignment)): |
|
554 self.report(messages.UnusedVariable, |
|
555 binding.source.lineno, name) |
|
556 self.deferAssignment(checkUnusedAssignments) |
|
557 self.popScope() |
|
558 |
|
559 self.deferFunction(runFunction) |
|
560 |
|
561 def CLASSDEF(self, node): |
|
562 """ |
|
563 Check names used in a class definition, including its decorators, base |
|
564 classes, and the body of its definition. Additionally, add its name to |
|
565 the current scope. |
|
566 """ |
|
567 # decorator_list is present as of Python 2.6 |
|
568 for deco in getattr(node, 'decorator_list', []): |
|
569 self.handleNode(deco, node) |
|
570 for baseNode in node.bases: |
|
571 self.handleNode(baseNode, node) |
|
572 self.pushClassScope() |
|
573 for stmt in node.body: |
|
574 self.handleNode(stmt, node) |
|
575 self.popScope() |
|
576 self.addBinding(node.lineno, Binding(node.name, node)) |
|
577 |
|
578 def ASSIGN(self, node): |
|
579 self.handleNode(node.value, node) |
|
580 for target in node.targets: |
|
581 self.handleNode(target, node) |
|
582 |
|
583 def AUGASSIGN(self, node): |
|
584 # AugAssign is awkward: must set the context explicitly and visit twice, |
|
585 # once with AugLoad context, once with AugStore context |
|
586 node.target.ctx = _ast.AugLoad() |
|
587 self.handleNode(node.target, node) |
|
588 self.handleNode(node.value, node) |
|
589 node.target.ctx = _ast.AugStore() |
|
590 self.handleNode(node.target, node) |
|
591 |
|
592 def IMPORT(self, node): |
|
593 for alias in node.names: |
|
594 name = alias.asname or alias.name |
|
595 importation = Importation(name, node) |
|
596 self.addBinding(node.lineno, importation) |
|
597 |
|
598 def IMPORTFROM(self, node): |
|
599 if node.module == '__future__': |
|
600 if not self.futuresAllowed: |
|
601 self.report(messages.LateFutureImport, node.lineno, |
|
602 [n.name for n in node.names]) |
|
603 else: |
|
604 self.futuresAllowed = False |
|
605 |
|
606 for alias in node.names: |
|
607 if alias.name == '*': |
|
608 self.scope.importStarred = True |
|
609 self.report(messages.ImportStarUsed, node.lineno, node.module) |
|
610 continue |
|
611 name = alias.asname or alias.name |
|
612 importation = Importation(name, node) |
|
613 if node.module == '__future__': |
|
614 importation.used = (self.scope, node.lineno) |
|
615 self.addBinding(node.lineno, importation) |
|
616 |
|
617 # |
|
618 # eflag: FileType = Python2 |
|