Utilities/py3flakes/checker.py

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

eric ide

mercurial