UtilitiesPython2/py2flakes/checker.py

changeset 802
e8882d16384c
child 945
8cd4d08fa9f6
child 1510
e75ecf2bd9dd
equal deleted inserted replaced
801:16f9875e278b 802:e8882d16384c
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

eric ide

mercurial