UtilitiesPython2/py2flakes/checker.py

branch
Py2 comp.
changeset 2571
e6bb19eb87ea
parent 2552
1e893ea4e366
child 2572
dc6d76ab5d10
equal deleted inserted replaced
2552:1e893ea4e366 2571:e6bb19eb87ea
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

eric ide

mercurial