summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2019-09-23 09:30:51 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2019-09-23 09:30:51 +0200
commitb49c2817d19636ef8c9ad6e1f2894e7a62c16a76 (patch)
treeae934e7a15996f83b2c4de4a0b1c899cb657deee
parentd39f7abc50bebd46bec51d4776b622b5176d3420 (diff)
downloadpylint-git-b49c2817d19636ef8c9ad6e1f2894e7a62c16a76.tar.gz
Move private functions at the end of the variables checker class to make it more manageable
-rw-r--r--pylint/checkers/variables.py1145
1 files changed, 574 insertions, 571 deletions
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 70acb9ff6..a9b8d4f2b 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -330,6 +330,14 @@ def _assigned_locally(name_node):
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,
@@ -657,19 +665,6 @@ class VariablesChecker(BaseChecker):
self._type_annotation_names = []
self._postponed_evaluation_enabled = False
- # 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)
-
@utils.check_messages("redefined-outer-name")
def visit_for(self, node):
assigned_to = [
@@ -738,133 +733,6 @@ class VariablesChecker(BaseChecker):
self._check_imports(not_consumed)
- 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
-
- @staticmethod
- 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 _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 self._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 self._is_type_checking_import(stmt):
- self.add_message("unused-import", args=msg, node=stmt)
- del self._to_consume
-
def visit_classdef(self, node):
"""visit class: update consumption analysis variable
"""
@@ -963,136 +831,6 @@ class VariablesChecker(BaseChecker):
# do not print Redefining builtin for additional builtins
self.add_message("redefined-builtin", 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_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 leave_functiondef(self, node):
"""leave function: check function's locals are consumed"""
if node.type_comment_returns:
@@ -1178,124 +916,6 @@ class VariablesChecker(BaseChecker):
if default_message:
self.add_message("global-statement", node=node)
- 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 _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 _should_ignore_redefined_builtin(self, stmt):
- if not isinstance(stmt, astroid.ImportFrom):
- return False
- return stmt.modname in self.config.redefining_builtins_modules
-
def visit_assignname(self, node):
if isinstance(node.assign_type(), astroid.AugAssign):
self.visit_name(node)
@@ -1303,168 +923,6 @@ class VariablesChecker(BaseChecker):
def visit_delname(self, node):
self.visit_name(node)
- @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,
- ):
- # 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
- if isinstance(defstmt, (astroid.Import, astroid.ImportFrom)):
- defstmt_parent = defstmt.parent
- if isinstance(defstmt_parent, astroid.If):
- if defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS:
- 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
- )
-
@utils.check_messages(*MSGS)
def visit_name(self, node):
"""check that a name is defined if the current scope and doesn't
@@ -1666,24 +1124,6 @@ class VariablesChecker(BaseChecker):
if not utils.node_ignores_exception(node, NameError):
self.add_message("undefined-variable", args=name, node=node)
- 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
-
@utils.check_messages("no-name-in-module")
def visit_import(self, node):
"""check modules attribute accesses"""
@@ -1741,6 +1181,453 @@ class VariablesChecker(BaseChecker):
except astroid.InferenceError:
return
+ def leave_assign(self, node):
+ self._store_type_annotation_names(node)
+
+ def leave_with(self, node):
+ self._store_type_annotation_names(node)
+
+ # 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,
+ ):
+ # 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
+ if isinstance(defstmt, (astroid.Import, astroid.ImportFrom)):
+ defstmt_parent = defstmt.parent
+ if isinstance(defstmt_parent, astroid.If):
+ if defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS:
+ 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 (
@@ -1774,9 +1661,6 @@ class VariablesChecker(BaseChecker):
return
self._store_type_annotation_node(node.type_annotation)
- leave_assign = _store_type_annotation_names
- leave_with = _store_type_annotation_names
-
def _check_self_cls_assign(self, node):
"""Check that self/cls don't get assigned"""
assign_names = {
@@ -1887,6 +1771,125 @@ class VariablesChecker(BaseChecker):
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
+
class VariablesChecker3k(VariablesChecker):
"""Modified variables checker for 3k"""