diff -r f99d60d6b59b -r 2602857055c5 eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/Plugins/CheckerPlugins/SyntaxChecker/pyflakes/checker.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,1701 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# +# Original (c) 2005-2010 Divmod, Inc. +# +# This module is based on pyflakes but was modified to work with eric6 +""" +Main module. + +Implement the central Checker class. +Also, it models the Bindings and Scopes. +""" +import __future__ +import ast +import bisect +import collections +import doctest +import functools +import os +import re +import sys +import tokenize + +from . import messages + +PY2 = sys.version_info < (3, 0) +PY35_PLUS = sys.version_info >= (3, 5) # Python 3.5 and above +PY36_PLUS = sys.version_info >= (3, 6) # Python 3.6 and above +PY38_PLUS = sys.version_info >= (3, 8) +try: + sys.pypy_version_info + PYPY = True +except AttributeError: + PYPY = False + +builtin_vars = dir(__import__('__builtin__' if PY2 else 'builtins')) + +if PY2: + tokenize_tokenize = tokenize.generate_tokens +else: + tokenize_tokenize = tokenize.tokenize + +if PY2: + def getNodeType(node_class): + # workaround str.upper() which is locale-dependent + return str(unicode(node_class.__name__).upper()) # __IGNORE_WARNING__ + + def get_raise_argument(node): + return node.type + +else: + def getNodeType(node_class): + return node_class.__name__.upper() + + def get_raise_argument(node): + return node.exc + + # Silence `pyflakes` from reporting `undefined name 'unicode'` in Python 3. + unicode = str + +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if PY2: + def getAlternatives(n): + if isinstance(n, (ast.If, ast.TryFinally)): + return [n.body] + if isinstance(n, ast.TryExcept): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] +else: + def getAlternatives(n): + if isinstance(n, ast.If): + return [n.body] + if isinstance(n, ast.Try): + return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] + +if PY35_PLUS: + FOR_TYPES = (ast.For, ast.AsyncFor) + LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) +else: + FOR_TYPES = (ast.For,) + LOOP_TYPES = (ast.While, ast.For) + +# https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L102-L104 +TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') +# https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L1400 +TYPE_IGNORE_RE = re.compile(TYPE_COMMENT_RE.pattern + r'ignore\s*(#|$)') +# https://github.com/python/typed_ast/blob/55420396/ast27/Grammar/Grammar#L147 +TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') + + +class _FieldsOrder(dict): + """Fix order of AST node fields.""" + + def _get_fields(self, node_class): + # handle iter before target, and generators before element + fields = node_class._fields + if 'iter' in fields: + key_first = 'iter'.find + elif 'generators' in fields: + key_first = 'generators'.find + else: + key_first = 'value'.find + return tuple(sorted(fields, key=key_first, reverse=True)) + + def __missing__(self, node_class): + self[node_class] = fields = self._get_fields(node_class) + return fields + + +def counter(items): + """ + Simplest required implementation of collections.Counter. Required as 2.6 + does not have Counter in collections. + """ + results = {} + for item in items: + results[item] = results.get(item, 0) + 1 + return results + + +def iter_child_nodes(node, omit=None, _fields_order=_FieldsOrder()): + """ + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. + + :param node: AST node to be iterated upon + :param omit: String or tuple of strings denoting the + attributes of the node to be omitted from + further parsing + :param _fields_order: Order of AST node fields + """ + for name in _fields_order[node.__class__]: + if omit and name in omit: + continue + field = getattr(node, name, None) + if isinstance(field, ast.AST): + yield field + elif isinstance(field, list): + for item in field: + yield item + + +def convert_to_value(item): + if isinstance(item, ast.Str): + return item.s + elif hasattr(ast, 'Bytes') and isinstance(item, ast.Bytes): + return item.s + elif isinstance(item, ast.Tuple): + return tuple(convert_to_value(i) for i in item.elts) + elif isinstance(item, ast.Num): + return item.n + elif isinstance(item, ast.Name): + result = VariableKey(item=item) + constants_lookup = { + 'True': True, + 'False': False, + 'None': None, + } + return constants_lookup.get( + result.name, + result, + ) + elif (not PY2) and isinstance(item, ast.NameConstant): + # None, True, False are nameconstants in python3, but names in 2 + return item.value + else: + return UnhandledKeyType() + + +def is_notimplemented_name_node(node): + return isinstance(node, ast.Name) and getNodeName(node) == 'NotImplemented' + + +class Binding(object): + """ + Represents the binding of a value to a name. + + The checker uses this to keep track of which names have been bound and + which names have not. See L{Assignment} for a special type of binding that + is checked with stricter rules. + + @ivar used: pair of (L{Scope}, node) indicating the scope and + the node that this binding was last used. + """ + + def __init__(self, name, source): + self.name = name + self.source = source + self.used = False + + def __str__(self): + return self.name + + def __repr__(self): + return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, + self.name, + self.source.lineno, + id(self)) + + def redefines(self, other): + return isinstance(other, Definition) and self.name == other.name + + +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + + +class Builtin(Definition): + """A definition created for all Python builtins.""" + + def __init__(self, name): + super(Builtin, self).__init__(name, None) + + def __repr__(self): + return '<%s object %r at 0x%x>' % (self.__class__.__name__, + self.name, + id(self)) + + +class UnhandledKeyType(object): + """ + A dictionary key of a type that we cannot or do not check for duplicates. + """ + + +class VariableKey(object): + """ + A dictionary key which is a variable. + + @ivar item: The variable AST object. + """ + def __init__(self, item): + self.name = item.id + + def __eq__(self, compare): + return ( + compare.__class__ == self.__class__ and + compare.name == self.name + ) + + def __hash__(self): + return hash(self.name) + + +class Importation(Definition): + """ + A binding created by an import statement. + + @ivar fullName: The complete name given to the import statement, + possibly including multiple dotted components. + @type fullName: C{str} + """ + + def __init__(self, name, source, full_name=None): + self.fullName = full_name or name + self.redefined = [] + super(Importation, self).__init__(name, source) + + def redefines(self, other): + if isinstance(other, SubmoduleImportation): + # See note in SubmoduleImportation about RedefinedWhileUnused + return self.fullName == other.fullName + return isinstance(other, Definition) and self.name == other.name + + def _has_alias(self): + """Return whether importation needs an as clause.""" + return not self.fullName.split('.')[-1] == self.name + + @property + def source_statement(self): + """Generate a source statement equivalent to the import.""" + if self._has_alias(): + return 'import %s as %s' % (self.fullName, self.name) + else: + return 'import %s' % self.fullName + + def __str__(self): + """Return import full name with alias.""" + if self._has_alias(): + return self.fullName + ' as ' + self.name + else: + return self.fullName + + +class SubmoduleImportation(Importation): + """ + A binding created by a submodule import statement. + + A submodule import is a special case where the root module is implicitly + imported, without an 'as' clause, and the submodule is also imported. + Python does not restrict which attributes of the root module may be used. + + This class is only used when the submodule import is without an 'as' clause. + + pyflakes handles this case by registering the root module name in the scope, + allowing any attribute of the root module to be accessed. + + RedefinedWhileUnused is suppressed in `redefines` unless the submodule + name is also the same, to avoid false positives. + """ + + def __init__(self, name, source): + # A dot should only appear in the name when it is a submodule import + assert '.' in name and (not source or isinstance(source, ast.Import)) + package_name = name.split('.')[0] + super(SubmoduleImportation, self).__init__(package_name, source) + self.fullName = name + + def redefines(self, other): + if isinstance(other, Importation): + return self.fullName == other.fullName + return super(SubmoduleImportation, self).redefines(other) + + def __str__(self): + return self.fullName + + @property + def source_statement(self): + return 'import ' + self.fullName + + +class ImportationFrom(Importation): + + def __init__(self, name, source, module, real_name=None): + self.module = module + self.real_name = real_name or name + + if module.endswith('.'): + full_name = module + self.real_name + else: + full_name = module + '.' + self.real_name + + super(ImportationFrom, self).__init__(name, source, full_name) + + def __str__(self): + """Return import full name with alias.""" + if self.real_name != self.name: + return self.fullName + ' as ' + self.name + else: + return self.fullName + + @property + def source_statement(self): + if self.real_name != self.name: + return 'from %s import %s as %s' % (self.module, + self.real_name, + self.name) + else: + return 'from %s import %s' % (self.module, self.name) + + +class StarImportation(Importation): + """A binding created by a 'from x import *' statement.""" + + def __init__(self, name, source): + super(StarImportation, self).__init__('*', source) + # Each star importation needs a unique name, and + # may not be the module name otherwise it will be deemed imported + self.name = name + '.*' + self.fullName = name + + @property + def source_statement(self): + return 'from ' + self.fullName + ' import *' + + def __str__(self): + # When the module ends with a ., avoid the ambiguous '..*' + if self.fullName.endswith('.'): + return self.source_statement + else: + return self.name + + +class FutureImportation(ImportationFrom): + """ + A binding created by a from `__future__` import statement. + + `__future__` imports are implicitly used. + """ + + def __init__(self, name, source, scope): + super(FutureImportation, self).__init__(name, source, '__future__') + self.used = (scope, source) + + +class Argument(Binding): + """ + Represents binding a name as an argument. + """ + + +class Assignment(Binding): + """ + Represents binding a name with an explicit assignment. + + The checker will raise warnings for any Assignment that isn't used. Also, + the checker does not consider assignments in tuple/list unpacking to be + Assignments, rather it treats them as simple Bindings. + """ + + +class FunctionDefinition(Definition): + pass + + +class ClassDefinition(Definition): + pass + + +class ExportBinding(Binding): + """ + A binding created by an C{__all__} assignment. If the names in the list + can be determined statically, they will be treated as names for export and + additional checking applied to them. + + The only recognized C{__all__} assignment via list concatenation is in the + following format: + + __all__ = ['a'] + ['b'] + ['c'] + + Names which are imported and not otherwise used but appear in the value of + C{__all__} will not have an unused import warning reported for them. + """ + + def __init__(self, name, source, scope): + if '__all__' in scope and isinstance(source, ast.AugAssign): + self.names = list(scope['__all__'].names) + else: + self.names = [] + + def _add_to_names(container): + for node in container.elts: + if isinstance(node, ast.Str): + self.names.append(node.s) + + if isinstance(source.value, (ast.List, ast.Tuple)): + _add_to_names(source.value) + # If concatenating lists + elif isinstance(source.value, ast.BinOp): + currentValue = source.value + while isinstance(currentValue.right, ast.List): + left = currentValue.left + right = currentValue.right + _add_to_names(right) + # If more lists are being added + if isinstance(left, ast.BinOp): + currentValue = left + # If just two lists are being added + elif isinstance(left, ast.List): + _add_to_names(left) + # All lists accounted for - done + break + # If not list concatenation + else: + break + super(ExportBinding, self).__init__(name, source) + + +class Scope(dict): + importStarred = False # set to True when import * is found + + def __repr__(self): + scope_cls = self.__class__.__name__ + return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) + + +class ClassScope(Scope): + pass + + +class FunctionScope(Scope): + """ + I represent a name scope for a function. + + @ivar globals: Names declared 'global' in this function. + """ + usesLocals = False + alwaysUsed = {'__tracebackhide__', '__traceback_info__', + '__traceback_supplement__'} + + def __init__(self): + super(FunctionScope, self).__init__() + # Simplify: manage the special locals as globals + self.globals = self.alwaysUsed.copy() + self.returnValue = None # First non-empty return + self.isGenerator = False # Detect a generator + + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if (not binding.used and + name != '_' and # see issue #202 + name not in self.globals and + not self.usesLocals and + isinstance(binding, Assignment)): + yield name, binding + + +class GeneratorScope(Scope): + pass + + +class ModuleScope(Scope): + """Scope for a module.""" + _futures_allowed = True + _annotations_future_enabled = False + + +class DoctestScope(ModuleScope): + """Scope for a doctest.""" + + +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] +# module scope annotation will store in `__annotations__`, see also PEP 526. +if PY36_PLUS: + _MAGIC_GLOBALS.append('__annotations__') + + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # an ExceptHandler node + return node.name + + +def is_typing_overload(value, scope): + def is_typing_overload_decorator(node): + return ( + ( + isinstance(node, ast.Name) and + node.id in scope and + scope[node.id].fullName == 'typing.overload' + ) or ( + isinstance(node, ast.Attribute) and + isinstance(node.value, ast.Name) and + node.value.id == 'typing' and + node.attr == 'overload' + ) + ) + + return ( + isinstance(value.source, ast.FunctionDef) and + len(value.source.decorator_list) == 1 and + is_typing_overload_decorator(value.source.decorator_list[0]) + ) + + +def make_tokens(code): + # PY3: tokenize.tokenize requires readline of bytes + if not isinstance(code, bytes): + code = code.encode('UTF-8') + lines = iter(code.splitlines(True)) + # next(lines, b'') is to prevent an error in pypy3 + return tuple(tokenize_tokenize(lambda: next(lines, b''))) + + +class _TypeableVisitor(ast.NodeVisitor): + """Collect the line number and nodes which are deemed typeable by + PEP 484 + + https://www.python.org/dev/peps/pep-0484/#type-comments + """ + def __init__(self): + self.typeable_lines = [] # type: List[int] + self.typeable_nodes = {} # type: Dict[int, ast.AST] + + def _typeable(self, node): + # if there is more than one typeable thing on a line last one wins + self.typeable_lines.append(node.lineno) + self.typeable_nodes[node.lineno] = node + + self.generic_visit(node) + + visit_Assign = visit_For = visit_FunctionDef = visit_With = _typeable + visit_AsyncFor = visit_AsyncFunctionDef = visit_AsyncWith = _typeable + + +def _collect_type_comments(tree, tokens): + visitor = _TypeableVisitor() + visitor.visit(tree) + + type_comments = collections.defaultdict(list) + for tp, text, start, _, _ in tokens: + if ( + tp != tokenize.COMMENT or # skip non comments + not TYPE_COMMENT_RE.match(text) or # skip non-type comments + TYPE_IGNORE_RE.match(text) # skip ignores + ): + continue + + # search for the typeable node at or before the line number of the + # type comment. + # if the bisection insertion point is before any nodes this is an + # invalid type comment which is ignored. + lineno, _ = start + idx = bisect.bisect_right(visitor.typeable_lines, lineno) + if idx == 0: + continue + node = visitor.typeable_nodes[visitor.typeable_lines[idx - 1]] + type_comments[node].append((start, text)) + + return type_comments + + +class Checker(object): + """ + I check the cleanliness and sanity of Python code. + + @ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements + of the list are two-tuples. The first element is the callable passed + to L{deferFunction}. The second element is a copy of the scope stack + at the time L{deferFunction} was called. + + @ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for + callables which are deferred assignment checks. + """ + + _ast_node_scope = { + ast.Module: ModuleScope, + ast.ClassDef: ClassScope, + ast.FunctionDef: FunctionScope, + ast.Lambda: FunctionScope, + ast.ListComp: GeneratorScope, + ast.SetComp: GeneratorScope, + ast.GeneratorExp: GeneratorScope, + ast.DictComp: GeneratorScope, + } + if PY35_PLUS: + _ast_node_scope[ast.AsyncFunctionDef] = FunctionScope, + + nodeDepth = 0 + offset = None + traceTree = False + + builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) + _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') + if _customBuiltIns: + builtIns.update(_customBuiltIns.split(',')) + del _customBuiltIns + + # TODO: file_tokens= is required to perform checks on type comments, + # eventually make this a required positional argument. For now it + # is defaulted to `()` for api compatibility. + def __init__(self, tree, filename='(none)', builtins=None, + withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()): + self._nodeHandlers = {} + self._deferredFunctions = [] + self._deferredAssignments = [] + self.deadScopes = [] + self.messages = [] + self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) + self.withDoctest = withDoctest + try: + self.scopeStack = [Checker._ast_node_scope[type(tree)]()] + except KeyError: + raise RuntimeError('No scope implemented for the node %r' % tree) + self.exceptHandlers = [()] + self.root = tree + self._type_comments = _collect_type_comments(tree, file_tokens) + for builtin in self.builtIns: + self.addBinding(None, Builtin(builtin)) + self.handleChildren(tree) + self.runDeferred(self._deferredFunctions) + # Set _deferredFunctions to None so that deferFunction will fail + # noisily if called after we've run through the deferred functions. + self._deferredFunctions = None + self.runDeferred(self._deferredAssignments) + # Set _deferredAssignments to None so that deferAssignment will fail + # noisily if called after we've run through the deferred assignments. + self._deferredAssignments = None + del self.scopeStack[1:] + self.popScope() + self.checkDeadScopes() + + def deferFunction(self, callable): + """ + Schedule a function handler to be called just before completion. + + This is used for handling function bodies, which must be deferred + because code later in the file might modify the global scope. When + `callable` is called, the scope at the time this is called will be + restored, however it will contain any new bindings added to it. + """ + self._deferredFunctions.append((callable, self.scopeStack[:], self.offset)) + + def deferAssignment(self, callable): + """ + Schedule an assignment handler to be called just after deferred + function handlers. + """ + self._deferredAssignments.append((callable, self.scopeStack[:], self.offset)) + + def runDeferred(self, deferred): + """ + Run the callables in C{deferred} using their associated scope stack. + """ + for handler, scope, offset in deferred: + self.scopeStack = scope + self.offset = offset + handler() + + def _in_doctest(self): + return (len(self.scopeStack) >= 2 and + isinstance(self.scopeStack[1], DoctestScope)) + + @property + def futuresAllowed(self): + if not all(isinstance(scope, ModuleScope) + for scope in self.scopeStack): + return False + + return self.scope._futures_allowed + + @futuresAllowed.setter + def futuresAllowed(self, value): + assert value is False + if isinstance(self.scope, ModuleScope): + self.scope._futures_allowed = False + + @property + def annotationsFutureEnabled(self): + scope = self.scopeStack[0] + if not isinstance(scope, ModuleScope): + return False + return scope._annotations_future_enabled + + @annotationsFutureEnabled.setter + def annotationsFutureEnabled(self, value): + assert value is True + assert isinstance(self.scope, ModuleScope) + self.scope._annotations_future_enabled = True + + @property + def scope(self): + return self.scopeStack[-1] + + def popScope(self): + self.deadScopes.append(self.scopeStack.pop()) + + def checkDeadScopes(self): + """ + Look at scopes which have been fully examined and report names in them + which were imported but unused. + """ + for scope in self.deadScopes: + # imports in classes are public members + if isinstance(scope, ClassScope): + continue + + all_binding = scope.get('__all__') + if all_binding and not isinstance(all_binding, ExportBinding): + all_binding = None + + if all_binding: + all_names = set(all_binding.names) + undefined = all_names.difference(scope) + else: + all_names = undefined = [] + + if undefined: + if not scope.importStarred and \ + os.path.basename(self.filename) != '__init__.py': + # Look for possible mistakes in the export list + for name in undefined: + self.report(messages.UndefinedExport, + scope['__all__'].source, name) + + # mark all import '*' as used by the undefined in __all__ + if scope.importStarred: + from_list = [] + for binding in scope.values(): + if isinstance(binding, StarImportation): + binding.used = all_binding + from_list.append(binding.fullName) + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + for name in undefined: + self.report(messages.ImportStarUsage, + scope['__all__'].source, name, from_list) + + # Look for imported names that aren't used. + for value in scope.values(): + if isinstance(value, Importation): + used = value.used or value.name in all_names + if not used: + messg = messages.UnusedImport + self.report(messg, value.source, str(value)) + for node in value.redefined: + if isinstance(self.getParent(node), FOR_TYPES): + messg = messages.ImportShadowedByLoopVar + elif used: + continue + else: + messg = messages.RedefinedWhileUnused + self.report(messg, node, value.name, value.source) + + def pushScope(self, scopeClass=FunctionScope): + self.scopeStack.append(scopeClass()) + + def report(self, messageClass, *args, **kwargs): + self.messages.append(messageClass(self.filename, *args, **kwargs)) + + def getParent(self, node): + # Lookup the first parent which is not Tuple, List or Starred + while True: + node = node.parent + if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): + return node + + def getCommonAncestor(self, lnode, rnode, stop): + if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and + hasattr(rnode, 'parent')): + return None + if lnode is rnode: + return lnode + + if (lnode.depth > rnode.depth): + return self.getCommonAncestor(lnode.parent, rnode, stop) + if (lnode.depth < rnode.depth): + return self.getCommonAncestor(lnode, rnode.parent, stop) + return self.getCommonAncestor(lnode.parent, rnode.parent, stop) + + def descendantOf(self, node, ancestors, stop): + for a in ancestors: + if self.getCommonAncestor(node, a, stop): + return True + return False + + def _getAncestor(self, node, ancestor_type): + parent = node + while True: + if parent is self.root: + return None + parent = self.getParent(parent) + if isinstance(parent, ancestor_type): + return parent + + def getScopeNode(self, node): + return self._getAncestor(node, tuple(Checker._ast_node_scope.keys())) + + def differentForks(self, lnode, rnode): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode, self.root) + parts = getAlternatives(ancestor) + if parts: + for items in parts: + if self.descendantOf(lnode, items, ancestor) ^ \ + self.descendantOf(rnode, items, ancestor): + return True + return False + + def addBinding(self, node, value): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the new value, a Binding instance + """ + # assert value.source in (node, node.parent): + for scope in self.scopeStack[::-1]: + if value.name in scope: + break + existing = scope.get(value.name) + + if (existing and not isinstance(existing, Builtin) and + not self.differentForks(node, existing.source)): + + parent_stmt = self.getParent(value.source) + if isinstance(existing, Importation) and isinstance(parent_stmt, FOR_TYPES): + self.report(messages.ImportShadowedByLoopVar, + node, value.name, existing.source) + + elif scope is self.scope: + if (isinstance(parent_stmt, ast.comprehension) and + not isinstance(self.getParent(existing.source), + (FOR_TYPES, ast.comprehension))): + self.report(messages.RedefinedInListComp, + node, value.name, existing.source) + elif not existing.used and value.redefines(existing): + if value.name != '_' or isinstance(existing, Importation): + if not is_typing_overload(existing, self.scope): + self.report(messages.RedefinedWhileUnused, + node, value.name, existing.source) + + elif isinstance(existing, Importation) and value.redefines(existing): + existing.redefined.append(node) + + if value.name in self.scope: + # then assume the rebound name is used as a global or within a loop + value.used = self.scope[value.name].used + + self.scope[value.name] = value + + def getNodeHandler(self, node_class): + try: + return self._nodeHandlers[node_class] + except KeyError: + nodeType = getNodeType(node_class) + self._nodeHandlers[node_class] = handler = getattr(self, nodeType) + return handler + + def handleNodeLoad(self, node): + name = getNodeName(node) + if not name: + return + + in_generators = None + importStarred = None + + # try enclosing function scopes and global scope + for scope in self.scopeStack[-1::-1]: + if isinstance(scope, ClassScope): + if not PY2 and name == '__class__': + return + elif in_generators is False: + # only generators used in a class scope can access the + # names of the class. this is skipped during the first + # iteration + continue + + if (name == 'print' and + isinstance(scope.get(name, None), Builtin)): + parent = self.getParent(node) + if (isinstance(parent, ast.BinOp) and + isinstance(parent.op, ast.RShift)): + self.report(messages.InvalidPrintSyntax, node) + + try: + scope[name].used = (self.scope, node) + + # if the name of SubImportation is same as + # alias of other Importation and the alias + # is used, SubImportation also should be marked as used. + n = scope[name] + if isinstance(n, Importation) and n._has_alias(): + try: + scope[n.fullName].used = (self.scope, node) + except KeyError: + pass + except KeyError: + pass + else: + return + + importStarred = importStarred or scope.importStarred + + if in_generators is not False: + in_generators = isinstance(scope, GeneratorScope) + + if importStarred: + from_list = [] + + for scope in self.scopeStack[-1::-1]: + for binding in scope.values(): + if isinstance(binding, StarImportation): + # mark '*' imports as used for each scope + binding.used = (self.scope, node) + from_list.append(binding.fullName) + + # report * usage, with a list of possible sources + from_list = ', '.join(sorted(from_list)) + self.report(messages.ImportStarUsage, node, name, from_list) + return + + if name == '__path__' and os.path.basename(self.filename) == '__init__.py': + # the special name __path__ is valid only in packages + return + + if name == '__module__' and isinstance(self.scope, ClassScope): + return + + # protected with a NameError handler? + if 'NameError' not in self.exceptHandlers[-1]: + self.report(messages.UndefinedName, node, name) + + def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name not in self.scope: + # for each function or module scope above us + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): + continue + # if the name was defined in that scope, and the name has + # been accessed already in the current scope, and hasn't + # been declared global + used = name in scope and scope[name].used + if used and used[0] is self.scope and name not in self.scope.globals: + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source) + break + + parent_stmt = self.getParent(node) + if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( + parent_stmt != node.parent and + not self.isLiteralTupleUnpacking(parent_stmt)): + binding = Binding(name, node) + elif name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, node.parent, self.scope) + elif isinstance(getattr(node, 'ctx', None), ast.Param): + binding = Argument(name, self.getScopeNode(node)) + else: + binding = Assignment(name, node) + self.addBinding(node, binding) + + def handleNodeDelete(self, node): + + def on_conditional_branch(): + """ + Return `True` if node is part of a conditional body. + """ + current = getattr(node, 'parent', None) + while current: + if isinstance(current, (ast.If, ast.While, ast.IfExp)): + return True + current = getattr(current, 'parent', None) + return False + + name = getNodeName(node) + if not name: + return + + if on_conditional_branch(): + # We cannot predict if this conditional branch is going to + # be executed. + return + + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + self.scope.globals.remove(name) + else: + try: + del self.scope[name] + except KeyError: + self.report(messages.UndefinedName, node, name) + + def _handle_type_comments(self, node): + for (lineno, col_offset), comment in self._type_comments.get(node, ()): + comment = comment.split(':', 1)[1].strip() + func_match = TYPE_FUNC_RE.match(comment) + if func_match: + parts = ( + func_match.group(1).replace('*', ''), + func_match.group(2).strip(), + ) + else: + parts = (comment,) + + for part in parts: + if PY2: + part = part.replace('...', 'Ellipsis') + self.deferFunction(functools.partial( + self.handleStringAnnotation, + part, node, lineno, col_offset, + messages.CommentAnnotationSyntaxError, + )) + + def handleChildren(self, tree, omit=None): + self._handle_type_comments(tree) + for node in iter_child_nodes(tree, omit=omit): + self.handleNode(node, tree) + + def isLiteralTupleUnpacking(self, node): + if isinstance(node, ast.Assign): + for child in node.targets + [node.value]: + if not hasattr(child, 'elts'): + return False + return True + + def isDocstring(self, node): + """ + Determine if the given node is a docstring, as long as it is at the + correct place in the node tree. + """ + return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and + isinstance(node.value, ast.Str)) + + def getDocstring(self, node): + if isinstance(node, ast.Expr): + node = node.value + if not isinstance(node, ast.Str): + return (None, None) + + if PYPY or PY38_PLUS: + doctest_lineno = node.lineno - 1 + else: + # Computed incorrectly if the docstring has backslash + doctest_lineno = node.lineno - node.s.count('\n') - 1 + + return (node.s, doctest_lineno) + + def handleNode(self, node, parent): + if node is None: + return + if self.offset and getattr(node, 'lineno', None) is not None: + node.lineno += self.offset[0] + node.col_offset += self.offset[1] + if self.traceTree: + print(' ' * self.nodeDepth + node.__class__.__name__) + if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or + self.isDocstring(node)): + self.futuresAllowed = False + self.nodeDepth += 1 + node.depth = self.nodeDepth + node.parent = parent + try: + handler = self.getNodeHandler(node.__class__) + handler(node) + finally: + self.nodeDepth -= 1 + if self.traceTree: + print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) + + _getDoctestExamples = doctest.DocTestParser().get_examples + + def handleDoctests(self, node): + try: + if hasattr(node, 'docstring'): + docstring = node.docstring + + # This is just a reasonable guess. In Python 3.7, docstrings no + # longer have line numbers associated with them. This will be + # incorrect if there are empty lines between the beginning + # of the function and the docstring. + node_lineno = node.lineno + if hasattr(node, 'args'): + node_lineno = max([node_lineno] + + [arg.lineno for arg in node.args.args]) + else: + (docstring, node_lineno) = self.getDocstring(node.body[0]) + examples = docstring and self._getDoctestExamples(docstring) + except (ValueError, IndexError): + # e.g. line 6 of the docstring for <string> has inconsistent + # leading whitespace: ... + return + if not examples: + return + + # Place doctest in module scope + saved_stack = self.scopeStack + self.scopeStack = [self.scopeStack[0]] + node_offset = self.offset or (0, 0) + self.pushScope(DoctestScope) + self.addBinding(None, Builtin('_')) + for example in examples: + try: + tree = ast.parse(example.source, "<doctest>") + except SyntaxError: + e = sys.exc_info()[1] + if PYPY: + e.offset += 1 + position = (node_lineno + example.lineno + e.lineno, + example.indent + 4 + (e.offset or 0)) + self.report(messages.DoctestSyntaxError, node, position) + else: + self.offset = (node_offset[0] + node_lineno + example.lineno, + node_offset[1] + example.indent + 4) + self.handleChildren(tree) + self.offset = node_offset + self.popScope() + self.scopeStack = saved_stack + + def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): + try: + tree = ast.parse(s) + except SyntaxError: + self.report(err, node, s) + return + + body = tree.body + if len(body) != 1 or not isinstance(body[0], ast.Expr): + self.report(err, node, s) + return + + parsed_annotation = tree.body[0].value + for descendant in ast.walk(parsed_annotation): + if ( + 'lineno' in descendant._attributes and + 'col_offset' in descendant._attributes + ): + descendant.lineno = ref_lineno + descendant.col_offset = ref_col_offset + + self.handleNode(parsed_annotation, node) + + def handleAnnotation(self, annotation, node): + if isinstance(annotation, ast.Str): + # Defer handling forward annotation. + self.deferFunction(functools.partial( + self.handleStringAnnotation, + annotation.s, + node, + annotation.lineno, + annotation.col_offset, + messages.ForwardAnnotationSyntaxError, + )) + elif self.annotationsFutureEnabled: + self.deferFunction(lambda: self.handleNode(annotation, node)) + else: + self.handleNode(annotation, node) + + def ignore(self, node): + pass + + # "stmt" type nodes + DELETE = PRINT = FOR = ASYNCFOR = WHILE = IF = WITH = WITHITEM = \ + ASYNCWITH = ASYNCWITHITEM = TRYFINALLY = EXEC = \ + EXPR = ASSIGN = handleChildren + + PASS = ignore + + # "expr" type nodes + BOOLOP = BINOP = UNARYOP = IFEXP = SET = \ + CALL = REPR = ATTRIBUTE = SUBSCRIPT = \ + STARRED = NAMECONSTANT = handleChildren + + NUM = STR = BYTES = ELLIPSIS = CONSTANT = ignore + + # "slice" type nodes + SLICE = EXTSLICE = INDEX = handleChildren + + # expression contexts are node instances too, though being constants + LOAD = STORE = DEL = AUGLOAD = AUGSTORE = PARAM = ignore + + # same for operators + AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = \ + MATMULT = ignore + + def RAISE(self, node): + self.handleChildren(node) + + arg = get_raise_argument(node) + + if isinstance(arg, ast.Call): + if is_notimplemented_name_node(arg.func): + # Handle "raise NotImplemented(...)" + self.report(messages.RaiseNotImplemented, node) + elif is_notimplemented_name_node(arg): + # Handle "raise NotImplemented" + self.report(messages.RaiseNotImplemented, node) + + # additional node types + COMPREHENSION = KEYWORD = FORMATTEDVALUE = JOINEDSTR = handleChildren + + def DICT(self, node): + # Complain if there are duplicate keys with different values + # If they have the same value it's not going to cause potentially + # unexpected behaviour so we'll not complain. + keys = [ + convert_to_value(key) for key in node.keys + ] + + key_counts = counter(keys) + duplicate_keys = [ + key for key, count in key_counts.items() + if count > 1 + ] + + for key in duplicate_keys: + key_indices = [i for i, i_key in enumerate(keys) if i_key == key] + + values = counter( + convert_to_value(node.values[index]) + for index in key_indices + ) + if any(count == 1 for value, count in values.items()): + for key_index in key_indices: + key_node = node.keys[key_index] + if isinstance(key, VariableKey): + self.report(messages.MultiValueRepeatedKeyVariable, + key_node, + key.name) + else: + self.report( + messages.MultiValueRepeatedKeyLiteral, + key_node, + key, + ) + self.handleChildren(node) + + def ASSERT(self, node): + if isinstance(node.test, ast.Tuple) and node.test.elts != []: + self.report(messages.AssertTuple, node) + self.handleChildren(node) + + def GLOBAL(self, node): + """ + Keep track of globals declarations. + """ + global_scope_index = 1 if self._in_doctest() else 0 + global_scope = self.scopeStack[global_scope_index] + + # Ignore 'global' statement in global scope. + if self.scope is not global_scope: + + # One 'global' statement can bind multiple (comma-delimited) names. + for node_name in node.names: + node_value = Assignment(node_name, node) + + # Remove UndefinedName messages already reported for this name. + # TO DO: if the global is not used in this scope, it does not + # become a globally defined name. See test_unused_global. + self.messages = [ + m for m in self.messages if not + isinstance(m, messages.UndefinedName) or + m.message_args[0] != node_name] + + # Bind name to global scope if it doesn't exist already. + global_scope.setdefault(node_name, node_value) + + # Bind name to non-global scopes, but as already "used". + node_value.used = (global_scope, node) + for scope in self.scopeStack[global_scope_index + 1:]: + scope[node_name] = node_value + + NONLOCAL = GLOBAL + + def GENERATOREXP(self, node): + self.pushScope(GeneratorScope) + self.handleChildren(node) + self.popScope() + + LISTCOMP = handleChildren if PY2 else GENERATOREXP + + DICTCOMP = SETCOMP = GENERATOREXP + + def NAME(self, node): + """ + Handle occurrence of Name (which can be a load/store/delete access.) + """ + # Locate the name in locals / function / globals scopes. + if isinstance(node.ctx, (ast.Load, ast.AugLoad)): + self.handleNodeLoad(node) + if (node.id == 'locals' and isinstance(self.scope, FunctionScope) and + isinstance(node.parent, ast.Call)): + # we are doing locals() call in current scope + self.scope.usesLocals = True + elif isinstance(node.ctx, (ast.Store, ast.AugStore, ast.Param)): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(node) + else: + # Unknown context + raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) + + def CONTINUE(self, node): + # Walk the tree up until we see a loop (OK), a function or class + # definition (not OK), for 'continue', a finally block (not OK), or + # the top module scope (not OK) + n = node + while hasattr(n, 'parent'): + n, n_child = n.parent, n + if isinstance(n, LOOP_TYPES): + # Doesn't apply unless it's in the loop itself + if n_child not in n.orelse: + return + if isinstance(n, (ast.FunctionDef, ast.ClassDef)): + break + # Handle Try/TryFinally difference in Python < and >= 3.3 + if hasattr(n, 'finalbody') and isinstance(node, ast.Continue): + if n_child in n.finalbody: + self.report(messages.ContinueInFinally, node) + return + if isinstance(node, ast.Continue): + self.report(messages.ContinueOutsideLoop, node) + else: # ast.Break + self.report(messages.BreakOutsideLoop, node) + + BREAK = CONTINUE + + def RETURN(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.ReturnOutsideFunction, node) + return + + if ( + node.value and + hasattr(self.scope, 'returnValue') and + not self.scope.returnValue + ): + self.scope.returnValue = node.value + self.handleNode(node.value, node) + + def YIELD(self, node): + if isinstance(self.scope, (ClassScope, ModuleScope)): + self.report(messages.YieldOutsideFunction, node) + return + + self.scope.isGenerator = True + self.handleNode(node.value, node) + + AWAIT = YIELDFROM = YIELD + + def FUNCTIONDEF(self, node): + for deco in node.decorator_list: + self.handleNode(deco, node) + self.LAMBDA(node) + self.addBinding(node, FunctionDefinition(node.name, node)) + # doctest does not process doctest within a doctest, + # or in nested functions. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + + ASYNCFUNCTIONDEF = FUNCTIONDEF + + def LAMBDA(self, node): + args = [] + annotations = [] + + if PY2: + def addArgs(arglist): + for arg in arglist: + if isinstance(arg, ast.Tuple): + addArgs(arg.elts) + else: + args.append(arg.id) + addArgs(node.args.args) + defaults = node.args.defaults + else: + for arg in node.args.args + node.args.kwonlyargs: + args.append(arg.arg) + annotations.append(arg.annotation) + defaults = node.args.defaults + node.args.kw_defaults + + # Only for Python3 FunctionDefs + is_py3_func = hasattr(node, 'returns') + + for arg_name in ('vararg', 'kwarg'): + wildcard = getattr(node.args, arg_name) + if not wildcard: + continue + args.append(wildcard if PY2 else wildcard.arg) + if is_py3_func: + if PY2: # Python 2.7 + argannotation = arg_name + 'annotation' + annotations.append(getattr(node.args, argannotation)) + else: # Python >= 3.4 + annotations.append(wildcard.annotation) + + if is_py3_func: + annotations.append(node.returns) + + if len(set(args)) < len(args): + for (idx, arg) in enumerate(args): + if arg in args[:idx]: + self.report(messages.DuplicateArgument, node, arg) + + for annotation in annotations: + self.handleAnnotation(annotation, node) + + for default in defaults: + self.handleNode(default, node) + + def runFunction(): + + self.pushScope() + + self.handleChildren(node, omit='decorator_list') + + def checkUnusedAssignments(): + """ + Check to see if any assignments have not been used. + """ + for name, binding in self.scope.unusedAssignments(): + self.report(messages.UnusedVariable, binding.source, name) + self.deferAssignment(checkUnusedAssignments) + + if PY2: + def checkReturnWithArgumentInsideGenerator(): + """ + Check to see if there is any return statement with + arguments but the function is a generator. + """ + if self.scope.isGenerator and self.scope.returnValue: + self.report(messages.ReturnWithArgsInsideGenerator, + self.scope.returnValue) + self.deferAssignment(checkReturnWithArgumentInsideGenerator) + self.popScope() + + self.deferFunction(runFunction) + + def ARGUMENTS(self, node): + self.handleChildren(node, omit=('defaults', 'kw_defaults')) + if PY2: + scope_node = self.getScopeNode(node) + if node.vararg: + self.addBinding(node, Argument(node.vararg, scope_node)) + if node.kwarg: + self.addBinding(node, Argument(node.kwarg, scope_node)) + + def ARG(self, node): + self.addBinding(node, Argument(node.arg, self.getScopeNode(node))) + + def CLASSDEF(self, node): + """ + Check names used in a class definition, including its decorators, base + classes, and the body of its definition. Additionally, add its name to + the current scope. + """ + for deco in node.decorator_list: + self.handleNode(deco, node) + for baseNode in node.bases: + self.handleNode(baseNode, node) + if not PY2: + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) + self.pushScope(ClassScope) + # doctest does not process doctest within a doctest + # classes within classes are processed. + if (self.withDoctest and + not self._in_doctest() and + not isinstance(self.scope, FunctionScope)): + self.deferFunction(lambda: self.handleDoctests(node)) + for stmt in node.body: + self.handleNode(stmt, node) + self.popScope() + self.addBinding(node, ClassDefinition(node.name, node)) + + def AUGASSIGN(self, node): + self.handleNodeLoad(node.target) + self.handleNode(node.value, node) + self.handleNode(node.target, node) + + def TUPLE(self, node): + if not PY2 and isinstance(node.ctx, ast.Store): + # Python 3 advanced tuple unpacking: a, *b, c = d. + # Only one starred expression is allowed, and no more than 1<<8 + # assignments are allowed before a stared expression. There is + # also a limit of 1<<24 expressions after the starred expression, + # which is impossible to test due to memory restrictions, but we + # add it here anyway + has_starred = False + star_loc = -1 + for i, n in enumerate(node.elts): + if isinstance(n, ast.Starred): + if has_starred: + self.report(messages.TwoStarredExpressions, node) + # The SyntaxError doesn't distinguish two from more + # than two. + break + has_starred = True + star_loc = i + if star_loc >= 1 << 8 or len(node.elts) - star_loc - 1 >= 1 << 24: + self.report(messages.TooManyExpressionsInStarredAssignment, node) + self.handleChildren(node) + + LIST = TUPLE + + def IMPORT(self, node): + for alias in node.names: + if '.' in alias.name and not alias.asname: + importation = SubmoduleImportation(alias.name, node) + else: + name = alias.asname or alias.name + importation = Importation(name, node, alias.name) + self.addBinding(node, importation) + + def IMPORTFROM(self, node): + if node.module == '__future__': + if not self.futuresAllowed: + self.report(messages.LateFutureImport, + node, [n.name for n in node.names]) + else: + self.futuresAllowed = False + + module = ('.' * node.level) + (node.module or '') + + for alias in node.names: + name = alias.asname or alias.name + if node.module == '__future__': + importation = FutureImportation(name, node, self.scope) + if alias.name not in __future__.all_feature_names: + self.report(messages.FutureFeatureNotDefined, + node, alias.name) + if alias.name == 'annotations': + self.annotationsFutureEnabled = True + elif alias.name == '*': + # Only Python 2, local import * is a SyntaxWarning + if not PY2 and not isinstance(self.scope, ModuleScope): + self.report(messages.ImportStarNotPermitted, + node, module) + continue + + self.scope.importStarred = True + self.report(messages.ImportStarUsed, node, module) + importation = StarImportation(module, node) + else: + importation = ImportationFrom(name, node, + module, alias.name) + self.addBinding(node, importation) + + def TRY(self, node): + handler_names = [] + # List the exception handlers + for i, handler in enumerate(node.handlers): + if isinstance(handler.type, ast.Tuple): + for exc_type in handler.type.elts: + handler_names.append(getNodeName(exc_type)) + elif handler.type: + handler_names.append(getNodeName(handler.type)) + + if handler.type is None and i < len(node.handlers) - 1: + self.report(messages.DefaultExceptNotLast, handler) + # Memorize the except handlers and process the body + self.exceptHandlers.append(handler_names) + for child in node.body: + self.handleNode(child, node) + self.exceptHandlers.pop() + # Process the other nodes: "except:", "else:", "finally:" + self.handleChildren(node, omit='body') + + TRYEXCEPT = TRY + + def EXCEPTHANDLER(self, node): + if PY2 or node.name is None: + self.handleChildren(node) + return + + # If the name already exists in the scope, modify state of existing + # binding. + if node.name in self.scope: + self.handleNodeStore(node) + + # 3.x: the name of the exception, which is not a Name node, but a + # simple string, creates a local that is only bound within the scope of + # the except: block. As such, temporarily remove the existing binding + # to more accurately determine if the name is used in the except: + # block. + + try: + prev_definition = self.scope.pop(node.name) + except KeyError: + prev_definition = None + + self.handleNodeStore(node) + self.handleChildren(node) + + # See discussion on https://github.com/PyCQA/pyflakes/pull/59 + + # We're removing the local name since it's being unbound after leaving + # the except: block and it's always unbound if the except: block is + # never entered. This will cause an "undefined name" error raised if + # the checked code tries to use the name afterwards. + # + # Unless it's been removed already. Then do nothing. + + try: + binding = self.scope.pop(node.name) + except KeyError: + pass + else: + if not binding.used: + self.report(messages.UnusedVariable, node, node.name) + + # Restore. + if prev_definition: + self.scope[node.name] = prev_definition + + def ANNASSIGN(self, node): + if node.value: + # Only bind the *targets* if the assignment has a value. + # Otherwise it's not really ast.Store and shouldn't silence + # UndefinedLocal warnings. + self.handleNode(node.target, node) + self.handleAnnotation(node.annotation, node) + if node.value: + # If the assignment has value, handle the *value* now. + self.handleNode(node.value, node) + + def COMPARE(self, node): + literals = (ast.Str, ast.Num) + if not PY2: + literals += (ast.Bytes,) + + left = node.left + for op, right in zip(node.ops, node.comparators): + if (isinstance(op, (ast.Is, ast.IsNot)) and + (isinstance(left, literals) or isinstance(right, literals))): + self.report(messages.IsLiteral, node) + left = right + + self.handleChildren(node) + + +# +# eflag: noqa = M702 \ No newline at end of file