UtilitiesPython2/py2flakes/checker.py

changeset 1535
f2650ea19bb8
parent 1509
c0b5e693b0eb
child 1544
d06ffad32855
equal deleted inserted replaced
1534:e5b76a5eda84 1535:f2650ea19bb8
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (c) 2011 - 2012 Detlev Offenbach <detlev@die-offenbachs.de> 3 # Copyright (c) 2011 - 2012 Detlev Offenbach <detlev@die-offenbachs.de>
4 # 4 #
5 # Original (c) 2005-2008 Divmod, Inc. 5 # Original (c) 2005-2010 Divmod, Inc.
6 # 6 #
7 # This module is based on pyflakes for Python2 but was heavily hacked to 7 # This module is based on pyflakes for Python2 but was heavily hacked to
8 # work within eric5 8 # work within eric5
9 9
10 import __builtin__ 10 import __builtin__
11 import os.path 11 import os.path
12 import compiler 12 import _ast
13 from compiler import ast
14 13
15 from py2flakes import messages 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
16 35
17 36
18 class Binding(object): 37 class Binding(object):
19 """ 38 """
20 Represents the binding of a value to a name. 39 Represents the binding of a value to a name.
95 def names(self): 114 def names(self):
96 """ 115 """
97 Return a list of the names referenced by this binding. 116 Return a list of the names referenced by this binding.
98 """ 117 """
99 names = [] 118 names = []
100 if isinstance(self.source, ast.List): 119 if isinstance(self.source, _ast.List):
101 for node in self.source.nodes: 120 for node in self.source.elts:
102 if isinstance(node, ast.Const): 121 if isinstance(node, _ast.Str):
103 names.append(node.value) 122 names.append(node.s)
104 return names 123 return names
105 124
106 125
107 class Scope(dict): 126 class Scope(dict):
108 """ 127 """
109 Class defining the scope base class. 128 Class defining the scope base class.
110 """ 129 """
111 importStarred = False # set to True when import * is found 130 importStarred = False # set to True when import * is found
112 131
113 def __repr__(self): 132 def __repr__(self):
114 return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) 133 return '<%s at 0x%x %s>' % (
134 self.__class__.__name__, id(self), dict.__repr__(self))
115 135
116 def __init__(self): 136 def __init__(self):
117 super(Scope, self).__init__() 137 super(Scope, self).__init__()
118 138
119 139
164 self.filename = filename 184 self.filename = filename
165 self.scopeStack = [ModuleScope()] 185 self.scopeStack = [ModuleScope()]
166 self.futuresAllowed = True 186 self.futuresAllowed = True
167 187
168 if isinstance(module, str): 188 if isinstance(module, str):
169 module = compiler.parse(module) 189 module = ast.parse(module, filename, "exec")
170 self.handleChildren(module) 190 self.handleChildren(module)
171 self._runDeferred(self._deferredFunctions) 191 self._runDeferred(self._deferredFunctions)
172 # Set _deferredFunctions to None so that deferFunction will fail 192 # Set _deferredFunctions to None so that deferFunction will fail
173 # noisily if called after we've run through the deferred functions. 193 # noisily if called after we've run through the deferred functions.
174 self._deferredFunctions = None 194 self._deferredFunctions = None
250 270
251 def report(self, messageClass, *args, **kwargs): 271 def report(self, messageClass, *args, **kwargs):
252 self.messages.append(messageClass(self.filename, *args, **kwargs)) 272 self.messages.append(messageClass(self.filename, *args, **kwargs))
253 273
254 def handleChildren(self, tree): 274 def handleChildren(self, tree):
255 for node in tree.getChildNodes(): 275 for node in iter_child_nodes(tree):
256 self.handleNode(node, 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))
257 286
258 def handleNode(self, node, parent): 287 def handleNode(self, node, parent):
259 node.parent = parent 288 node.parent = parent
260 if self.traceTree: 289 if self.traceTree:
261 print ' ' * self.nodeDepth + node.__class__.__name__ 290 print ' ' * self.nodeDepth + node.__class__.__name__
262 self.nodeDepth += 1 291 self.nodeDepth += 1
292 if self.futuresAllowed and not \
293 (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
294 self.futuresAllowed = False
263 nodeType = node.__class__.__name__.upper() 295 nodeType = node.__class__.__name__.upper()
264 if nodeType not in ('STMT', 'FROM'):
265 self.futuresAllowed = False
266 try: 296 try:
267 handler = getattr(self, nodeType) 297 handler = getattr(self, nodeType)
268 handler(node) 298 handler(node)
269 finally: 299 finally:
270 self.nodeDepth -= 1 300 self.nodeDepth -= 1
272 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ 302 print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__
273 303
274 def ignore(self, node): 304 def ignore(self, node):
275 pass 305 pass
276 306
277 STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \ 307 # "stmt" type nodes
278 ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = \ 308 RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
279 RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \ 309 TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
280 SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \ 310
281 RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \ 311 CONTINUE = BREAK = PASS = ignore
282 FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \ 312
283 AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = \ 313 # "expr" type nodes
284 IFEXP = handleChildren 314 BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \
285 315 CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren
286 CONST = PASS = CONTINUE = BREAK = ELLIPSIS = ignore 316
317 NUM = STR = ELLIPSIS = ignore
318
319 # "slice" type nodes
320 SLICE = EXTSLICE = INDEX = handleChildren
321
322 # expression contexts are node instances too, though being constants
323 LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore
324
325 # same for operators
326 AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \
327 BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \
328 EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
329
330 # additional node types
331 COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
287 332
288 def addBinding(self, lineno, value, reportRedef=True): 333 def addBinding(self, lineno, value, reportRedef=True):
289 ''' 334 '''
290 Called when a binding is altered. 335 Called when a binding is altered.
291 336
302 347
303 if not isinstance(self.scope, ClassScope): 348 if not isinstance(self.scope, ClassScope):
304 for scope in self.scopeStack[::-1]: 349 for scope in self.scopeStack[::-1]:
305 existing = scope.get(value.name) 350 existing = scope.get(value.name)
306 if (isinstance(existing, Importation) 351 if (isinstance(existing, Importation)
307 and not existing.used 352 and not existing.used
308 and (not isinstance(value, Importation) or value.fullName == existing.fullName) 353 and (not isinstance(value, Importation) or
309 and reportRedef): 354 value.fullName == existing.fullName)
355 and reportRedef):
310 356
311 self.report(messages.RedefinedWhileUnused, 357 self.report(messages.RedefinedWhileUnused,
312 lineno, value.name, scope[value.name].source.lineno) 358 lineno, value.name, scope[value.name].source.lineno)
313 359
314 if isinstance(value, UnBinding): 360 if isinstance(value, UnBinding):
317 except KeyError: 363 except KeyError:
318 self.report(messages.UndefinedName, lineno, value.name) 364 self.report(messages.UndefinedName, lineno, value.name)
319 else: 365 else:
320 self.scope[value.name] = value 366 self.scope[value.name] = value
321 367
322 def WITH(self, node):
323 """
324 Handle 'with' by checking the target of the statement (which can be an
325 identifier, a list or tuple of targets, an attribute, etc) for
326 undefined names and defining any it adds to the scope and by continuing
327 to process the suite within the statement.
328 """
329 # Check the "foo" part of a "with foo as bar" statement. Do this no
330 # matter what, since there's always a "foo" part.
331 self.handleNode(node.expr, node)
332
333 if node.vars is not None:
334 self.handleNode(node.vars, node)
335
336 self.handleChildren(node.body)
337
338 def GLOBAL(self, node): 368 def GLOBAL(self, node):
339 """ 369 """
340 Keep track of globals declarations. 370 Keep track of globals declarations.
341 """ 371 """
342 if isinstance(self.scope, FunctionScope): 372 if isinstance(self.scope, FunctionScope):
343 self.scope.globals.update(dict.fromkeys(node.names)) 373 self.scope.globals.update(dict.fromkeys(node.names))
344 374
345 def LISTCOMP(self, node): 375 def LISTCOMP(self, node):
346 for qual in node.quals: 376 # handle generators before element
347 self.handleNode(qual, node) 377 for gen in node.generators:
348 self.handleNode(node.expr, node) 378 self.handleNode(gen, node)
349 379 self.handleNode(node.elt, node)
350 GENEXPRINNER = LISTCOMP 380
381 GENERATOREXP = SETCOMP = LISTCOMP
382
383 # dictionary comprehensions; introduced in Python 2.7
384 def DICTCOMP(self, node):
385 for gen in node.generators:
386 self.handleNode(gen, node)
387 self.handleNode(node.key, node)
388 self.handleNode(node.value, node)
351 389
352 def FOR(self, node): 390 def FOR(self, node):
353 """ 391 """
354 Process bindings for loop variables. 392 Process bindings for loop variables.
355 """ 393 """
356 vars = [] 394 vars = []
357 395
358 def collectLoopVars(n): 396 def collectLoopVars(n):
359 if hasattr(n, 'name'): 397 if isinstance(n, _ast.Name):
360 vars.append(n.name) 398 vars.append(n.id)
399 elif isinstance(n, _ast.expr_context):
400 return
361 else: 401 else:
362 for c in n.getChildNodes(): 402 for c in iter_child_nodes(n):
363 collectLoopVars(c) 403 collectLoopVars(c)
364 404
365 collectLoopVars(node.assign) 405 collectLoopVars(node.target)
366 for varn in vars: 406 for varn in vars:
367 if (isinstance(self.scope.get(varn), Importation) 407 if (isinstance(self.scope.get(varn), Importation)
368 # unused ones will get an unused import warning 408 # unused ones will get an unused import warning
369 and self.scope[varn].used): 409 and self.scope[varn].used):
370 self.report(messages.ImportShadowedByLoopVar, 410 self.report(messages.ImportShadowedByLoopVar,
374 414
375 def NAME(self, node): 415 def NAME(self, node):
376 """ 416 """
377 Locate the name in locals / function / globals scopes. 417 Locate the name in locals / function / globals scopes.
378 """ 418 """
379 # try local scope 419 if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)):
380 importStarred = self.scope.importStarred 420 # try local scope
381 try: 421 importStarred = self.scope.importStarred
382 self.scope[node.name].used = (self.scope, node.lineno)
383 except KeyError:
384 pass
385 else:
386 return
387
388 # try enclosing function scopes
389
390 for scope in self.scopeStack[-2:0:-1]:
391 importStarred = importStarred or scope.importStarred
392 if not isinstance(scope, FunctionScope):
393 continue
394 try: 422 try:
395 scope[node.name].used = (self.scope, node.lineno) 423 self.scope[node.id].used = (self.scope, node.lineno)
396 except KeyError: 424 except KeyError:
397 pass 425 pass
398 else: 426 else:
399 return 427 return
400 428
401 # try global scope 429 # try enclosing function scopes
402 430
403 importStarred = importStarred or self.scopeStack[0].importStarred 431 for scope in self.scopeStack[-2:0:-1]:
404 try: 432 importStarred = importStarred or scope.importStarred
405 self.scopeStack[0][node.name].used = (self.scope, node.lineno) 433 if not isinstance(scope, FunctionScope):
406 except KeyError: 434 continue
407 if ((not hasattr(__builtin__, node.name)) 435 try:
408 and node.name not in _MAGIC_GLOBALS 436 scope[node.id].used = (self.scope, node.lineno)
409 and not importStarred): 437 except KeyError:
410 if (os.path.basename(self.filename) == '__init__.py' and
411 node.name == '__path__'):
412 # the special name __path__ is valid only in packages
413 pass 438 pass
414 else: 439 else:
415 self.report(messages.UndefinedName, node.lineno, node.name) 440 return
416 441
417 def FUNCTION(self, node): 442 # try global scope
418 if getattr(node, "decorators", None) is not None: 443
419 self.handleChildren(node.decorators) 444 importStarred = importStarred or self.scopeStack[0].importStarred
445 try:
446 self.scopeStack[0][node.id].used = (self.scope, node.lineno)
447 except KeyError:
448 if ((not hasattr(__builtin__, node.id))
449 and node.id not in _MAGIC_GLOBALS
450 and not importStarred):
451 if (os.path.basename(self.filename) == '__init__.py' and
452 node.id == '__path__'):
453 # the special name __path__ is valid only in packages
454 pass
455 else:
456 self.report(messages.UndefinedName, node.lineno, node.id)
457 elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)):
458 # if the name hasn't already been defined in the current scope
459 if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
460 # for each function or module scope above us
461 for scope in self.scopeStack[:-1]:
462 if not isinstance(scope, (FunctionScope, ModuleScope)):
463 continue
464 # if the name was defined in that scope, and the name has
465 # been accessed already in the current scope, and hasn't
466 # been declared global
467 if (node.id in scope
468 and scope[node.id].used
469 and scope[node.id].used[0] is self.scope
470 and node.id not in self.scope.globals):
471 # then it's probably a mistake
472 self.report(messages.UndefinedLocal,
473 scope[node.id].used[1],
474 node.id,
475 scope[node.id].source.lineno)
476 break
477
478 if isinstance(node.parent,
479 (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)):
480 binding = Binding(node.id, node)
481 elif (node.id == '__all__' and
482 isinstance(self.scope, ModuleScope)):
483 binding = ExportBinding(node.id, node.parent.value)
484 else:
485 binding = Assignment(node.id, node)
486 if node.id in self.scope:
487 binding.used = self.scope[node.id].used
488 self.addBinding(node.lineno, binding)
489 elif isinstance(node.ctx, _ast.Del):
490 if isinstance(self.scope, FunctionScope) and \
491 node.id in self.scope.globals:
492 del self.scope.globals[node.id]
493 else:
494 self.addBinding(node.lineno, UnBinding(node.id, node))
495 else:
496 # must be a Param context -- this only happens for names in function
497 # arguments, but these aren't dispatched through here
498 raise RuntimeError(
499 "Got impossible expression context: %r" % (node.ctx,))
500
501 def FUNCTIONDEF(self, node):
502 if hasattr(node, 'decorators'):
503 for deco in node.decorators:
504 self.handleNode(deco, node)
505 else:
506 for deco in node.decorator_list:
507 self.handleNode(deco, node)
420 self.addBinding(node.lineno, FunctionDefinition(node.name, node)) 508 self.addBinding(node.lineno, FunctionDefinition(node.name, node))
421 self.LAMBDA(node) 509 self.LAMBDA(node)
422 510
423 def LAMBDA(self, node): 511 def LAMBDA(self, node):
424 for default in node.defaults: 512 for default in node.args.defaults:
425 self.handleNode(default, node) 513 self.handleNode(default, node)
426 514
427 def runFunction(): 515 def runFunction():
428 args = [] 516 args = []
429 517
430 def addArgs(arglist): 518 def addArgs(arglist):
431 for arg in arglist: 519 for arg in arglist:
432 if isinstance(arg, tuple): 520 if isinstance(arg, _ast.Tuple):
433 addArgs(arg) 521 addArgs(arg.elts)
434 else: 522 else:
435 if arg in args: 523 if arg.id in args:
436 self.report(messages.DuplicateArgument, node.lineno, arg) 524 self.report(messages.DuplicateArgument, node.lineno, arg.id)
437 args.append(arg) 525 args.append(arg.id)
438 526
439 self.pushFunctionScope() 527 self.pushFunctionScope()
440 addArgs(node.argnames) 528 addArgs(node.args.args)
529 # vararg/kwarg identifiers are not Name nodes
530 if node.args.vararg:
531 args.append(node.args.vararg)
532 if node.args.kwarg:
533 args.append(node.args.kwarg)
441 for name in args: 534 for name in args:
442 self.addBinding(node.lineno, Argument(name, node), reportRedef=False) 535 self.addBinding(node.lineno, Argument(name, node), reportRedef=False)
443 self.handleNode(node.code, node) 536 if isinstance(node.body, list):
537 # case for FunctionDefs
538 for stmt in node.body:
539 self.handleNode(stmt, node)
540 else:
541 # case for Lambdas
542 self.handleNode(node.body, node)
444 543
445 def checkUnusedAssignments(): 544 def checkUnusedAssignments():
446 """ 545 """
447 Check to see if any assignments have not been used. 546 Check to see if any assignments have not been used.
448 """ 547 """
454 self.deferAssignment(checkUnusedAssignments) 553 self.deferAssignment(checkUnusedAssignments)
455 self.popScope() 554 self.popScope()
456 555
457 self.deferFunction(runFunction) 556 self.deferFunction(runFunction)
458 557
459 def CLASS(self, node): 558 def CLASSDEF(self, node):
460 """ 559 """
461 Check names used in a class definition, including its decorators, base 560 Check names used in a class definition, including its decorators, base
462 classes, and the body of its definition. Additionally, add its name to 561 classes, and the body of its definition. Additionally, add its name to
463 the current scope. 562 the current scope.
464 """ 563 """
465 if getattr(node, "decorators", None) is not None: 564 # decorator_list is present as of Python 2.6
466 self.handleChildren(node.decorators) 565 for deco in getattr(node, 'decorator_list', []):
566 self.handleNode(deco, node)
467 for baseNode in node.bases: 567 for baseNode in node.bases:
468 self.handleNode(baseNode, node) 568 self.handleNode(baseNode, node)
569 self.pushClassScope()
570 for stmt in node.body:
571 self.handleNode(stmt, node)
572 self.popScope()
469 self.addBinding(node.lineno, Binding(node.name, node)) 573 self.addBinding(node.lineno, Binding(node.name, node))
470 self.pushClassScope()
471 self.handleChildren(node.code)
472 self.popScope()
473
474 def ASSNAME(self, node):
475 if node.flags == 'OP_DELETE':
476 if isinstance(self.scope, FunctionScope) and node.name in self.scope.globals:
477 del self.scope.globals[node.name]
478 else:
479 self.addBinding(node.lineno, UnBinding(node.name, node))
480 else:
481 # if the name hasn't already been defined in the current scope
482 if isinstance(self.scope, FunctionScope) and node.name not in self.scope:
483 # for each function or module scope above us
484 for scope in self.scopeStack[:-1]:
485 if not isinstance(scope, (FunctionScope, ModuleScope)):
486 continue
487 # if the name was defined in that scope, and the name has
488 # been accessed already in the current scope, and hasn't
489 # been declared global
490 if (node.name in scope
491 and scope[node.name].used
492 and scope[node.name].used[0] is self.scope
493 and node.name not in self.scope.globals):
494 # then it's probably a mistake
495 self.report(messages.UndefinedLocal,
496 scope[node.name].used[1],
497 node.name,
498 scope[node.name].source.lineno)
499 break
500
501 if isinstance(node.parent,
502 (ast.For, ast.ListCompFor, ast.GenExprFor,
503 ast.AssTuple, ast.AssList)):
504 binding = Binding(node.name, node)
505 elif (node.name == '__all__' and
506 isinstance(self.scope, ModuleScope) and
507 isinstance(node.parent, ast.Assign)):
508 binding = ExportBinding(node.name, node.parent.expr)
509 else:
510 binding = Assignment(node.name, node)
511 if node.name in self.scope:
512 binding.used = self.scope[node.name].used
513 self.addBinding(node.lineno, binding)
514 574
515 def ASSIGN(self, node): 575 def ASSIGN(self, node):
516 self.handleNode(node.expr, node) 576 self.handleNode(node.value, node)
517 for subnode in node.nodes[::-1]: 577 for target in node.targets:
518 self.handleNode(subnode, node) 578 self.handleNode(target, node)
579
580 def AUGASSIGN(self, node):
581 # AugAssign is awkward: must set the context explicitly and visit twice,
582 # once with AugLoad context, once with AugStore context
583 node.target.ctx = _ast.AugLoad()
584 self.handleNode(node.target, node)
585 self.handleNode(node.value, node)
586 node.target.ctx = _ast.AugStore()
587 self.handleNode(node.target, node)
519 588
520 def IMPORT(self, node): 589 def IMPORT(self, node):
521 for name, alias in node.names: 590 for alias in node.names:
522 name = alias or name 591 name = alias.asname or alias.name
523 importation = Importation(name, node) 592 importation = Importation(name, node)
524 self.addBinding(node.lineno, importation) 593 self.addBinding(node.lineno, importation)
525 594
526 def FROM(self, node): 595 def IMPORTFROM(self, node):
527 if node.modname == '__future__': 596 if node.module == '__future__':
528 if not self.futuresAllowed: 597 if not self.futuresAllowed:
529 self.report(messages.LateFutureImport, node.lineno, [n[0] for n in node.names]) 598 self.report(messages.LateFutureImport, node.lineno,
599 [n.name for n in node.names])
530 else: 600 else:
531 self.futuresAllowed = False 601 self.futuresAllowed = False
532 602
533 for name, alias in node.names: 603 for alias in node.names:
534 if name == '*': 604 if alias.name == '*':
535 self.scope.importStarred = True 605 self.scope.importStarred = True
536 self.report(messages.ImportStarUsed, node.lineno, node.modname) 606 self.report(messages.ImportStarUsed, node.lineno, node.module)
537 continue 607 continue
538 name = alias or name 608 name = alias.asname or alias.name
539 importation = Importation(name, node) 609 importation = Importation(name, node)
540 if node.modname == '__future__': 610 if node.module == '__future__':
541 importation.used = (self.scope, node.lineno) 611 importation.used = (self.scope, node.lineno)
542 self.addBinding(node.lineno, importation) 612 self.addBinding(node.lineno, importation)
543 613
544 # 614 #
545 # eflag: FileType = Python2 615 # eflag: FileType = Python2

eric ide

mercurial