diff options
Diffstat (limited to 'venv/Lib/site-packages/pylint/checkers/variables.py')
-rw-r--r-- | venv/Lib/site-packages/pylint/checkers/variables.py | 1987 |
1 files changed, 1987 insertions, 0 deletions
diff --git a/venv/Lib/site-packages/pylint/checkers/variables.py b/venv/Lib/site-packages/pylint/checkers/variables.py new file mode 100644 index 0000000..e13f9b5 --- /dev/null +++ b/venv/Lib/site-packages/pylint/checkers/variables.py @@ -0,0 +1,1987 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr> +# Copyright (c) 2009 Mads Kiilerich <mads@kiilerich.com> +# Copyright (c) 2010 Daniel Harding <dharding@gmail.com> +# Copyright (c) 2011-2014, 2017 Google, Inc. +# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com> +# Copyright (c) 2013-2018 Claudiu Popa <pcmanticore@gmail.com> +# Copyright (c) 2014 Michal Nowikowski <godfryd@gmail.com> +# Copyright (c) 2014 Brett Cannon <brett@python.org> +# Copyright (c) 2014 Ricardo Gemignani <ricardo.gemignani@gmail.com> +# Copyright (c) 2014 Arun Persaud <arun@nubati.net> +# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru> +# Copyright (c) 2015 Radu Ciorba <radu@devrandom.ro> +# Copyright (c) 2015 Simu Toni <simutoni@gmail.com> +# Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro> +# Copyright (c) 2016, 2018 Ashley Whetter <ashley@awhetter.co.uk> +# Copyright (c) 2016, 2018 Jakub Wilk <jwilk@jwilk.net> +# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com> +# Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com> +# Copyright (c) 2016 Grant Welch <gwelch925+github@gmail.com> +# Copyright (c) 2017 Ville Skyttä <ville.skytta@iki.fi> +# Copyright (c) 2017-2018 hippo91 <guillaume.peillex@gmail.com> +# Copyright (c) 2017 Dan Garrette <dhgarrette@gmail.com> +# Copyright (c) 2018 Bryce Guinta <bryce.guinta@protonmail.com> +# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com> +# Copyright (c) 2018 Mike Frysinger <vapier@gmail.com> +# Copyright (c) 2018 ssolanki <sushobhitsolanki@gmail.com> +# Copyright (c) 2018 Marianna Polatoglou <mpolatoglou@bloomberg.net> +# Copyright (c) 2018 mar-chi-pan <mar.polatoglou@gmail.com> +# Copyright (c) 2018 Ville Skyttä <ville.skytta@upcloud.com> + +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/master/COPYING + +"""variables checkers for Python code +""" +import collections +import copy +import itertools +import os +import re +from functools import lru_cache + +import astroid +from astroid import decorators, modutils, objects +from astroid.context import InferenceContext + +from pylint.checkers import BaseChecker, utils +from pylint.checkers.utils import is_postponed_evaluation_enabled +from pylint.interfaces import HIGH, INFERENCE, INFERENCE_FAILURE, IAstroidChecker +from pylint.utils import get_global_option + +SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") +FUTURE = "__future__" +# regexp for ignored argument name +IGNORED_ARGUMENT_NAMES = re.compile("_.*|^ignored_|^unused_") +# In Python 3.7 abc has a Python implementation which is preferred +# by astroid. Unfortunately this also messes up our explicit checks +# for `abc` +METACLASS_NAME_TRANSFORMS = {"_py_abc": "abc"} +TYPING_TYPE_CHECKS_GUARDS = frozenset({"typing.TYPE_CHECKING", "TYPE_CHECKING"}) +BUILTIN_RANGE = "builtins.range" +TYPING_MODULE = "typing" +TYPING_NAMES = frozenset( + { + "Any", + "Callable", + "ClassVar", + "Generic", + "Optional", + "Tuple", + "Type", + "TypeVar", + "Union", + "AbstractSet", + "ByteString", + "Container", + "ContextManager", + "Hashable", + "ItemsView", + "Iterable", + "Iterator", + "KeysView", + "Mapping", + "MappingView", + "MutableMapping", + "MutableSequence", + "MutableSet", + "Sequence", + "Sized", + "ValuesView", + "Awaitable", + "AsyncIterator", + "AsyncIterable", + "Coroutine", + "Collection", + "AsyncGenerator", + "AsyncContextManager", + "Reversible", + "SupportsAbs", + "SupportsBytes", + "SupportsComplex", + "SupportsFloat", + "SupportsInt", + "SupportsRound", + "Counter", + "Deque", + "Dict", + "DefaultDict", + "List", + "Set", + "FrozenSet", + "NamedTuple", + "Generator", + "AnyStr", + "Text", + "Pattern", + } +) + + +def _is_from_future_import(stmt, name): + """Check if the name is a future import from another module.""" + try: + module = stmt.do_import_module(stmt.modname) + except astroid.AstroidBuildingException: + return None + + for local_node in module.locals.get(name, []): + if isinstance(local_node, astroid.ImportFrom) and local_node.modname == FUTURE: + return True + return None + + +def in_for_else_branch(parent, stmt): + """Returns True if stmt in inside the else branch for a parent For stmt.""" + return isinstance(parent, astroid.For) and any( + else_stmt.parent_of(stmt) or else_stmt == stmt for else_stmt in parent.orelse + ) + + +@lru_cache(maxsize=1000) +def overridden_method(klass, name): + """get overridden method if any""" + try: + parent = next(klass.local_attr_ancestors(name)) + except (StopIteration, KeyError): + return None + try: + meth_node = parent[name] + except KeyError: + # We have found an ancestor defining <name> but it's not in the local + # dictionary. This may happen with astroid built from living objects. + return None + if isinstance(meth_node, astroid.FunctionDef): + return meth_node + return None + + +def _get_unpacking_extra_info(node, inferred): + """return extra information to add to the message for unpacking-non-sequence + and unbalanced-tuple-unpacking errors + """ + more = "" + inferred_module = inferred.root().name + if node.root().name == inferred_module: + if node.lineno == inferred.lineno: + more = " %s" % inferred.as_string() + elif inferred.lineno: + more = " defined at line %s" % inferred.lineno + elif inferred.lineno: + more = " defined at line %s of %s" % (inferred.lineno, inferred_module) + return more + + +def _detect_global_scope(node, frame, defframe): + """ Detect that the given frames shares a global + scope. + + Two frames shares a global scope when neither + of them are hidden under a function scope, as well + as any of parent scope of them, until the root scope. + In this case, depending from something defined later on + will not work, because it is still undefined. + + Example: + class A: + # B has the same global scope as `C`, leading to a NameError. + class B(C): ... + class C: ... + + """ + def_scope = scope = None + if frame and frame.parent: + scope = frame.parent.scope() + if defframe and defframe.parent: + def_scope = defframe.parent.scope() + if isinstance(frame, astroid.FunctionDef): + # If the parent of the current node is a + # function, then it can be under its scope + # (defined in, which doesn't concern us) or + # the `->` part of annotations. The same goes + # for annotations of function arguments, they'll have + # their parent the Arguments node. + if not isinstance(node.parent, (astroid.FunctionDef, astroid.Arguments)): + return False + elif any( + not isinstance(f, (astroid.ClassDef, astroid.Module)) for f in (frame, defframe) + ): + # Not interested in other frames, since they are already + # not in a global scope. + return False + + break_scopes = [] + for current_scope in (scope, def_scope): + # Look for parent scopes. If there is anything different + # than a module or a class scope, then they frames don't + # share a global scope. + parent_scope = current_scope + while parent_scope: + if not isinstance(parent_scope, (astroid.ClassDef, astroid.Module)): + break_scopes.append(parent_scope) + break + if parent_scope.parent: + parent_scope = parent_scope.parent.scope() + else: + break + if break_scopes and len(set(break_scopes)) != 1: + # Store different scopes than expected. + # If the stored scopes are, in fact, the very same, then it means + # that the two frames (frame and defframe) shares the same scope, + # and we could apply our lineno analysis over them. + # For instance, this works when they are inside a function, the node + # that uses a definition and the definition itself. + return False + # At this point, we are certain that frame and defframe shares a scope + # and the definition of the first depends on the second. + return frame.lineno < defframe.lineno + + +def _infer_name_module(node, name): + context = InferenceContext() + context.lookupname = name + return node.infer(context, asname=False) + + +def _fix_dot_imports(not_consumed): + """ Try to fix imports with multiple dots, by returning a dictionary + with the import names expanded. The function unflattens root imports, + like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree' + and 'xml.sax' respectively. + """ + names = {} + for name, stmts in not_consumed.items(): + if any( + isinstance(stmt, astroid.AssignName) + and isinstance(stmt.assign_type(), astroid.AugAssign) + for stmt in stmts + ): + continue + for stmt in stmts: + if not isinstance(stmt, (astroid.ImportFrom, astroid.Import)): + continue + for imports in stmt.names: + second_name = None + import_module_name = imports[0] + if import_module_name == "*": + # In case of wildcard imports, + # pick the name from inside the imported module. + second_name = name + else: + name_matches_dotted_import = False + if ( + import_module_name.startswith(name) + and import_module_name.find(".") > -1 + ): + name_matches_dotted_import = True + + if name_matches_dotted_import or name in imports: + # Most likely something like 'xml.etree', + # which will appear in the .locals as 'xml'. + # Only pick the name if it wasn't consumed. + second_name = import_module_name + if second_name and second_name not in names: + names[second_name] = stmt + return sorted(names.items(), key=lambda a: a[1].fromlineno) + + +def _find_frame_imports(name, frame): + """ + Detect imports in the frame, with the required + *name*. Such imports can be considered assignments. + Returns True if an import for the given name was found. + """ + imports = frame.nodes_of_class((astroid.Import, astroid.ImportFrom)) + for import_node in imports: + for import_name, import_alias in import_node.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias == name: + return True + elif import_name and import_name == name: + return True + return None + + +def _import_name_is_global(stmt, global_names): + for import_name, import_alias in stmt.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias in global_names: + return True + elif import_name in global_names: + return True + return False + + +def _flattened_scope_names(iterator): + values = (set(stmt.names) for stmt in iterator) + return set(itertools.chain.from_iterable(values)) + + +def _assigned_locally(name_node): + """ + Checks if name_node has corresponding assign statement in same scope + """ + assign_stmts = name_node.scope().nodes_of_class(astroid.AssignName) + return any(a.name == name_node.name for a in assign_stmts) + + +def _is_type_checking_import(node): + parent = node.parent + if not isinstance(parent, astroid.If): + return False + test = parent.test + return test.as_string() in TYPING_TYPE_CHECKS_GUARDS + + +def _has_locals_call_after_node(stmt, scope): + skip_nodes = ( + astroid.FunctionDef, + astroid.ClassDef, + astroid.Import, + astroid.ImportFrom, + ) + for call in scope.nodes_of_class(astroid.Call, skip_klass=skip_nodes): + inferred = utils.safe_infer(call.func) + if ( + utils.is_builtin_object(inferred) + and getattr(inferred, "name", None) == "locals" + ): + if stmt.lineno < call.lineno: + return True + return False + + +MSGS = { + "E0601": ( + "Using variable %r before assignment", + "used-before-assignment", + "Used when a local variable is accessed before its assignment.", + ), + "E0602": ( + "Undefined variable %r", + "undefined-variable", + "Used when an undefined variable is accessed.", + ), + "E0603": ( + "Undefined variable name %r in __all__", + "undefined-all-variable", + "Used when an undefined variable name is referenced in __all__.", + ), + "E0604": ( + "Invalid object %r in __all__, must contain only strings", + "invalid-all-object", + "Used when an invalid (non-string) object occurs in __all__.", + ), + "E0611": ( + "No name %r in module %r", + "no-name-in-module", + "Used when a name cannot be found in a module.", + ), + "W0601": ( + "Global variable %r undefined at the module level", + "global-variable-undefined", + 'Used when a variable is defined through the "global" statement ' + "but the variable is not defined in the module scope.", + ), + "W0602": ( + "Using global for %r but no assignment is done", + "global-variable-not-assigned", + 'Used when a variable is defined through the "global" statement ' + "but no assignment to this variable is done.", + ), + "W0603": ( + "Using the global statement", # W0121 + "global-statement", + 'Used when you use the "global" statement to update a global ' + "variable. Pylint just try to discourage this " + "usage. That doesn't mean you cannot use it !", + ), + "W0604": ( + "Using the global statement at the module level", # W0103 + "global-at-module-level", + 'Used when you use the "global" statement at the module level ' + "since it has no effect", + ), + "W0611": ( + "Unused %s", + "unused-import", + "Used when an imported module or variable is not used.", + ), + "W0612": ( + "Unused variable %r", + "unused-variable", + "Used when a variable is defined but not used.", + ), + "W0613": ( + "Unused argument %r", + "unused-argument", + "Used when a function or method argument is not used.", + ), + "W0614": ( + "Unused import %s from wildcard import", + "unused-wildcard-import", + "Used when an imported module or variable is not used from a " + "`'from X import *'` style import.", + ), + "W0621": ( + "Redefining name %r from outer scope (line %s)", + "redefined-outer-name", + "Used when a variable's name hides a name defined in the outer scope.", + ), + "W0622": ( + "Redefining built-in %r", + "redefined-builtin", + "Used when a variable or function override a built-in.", + ), + "W0623": ( + "Redefining name %r from %s in exception handler", + "redefine-in-handler", + "Used when an exception handler assigns the exception to an existing name", + ), + "W0631": ( + "Using possibly undefined loop variable %r", + "undefined-loop-variable", + "Used when a loop variable (i.e. defined by a for loop or " + "a list comprehension or a generator expression) is used outside " + "the loop.", + ), + "W0632": ( + "Possible unbalanced tuple unpacking with " + "sequence%s: " + "left side has %d label(s), right side has %d value(s)", + "unbalanced-tuple-unpacking", + "Used when there is an unbalanced tuple unpacking in assignment", + {"old_names": [("E0632", "old-unbalanced-tuple-unpacking")]}, + ), + "E0633": ( + "Attempting to unpack a non-sequence%s", + "unpacking-non-sequence", + "Used when something which is not " + "a sequence is used in an unpack assignment", + {"old_names": [("W0633", "old-unpacking-non-sequence")]}, + ), + "W0640": ( + "Cell variable %s defined in loop", + "cell-var-from-loop", + "A variable used in a closure is defined in a loop. " + "This will result in all closures using the same value for " + "the closed-over variable.", + ), + "W0641": ( + "Possibly unused variable %r", + "possibly-unused-variable", + "Used when a variable is defined but might not be used. " + "The possibility comes from the fact that locals() might be used, " + "which could consume or not the said variable", + ), + "W0642": ( + "Invalid assignment to %s in method", + "self-cls-assignment", + "Invalid assignment to self or cls in instance or class method " + "respectively.", + ), +} + + +ScopeConsumer = collections.namedtuple( + "ScopeConsumer", "to_consume consumed scope_type" +) + + +class NamesConsumer: + """ + A simple class to handle consumed, to consume and scope type info of node locals + """ + + def __init__(self, node, scope_type): + self._atomic = ScopeConsumer(copy.copy(node.locals), {}, scope_type) + + def __repr__(self): + msg = "\nto_consume : {:s}\n".format( + ", ".join( + [ + "{}->{}".format(key, val) + for key, val in self._atomic.to_consume.items() + ] + ) + ) + msg += "consumed : {:s}\n".format( + ", ".join( + [ + "{}->{}".format(key, val) + for key, val in self._atomic.consumed.items() + ] + ) + ) + msg += "scope_type : {:s}\n".format(self._atomic.scope_type) + return msg + + def __iter__(self): + return iter(self._atomic) + + @property + def to_consume(self): + return self._atomic.to_consume + + @property + def consumed(self): + return self._atomic.consumed + + @property + def scope_type(self): + return self._atomic.scope_type + + def mark_as_consumed(self, name, new_node): + """ + Mark the name as consumed and delete it from + the to_consume dictionary + """ + self.consumed[name] = new_node + del self.to_consume[name] + + def get_next_to_consume(self, node): + # mark the name as consumed if it's defined in this scope + name = node.name + parent_node = node.parent + found_node = self.to_consume.get(name) + if ( + found_node + and isinstance(parent_node, astroid.Assign) + and parent_node == found_node[0].parent + ): + lhs = found_node[0].parent.targets[0] + if lhs.name == name: # this name is defined in this very statement + found_node = None + return found_node + + +# pylint: disable=too-many-public-methods +class VariablesChecker(BaseChecker): + """checks for + * unused variables / imports + * undefined variables + * redefinition of variable from builtins or from an outer scope + * use of variable before assignment + * __all__ consistency + * self/cls assignment + """ + + __implements__ = IAstroidChecker + + name = "variables" + msgs = MSGS + priority = -1 + options = ( + ( + "init-import", + { + "default": 0, + "type": "yn", + "metavar": "<y_or_n>", + "help": "Tells whether we should check for unused import in " + "__init__ files.", + }, + ), + ( + "dummy-variables-rgx", + { + "default": "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_", + "type": "regexp", + "metavar": "<regexp>", + "help": "A regular expression matching the name of dummy " + "variables (i.e. expected to not be used).", + }, + ), + ( + "additional-builtins", + { + "default": (), + "type": "csv", + "metavar": "<comma separated list>", + "help": "List of additional names supposed to be defined in " + "builtins. Remember that you should avoid defining new builtins " + "when possible.", + }, + ), + ( + "callbacks", + { + "default": ("cb_", "_cb"), + "type": "csv", + "metavar": "<callbacks>", + "help": "List of strings which can identify a callback " + "function by name. A callback name must start or " + "end with one of those strings.", + }, + ), + ( + "redefining-builtins-modules", + { + "default": ( + "six.moves", + "past.builtins", + "future.builtins", + "builtins", + "io", + ), + "type": "csv", + "metavar": "<comma separated list>", + "help": "List of qualified module names which can have objects " + "that can redefine builtins.", + }, + ), + ( + "ignored-argument-names", + { + "default": IGNORED_ARGUMENT_NAMES, + "type": "regexp", + "metavar": "<regexp>", + "help": "Argument names that match this expression will be " + "ignored. Default to name with leading underscore.", + }, + ), + ( + "allow-global-unused-variables", + { + "default": True, + "type": "yn", + "metavar": "<y_or_n>", + "help": "Tells whether unused global variables should be treated as a violation.", + }, + ), + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._to_consume = ( + None + ) # list of tuples: (to_consume:dict, consumed:dict, scope_type:str) + self._checking_mod_attr = None + self._loop_variables = [] + self._type_annotation_names = [] + self._postponed_evaluation_enabled = False + + @utils.check_messages("redefined-outer-name") + def visit_for(self, node): + assigned_to = [ + var.name for var in node.target.nodes_of_class(astroid.AssignName) + ] + + # Only check variables that are used + dummy_rgx = self.config.dummy_variables_rgx + assigned_to = [var for var in assigned_to if not dummy_rgx.match(var)] + + for variable in assigned_to: + for outer_for, outer_variables in self._loop_variables: + if variable in outer_variables and not in_for_else_branch( + outer_for, node + ): + self.add_message( + "redefined-outer-name", + args=(variable, outer_for.fromlineno), + node=node, + ) + break + + self._loop_variables.append((node, assigned_to)) + + @utils.check_messages("redefined-outer-name") + def leave_for(self, node): + self._loop_variables.pop() + self._store_type_annotation_names(node) + + def visit_module(self, node): + """visit module : update consumption analysis variable + checks globals doesn't overrides builtins + """ + self._to_consume = [NamesConsumer(node, "module")] + self._postponed_evaluation_enabled = is_postponed_evaluation_enabled(node) + + for name, stmts in node.locals.items(): + if utils.is_builtin(name) and not utils.is_inside_except(stmts[0]): + if self._should_ignore_redefined_builtin(stmts[0]) or name == "__doc__": + continue + self.add_message("redefined-builtin", args=name, node=stmts[0]) + + @utils.check_messages( + "unused-import", + "unused-wildcard-import", + "redefined-builtin", + "undefined-all-variable", + "invalid-all-object", + "unused-variable", + ) + def leave_module(self, node): + """leave module: check globals + """ + assert len(self._to_consume) == 1 + + self._check_metaclasses(node) + not_consumed = self._to_consume.pop().to_consume + # attempt to check for __all__ if defined + if "__all__" in node.locals: + self._check_all(node, not_consumed) + + # check for unused globals + self._check_globals(not_consumed) + + # don't check unused imports in __init__ files + if not self.config.init_import and node.package: + return + + self._check_imports(not_consumed) + + def visit_classdef(self, node): + """visit class: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "class")) + + def leave_classdef(self, _): + """leave class: update consumption analysis variable + """ + # do not check for not used locals here (no sense) + self._to_consume.pop() + + def visit_lambda(self, node): + """visit lambda: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "lambda")) + + def leave_lambda(self, _): + """leave lambda: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_generatorexp(self, node): + """visit genexpr: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "comprehension")) + + def leave_generatorexp(self, _): + """leave genexpr: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_dictcomp(self, node): + """visit dictcomp: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "comprehension")) + + def leave_dictcomp(self, _): + """leave dictcomp: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_setcomp(self, node): + """visit setcomp: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "comprehension")) + + def leave_setcomp(self, _): + """leave setcomp: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_functiondef(self, node): + """visit function: update consumption analysis variable and check locals + """ + self._to_consume.append(NamesConsumer(node, "function")) + if not ( + self.linter.is_message_enabled("redefined-outer-name") + or self.linter.is_message_enabled("redefined-builtin") + ): + return + globs = node.root().globals + for name, stmt in node.items(): + if utils.is_inside_except(stmt): + continue + if name in globs and not isinstance(stmt, astroid.Global): + definition = globs[name][0] + if ( + isinstance(definition, astroid.ImportFrom) + and definition.modname == FUTURE + ): + # It is a __future__ directive, not a symbol. + continue + + # Do not take in account redefined names for the purpose + # of type checking.: + if any( + isinstance(definition.parent, astroid.If) + and definition.parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS + for definition in globs[name] + ): + continue + + line = definition.fromlineno + if not self._is_name_ignored(stmt, name): + self.add_message( + "redefined-outer-name", args=(name, line), node=stmt + ) + + elif utils.is_builtin(name) and not self._should_ignore_redefined_builtin( + stmt + ): + # do not print Redefining builtin for additional builtins + self.add_message("redefined-builtin", args=name, node=stmt) + + def leave_functiondef(self, node): + """leave function: check function's locals are consumed""" + self._check_metaclasses(node) + + if node.type_comment_returns: + self._store_type_annotation_node(node.type_comment_returns) + if node.type_comment_args: + for argument_annotation in node.type_comment_args: + self._store_type_annotation_node(argument_annotation) + + not_consumed = self._to_consume.pop().to_consume + if not ( + self.linter.is_message_enabled("unused-variable") + or self.linter.is_message_enabled("possibly-unused-variable") + or self.linter.is_message_enabled("unused-argument") + ): + return + + # Don't check arguments of function which are only raising an exception. + if utils.is_error(node): + return + + # Don't check arguments of abstract methods or within an interface. + is_method = node.is_method() + if is_method and node.is_abstract(): + return + + global_names = _flattened_scope_names(node.nodes_of_class(astroid.Global)) + nonlocal_names = _flattened_scope_names(node.nodes_of_class(astroid.Nonlocal)) + for name, stmts in not_consumed.items(): + self._check_is_unused(name, node, stmts[0], global_names, nonlocal_names) + + visit_asyncfunctiondef = visit_functiondef + leave_asyncfunctiondef = leave_functiondef + + @utils.check_messages( + "global-variable-undefined", + "global-variable-not-assigned", + "global-statement", + "global-at-module-level", + "redefined-builtin", + ) + def visit_global(self, node): + """check names imported exists in the global scope""" + frame = node.frame() + if isinstance(frame, astroid.Module): + self.add_message("global-at-module-level", node=node) + return + + module = frame.root() + default_message = True + locals_ = node.scope().locals + for name in node.names: + try: + assign_nodes = module.getattr(name) + except astroid.NotFoundError: + # unassigned global, skip + assign_nodes = [] + + not_defined_locally_by_import = not any( + isinstance(local, astroid.node_classes.Import) + for local in locals_.get(name, ()) + ) + if not assign_nodes and not_defined_locally_by_import: + self.add_message("global-variable-not-assigned", args=name, node=node) + default_message = False + continue + + for anode in assign_nodes: + if ( + isinstance(anode, astroid.AssignName) + and anode.name in module.special_attributes + ): + self.add_message("redefined-builtin", args=name, node=node) + break + if anode.frame() is module: + # module level assignment + break + else: + if not_defined_locally_by_import: + # global undefined at the module scope + self.add_message("global-variable-undefined", args=name, node=node) + default_message = False + + if default_message: + self.add_message("global-statement", node=node) + + def visit_assignname(self, node): + if isinstance(node.assign_type(), astroid.AugAssign): + self.visit_name(node) + + def visit_delname(self, node): + self.visit_name(node) + + @utils.check_messages(*MSGS) + def visit_name(self, node): + """check that a name is defined if the current scope and doesn't + redefine a built-in + """ + stmt = node.statement() + if stmt.fromlineno is None: + # name node from an astroid built from live code, skip + assert not stmt.root().file.endswith(".py") + return + + name = node.name + frame = stmt.scope() + # if the name node is used as a function default argument's value or as + # a decorator, then start from the parent frame of the function instead + # of the function frame - and thus open an inner class scope + if ( + utils.is_default_argument(node) + or utils.is_func_decorator(node) + or utils.is_ancestor_name(frame, node) + ): + start_index = len(self._to_consume) - 2 + else: + start_index = len(self._to_consume) - 1 + # iterates through parent scopes, from the inner to the outer + base_scope_type = self._to_consume[start_index].scope_type + # pylint: disable=too-many-nested-blocks; refactoring this block is a pain. + for i in range(start_index, -1, -1): + current_consumer = self._to_consume[i] + # if the current scope is a class scope but it's not the inner + # scope, ignore it. This prevents to access this scope instead of + # the globals one in function members when there are some common + # names. The only exception is when the starting scope is a + # comprehension and its direct outer scope is a class + if ( + current_consumer.scope_type == "class" + and i != start_index + and not (base_scope_type == "comprehension" and i == start_index - 1) + ): + if self._ignore_class_scope(node): + continue + + # the name has already been consumed, only check it's not a loop + # variable used outside the loop + # avoid the case where there are homonyms inside function scope and + # comprehension current scope (avoid bug #1731) + if name in current_consumer.consumed and not ( + current_consumer.scope_type == "comprehension" + and self._has_homonym_in_upper_function_scope(node, i) + ): + defnode = utils.assign_parent(current_consumer.consumed[name][0]) + self._check_late_binding_closure(node, defnode) + self._loopvar_name(node, name) + break + + found_node = current_consumer.get_next_to_consume(node) + if found_node is None: + continue + + # checks for use before assignment + defnode = utils.assign_parent(current_consumer.to_consume[name][0]) + + if defnode is not None: + self._check_late_binding_closure(node, defnode) + defstmt = defnode.statement() + defframe = defstmt.frame() + # The class reuses itself in the class scope. + recursive_klass = ( + frame is defframe + and defframe.parent_of(node) + and isinstance(defframe, astroid.ClassDef) + and node.name == defframe.name + ) + + if ( + recursive_klass + and utils.is_inside_lambda(node) + and ( + not utils.is_default_argument(node) + or node.scope().parent.scope() is not defframe + ) + ): + # Self-referential class references are fine in lambda's -- + # As long as they are not part of the default argument directly + # under the scope of the parent self-referring class. + # Example of valid default argument: + # class MyName3: + # myattr = 1 + # mylambda3 = lambda: lambda a=MyName3: a + # Example of invalid default argument: + # class MyName4: + # myattr = 1 + # mylambda4 = lambda a=MyName4: lambda: a + + # If the above conditional is True, + # there is no possibility of undefined-variable + # Also do not consume class name + # (since consuming blocks subsequent checks) + # -- quit + break + + maybee0601, annotation_return, use_outer_definition = self._is_variable_violation( + node, + name, + defnode, + stmt, + defstmt, + frame, + defframe, + base_scope_type, + recursive_klass, + ) + + if use_outer_definition: + continue + + if ( + maybee0601 + and not utils.is_defined_before(node) + and not astroid.are_exclusive(stmt, defstmt, ("NameError",)) + ): + + # Used and defined in the same place, e.g `x += 1` and `del x` + defined_by_stmt = defstmt is stmt and isinstance( + node, (astroid.DelName, astroid.AssignName) + ) + if ( + recursive_klass + or defined_by_stmt + or annotation_return + or isinstance(defstmt, astroid.Delete) + ): + if not utils.node_ignores_exception(node, NameError): + + # Handle postponed evaluation of annotations + if not ( + self._postponed_evaluation_enabled + and isinstance( + stmt, + ( + astroid.AnnAssign, + astroid.FunctionDef, + astroid.Arguments, + ), + ) + and name in node.root().locals + ): + self.add_message( + "undefined-variable", args=name, node=node + ) + elif base_scope_type != "lambda": + # E0601 may *not* occurs in lambda scope. + + # Handle postponed evaluation of annotations + if not ( + self._postponed_evaluation_enabled + and isinstance( + stmt, (astroid.AnnAssign, astroid.FunctionDef) + ) + ): + self.add_message( + "used-before-assignment", args=name, node=node + ) + elif base_scope_type == "lambda": + # E0601 can occur in class-level scope in lambdas, as in + # the following example: + # class A: + # x = lambda attr: f + attr + # f = 42 + if isinstance(frame, astroid.ClassDef) and name in frame.locals: + if isinstance(node.parent, astroid.Arguments): + if stmt.fromlineno <= defstmt.fromlineno: + # Doing the following is fine: + # class A: + # x = 42 + # y = lambda attr=x: attr + self.add_message( + "used-before-assignment", args=name, node=node + ) + else: + self.add_message( + "undefined-variable", args=name, node=node + ) + elif current_consumer.scope_type == "lambda": + self.add_message("undefined-variable", node=node, args=name) + + current_consumer.mark_as_consumed(name, found_node) + # check it's not a loop variable used outside the loop + self._loopvar_name(node, name) + break + else: + # we have not found the name, if it isn't a builtin, that's an + # undefined name ! + if not ( + name in astroid.Module.scope_attrs + or utils.is_builtin(name) + or name in self.config.additional_builtins + ): + if not utils.node_ignores_exception(node, NameError): + self.add_message("undefined-variable", args=name, node=node) + + @utils.check_messages("no-name-in-module") + def visit_import(self, node): + """check modules attribute accesses""" + if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node): + # No need to verify this, since ImportError is already + # handled by the client code. + return + + for name, _ in node.names: + parts = name.split(".") + try: + module = next(_infer_name_module(node, parts[0])) + except astroid.ResolveError: + continue + self._check_module_attrs(node, module, parts[1:]) + + @utils.check_messages("no-name-in-module") + def visit_importfrom(self, node): + """check modules attribute accesses""" + if not self._analyse_fallback_blocks and utils.is_from_fallback_block(node): + # No need to verify this, since ImportError is already + # handled by the client code. + return + + name_parts = node.modname.split(".") + try: + module = node.do_import_module(name_parts[0]) + except astroid.AstroidBuildingException: + return + module = self._check_module_attrs(node, module, name_parts[1:]) + if not module: + return + for name, _ in node.names: + if name == "*": + continue + self._check_module_attrs(node, module, name.split(".")) + + @utils.check_messages( + "unbalanced-tuple-unpacking", "unpacking-non-sequence", "self-cls-assignment" + ) + def visit_assign(self, node): + """Check unbalanced tuple unpacking for assignments + and unpacking non-sequences as well as in case self/cls + get assigned. + """ + self._check_self_cls_assign(node) + if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): + return + + targets = node.targets[0].itered() + try: + inferred = utils.safe_infer(node.value) + if inferred is not None: + self._check_unpacking(inferred, node, targets) + except astroid.InferenceError: + return + + # listcomp have now also their scope + def visit_listcomp(self, node): + """visit dictcomp: update consumption analysis variable + """ + self._to_consume.append(NamesConsumer(node, "comprehension")) + + def leave_listcomp(self, _): + """leave dictcomp: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def leave_assign(self, node): + self._store_type_annotation_names(node) + + def leave_with(self, node): + self._store_type_annotation_names(node) + + def visit_arguments(self, node): + for annotation in node.type_comment_args: + self._store_type_annotation_node(annotation) + + # Relying on other checker's options, which might not have been initialized yet. + @decorators.cachedproperty + def _analyse_fallback_blocks(self): + return get_global_option(self, "analyse-fallback-blocks", default=False) + + @decorators.cachedproperty + def _ignored_modules(self): + return get_global_option(self, "ignored-modules", default=[]) + + @decorators.cachedproperty + def _allow_global_unused_variables(self): + return get_global_option(self, "allow-global-unused-variables", default=True) + + @staticmethod + def _defined_in_function_definition(node, frame): + in_annotation_or_default = False + if isinstance(frame, astroid.FunctionDef) and node.statement() is frame: + in_annotation_or_default = ( + node in frame.args.annotations + or node in frame.args.kwonlyargs_annotations + or node is frame.args.varargannotation + or node is frame.args.kwargannotation + ) or frame.args.parent_of(node) + return in_annotation_or_default + + @staticmethod + def _is_variable_violation( + node, + name, + defnode, + stmt, + defstmt, + frame, + defframe, + base_scope_type, + recursive_klass, + ): + # pylint: disable=too-many-nested-blocks + # node: Node to check for violation + # name: name of node to check violation for + # frame: Scope of statement of node + # base_scope_type: local scope type + maybee0601 = True + annotation_return = False + use_outer_definition = False + if frame is not defframe: + maybee0601 = _detect_global_scope(node, frame, defframe) + elif defframe.parent is None: + # we are at the module level, check the name is not + # defined in builtins + if name in defframe.scope_attrs or astroid.builtin_lookup(name)[1]: + maybee0601 = False + else: + # we are in a local scope, check the name is not + # defined in global or builtin scope + # skip this lookup if name is assigned later in function scope/lambda + # Note: the node.frame() is not the same as the `frame` argument which is + # equivalent to frame.statement().scope() + forbid_lookup = ( + isinstance(frame, astroid.FunctionDef) + or isinstance(node.frame(), astroid.Lambda) + ) and _assigned_locally(node) + if not forbid_lookup and defframe.root().lookup(name)[1]: + maybee0601 = False + use_outer_definition = stmt == defstmt and not isinstance( + defnode, astroid.node_classes.Comprehension + ) + else: + # check if we have a nonlocal + if name in defframe.locals: + maybee0601 = not any( + isinstance(child, astroid.Nonlocal) and name in child.names + for child in defframe.get_children() + ) + + if ( + base_scope_type == "lambda" + and isinstance(frame, astroid.ClassDef) + and name in frame.locals + ): + + # This rule verifies that if the definition node of the + # checked name is an Arguments node and if the name + # is used a default value in the arguments defaults + # and the actual definition of the variable label + # is happening before the Arguments definition. + # + # bar = None + # foo = lambda bar=bar: bar + # + # In this case, maybee0601 should be False, otherwise + # it should be True. + maybee0601 = not ( + isinstance(defnode, astroid.Arguments) + and node in defnode.defaults + and frame.locals[name][0].fromlineno < defstmt.fromlineno + ) + elif isinstance(defframe, astroid.ClassDef) and isinstance( + frame, astroid.FunctionDef + ): + # Special rule for function return annotations, + # which uses the same name as the class where + # the function lives. + if node is frame.returns and defframe.parent_of(frame.returns): + maybee0601 = annotation_return = True + + if ( + maybee0601 + and defframe.name in defframe.locals + and defframe.locals[name][0].lineno < frame.lineno + ): + # Detect class assignments with the same + # name as the class. In this case, no warning + # should be raised. + maybee0601 = False + if isinstance(node.parent, astroid.Arguments): + maybee0601 = stmt.fromlineno <= defstmt.fromlineno + elif recursive_klass: + maybee0601 = True + else: + maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno + if maybee0601 and stmt.fromlineno == defstmt.fromlineno: + if ( + isinstance(defframe, astroid.FunctionDef) + and frame is defframe + and defframe.parent_of(node) + and stmt is not defstmt + ): + # Single statement function, with the statement on the + # same line as the function definition + maybee0601 = False + + # Look for type checking definitions inside a type checking guard. + if isinstance(defstmt, (astroid.Import, astroid.ImportFrom)): + defstmt_parent = defstmt.parent + + if ( + isinstance(defstmt_parent, astroid.If) + and defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS + ): + # Exempt those definitions that are used inside the type checking + # guard or that are defined in both type checking guard branches. + used_in_branch = defstmt_parent.parent_of(node) + defined_in_or_else = False + + for definition in defstmt_parent.orelse: + if isinstance(definition, astroid.Assign): + defined_in_or_else = any( + target.name == name for target in definition.targets + ) + if defined_in_or_else: + break + + if not used_in_branch and not defined_in_or_else: + maybee0601 = True + + return maybee0601, annotation_return, use_outer_definition + + def _ignore_class_scope(self, node): + """ + Return True if the node is in a local class scope, as an assignment. + + :param node: Node considered + :type node: astroid.Node + :return: True if the node is in a local class scope, as an assignment. False otherwise. + :rtype: bool + """ + # Detect if we are in a local class scope, as an assignment. + # For example, the following is fair game. + # + # class A: + # b = 1 + # c = lambda b=b: b * b + # + # class B: + # tp = 1 + # def func(self, arg: tp): + # ... + # class C: + # tp = 2 + # def func(self, arg=tp): + # ... + + name = node.name + frame = node.statement().scope() + in_annotation_or_default = self._defined_in_function_definition(node, frame) + if in_annotation_or_default: + frame_locals = frame.parent.scope().locals + else: + frame_locals = frame.locals + return not ( + (isinstance(frame, astroid.ClassDef) or in_annotation_or_default) + and name in frame_locals + ) + + def _loopvar_name(self, node, name): + # filter variables according to node's scope + if not self.linter.is_message_enabled("undefined-loop-variable"): + return + astmts = [stmt for stmt in node.lookup(name)[1] if hasattr(stmt, "assign_type")] + # If this variable usage exists inside a function definition + # that exists in the same loop, + # the usage is safe because the function will not be defined either if + # the variable is not defined. + scope = node.scope() + if isinstance(scope, astroid.FunctionDef) and any( + asmt.statement().parent_of(scope) for asmt in astmts + ): + return + + # filter variables according their respective scope test is_statement + # and parent to avoid #74747. This is not a total fix, which would + # introduce a mechanism similar to special attribute lookup in + # modules. Also, in order to get correct inference in this case, the + # scope lookup rules would need to be changed to return the initial + # assignment (which does not exist in code per se) as well as any later + # modifications. + if ( + not astmts + or (astmts[0].is_statement or astmts[0].parent) + and astmts[0].statement().parent_of(node) + ): + _astmts = [] + else: + _astmts = astmts[:1] + for i, stmt in enumerate(astmts[1:]): + if astmts[i].statement().parent_of(stmt) and not in_for_else_branch( + astmts[i].statement(), stmt + ): + continue + _astmts.append(stmt) + astmts = _astmts + if len(astmts) != 1: + return + + assign = astmts[0].assign_type() + if not ( + isinstance( + assign, (astroid.For, astroid.Comprehension, astroid.GeneratorExp) + ) + and assign.statement() is not node.statement() + ): + return + + # For functions we can do more by inferring the length of the itered object + if not isinstance(assign, astroid.For): + self.add_message("undefined-loop-variable", args=name, node=node) + return + + try: + inferred = next(assign.iter.infer()) + except astroid.InferenceError: + self.add_message("undefined-loop-variable", args=name, node=node) + else: + if ( + isinstance(inferred, astroid.Instance) + and inferred.qname() == BUILTIN_RANGE + ): + # Consider range() objects safe, even if they might not yield any results. + return + + # Consider sequences. + sequences = ( + astroid.List, + astroid.Tuple, + astroid.Dict, + astroid.Set, + objects.FrozenSet, + ) + if not isinstance(inferred, sequences): + self.add_message("undefined-loop-variable", args=name, node=node) + return + + elements = getattr(inferred, "elts", getattr(inferred, "items", [])) + if not elements: + self.add_message("undefined-loop-variable", args=name, node=node) + + def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names): + # pylint: disable=too-many-branches + # Ignore some special names specified by user configuration. + if self._is_name_ignored(stmt, name): + return + # Ignore names that were added dynamically to the Function scope + if ( + isinstance(node, astroid.FunctionDef) + and name == "__class__" + and len(node.locals["__class__"]) == 1 + and isinstance(node.locals["__class__"][0], astroid.ClassDef) + ): + return + + # Ignore names imported by the global statement. + if isinstance(stmt, (astroid.Global, astroid.Import, astroid.ImportFrom)): + # Detect imports, assigned to global statements. + if global_names and _import_name_is_global(stmt, global_names): + return + + argnames = list( + itertools.chain(node.argnames(), [arg.name for arg in node.args.kwonlyargs]) + ) + # Care about functions with unknown argument (builtins) + if name in argnames: + self._check_unused_arguments(name, node, stmt, argnames) + else: + if stmt.parent and isinstance( + stmt.parent, (astroid.Assign, astroid.AnnAssign) + ): + if name in nonlocal_names: + return + + qname = asname = None + if isinstance(stmt, (astroid.Import, astroid.ImportFrom)): + # Need the complete name, which we don't have in .locals. + if len(stmt.names) > 1: + import_names = next( + (names for names in stmt.names if name in names), None + ) + else: + import_names = stmt.names[0] + if import_names: + qname, asname = import_names + name = asname or qname + + if _has_locals_call_after_node(stmt, node.scope()): + message_name = "possibly-unused-variable" + else: + if isinstance(stmt, astroid.Import): + if asname is not None: + msg = "%s imported as %s" % (qname, asname) + else: + msg = "import %s" % name + self.add_message("unused-import", args=msg, node=stmt) + return + if isinstance(stmt, astroid.ImportFrom): + if asname is not None: + msg = "%s imported from %s as %s" % ( + qname, + stmt.modname, + asname, + ) + else: + msg = "%s imported from %s" % (name, stmt.modname) + self.add_message("unused-import", args=msg, node=stmt) + return + message_name = "unused-variable" + + # Don't check function stubs created only for type information + if utils.is_overload_stub(node): + return + + self.add_message(message_name, args=name, node=stmt) + + def _is_name_ignored(self, stmt, name): + authorized_rgx = self.config.dummy_variables_rgx + if ( + isinstance(stmt, astroid.AssignName) + and isinstance(stmt.parent, astroid.Arguments) + or isinstance(stmt, astroid.Arguments) + ): + regex = self.config.ignored_argument_names + else: + regex = authorized_rgx + return regex and regex.match(name) + + def _check_unused_arguments(self, name, node, stmt, argnames): + is_method = node.is_method() + klass = node.parent.frame() + if is_method and isinstance(klass, astroid.ClassDef): + confidence = ( + INFERENCE if utils.has_known_bases(klass) else INFERENCE_FAILURE + ) + else: + confidence = HIGH + + if is_method: + # Don't warn for the first argument of a (non static) method + if node.type != "staticmethod" and name == argnames[0]: + return + # Don't warn for argument of an overridden method + overridden = overridden_method(klass, node.name) + if overridden is not None and name in overridden.argnames(): + return + if node.name in utils.PYMETHODS and node.name not in ( + "__init__", + "__new__", + ): + return + # Don't check callback arguments + if any( + node.name.startswith(cb) or node.name.endswith(cb) + for cb in self.config.callbacks + ): + return + # Don't check arguments of singledispatch.register function. + if utils.is_registered_in_singledispatch_function(node): + return + + # Don't check function stubs created only for type information + if utils.is_overload_stub(node): + return + + # Don't check protocol classes + if utils.is_protocol_class(klass): + return + + self.add_message("unused-argument", args=name, node=stmt, confidence=confidence) + + def _check_late_binding_closure(self, node, assignment_node): + def _is_direct_lambda_call(): + return ( + isinstance(node_scope.parent, astroid.Call) + and node_scope.parent.func is node_scope + ) + + node_scope = node.scope() + if not isinstance(node_scope, (astroid.Lambda, astroid.FunctionDef)): + return + if isinstance(node.parent, astroid.Arguments): + return + + if isinstance(assignment_node, astroid.Comprehension): + if assignment_node.parent.parent_of(node.scope()): + self.add_message("cell-var-from-loop", node=node, args=node.name) + else: + assign_scope = assignment_node.scope() + maybe_for = assignment_node + while not isinstance(maybe_for, astroid.For): + if maybe_for is assign_scope: + break + maybe_for = maybe_for.parent + else: + if ( + maybe_for.parent_of(node_scope) + and not _is_direct_lambda_call() + and not isinstance(node_scope.statement(), astroid.Return) + ): + self.add_message("cell-var-from-loop", node=node, args=node.name) + + def _should_ignore_redefined_builtin(self, stmt): + if not isinstance(stmt, astroid.ImportFrom): + return False + return stmt.modname in self.config.redefining_builtins_modules + + def _has_homonym_in_upper_function_scope(self, node, index): + """ + Return True if there is a node with the same name in the to_consume dict of an upper scope + and if that scope is a function + + :param node: node to check for + :type node: astroid.Node + :param index: index of the current consumer inside self._to_consume + :type index: int + :return: True if there is a node with the same name in the to_consume dict of an upper scope + and if that scope is a function + :rtype: bool + """ + for _consumer in self._to_consume[index - 1 :: -1]: + if _consumer.scope_type == "function" and node.name in _consumer.to_consume: + return True + return False + + def _store_type_annotation_node(self, type_annotation): + """Given a type annotation, store all the name nodes it refers to""" + if isinstance(type_annotation, astroid.Name): + self._type_annotation_names.append(type_annotation.name) + return + + if not isinstance(type_annotation, astroid.Subscript): + return + + if ( + isinstance(type_annotation.value, astroid.Attribute) + and isinstance(type_annotation.value.expr, astroid.Name) + and type_annotation.value.expr.name == TYPING_MODULE + ): + self._type_annotation_names.append(TYPING_MODULE) + return + + self._type_annotation_names.extend( + annotation.name + for annotation in type_annotation.nodes_of_class(astroid.Name) + ) + + def _store_type_annotation_names(self, node): + type_annotation = node.type_annotation + if not type_annotation: + return + self._store_type_annotation_node(node.type_annotation) + + def _check_self_cls_assign(self, node): + """Check that self/cls don't get assigned""" + assign_names = { + target.name + for target in node.targets + if isinstance(target, astroid.AssignName) + } + scope = node.scope() + nonlocals_with_same_name = any( + child + for child in scope.body + if isinstance(child, astroid.Nonlocal) and assign_names & set(child.names) + ) + if nonlocals_with_same_name: + scope = node.scope().parent.scope() + + if not ( + isinstance(scope, astroid.scoped_nodes.FunctionDef) + and scope.is_method() + and "builtins.staticmethod" not in scope.decoratornames() + ): + return + argument_names = scope.argnames() + if not argument_names: + return + self_cls_name = argument_names[0] + target_assign_names = ( + target.name + for target in node.targets + if isinstance(target, astroid.node_classes.AssignName) + ) + if self_cls_name in target_assign_names: + self.add_message("self-cls-assignment", node=node, args=(self_cls_name)) + + def _check_unpacking(self, inferred, node, targets): + """ Check for unbalanced tuple unpacking + and unpacking non sequences. + """ + if utils.is_inside_abstract_class(node): + return + if utils.is_comprehension(node): + return + if inferred is astroid.Uninferable: + return + if ( + isinstance(inferred.parent, astroid.Arguments) + and isinstance(node.value, astroid.Name) + and node.value.name == inferred.parent.vararg + ): + # Variable-length argument, we can't determine the length. + return + if isinstance(inferred, (astroid.Tuple, astroid.List)): + # attempt to check unpacking is properly balanced + values = inferred.itered() + if len(targets) != len(values): + # Check if we have starred nodes. + if any(isinstance(target, astroid.Starred) for target in targets): + return + self.add_message( + "unbalanced-tuple-unpacking", + node=node, + args=( + _get_unpacking_extra_info(node, inferred), + len(targets), + len(values), + ), + ) + # attempt to check unpacking may be possible (ie RHS is iterable) + else: + if not utils.is_iterable(inferred): + self.add_message( + "unpacking-non-sequence", + node=node, + args=(_get_unpacking_extra_info(node, inferred),), + ) + + def _check_module_attrs(self, node, module, module_names): + """check that module_names (list of string) are accessible through the + given module + if the latest access name corresponds to a module, return it + """ + assert isinstance(module, astroid.Module), module + while module_names: + name = module_names.pop(0) + if name == "__dict__": + module = None + break + try: + module = next(module.getattr(name)[0].infer()) + if module is astroid.Uninferable: + return None + except astroid.NotFoundError: + if module.name in self._ignored_modules: + return None + self.add_message( + "no-name-in-module", args=(name, module.name), node=node + ) + return None + except astroid.InferenceError: + return None + if module_names: + modname = module.name if module else "__dict__" + self.add_message( + "no-name-in-module", node=node, args=(".".join(module_names), modname) + ) + return None + if isinstance(module, astroid.Module): + return module + return None + + def _check_all(self, node, not_consumed): + assigned = next(node.igetattr("__all__")) + if assigned is astroid.Uninferable: + return + + for elt in getattr(assigned, "elts", ()): + try: + elt_name = next(elt.infer()) + except astroid.InferenceError: + continue + if elt_name is astroid.Uninferable: + continue + if not elt_name.parent: + continue + + if not isinstance(elt_name, astroid.Const) or not isinstance( + elt_name.value, str + ): + self.add_message("invalid-all-object", args=elt.as_string(), node=elt) + continue + + elt_name = elt_name.value + # If elt is in not_consumed, remove it from not_consumed + if elt_name in not_consumed: + del not_consumed[elt_name] + continue + + if elt_name not in node.locals: + if not node.package: + self.add_message( + "undefined-all-variable", args=(elt_name,), node=elt + ) + else: + basename = os.path.splitext(node.file)[0] + if os.path.basename(basename) == "__init__": + name = node.name + "." + elt_name + try: + modutils.file_from_modpath(name.split(".")) + except ImportError: + self.add_message( + "undefined-all-variable", args=(elt_name,), node=elt + ) + except SyntaxError: + # don't yield a syntax-error warning, + # because it will be later yielded + # when the file will be checked + pass + + def _check_globals(self, not_consumed): + if self._allow_global_unused_variables: + return + for name, nodes in not_consumed.items(): + for node in nodes: + self.add_message("unused-variable", args=(name,), node=node) + + def _check_imports(self, not_consumed): + local_names = _fix_dot_imports(not_consumed) + checked = set() + for name, stmt in local_names: + for imports in stmt.names: + real_name = imported_name = imports[0] + if imported_name == "*": + real_name = name + as_name = imports[1] + if real_name in checked: + continue + if name not in (real_name, as_name): + continue + checked.add(real_name) + + if isinstance(stmt, astroid.Import) or ( + isinstance(stmt, astroid.ImportFrom) and not stmt.modname + ): + if isinstance(stmt, astroid.ImportFrom) and SPECIAL_OBJ.search( + imported_name + ): + # Filter special objects (__doc__, __all__) etc., + # because they can be imported for exporting. + continue + + if imported_name in self._type_annotation_names: + # Most likely a typing import if it wasn't used so far. + continue + + if as_name == "_": + continue + if as_name is None: + msg = "import %s" % imported_name + else: + msg = "%s imported as %s" % (imported_name, as_name) + if not _is_type_checking_import(stmt): + self.add_message("unused-import", args=msg, node=stmt) + elif isinstance(stmt, astroid.ImportFrom) and stmt.modname != FUTURE: + if SPECIAL_OBJ.search(imported_name): + # Filter special objects (__doc__, __all__) etc., + # because they can be imported for exporting. + continue + + if _is_from_future_import(stmt, name): + # Check if the name is in fact loaded from a + # __future__ import in another module. + continue + + if imported_name in self._type_annotation_names: + # Most likely a typing import if it wasn't used so far. + continue + + if imported_name == "*": + self.add_message("unused-wildcard-import", args=name, node=stmt) + else: + if as_name is None: + msg = "%s imported from %s" % (imported_name, stmt.modname) + else: + fields = (imported_name, stmt.modname, as_name) + msg = "%s imported from %s as %s" % fields + if not _is_type_checking_import(stmt): + self.add_message("unused-import", args=msg, node=stmt) + del self._to_consume + + def _check_metaclasses(self, node): + """ Update consumption analysis for metaclasses. """ + consumed = [] # [(scope_locals, consumed_key)] + + for child_node in node.get_children(): + if isinstance(child_node, astroid.ClassDef): + consumed.extend(self._check_classdef_metaclasses(child_node, node)) + + # Pop the consumed items, in order to avoid having + # unused-import and unused-variable false positives + for scope_locals, name in consumed: + scope_locals.pop(name, None) + + def _check_classdef_metaclasses(self, klass, parent_node): + if not klass._metaclass: + # Skip if this class doesn't use explicitly a metaclass, but inherits it from ancestors + return [] + + consumed = [] # [(scope_locals, consumed_key)] + metaclass = klass.metaclass() + + name = None + if isinstance(klass._metaclass, astroid.Name): + name = klass._metaclass.name + elif metaclass: + name = metaclass.root().name + + found = None + name = METACLASS_NAME_TRANSFORMS.get(name, name) + if name: + # check enclosing scopes starting from most local + for scope_locals, _, _ in self._to_consume[::-1]: + found = scope_locals.get(name) + if found: + consumed.append((scope_locals, name)) + break + + if found is None and not metaclass: + name = None + if isinstance(klass._metaclass, astroid.Name): + name = klass._metaclass.name + elif isinstance(klass._metaclass, astroid.Attribute): + name = klass._metaclass.as_string() + + if name is not None: + if not ( + name in astroid.Module.scope_attrs + or utils.is_builtin(name) + or name in self.config.additional_builtins + or name in parent_node.locals + ): + self.add_message("undefined-variable", node=klass, args=(name,)) + + return consumed + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(VariablesChecker(linter)) |