Skip to content

Commit 23c90df

Browse files
fix: avoid false positive when module-level names match class-level names
- Add scope comparision to avoid module-level constants to be incorrectly classified as variables when a class-level attribute with the same name exists Closes #10719
1 parent 4f0716a commit 23c90df

File tree

5 files changed

+49
-13
lines changed

5 files changed

+49
-13
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed false positive for ``invalid-name`` where module-level constants were incorrectly classified as variables when a class-level attribute with the same name exists.
2+
3+
Closes #10719

pylint/checkers/utils.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,24 +1847,42 @@ def is_reassigned_before_current(node: nodes.NodeNG, varname: str) -> bool:
18471847
"""Check if the given variable name is reassigned in the same scope before the
18481848
current node.
18491849
"""
1850-
return any(
1851-
a.name == varname and a.lineno < node.lineno
1852-
for a in node.scope().nodes_of_class(
1853-
(nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
1854-
)
1855-
)
1850+
node_scope = node.scope()
1851+
node_lineno = node.lineno
1852+
if node_lineno is None:
1853+
return False
1854+
for a in node_scope.nodes_of_class(
1855+
(nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
1856+
):
1857+
if a.name == varname and a.lineno is not None and a.lineno < node_lineno:
1858+
if isinstance(a, (nodes.ClassDef, nodes.FunctionDef)):
1859+
if a.parent and a.parent.scope() == node_scope:
1860+
return True
1861+
else:
1862+
if a.scope() == node_scope:
1863+
return True
1864+
return False
18561865

18571866

18581867
def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool:
18591868
"""Check if the given variable name is reassigned in the same scope after the
18601869
current node.
18611870
"""
1862-
return any(
1863-
a.name == varname and a.lineno > node.lineno
1864-
for a in node.scope().nodes_of_class(
1865-
(nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
1866-
)
1867-
)
1871+
node_scope = node.scope()
1872+
node_lineno = node.lineno
1873+
if node_lineno is None:
1874+
return False
1875+
for a in node_scope.nodes_of_class(
1876+
(nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
1877+
):
1878+
if a.name == varname and a.lineno is not None and a.lineno > node_lineno:
1879+
if isinstance(a, (nodes.ClassDef, nodes.FunctionDef)):
1880+
if a.parent and a.parent.scope() == node_scope:
1881+
return True
1882+
else:
1883+
if a.scope() == node_scope:
1884+
return True
1885+
return False
18681886

18691887

18701888
def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool:
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Test module-level constants with class attribute same name
2+
Regression test for #10719.
3+
"""
4+
# pylint: disable=missing-docstring, too-few-public-methods, redefined-builtin
5+
6+
7+
class Theme:
8+
INPUT = ">>> "
9+
10+
11+
INPUT = Theme()
12+
input = Theme()
13+
OUTPUT = Theme()
14+
output = Theme()

tests/functional/n/no/no_dummy_redefined.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Make sure warnings about redefinitions do not trigger for dummy variables."""
2+
# pylint: disable=invalid-name
23

34

45
_, INTERESTING = 'a=b'.split('=')
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
redefined-outer-name:11:4:11:9:clobbering:Redefining name 'value' from outer scope (line 6):UNDEFINED
1+
redefined-outer-name:12:4:12:9:clobbering:Redefining name 'value' from outer scope (line 7):UNDEFINED

0 commit comments

Comments
 (0)