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. |
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 |
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 |