From 1daee40d6f1cf3d379396dc272c123d987ad4cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 18 Oct 2021 14:58:01 +0200 Subject: Make ``undefined-variable`` check line numbers of metaclass parameters --- ChangeLog | 5 +++++ doc/whatsnew/2.12.rst | 5 +++++ pylint/checkers/variables.py | 19 +++++++++++++------ .../functional/u/undefined/undefined_variable_py30.py | 10 ++++++++++ .../u/undefined/undefined_variable_py30.txt | 1 + 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index f9eba4227..f26007d1e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,6 +26,11 @@ Release date: TBA Closes #5140 +* ``undefined-variable`` now correctly considers the line numbering and order of classes + used in metaclass declarations + + Closes #4031 + * Fix bug with importing namespace packages with relative imports Closes #2967 and #5131 diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst index 54214ac99..be1e5245c 100644 --- a/doc/whatsnew/2.12.rst +++ b/doc/whatsnew/2.12.rst @@ -69,3 +69,8 @@ Other Changes and never get assigned a value Closes #5140 + +* ``undefined-variable`` now correctly considers the line numbering and order of classes + used in metaclass declarations + + Closes #4031 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index ef53f19cb..1d78184c8 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2213,23 +2213,30 @@ class VariablesChecker(BaseChecker): elif metaclass: name = metaclass.root().name - found = None + found = False 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)) + found_nodes = scope_locals.get(name, []) + for found_node in found_nodes: + if found_node.lineno <= klass.lineno: + consumed.append((scope_locals, name)) + found = True + break + # Check parent scope + nodes_in_parent_scope = parent_node.locals.get(name, []) + for found_node_parent in nodes_in_parent_scope: + if found_node_parent.lineno <= klass.lineno: + found = True break if ( - found is None + not found and not metaclass and not ( name in nodes.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,)) diff --git a/tests/functional/u/undefined/undefined_variable_py30.py b/tests/functional/u/undefined/undefined_variable_py30.py index 556b1028b..b9b64ec5e 100644 --- a/tests/functional/u/undefined/undefined_variable_py30.py +++ b/tests/functional/u/undefined/undefined_variable_py30.py @@ -96,3 +96,13 @@ class MetaClass(type): class InheritingClass(metaclass=MetaClass, parameter=variable): # [undefined-variable] pass + + +# Test for #4031 +# https://github.com/PyCQA/pylint/issues/4031 +class Inheritor(metaclass=DefinedTooLate ): # [undefined-variable] + pass + + +class DefinedTooLate(): + pass diff --git a/tests/functional/u/undefined/undefined_variable_py30.txt b/tests/functional/u/undefined/undefined_variable_py30.txt index 4756b5cd2..e0a942202 100644 --- a/tests/functional/u/undefined/undefined_variable_py30.txt +++ b/tests/functional/u/undefined/undefined_variable_py30.txt @@ -7,3 +7,4 @@ undefined-variable:57:31:FalsePositive342.test_bad2:Undefined variable 'trop2':H undefined-variable:63:0:Bad:Undefined variable 'ABCMet':HIGH undefined-variable:66:0:SecondBad:Undefined variable 'ab':HIGH undefined-variable:97:53:InheritingClass:Undefined variable 'variable':HIGH +undefined-variable:103:0:Inheritor:Undefined variable 'DefinedTooLate':HIGH -- cgit v1.2.1