242 |
243 |
243 def handleBody(self, tree): |
244 def handleBody(self, tree): |
244 for node in tree.body: |
245 for node in tree.body: |
245 self.handleNode(node, tree) |
246 self.handleNode(node, tree) |
246 |
247 |
|
248 def handleChildren(self, tree): |
|
249 for node in ast.iter_child_nodes(tree): |
|
250 self.handleNode(node, tree) |
|
251 |
|
252 def isDocstring(self, node): |
|
253 """ |
|
254 Determine if the given node is a docstring, as long as it is at the |
|
255 correct place in the node tree. |
|
256 """ |
|
257 return isinstance(node, ast.Str) or \ |
|
258 (isinstance(node, ast.Expr) and |
|
259 isinstance(node.value, ast.Str)) |
|
260 |
247 def handleNode(self, node, parent): |
261 def handleNode(self, node, parent): |
248 if node: |
262 if node: |
249 node.parent = parent |
263 node.parent = parent |
250 if self.traceTree: |
264 if self.traceTree: |
251 print(' ' * self.nodeDepth + node.__class__.__name__) |
265 print(' ' * self.nodeDepth + node.__class__.__name__) |
252 self.nodeDepth += 1 |
266 self.nodeDepth += 1 |
|
267 if self.futuresAllowed and not \ |
|
268 (isinstance(node, ast.ImportFrom) or self.isDocstring(node)): |
|
269 self.futuresAllowed = False |
253 nodeType = node.__class__.__name__.upper() |
270 nodeType = node.__class__.__name__.upper() |
254 try: |
271 try: |
255 handler = getattr(self, nodeType) |
272 handler = getattr(self, nodeType) |
256 handler(node) |
273 handler(node) |
257 finally: |
274 finally: |
262 def ignore(self, node): |
279 def ignore(self, node): |
263 pass |
280 pass |
264 |
281 |
265 # ast nodes to be ignored |
282 # ast nodes to be ignored |
266 PASS = CONTINUE = BREAK = ELLIPSIS = NUM = STR = BYTES = \ |
283 PASS = CONTINUE = BREAK = ELLIPSIS = NUM = STR = BYTES = \ |
|
284 LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = \ |
267 ATTRIBUTES = AND = OR = ADD = SUB = MULT = DIV = \ |
285 ATTRIBUTES = AND = OR = ADD = SUB = MULT = DIV = \ |
268 MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = \ |
286 MOD = POW = LSHIFT = RSHIFT = BITOR = BITXOR = BITAND = FLOORDIV = \ |
269 INVERT = NOT = UADD = USUB = INVERT = NOT = UADD = USUB = ignore |
287 INVERT = NOT = UADD = USUB = EQ = NOTEQ = LT = LTE = GT = GTE = IS = \ |
270 |
288 ISNOT = IN = NOTIN = ignore |
|
289 |
|
290 # "stmt" type nodes |
|
291 RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ |
|
292 TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren |
|
293 |
|
294 # "expr" type nodes |
|
295 BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ |
|
296 CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren |
|
297 |
|
298 # "slice" type nodes |
|
299 SLICE = EXTSLICE = INDEX = handleChildren |
|
300 |
|
301 # additional node types |
|
302 COMPREHENSION = KEYWORD = handleChildren |
|
303 |
271 def addBinding(self, lineno, value, reportRedef = True): |
304 def addBinding(self, lineno, value, reportRedef = True): |
272 '''Called when a binding is altered. |
305 '''Called when a binding is altered. |
273 |
306 |
274 @param lineno line of the statement responsible for the change (integer) |
307 @param lineno line of the statement responsible for the change (integer) |
275 @param value the optional new value, a Binding instance, associated |
308 @param value the optional new value, a Binding instance, associated |
283 lineno, value.name, self.scope[value.name].source.lineno) |
316 lineno, value.name, self.scope[value.name].source.lineno) |
284 |
317 |
285 if not isinstance(self.scope, ClassScope): |
318 if not isinstance(self.scope, ClassScope): |
286 for scope in self.scopeStack[::-1]: |
319 for scope in self.scopeStack[::-1]: |
287 existing = scope.get(value.name) |
320 existing = scope.get(value.name) |
288 if (isinstance(existing, Importation) |
321 if isinstance(existing, Importation) and \ |
289 and not existing.used |
322 not existing.used and \ |
290 and (not isinstance(value, Importation) or value.fullName == existing.fullName) |
323 not isinstance(value, UnBinding) and \ |
291 and reportRedef): |
324 (not isinstance(value, Importation) or \ |
292 |
325 value.fullName == existing.fullName) and \ |
|
326 reportRedef: |
293 self.report(messages.RedefinedWhileUnused, |
327 self.report(messages.RedefinedWhileUnused, |
294 lineno, value.name, scope[value.name].source.lineno) |
328 lineno, value.name, scope[value.name].source.lineno) |
295 |
329 |
296 if isinstance(value, UnBinding): |
330 if isinstance(value, UnBinding): |
297 try: |
331 try: |
303 |
337 |
304 ############################################################ |
338 ############################################################ |
305 ## individual handler methods below |
339 ## individual handler methods below |
306 ############################################################ |
340 ############################################################ |
307 |
341 |
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 arg = None |
|
332 if node.optional_vars is not None: |
|
333 arg = Argument(node.optional_vars.id, node) |
|
334 self.addBinding(node.lineno, arg, reportRedef=False) |
|
335 self.handleBody(node) |
|
336 if arg: |
|
337 del self.scope[arg.name] |
|
338 |
|
339 def GLOBAL(self, node): |
342 def GLOBAL(self, node): |
340 """ |
343 """ |
341 Keep track of globals declarations. |
344 Keep track of globals declarations. |
342 """ |
345 """ |
343 if isinstance(self.scope, FunctionScope): |
346 if isinstance(self.scope, FunctionScope): |
356 for generator in node.generators: |
359 for generator in node.generators: |
357 self.handleNode(generator, node) |
360 self.handleNode(generator, node) |
358 self.handleNode(node.key, node) |
361 self.handleNode(node.key, node) |
359 self.handleNode(node.value, node) |
362 self.handleNode(node.value, node) |
360 |
363 |
361 def COMPREHENSION(self, node): |
|
362 node.target.parent = node |
|
363 self.handleAssignName(node.target) |
|
364 self.handleNode(node.iter, node) |
|
365 for elt in node.ifs: |
|
366 self.handleNode(elt, node) |
|
367 |
|
368 def FOR(self, node): |
364 def FOR(self, node): |
369 """ |
365 """ |
370 Process bindings for loop variables. |
366 Process bindings for loop variables. |
371 """ |
367 """ |
372 vars = [] |
368 vars = [] |
373 def collectLoopVars(n): |
369 def collectLoopVars(n): |
374 if isinstance(n, ast.Name): |
370 if isinstance(n, ast.Name): |
375 vars.append(n.id) |
371 vars.append(n.id) |
|
372 elif isinstance(n, ast.expr_context): |
|
373 return |
|
374 else: |
|
375 for c in ast.iter_child_nodes(n): |
|
376 collectLoopVars(c) |
376 |
377 |
377 collectLoopVars(node.target) |
378 collectLoopVars(node.target) |
378 for varn in vars: |
379 for varn in vars: |
379 if (isinstance(self.scope.get(varn), Importation) |
380 if (isinstance(self.scope.get(varn), Importation) |
380 # unused ones will get an unused import warning |
381 # unused ones will get an unused import warning |
381 and self.scope[varn].used): |
382 and self.scope[varn].used): |
382 self.report(messages.ImportShadowedByLoopVar, |
383 self.report(messages.ImportShadowedByLoopVar, |
383 node.lineno, varn, self.scope[varn].source.lineno) |
384 node.lineno, varn, self.scope[varn].source.lineno) |
384 |
385 |
385 node.target.parent = node |
386 self.handleChildren(node) |
386 self.handleAssignName(node.target) |
|
387 self.handleNode(node.iter, node) |
|
388 self.handleBody(node) |
|
389 |
387 |
390 def NAME(self, node): |
388 def NAME(self, node): |
391 """ |
389 """ |
392 Locate the name in locals / function / globals scopes. |
390 Handle occurrence of Name (which can be a load/store/delete access.) |
393 """ |
391 """ |
394 # try local scope |
392 # Locate the name in locals / function / globals scopes. |
395 importStarred = self.scope.importStarred |
393 if isinstance(node.ctx, (ast.Load, ast.AugLoad)): |
396 try: |
394 # try local scope |
397 self.scope[node.id].used = (self.scope, node.lineno) |
395 importStarred = self.scope.importStarred |
398 except KeyError: |
|
399 pass |
|
400 else: |
|
401 return |
|
402 |
|
403 # try enclosing function scopes |
|
404 for scope in self.scopeStack[-2:0:-1]: |
|
405 importStarred = importStarred or scope.importStarred |
|
406 if not isinstance(scope, FunctionScope): |
|
407 continue |
|
408 try: |
396 try: |
409 scope[node.id].used = (self.scope, node.lineno) |
397 self.scope[node.id].used = (self.scope, node.lineno) |
410 except KeyError: |
398 except KeyError: |
411 pass |
399 pass |
412 else: |
400 else: |
413 return |
401 return |
414 |
402 |
415 # try global scope |
403 # try enclosing function scopes |
416 importStarred = importStarred or self.scopeStack[0].importStarred |
404 for scope in self.scopeStack[-2:0:-1]: |
417 try: |
405 importStarred = importStarred or scope.importStarred |
418 self.scopeStack[0][node.id].used = (self.scope, node.lineno) |
406 if not isinstance(scope, FunctionScope): |
419 except KeyError: |
407 continue |
420 if ((not hasattr(builtins, node.id)) |
408 try: |
421 and node.id not in _MAGIC_GLOBALS |
409 scope[node.id].used = (self.scope, node.lineno) |
422 and not importStarred): |
410 except KeyError: |
423 if (os.path.basename(self.filename) == '__init__.py' and |
|
424 node.id == '__path__'): |
|
425 # the special name __path__ is valid only in packages |
|
426 pass |
411 pass |
427 else: |
412 else: |
428 self.report(messages.UndefinedName, node.lineno, node.id) |
413 return |
|
414 |
|
415 # try global scope |
|
416 importStarred = importStarred or self.scopeStack[0].importStarred |
|
417 try: |
|
418 self.scopeStack[0][node.id].used = (self.scope, node.lineno) |
|
419 except KeyError: |
|
420 if ((not hasattr(builtins, node.id)) |
|
421 and node.id not in _MAGIC_GLOBALS |
|
422 and not importStarred): |
|
423 if (os.path.basename(self.filename) == '__init__.py' and |
|
424 node.id == '__path__'): |
|
425 # the special name __path__ is valid only in packages |
|
426 pass |
|
427 else: |
|
428 self.report(messages.UndefinedName, node.lineno, node.id) |
|
429 elif isinstance(node.ctx, (ast.Store, ast.AugStore)): |
|
430 # if the name hasn't already been defined in the current scope |
|
431 if isinstance(self.scope, FunctionScope) and node.id not in self.scope: |
|
432 # for each function or module scope above us |
|
433 for scope in self.scopeStack[:-1]: |
|
434 if not isinstance(scope, (FunctionScope, ModuleScope)): |
|
435 continue |
|
436 # if the name was defined in that scope, and the name has |
|
437 # been accessed already in the current scope, and hasn't |
|
438 # been declared global |
|
439 if (node.id in scope |
|
440 and scope[node.id].used |
|
441 and scope[node.id].used[0] is self.scope |
|
442 and node.id not in self.scope.globals): |
|
443 # then it's probably a mistake |
|
444 self.report(messages.UndefinedLocal, |
|
445 scope[node.id].used[1], |
|
446 node.id, |
|
447 scope[node.id].source.lineno) |
|
448 break |
|
449 |
|
450 if isinstance(node.parent, |
|
451 (ast.For, ast.comprehension, ast.Tuple, ast.List)): |
|
452 binding = Binding(node.id, node) |
|
453 elif (node.id == '__all__' and |
|
454 isinstance(self.scope, ModuleScope)): |
|
455 binding = ExportBinding(node.id, node.parent.value) |
|
456 else: |
|
457 binding = Assignment(node.id, node) |
|
458 if node.id in self.scope: |
|
459 binding.used = self.scope[node.id].used |
|
460 self.addBinding(node.lineno, binding) |
|
461 elif isinstance(node.ctx, ast.Del): |
|
462 if isinstance(self.scope, FunctionScope) and \ |
|
463 node.id in self.scope.globals: |
|
464 del self.scope.globals[node.id] |
|
465 else: |
|
466 self.addBinding(node.lineno, UnBinding(node.id, node)) |
|
467 else: |
|
468 # must be a Param context -- this only happens for names in function |
|
469 # arguments, but these aren't dispatched through here |
|
470 raise RuntimeError( |
|
471 "Got impossible expression context: {0:r}".format(node.ctx,)) |
429 |
472 |
430 def FUNCTIONDEF(self, node): |
473 def FUNCTIONDEF(self, node): |
431 if getattr(node, "decorator_list", None) is not None: |
474 if getattr(node, "decorator_list", None) is not None: |
432 for decorator in node.decorator_list: |
475 for decorator in node.decorator_list: |
433 self.handleNode(decorator, node) |
476 self.handleNode(decorator, node) |
461 binding.source.lineno, name) |
504 binding.source.lineno, name) |
462 |
505 |
463 self.pushFunctionScope() |
506 self.pushFunctionScope() |
464 addArgs(node.args.args) |
507 addArgs(node.args.args) |
465 addArgs(node.args.kwonlyargs) |
508 addArgs(node.args.kwonlyargs) |
|
509 # vararg/kwarg identifiers are not Name nodes |
|
510 if node.args.vararg: |
|
511 args.append(node.args.vararg) |
|
512 if node.args.kwarg: |
|
513 args.append(node.args.kwarg) |
466 for name in args: |
514 for name in args: |
467 self.addBinding(node.lineno, Argument(name, node), reportRedef=False) |
515 self.addBinding(node.lineno, Argument(name, node), reportRedef=False) |
468 if node.args.vararg: |
|
469 self.addBinding(node.lineno, Argument(node.args.vararg, node), |
|
470 reportRedef=False) |
|
471 if node.args.kwarg: |
|
472 self.addBinding(node.lineno, Argument(node.args.kwarg, node), |
|
473 reportRedef=False) |
|
474 if isinstance(node.body, list): |
516 if isinstance(node.body, list): |
475 self.handleBody(node) |
517 self.handleBody(node) |
476 else: |
518 else: |
477 self.handleNode(node.body, node) |
519 self.handleNode(node.body, node) |
478 self.deferAssignment(checkUnusedAssignments) |
520 self.deferAssignment(checkUnusedAssignments) |
553 binding.used = self.scope[node.id].used |
595 binding.used = self.scope[node.id].used |
554 self.addBinding(node.lineno, binding) |
596 self.addBinding(node.lineno, binding) |
555 |
597 |
556 def ASSIGN(self, node): |
598 def ASSIGN(self, node): |
557 self.handleNode(node.value, node) |
599 self.handleNode(node.value, node) |
558 for subnode in node.targets[::-1]: |
600 for target in node.targets: |
559 subnode.parent = node |
601 self.handleNode(target, node) |
560 if isinstance(subnode, ast.Attribute): |
|
561 self.handleNode(subnode.value, subnode) |
|
562 else: |
|
563 self.handleAssignName(subnode) |
|
564 |
602 |
565 def AUGASSIGN(self, node): |
603 def AUGASSIGN(self, node): |
|
604 # AugAssign is awkward: must set the context explicitly and visit twice, |
|
605 # once with AugLoad context, once with AugStore context |
|
606 node.target.ctx = ast.AugLoad() |
|
607 self.handleNode(node.target, node) |
566 self.handleNode(node.value, node) |
608 self.handleNode(node.value, node) |
|
609 node.target.ctx = ast.AugStore() |
567 self.handleNode(node.target, node) |
610 self.handleNode(node.target, node) |
568 |
611 |
569 def IMPORT(self, node): |
612 def IMPORT(self, node): |
570 for alias in node.names: |
613 for alias in node.names: |
571 name = alias.asname or alias.name |
614 name = alias.asname or alias.name |
589 importation = Importation(name, node) |
632 importation = Importation(name, node) |
590 if node.module == '__future__': |
633 if node.module == '__future__': |
591 importation.used = (self.scope, node.lineno) |
634 importation.used = (self.scope, node.lineno) |
592 self.addBinding(node.lineno, importation) |
635 self.addBinding(node.lineno, importation) |
593 |
636 |
594 def CALL(self, node): |
|
595 self.handleNode(node.func, node) |
|
596 for arg in node.args: |
|
597 self.handleNode(arg, node) |
|
598 for kw in node.keywords: |
|
599 self.handleNode(kw, node) |
|
600 node.starargs and self.handleNode(node.starargs, node) |
|
601 node.kwargs and self.handleNode(node.kwargs, node) |
|
602 |
|
603 def KEYWORD(self, node): |
|
604 self.handleNode(node.value, node) |
|
605 |
|
606 def BOOLOP(self, node): |
|
607 for val in node.values: |
|
608 self.handleNode(val, node) |
|
609 |
|
610 def BINOP(self, node): |
|
611 self.handleNode(node.left, node) |
|
612 self.handleNode(node.right, node) |
|
613 |
|
614 def UNARYOP(self, node): |
|
615 self.handleNode(node.operand, node) |
|
616 |
|
617 def RETURN(self, node): |
|
618 node.value and self.handleNode(node.value, node) |
|
619 |
|
620 def DELETE(self, node): |
|
621 for tgt in node.targets: |
|
622 self.handleNode(tgt, node) |
|
623 |
|
624 def EXPR(self, node): |
|
625 self.handleNode(node.value, node) |
|
626 |
|
627 def ATTRIBUTE(self, node): |
|
628 self.handleNode(node.value, node) |
|
629 |
|
630 def IF(self, node): |
|
631 self.handleNode(node.test, node) |
|
632 self.handleBody(node) |
|
633 for stmt in node.orelse: |
|
634 self.handleNode(stmt, node) |
|
635 |
|
636 WHILE = IF |
|
637 |
|
638 def RAISE(self, node): |
|
639 node.exc and self.handleNode(node.exc, node) |
|
640 node.cause and self.handleNode(node.cause, node) |
|
641 |
|
642 def TRYEXCEPT(self, node): |
|
643 self.handleBody(node) |
|
644 for handler in node.handlers: |
|
645 self.handleNode(handler, node) |
|
646 for stmt in node.orelse: |
|
647 self.handleNode(stmt, node) |
|
648 |
|
649 def TRYFINALLY(self, node): |
|
650 self.handleBody(node) |
|
651 for stmt in node.finalbody: |
|
652 self.handleNode(stmt, node) |
|
653 |
|
654 def EXCEPTHANDLER(self, node): |
637 def EXCEPTHANDLER(self, node): |
655 node.type and self.handleNode(node.type, node) |
638 node.type and self.handleNode(node.type, node) |
656 if node.name: |
639 if node.name: |
657 node.id = node.name |
640 node.id = node.name |
658 self.handleAssignName(node) |
641 self.handleAssignName(node) |
659 self.handleBody(node) |
642 self.handleBody(node) |
660 |
643 |
661 def ASSERT(self, node): |
|
662 self.handleNode(node.test, node) |
|
663 node.msg and self.handleNode(node.msg, node) |
|
664 |
|
665 def COMPARE(self, node): |
|
666 self.handleNode(node.left, node) |
|
667 for comparator in node.comparators: |
|
668 self.handleNode(comparator, node) |
|
669 |
|
670 def YIELD(self, node): |
|
671 node.value and self.handleNode(node.value, node) |
|
672 |
|
673 def SUBSCRIPT(self, node): |
|
674 self.handleNode(node.value, node) |
|
675 self.handleNode(node.slice, node) |
|
676 |
|
677 def SLICE(self, node): |
|
678 node.lower and self.handleNode(node.lower, node) |
|
679 node.upper and self.handleNode(node.upper, node) |
|
680 node.step and self.handleNode(node.step, node) |
|
681 |
|
682 def EXTSLICE(self, node): |
|
683 for slice in node.dims: |
|
684 self.handleNode(slice, node) |
|
685 |
|
686 def INDEX(self, node): |
|
687 self.handleNode(node.value, node) |
|
688 |
|
689 def IFEXP(self, node): |
|
690 self.handleNode(node.test, node) |
|
691 self.handleNode(node.body, node) |
|
692 self.handleNode(node.orelse, node) |
|
693 |
|
694 def STARRED(self, node): |
644 def STARRED(self, node): |
695 self.handleNode(node.value, node) |
645 self.handleNode(node.value, node) |