|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 # Original (c) 2005-2008 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 compiler |
|
13 from compiler import ast |
|
14 |
|
15 from py2flakes import messages |
|
16 |
|
17 class Binding(object): |
|
18 """ |
|
19 Represents the binding of a value to a name. |
|
20 |
|
21 The checker uses this to keep track of which names have been bound and |
|
22 which names have not. See L{Assignment} for a special type of binding that |
|
23 is checked with stricter rules. |
|
24 """ |
|
25 def __init__(self, name, source): |
|
26 self.name = name |
|
27 self.source = source |
|
28 self.used = False |
|
29 |
|
30 def __str__(self): |
|
31 return self.name |
|
32 |
|
33 def __repr__(self): |
|
34 return '<%s object %r from line %r at 0x%x>' % ( |
|
35 self.__class__.__name__, |
|
36 self.name, |
|
37 self.source.lineno, |
|
38 id(self)) |
|
39 |
|
40 class UnBinding(Binding): |
|
41 ''' |
|
42 Created by the 'del' operator. |
|
43 ''' |
|
44 |
|
45 class Importation(Binding): |
|
46 """ |
|
47 A binding created by an import statement. |
|
48 """ |
|
49 def __init__(self, name, source): |
|
50 self.fullName = name |
|
51 name = name.split('.')[0] |
|
52 super(Importation, self).__init__(name, source) |
|
53 |
|
54 class Argument(Binding): |
|
55 """ |
|
56 Represents binding a name as an argument. |
|
57 """ |
|
58 |
|
59 class Assignment(Binding): |
|
60 """ |
|
61 Represents binding a name with an explicit assignment. |
|
62 |
|
63 The checker will raise warnings for any Assignment that isn't used. Also, |
|
64 the checker does not consider assignments in tuple/list unpacking to be |
|
65 Assignments, rather it treats them as simple Bindings. |
|
66 """ |
|
67 |
|
68 class FunctionDefinition(Binding): |
|
69 """ |
|
70 Represents a function definition. |
|
71 """ |
|
72 pass |
|
73 |
|
74 class ExportBinding(Binding): |
|
75 """ |
|
76 A binding created by an __all__ assignment. If the names in the list |
|
77 can be determined statically, they will be treated as names for export and |
|
78 additional checking applied to them. |
|
79 |
|
80 The only __all__ assignment that can be recognized is one which takes |
|
81 the value of a literal list containing literal strings. For example:: |
|
82 |
|
83 __all__ = ["foo", "bar"] |
|
84 |
|
85 Names which are imported and not otherwise used but appear in the value of |
|
86 __all__ will not have an unused import warning reported for them. |
|
87 """ |
|
88 def names(self): |
|
89 """ |
|
90 Return a list of the names referenced by this binding. |
|
91 """ |
|
92 names = [] |
|
93 if isinstance(self.source, ast.List): |
|
94 for node in self.source.nodes: |
|
95 if isinstance(node, ast.Const): |
|
96 names.append(node.value) |
|
97 return names |
|
98 |
|
99 class Scope(dict): |
|
100 """ |
|
101 Class defining the scope base class. |
|
102 """ |
|
103 importStarred = False # set to True when import * is found |
|
104 |
|
105 def __repr__(self): |
|
106 return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) |
|
107 |
|
108 def __init__(self): |
|
109 super(Scope, self).__init__() |
|
110 |
|
111 class ClassScope(Scope): |
|
112 """ |
|
113 Class representing a name scope for a class. |
|
114 """ |
|
115 pass |
|
116 |
|
117 class FunctionScope(Scope): |
|
118 """ |
|
119 Class representing a name scope for a function. |
|
120 """ |
|
121 def __init__(self): |
|
122 super(FunctionScope, self).__init__() |
|
123 self.globals = {} |
|
124 |
|
125 class ModuleScope(Scope): |
|
126 """ |
|
127 Class representing a name scope for a module. |
|
128 """ |
|
129 pass |
|
130 |
|
131 # Globally defined names which are not attributes of the __builtin__ module. |
|
132 _MAGIC_GLOBALS = ['__file__', '__builtins__'] |
|
133 |
|
134 class Checker(object): |
|
135 """ |
|
136 Class to check the cleanliness and sanity of Python code. |
|
137 """ |
|
138 nodeDepth = 0 |
|
139 traceTree = False |
|
140 |
|
141 def __init__(self, module, filename='(none)'): |
|
142 """ |
|
143 Constructor |
|
144 |
|
145 @param module parsed module tree or module source code |
|
146 @param filename name of the module file (string) |
|
147 """ |
|
148 self._deferredFunctions = [] |
|
149 self._deferredAssignments = [] |
|
150 self.dead_scopes = [] |
|
151 self.messages = [] |
|
152 self.filename = filename |
|
153 self.scopeStack = [ModuleScope()] |
|
154 self.futuresAllowed = True |
|
155 |
|
156 if isinstance(module, str): |
|
157 module = compiler.parse(module) |
|
158 self.handleChildren(module) |
|
159 self._runDeferred(self._deferredFunctions) |
|
160 # Set _deferredFunctions to None so that deferFunction will fail |
|
161 # noisily if called after we've run through the deferred functions. |
|
162 self._deferredFunctions = None |
|
163 self._runDeferred(self._deferredAssignments) |
|
164 # Set _deferredAssignments to None so that deferAssignment will fail |
|
165 # noisly if called after we've run through the deferred assignments. |
|
166 self._deferredAssignments = None |
|
167 del self.scopeStack[1:] |
|
168 self.popScope() |
|
169 self.check_dead_scopes() |
|
170 |
|
171 def deferFunction(self, callable): |
|
172 ''' |
|
173 Schedule a function handler to be called just before completion. |
|
174 |
|
175 This is used for handling function bodies, which must be deferred |
|
176 because code later in the file might modify the global scope. When |
|
177 `callable` is called, the scope at the time this is called will be |
|
178 restored, however it will contain any new bindings added to it. |
|
179 ''' |
|
180 self._deferredFunctions.append((callable, self.scopeStack[:])) |
|
181 |
|
182 def deferAssignment(self, callable): |
|
183 """ |
|
184 Schedule an assignment handler to be called just after deferred |
|
185 function handlers. |
|
186 """ |
|
187 self._deferredAssignments.append((callable, self.scopeStack[:])) |
|
188 |
|
189 def _runDeferred(self, deferred): |
|
190 """ |
|
191 Run the callables in C{deferred} using their associated scope stack. |
|
192 """ |
|
193 for handler, scope in deferred: |
|
194 self.scopeStack = scope |
|
195 handler() |
|
196 |
|
197 def scope(self): |
|
198 return self.scopeStack[-1] |
|
199 scope = property(scope) |
|
200 |
|
201 def popScope(self): |
|
202 self.dead_scopes.append(self.scopeStack.pop()) |
|
203 |
|
204 def check_dead_scopes(self): |
|
205 """ |
|
206 Look at scopes which have been fully examined and report names in them |
|
207 which were imported but unused. |
|
208 """ |
|
209 for scope in self.dead_scopes: |
|
210 export = isinstance(scope.get('__all__'), ExportBinding) |
|
211 if export: |
|
212 all = scope['__all__'].names() |
|
213 if os.path.split(self.filename)[1] != '__init__.py': |
|
214 # Look for possible mistakes in the export list |
|
215 undefined = set(all) - set(scope) |
|
216 for name in undefined: |
|
217 self.report( |
|
218 messages.UndefinedExport, |
|
219 scope['__all__'].source.lineno, |
|
220 name) |
|
221 else: |
|
222 all = [] |
|
223 |
|
224 # Look for imported names that aren't used. |
|
225 for importation in scope.itervalues(): |
|
226 if isinstance(importation, Importation): |
|
227 if not importation.used and importation.name not in all: |
|
228 self.report( |
|
229 messages.UnusedImport, |
|
230 importation.source.lineno, |
|
231 importation.name) |
|
232 |
|
233 def pushFunctionScope(self): |
|
234 self.scopeStack.append(FunctionScope()) |
|
235 |
|
236 def pushClassScope(self): |
|
237 self.scopeStack.append(ClassScope()) |
|
238 |
|
239 def report(self, messageClass, *args, **kwargs): |
|
240 self.messages.append(messageClass(self.filename, *args, **kwargs)) |
|
241 |
|
242 def handleChildren(self, tree): |
|
243 for node in tree.getChildNodes(): |
|
244 self.handleNode(node, tree) |
|
245 |
|
246 def handleNode(self, node, parent): |
|
247 node.parent = parent |
|
248 if self.traceTree: |
|
249 print ' ' * self.nodeDepth + node.__class__.__name__ |
|
250 self.nodeDepth += 1 |
|
251 nodeType = node.__class__.__name__.upper() |
|
252 if nodeType not in ('STMT', 'FROM'): |
|
253 self.futuresAllowed = False |
|
254 try: |
|
255 handler = getattr(self, nodeType) |
|
256 handler(node) |
|
257 finally: |
|
258 self.nodeDepth -= 1 |
|
259 if self.traceTree: |
|
260 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ |
|
261 |
|
262 def ignore(self, node): |
|
263 pass |
|
264 |
|
265 STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \ |
|
266 ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = \ |
|
267 RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \ |
|
268 SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \ |
|
269 RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \ |
|
270 FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \ |
|
271 AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = \ |
|
272 IFEXP = handleChildren |
|
273 |
|
274 CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore |
|
275 |
|
276 def addBinding(self, lineno, value, reportRedef=True): |
|
277 ''' |
|
278 Called when a binding is altered. |
|
279 |
|
280 @param lineno line of the statement responsible for the change (integer) |
|
281 @param value the optional new value, a Binding instance, associated |
|
282 with the binding; if None, the binding is deleted if it exists |
|
283 @param reportRedef flag indicating if rebinding while unused will be |
|
284 reported (boolean) |
|
285 ''' |
|
286 if (isinstance(self.scope.get(value.name), FunctionDefinition) |
|
287 and isinstance(value, FunctionDefinition)): |
|
288 self.report(messages.RedefinedFunction, |
|
289 lineno, value.name, self.scope[value.name].source.lineno) |
|
290 |
|
291 if not isinstance(self.scope, ClassScope): |
|
292 for scope in self.scopeStack[::-1]: |
|
293 existing = scope.get(value.name) |
|
294 if (isinstance(existing, Importation) |
|
295 and not existing.used |
|
296 and (not isinstance(value, Importation) or value.fullName == existing.fullName) |
|
297 and reportRedef): |
|
298 |
|
299 self.report(messages.RedefinedWhileUnused, |
|
300 lineno, value.name, scope[value.name].source.lineno) |
|
301 |
|
302 if isinstance(value, UnBinding): |
|
303 try: |
|
304 del self.scope[value.name] |
|
305 except KeyError: |
|
306 self.report(messages.UndefinedName, lineno, value.name) |
|
307 else: |
|
308 self.scope[value.name] = value |
|
309 |
|
310 def WITH(self, node): |
|
311 """ |
|
312 Handle 'with' by checking the target of the statement (which can be an |
|
313 identifier, a list or tuple of targets, an attribute, etc) for |
|
314 undefined names and defining any it adds to the scope and by continuing |
|
315 to process the suite within the statement. |
|
316 """ |
|
317 # Check the "foo" part of a "with foo as bar" statement. Do this no |
|
318 # matter what, since there's always a "foo" part. |
|
319 self.handleNode(node.expr, node) |
|
320 |
|
321 if node.vars is not None: |
|
322 self.handleNode(node.vars, node) |
|
323 |
|
324 self.handleChildren(node.body) |
|
325 |
|
326 |
|
327 def GLOBAL(self, node): |
|
328 """ |
|
329 Keep track of globals declarations. |
|
330 """ |
|
331 if isinstance(self.scope, FunctionScope): |
|
332 self.scope.globals.update(dict.fromkeys(node.names)) |
|
333 |
|
334 def LISTCOMP(self, node): |
|
335 for qual in node.quals: |
|
336 self.handleNode(qual, node) |
|
337 self.handleNode(node.expr, node) |
|
338 |
|
339 GENEXPRINNER = LISTCOMP |
|
340 |
|
341 def FOR(self, node): |
|
342 """ |
|
343 Process bindings for loop variables. |
|
344 """ |
|
345 vars = [] |
|
346 def collectLoopVars(n): |
|
347 if hasattr(n, 'name'): |
|
348 vars.append(n.name) |
|
349 else: |
|
350 for c in n.getChildNodes(): |
|
351 collectLoopVars(c) |
|
352 |
|
353 collectLoopVars(node.assign) |
|
354 for varn in vars: |
|
355 if (isinstance(self.scope.get(varn), Importation) |
|
356 # unused ones will get an unused import warning |
|
357 and self.scope[varn].used): |
|
358 self.report(messages.ImportShadowedByLoopVar, |
|
359 node.lineno, varn, self.scope[varn].source.lineno) |
|
360 |
|
361 self.handleChildren(node) |
|
362 |
|
363 def NAME(self, node): |
|
364 """ |
|
365 Locate the name in locals / function / globals scopes. |
|
366 """ |
|
367 # try local scope |
|
368 importStarred = self.scope.importStarred |
|
369 try: |
|
370 self.scope[node.name].used = (self.scope, node.lineno) |
|
371 except KeyError: |
|
372 pass |
|
373 else: |
|
374 return |
|
375 |
|
376 # try enclosing function scopes |
|
377 |
|
378 for scope in self.scopeStack[-2:0:-1]: |
|
379 importStarred = importStarred or scope.importStarred |
|
380 if not isinstance(scope, FunctionScope): |
|
381 continue |
|
382 try: |
|
383 scope[node.name].used = (self.scope, node.lineno) |
|
384 except KeyError: |
|
385 pass |
|
386 else: |
|
387 return |
|
388 |
|
389 # try global scope |
|
390 |
|
391 importStarred = importStarred or self.scopeStack[0].importStarred |
|
392 try: |
|
393 self.scopeStack[0][node.name].used = (self.scope, node.lineno) |
|
394 except KeyError: |
|
395 if ((not hasattr(__builtin__, node.name)) |
|
396 and node.name not in _MAGIC_GLOBALS |
|
397 and not importStarred): |
|
398 if (os.path.basename(self.filename) == '__init__.py' and |
|
399 node.name == '__path__'): |
|
400 # the special name __path__ is valid only in packages |
|
401 pass |
|
402 else: |
|
403 self.report(messages.UndefinedName, node.lineno, node.name) |
|
404 |
|
405 |
|
406 def FUNCTION(self, node): |
|
407 if getattr(node, "decorators", None) is not None: |
|
408 self.handleChildren(node.decorators) |
|
409 self.addBinding(node.lineno, FunctionDefinition(node.name, node)) |
|
410 self.LAMBDA(node) |
|
411 |
|
412 def LAMBDA(self, node): |
|
413 for default in node.defaults: |
|
414 self.handleNode(default, node) |
|
415 |
|
416 def runFunction(): |
|
417 args = [] |
|
418 |
|
419 def addArgs(arglist): |
|
420 for arg in arglist: |
|
421 if isinstance(arg, tuple): |
|
422 addArgs(arg) |
|
423 else: |
|
424 if arg in args: |
|
425 self.report(messages.DuplicateArgument, node.lineno, arg) |
|
426 args.append(arg) |
|
427 |
|
428 self.pushFunctionScope() |
|
429 addArgs(node.argnames) |
|
430 for name in args: |
|
431 self.addBinding(node.lineno, Argument(name, node), reportRedef=False) |
|
432 self.handleNode(node.code, node) |
|
433 def checkUnusedAssignments(): |
|
434 """ |
|
435 Check to see if any assignments have not been used. |
|
436 """ |
|
437 for name, binding in self.scope.iteritems(): |
|
438 if (not binding.used and not name in self.scope.globals |
|
439 and isinstance(binding, Assignment)): |
|
440 self.report(messages.UnusedVariable, |
|
441 binding.source.lineno, name) |
|
442 self.deferAssignment(checkUnusedAssignments) |
|
443 self.popScope() |
|
444 |
|
445 self.deferFunction(runFunction) |
|
446 |
|
447 |
|
448 def CLASS(self, node): |
|
449 """ |
|
450 Check names used in a class definition, including its decorators, base |
|
451 classes, and the body of its definition. Additionally, add its name to |
|
452 the current scope. |
|
453 """ |
|
454 if getattr(node, "decorators", None) is not None: |
|
455 self.handleChildren(node.decorators) |
|
456 for baseNode in node.bases: |
|
457 self.handleNode(baseNode, node) |
|
458 self.addBinding(node.lineno, Binding(node.name, node)) |
|
459 self.pushClassScope() |
|
460 self.handleChildren(node.code) |
|
461 self.popScope() |
|
462 |
|
463 |
|
464 def ASSNAME(self, node): |
|
465 if node.flags == 'OP_DELETE': |
|
466 if isinstance(self.scope, FunctionScope) and node.name in self.scope.globals: |
|
467 del self.scope.globals[node.name] |
|
468 else: |
|
469 self.addBinding(node.lineno, UnBinding(node.name, node)) |
|
470 else: |
|
471 # if the name hasn't already been defined in the current scope |
|
472 if isinstance(self.scope, FunctionScope) and node.name not in self.scope: |
|
473 # for each function or module scope above us |
|
474 for scope in self.scopeStack[:-1]: |
|
475 if not isinstance(scope, (FunctionScope, ModuleScope)): |
|
476 continue |
|
477 # if the name was defined in that scope, and the name has |
|
478 # been accessed already in the current scope, and hasn't |
|
479 # been declared global |
|
480 if (node.name in scope |
|
481 and scope[node.name].used |
|
482 and scope[node.name].used[0] is self.scope |
|
483 and node.name not in self.scope.globals): |
|
484 # then it's probably a mistake |
|
485 self.report(messages.UndefinedLocal, |
|
486 scope[node.name].used[1], |
|
487 node.name, |
|
488 scope[node.name].source.lineno) |
|
489 break |
|
490 |
|
491 if isinstance(node.parent, |
|
492 (ast.For, ast.ListCompFor, ast.GenExprFor, |
|
493 ast.AssTuple, ast.AssList)): |
|
494 binding = Binding(node.name, node) |
|
495 elif (node.name == '__all__' and |
|
496 isinstance(self.scope, ModuleScope) and |
|
497 isinstance(node.parent, ast.Assign)): |
|
498 binding = ExportBinding(node.name, node.parent.expr) |
|
499 else: |
|
500 binding = Assignment(node.name, node) |
|
501 if node.name in self.scope: |
|
502 binding.used = self.scope[node.name].used |
|
503 self.addBinding(node.lineno, binding) |
|
504 |
|
505 def ASSIGN(self, node): |
|
506 self.handleNode(node.expr, node) |
|
507 for subnode in node.nodes[::-1]: |
|
508 self.handleNode(subnode, node) |
|
509 |
|
510 def IMPORT(self, node): |
|
511 for name, alias in node.names: |
|
512 name = alias or name |
|
513 importation = Importation(name, node) |
|
514 self.addBinding(node.lineno, importation) |
|
515 |
|
516 def FROM(self, node): |
|
517 if node.modname == '__future__': |
|
518 if not self.futuresAllowed: |
|
519 self.report(messages.LateFutureImport, node.lineno, [n[0] for n in node.names]) |
|
520 else: |
|
521 self.futuresAllowed = False |
|
522 |
|
523 for name, alias in node.names: |
|
524 if name == '*': |
|
525 self.scope.importStarred = True |
|
526 self.report(messages.ImportStarUsed, node.lineno, node.modname) |
|
527 continue |
|
528 name = alias or name |
|
529 importation = Importation(name, node) |
|
530 if node.modname == '__future__': |
|
531 importation.used = (self.scope, node.lineno) |
|
532 self.addBinding(node.lineno, importation) |
|
533 |
|
534 # |
|
535 # eflag: FileType = Python2 |