Utilities/py3flakes/checker.py

changeset 88
3701923bccf2
child 125
064cfadcf15c
equal deleted inserted replaced
87:4cc5c8d1184d 88:3701923bccf2
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 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 with Python3
9
10 import builtins
11 import os.path
12 import ast
13
14 from . import messages
15
16 class Binding(object):
17 """
18 Represents the binding of a value to a name.
19
20 The checker uses this to keep track of which names have been bound and
21 which names have not. See Assignment for a special type of binding that
22 is checked with stricter rules.
23 """
24 def __init__(self, name, source):
25 self.name = name
26 self.source = source
27 self.used = False
28
29 def __str__(self):
30 return self.name
31
32 def __repr__(self):
33 return '<{0} object {1!r} from line {2!r} at 0x{3:x}>'.format(
34 self.__class__.__name__,
35 self.name,
36 self.source.lineno,
37 id(self))
38
39 class UnBinding(Binding):
40 '''
41 Created by the 'del' operator.
42 '''
43
44 class Importation(Binding):
45 """
46 A binding created by an import statement.
47 """
48 def __init__(self, name, source):
49 self.fullName = name
50 name = name.split('.')[0]
51 super(Importation, self).__init__(name, source)
52
53 class Argument(Binding):
54 """
55 Represents binding a name as an argument.
56 """
57
58 class Assignment(Binding):
59 """
60 Represents binding a name with an explicit assignment.
61
62 The checker will raise warnings for any Assignment that isn't used. Also,
63 the checker does not consider assignments in tuple/list unpacking to be
64 Assignments, rather it treats them as simple Bindings.
65 """
66
67 class FunctionDefinition(Binding):
68 """
69 Represents a function definition.
70 """
71 pass
72
73 class ExportBinding(Binding):
74 """
75 A binding created by an __all__ assignment. If the names in the list
76 can be determined statically, they will be treated as names for export and
77 additional checking applied to them.
78
79 The only __all__ assignment that can be recognized is one which takes
80 the value of a literal list containing literal strings. For example::
81
82 __all__ = ["foo", "bar"]
83
84 Names which are imported and not otherwise used but appear in the value of
85 __all__ will not have an unused import warning reported for them.
86 """
87 def names(self):
88 """
89 Return a list of the names referenced by this binding.
90 """
91 names = []
92 if isinstance(self.source, ast.List):
93 for node in self.source.elts:
94 if isinstance(node, (ast.Str, ast.Bytes)):
95 names.append(node.s)
96 elif isinstance(node, ast.Num):
97 names.append(node.n)
98 return names
99
100 class Scope(dict):
101 """
102 Class defining the scope base class.
103 """
104 importStarred = False # set to True when import * is found
105
106 def __repr__(self):
107 return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
108
109 def __init__(self):
110 super(Scope, self).__init__()
111
112 class ClassScope(Scope):
113 """
114 Class representing a name scope for a class.
115 """
116 pass
117
118 class FunctionScope(Scope):
119 """
120 Class representing a name scope for a function.
121 """
122 def __init__(self):
123 super(FunctionScope, self).__init__()
124 self.globals = {}
125
126 class ModuleScope(Scope):
127 """
128 Class representing a name scope for a module.
129 """
130 pass
131
132 # Globally defined names which are not attributes of the builtins module.
133 _MAGIC_GLOBALS = ['__file__', '__builtins__']
134
135 class Checker(object):
136 """
137 Class to check the cleanliness and sanity of Python code.
138 """
139 nodeDepth = 0
140 traceTree = False
141
142 def __init__(self, module, filename = '(none)'):
143 """
144 Constructor
145
146 @param module parsed module tree or module source code
147 @param filename name of the module file (string)
148 """
149 self._deferredFunctions = []
150 self._deferredAssignments = []
151 self.dead_scopes = []
152 self.messages = []
153 self.filename = filename
154 self.scopeStack = [ModuleScope()]
155 self.futuresAllowed = True
156
157 if isinstance(module, str):
158 module = ast.parse(module, filename, "exec")
159 self.handleBody(module)
160 self._runDeferred(self._deferredFunctions)
161 # Set _deferredFunctions to None so that deferFunction will fail
162 # noisily if called after we've run through the deferred functions.
163 self._deferredFunctions = None
164 self._runDeferred(self._deferredAssignments)
165 # Set _deferredAssignments to None so that deferAssignment will fail
166 # noisly if called after we've run through the deferred assignments.
167 self._deferredAssignments = None
168 del self.scopeStack[1:]
169 self.popScope()
170 self.check_dead_scopes()
171
172 def deferFunction(self, callable):
173 '''
174 Schedule a function handler to be called just before completion.
175
176 This is used for handling function bodies, which must be deferred
177 because code later in the file might modify the global scope. When
178 `callable` is called, the scope at the time this is called will be
179 restored, however it will contain any new bindings added to it.
180 '''
181 self._deferredFunctions.append((callable, self.scopeStack[:]))
182
183 def deferAssignment(self, callable):
184 """
185 Schedule an assignment handler to be called just after deferred
186 function handlers.
187 """
188 self._deferredAssignments.append((callable, self.scopeStack[:]))
189
190 def _runDeferred(self, deferred):
191 """
192 Run the callables in deferred using their associated scope stack.
193 """
194 for handler, scope in deferred:
195 self.scopeStack = scope
196 handler()
197
198 def scope(self):
199 return self.scopeStack[-1]
200 scope = property(scope)
201
202 def popScope(self):
203 self.dead_scopes.append(self.scopeStack.pop())
204
205 def check_dead_scopes(self):
206 """
207 Look at scopes which have been fully examined and report names in them
208 which were imported but unused.
209 """
210 for scope in self.dead_scopes:
211 export = isinstance(scope.get('__all__'), ExportBinding)
212 if export:
213 all = scope['__all__'].names()
214 if os.path.split(self.filename)[1] != '__init__.py':
215 # Look for possible mistakes in the export list
216 undefined = set(all) - set(scope)
217 for name in undefined:
218 self.report(
219 messages.UndefinedExport,
220 scope['__all__'].source.lineno,
221 name)
222 else:
223 all = []
224
225 # Look for imported names that aren't used.
226 for importation in scope.values():
227 if isinstance(importation, Importation):
228 if not importation.used and importation.name not in all:
229 self.report(
230 messages.UnusedImport,
231 importation.source.lineno,
232 importation.name)
233
234 def pushFunctionScope(self):
235 self.scopeStack.append(FunctionScope())
236
237 def pushClassScope(self):
238 self.scopeStack.append(ClassScope())
239
240 def report(self, messageClass, *args, **kwargs):
241 self.messages.append(messageClass(self.filename, *args, **kwargs))
242
243 def handleBody(self, tree):
244 for node in tree.body:
245 self.handleNode(node, tree)
246
247 def handleNode(self, node, parent):
248 if node:
249 node.parent = parent
250 if self.traceTree:
251 print(' ' * self.nodeDepth + node.__class__.__name__)
252 self.nodeDepth += 1
253 nodeType = node.__class__.__name__.upper()
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 # ast nodes to be ignored
266 PASS = CONTINUE = BREAK = ELLIPSIS = NUM = STR = BYTES = \
267 ATTRIBUTES = AND = OR = ADD = SUB = MULT = DIV = \
268 MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = \
269 INVERT = NOT = UADD = USUB = INVERT = NOT = UADD = USUB = ignore
270
271 def addBinding(self, lineno, value, reportRedef = True):
272 '''Called when a binding is altered.
273
274 @param lineno line of the statement responsible for the change (integer)
275 @param value the optional new value, a Binding instance, associated
276 with the binding; if None, the binding is deleted if it exists
277 @param reportRedef flag indicating if rebinding while unused will be
278 reported (boolean)
279 '''
280 if (isinstance(self.scope.get(value.name), FunctionDefinition)
281 and isinstance(value, FunctionDefinition)):
282 self.report(messages.RedefinedFunction,
283 lineno, value.name, self.scope[value.name].source.lineno)
284
285 if not isinstance(self.scope, ClassScope):
286 for scope in self.scopeStack[::-1]:
287 existing = scope.get(value.name)
288 if (isinstance(existing, Importation)
289 and not existing.used
290 and (not isinstance(value, Importation) or value.fullName == existing.fullName)
291 and reportRedef):
292
293 self.report(messages.RedefinedWhileUnused,
294 lineno, value.name, scope[value.name].source.lineno)
295
296 if isinstance(value, UnBinding):
297 try:
298 del self.scope[value.name]
299 except KeyError:
300 self.report(messages.UndefinedName, lineno, value.name)
301 else:
302 self.scope[value.name] = value
303
304 ############################################################
305 ## individual handler methods below
306 ############################################################
307
308 def LIST(self, node):
309 for elt in node.elts:
310 self.handleNode(elt, node)
311
312 SET = TUPLE = LIST
313
314 def DICT(self, node):
315 for key in node.keys:
316 self.handleNode(key, node)
317 for val in node.values:
318 self.handleNode(val, node)
319
320 def WITH(self, node):
321 """
322 Handle with by checking the target of the statement (which can be an
323 identifier, a list or tuple of targets, an attribute, etc) for
324 undefined names and defining any it adds to the scope and by continuing
325 to process the suite within the statement.
326 """
327 # Check the "foo" part of a "with foo as bar" statement. Do this no
328 # matter what, since there's always a "foo" part.
329 self.handleNode(node.context_expr, node)
330
331 if node.optional_vars is not None:
332 self.handleNode(node.optional_vars, node)
333
334 self.handleBody(node)
335
336 def GLOBAL(self, node):
337 """
338 Keep track of globals declarations.
339 """
340 if isinstance(self.scope, FunctionScope):
341 self.scope.globals.update(dict.fromkeys(node.names))
342
343 NONLOCAL = GLOBAL
344
345 def LISTCOMP(self, node):
346 for generator in node.generators:
347 self.handleNode(generator, node)
348 self.handleNode(node.elt, node)
349
350 SETCOMP = GENERATOREXP = LISTCOMP
351
352 def DICTCOMP(self, node):
353 for generator in node.generators:
354 self.handleNode(generator, node)
355 self.handleNode(node.key, node)
356 self.handleNode(node.value, node)
357
358 def COMPREHENSION(self, node):
359 node.target.parent = node
360 self.handleAssignName(node.target)
361 self.handleNode(node.iter, node)
362 for elt in node.ifs:
363 self.handleNode(elt, node)
364
365 def FOR(self, node):
366 """
367 Process bindings for loop variables.
368 """
369 vars = []
370 def collectLoopVars(n):
371 if isinstance(n, ast.Name):
372 vars.append(n.id)
373
374 collectLoopVars(node.target)
375 for varn in vars:
376 if (isinstance(self.scope.get(varn), Importation)
377 # unused ones will get an unused import warning
378 and self.scope[varn].used):
379 self.report(messages.ImportShadowedByLoopVar,
380 node.lineno, varn, self.scope[varn].source.lineno)
381
382 node.target.parent = node
383 self.handleAssignName(node.target)
384 self.handleNode(node.iter, node)
385 self.handleBody(node)
386
387 def NAME(self, node):
388 """
389 Locate the name in locals / function / globals scopes.
390 """
391 # try local scope
392 importStarred = self.scope.importStarred
393 try:
394 self.scope[node.id].used = (self.scope, node.lineno)
395 except KeyError:
396 pass
397 else:
398 return
399
400 # try enclosing function scopes
401 for scope in self.scopeStack[-2:0:-1]:
402 importStarred = importStarred or scope.importStarred
403 if not isinstance(scope, FunctionScope):
404 continue
405 try:
406 scope[node.id].used = (self.scope, node.lineno)
407 except KeyError:
408 pass
409 else:
410 return
411
412 # try global scope
413 importStarred = importStarred or self.scopeStack[0].importStarred
414 try:
415 self.scopeStack[0][node.id].used = (self.scope, node.lineno)
416 except KeyError:
417 if ((not hasattr(builtins, node.id))
418 and node.id not in _MAGIC_GLOBALS
419 and not importStarred):
420 if (os.path.basename(self.filename) == '__init__.py' and
421 node.id == '__path__'):
422 # the special name __path__ is valid only in packages
423 pass
424 else:
425 self.report(messages.UndefinedName, node.lineno, node.id)
426
427 def FUNCTIONDEF(self, node):
428 if getattr(node, "decorator_list", None) is not None:
429 for decorator in node.decorator_list:
430 self.handleNode(decorator, node)
431 self.addBinding(node.lineno, FunctionDefinition(node.name, node))
432 self.LAMBDA(node)
433
434 def LAMBDA(self, node):
435 for default in node.args.defaults + node.args.kw_defaults:
436 self.handleNode(default, node)
437
438 def runFunction():
439 args = []
440
441 def addArgs(arglist):
442 for arg in arglist:
443 if isinstance(arg.arg, tuple):
444 addArgs(arg.arg)
445 else:
446 if arg.arg in args:
447 self.report(messages.DuplicateArgument, node.lineno, arg.arg)
448 args.append(arg.arg)
449
450 def checkUnusedAssignments():
451 """
452 Check to see if any assignments have not been used.
453 """
454 for name, binding in self.scope.items():
455 if (not binding.used and not name in self.scope.globals
456 and isinstance(binding, Assignment)):
457 self.report(messages.UnusedVariable,
458 binding.source.lineno, name)
459
460 self.pushFunctionScope()
461 addArgs(node.args.args)
462 addArgs(node.args.kwonlyargs)
463 for name in args:
464 self.addBinding(node.lineno, Argument(name, node), reportRedef=False)
465 if node.args.vararg:
466 self.addBinding(node.lineno, Argument(node.args.vararg, node),
467 reportRedef=False)
468 if node.args.kwarg:
469 self.addBinding(node.lineno, Argument(node.args.kwarg, node),
470 reportRedef=False)
471 if isinstance(node.body, list):
472 self.handleBody(node)
473 else:
474 self.handleNode(node.body, node)
475 self.deferAssignment(checkUnusedAssignments)
476 self.popScope()
477
478 self.deferFunction(runFunction)
479
480 def CLASSDEF(self, node):
481 """
482 Check names used in a class definition, including its decorators, base
483 classes, and the body of its definition. Additionally, add its name to
484 the current scope.
485 """
486 if getattr(node, "decorator_list", None) is not None:
487 for decorator in node.decorator_list:
488 self.handleNode(decorator, node)
489 for baseNode in node.bases:
490 self.handleNode(baseNode, node)
491 self.addBinding(node.lineno, Binding(node.name, node))
492 self.pushClassScope()
493 self.handleBody(node)
494 self.popScope()
495
496 def handleAssignName(self, node):
497 # special handling for ast.Subscript and ast.Starred
498 if isinstance(node, (ast.Subscript, ast.Starred)):
499 node.value.parent = node
500 self.handleAssignName(node.value)
501 if isinstance(node, ast.Subscript):
502 if isinstance(node.slice, ast.Slice):
503 self.handleNode(node.slice.lower, node)
504 self.handleNode(node.slice.upper, node)
505 else:
506 self.handleNode(node.slice.value, node)
507 return
508
509 # if the name hasn't already been defined in the current scope
510 if isinstance(node, ast.Tuple):
511 for elt in node.elts:
512 elt.parent = node
513 self.handleAssignName(elt)
514 return
515
516 if isinstance(node, ast.Attribute):
517 self.handleNode(node.value, node)
518 return
519
520 if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
521 # for each function or module scope above us
522 for scope in self.scopeStack[:-1]:
523 if not isinstance(scope, (FunctionScope, ModuleScope)):
524 continue
525 # if the name was defined in that scope, and the name has
526 # been accessed already in the current scope, and hasn't
527 # been declared global
528 if (node.id in scope
529 and scope[node.id].used
530 and scope[node.id].used[0] is self.scope
531 and node.id not in self.scope.globals):
532 # then it's probably a mistake
533 self.report(messages.UndefinedLocal,
534 scope[node.id].used[1],
535 node.id,
536 scope[node.id].source.lineno)
537 break
538
539 if isinstance(node.parent,
540 (ast.For, ast.ListComp, ast.GeneratorExp,
541 ast.Tuple, ast.List)):
542 binding = Binding(node.id, node)
543 elif (node.id == '__all__' and
544 isinstance(self.scope, ModuleScope) and
545 isinstance(node.parent, ast.Assign)):
546 binding = ExportBinding(node.id, node.parent.value)
547 else:
548 binding = Assignment(node.id, node)
549 if node.id in self.scope:
550 binding.used = self.scope[node.id].used
551 self.addBinding(node.lineno, binding)
552
553 def ASSIGN(self, node):
554 self.handleNode(node.value, node)
555 for subnode in node.targets[::-1]:
556 subnode.parent = node
557 if isinstance(subnode, ast.Attribute):
558 self.handleNode(subnode.value, subnode)
559 else:
560 self.handleAssignName(subnode)
561
562 def AUGASSIGN(self, node):
563 self.handleNode(node.value, node)
564 self.handleNode(node.target, node)
565
566 def IMPORT(self, node):
567 for alias in node.names:
568 name = alias.asname or alias.name
569 importation = Importation(name, node)
570 self.addBinding(node.lineno, importation)
571
572 def IMPORTFROM(self, node):
573 if node.module == '__future__':
574 if not self.futuresAllowed:
575 self.report(messages.LateFutureImport, node.lineno,
576 [n.name for n in node.names])
577 else:
578 self.futuresAllowed = False
579
580 for alias in node.names:
581 if alias.name == '*':
582 self.scope.importStarred = True
583 self.report(messages.ImportStarUsed, node.lineno, node.module)
584 continue
585 name = alias.asname or alias.name
586 importation = Importation(name, node)
587 if node.module == '__future__':
588 importation.used = (self.scope, node.lineno)
589 self.addBinding(node.lineno, importation)
590
591 def CALL(self, node):
592 self.handleNode(node.func, node)
593 for arg in node.args:
594 self.handleNode(arg, node)
595 for kw in node.keywords:
596 self.handleNode(kw, node)
597 node.starargs and self.handleNode(node.starargs, node)
598 node.kwargs and self.handleNode(node.kwargs, node)
599
600 def KEYWORD(self, node):
601 self.handleNode(node.value, node)
602
603 def BOOLOP(self, node):
604 for val in node.values:
605 self.handleNode(val, node)
606
607 def BINOP(self, node):
608 self.handleNode(node.left, node)
609 self.handleNode(node.right, node)
610
611 def UNARYOP(self, node):
612 self.handleNode(node.operand, node)
613
614 def RETURN(self, node):
615 node.value and self.handleNode(node.value, node)
616
617 def DELETE(self, node):
618 for tgt in node.targets:
619 self.handleNode(tgt, node)
620
621 def EXPR(self, node):
622 self.handleNode(node.value, node)
623
624 def ATTRIBUTE(self, node):
625 self.handleNode(node.value, node)
626
627 def IF(self, node):
628 self.handleNode(node.test, node)
629 self.handleBody(node)
630 for stmt in node.orelse:
631 self.handleNode(stmt, node)
632
633 WHILE = IF
634
635 def RAISE(self, node):
636 node.exc and self.handleNode(node.exc, node)
637 node.cause and self.handleNode(node.cause, node)
638
639 def TRYEXCEPT(self, node):
640 self.handleBody(node)
641 for handler in node.handlers:
642 self.handleNode(handler, node)
643 for stmt in node.orelse:
644 self.handleNode(stmt, node)
645
646 def TRYFINALLY(self, node):
647 self.handleBody(node)
648 for stmt in node.finalbody:
649 self.handleNode(stmt, node)
650
651 def EXCEPTHANDLER(self, node):
652 node.type and self.handleNode(node.type, node)
653 if node.name:
654 node.id = node.name
655 self.handleAssignName(node)
656 self.handleBody(node)
657
658 def ASSERT(self, node):
659 self.handleNode(node.test, node)
660 node.msg and self.handleNode(node.msg, node)
661
662 def COMPARE(self, node):
663 self.handleNode(node.left, node)
664 for comparator in node.comparators:
665 self.handleNode(comparator, node)
666
667 def YIELD(self, node):
668 node.value and self.handleNode(node.value, node)
669
670 def SUBSCRIPT(self, node):
671 self.handleNode(node.value, node)
672 self.handleNode(node.slice, node)
673
674 def SLICE(self, node):
675 node.lower and self.handleNode(node.lower, node)
676 node.upper and self.handleNode(node.upper, node)
677 node.step and self.handleNode(node.step, node)
678
679 def EXTSLICE(self, node):
680 for slice in node.dims:
681 self.handleNode(slice, node)
682
683 def INDEX(self, node):
684 self.handleNode(node.value, node)
685
686 def IFEXP(self, node):
687 self.handleNode(node.test, node)
688 self.handleNode(node.body, node)
689 self.handleNode(node.orelse, node)
690
691 def STARRED(self, node):
692 self.handleNode(node.value, node)

eric ide

mercurial