Skip to content

Commit 46fe6c6

Browse files
Apply variable name regex to module-level names that are reassigned
1 parent 8138620 commit 46fe6c6

File tree

92 files changed

+552
-514
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+552
-514
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
`invalid-name` now distinguishes module-level names that are assigned only once
2+
from those that are reassigned and now applies `--variable-rgx` to the latter.
3+
4+
Also, `invalid-name` is triggered for module-level names for additional types
5+
(e.g. lists and sets).
6+
7+
Closes #3585

pylint/checkers/base/name_checker/checker.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -461,14 +461,28 @@ def visit_assignname( # pylint: disable=too-many-branches
461461
elif isinstance(inferred_assign_type, nodes.ClassDef):
462462
self._check_name("class", node.name, node)
463463

464-
# Don't emit if the name redefines an import in an ImportError except handler.
465-
elif not _redefines_import(node) and isinstance(
466-
inferred_assign_type, nodes.Const
464+
# Don't emit if the name redefines an import in an ImportError except handler
465+
# nor any other reassignment.
466+
elif (
467+
not (redefines_import := _redefines_import(node))
468+
and not isinstance(
469+
inferred_assign_type, (nodes.FunctionDef, nodes.Lambda)
470+
)
471+
and not utils.is_reassigned_before_current(node, node.name)
472+
and not utils.is_reassigned_after_current(node, node.name)
473+
and not utils.get_node_first_ancestor_of_type(
474+
node, (nodes.For, nodes.While)
475+
)
467476
):
468477
self._check_name("const", node.name, node)
469-
else:
478+
elif inferred_assign_type is not None and not isinstance(
479+
inferred_assign_type, astroid.util.UninferableBase
480+
):
470481
self._check_name(
471-
"variable", node.name, node, disallowed_check_only=True
482+
"variable",
483+
node.name,
484+
node,
485+
disallowed_check_only=redefines_import,
472486
)
473487

474488
# Check names defined in AnnAssign nodes
@@ -608,11 +622,11 @@ def _assigns_typevar(node: nodes.NodeNG | None) -> bool:
608622
def _assigns_typealias(node: nodes.NodeNG | None) -> bool:
609623
"""Check if a node is assigning a TypeAlias."""
610624
inferred = utils.safe_infer(node)
611-
if isinstance(inferred, nodes.ClassDef):
625+
if isinstance(inferred, (nodes.ClassDef, astroid.bases.UnionType)):
612626
qname = inferred.qname()
613627
if qname == "typing.TypeAlias":
614628
return True
615-
if qname == ".Union":
629+
if qname in {".Union", "builtins.UnionType"}:
616630
# Union is a special case because it can be used as a type alias
617631
# or as a type annotation. We only want to check the former.
618632
assert node is not None

pylint/checkers/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,18 @@ def is_sys_guard(node: nodes.If) -> bool:
18651865
return False
18661866

18671867

1868+
def is_reassigned_before_current(node: nodes.NodeNG, varname: str) -> bool:
1869+
"""Check if the given variable name is reassigned in the same scope before the
1870+
current node.
1871+
"""
1872+
return any(
1873+
a.name == varname and a.lineno < node.lineno
1874+
for a in node.scope().nodes_of_class(
1875+
(nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
1876+
)
1877+
)
1878+
1879+
18681880
def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool:
18691881
"""Check if the given variable name is reassigned in the same scope after the
18701882
current node.

tests/functional/a/arguments.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ def function_default_arg(one=1, two=2):
6262
function_default_arg(1, one=5) # [redundant-keyword-arg]
6363

6464
# Remaining tests are for coverage of correct names in messages.
65-
LAMBDA = lambda arg: 1
65+
my_lambda = lambda arg: 1
6666

67-
LAMBDA() # [no-value-for-parameter]
67+
my_lambda() # [no-value-for-parameter]
6868

6969
def method_tests():
7070
"""Method invocations."""
@@ -260,7 +260,7 @@ def func(one, two, three):
260260
return one + two + three
261261

262262

263-
CALL = lambda *args: func(*args)
263+
call = lambda *args: func(*args)
264264

265265

266266
# Ensure `too-many-function-args` is not emitted when a function call is assigned
@@ -275,9 +275,9 @@ def _print_selection(self):
275275
pick_apple = _pick_fruit("apple")
276276
pick_pear = _pick_fruit("pear")
277277

278-
picker = FruitPicker()
279-
picker.pick_apple()
280-
picker.pick_pear()
278+
PICKER = FruitPicker()
279+
PICKER.pick_apple()
280+
PICKER.pick_pear()
281281

282282

283283
def name1(apple, /, **kwargs):

tests/functional/a/arguments.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ no-value-for-parameter:59:0:59:21::No value for argument 'first_argument' in fun
99
unexpected-keyword-arg:59:0:59:21::Unexpected keyword argument 'bob' in function call:UNDEFINED
1010
unexpected-keyword-arg:60:0:60:40::Unexpected keyword argument 'coin' in function call:UNDEFINED
1111
redundant-keyword-arg:62:0:62:30::Argument 'one' passed by position and keyword in function call:UNDEFINED
12-
no-value-for-parameter:67:0:67:8::No value for argument 'arg' in lambda call:UNDEFINED
12+
no-value-for-parameter:67:0:67:11::No value for argument 'arg' in lambda call:UNDEFINED
1313
no-value-for-parameter:72:4:72:24:method_tests:No value for argument 'arg' in staticmethod call:UNDEFINED
1414
no-value-for-parameter:73:4:73:29:method_tests:No value for argument 'arg' in staticmethod call:UNDEFINED
1515
no-value-for-parameter:75:4:75:23:method_tests:No value for argument 'arg' in classmethod call:UNDEFINED

tests/functional/a/assignment/assignment_from_no_return_2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# pylint: disable=useless-return, condition-evals-to-constant
1+
# pylint: disable=useless-return, condition-evals-to-constant, invalid-name
22
"""check assignment to function call where the function doesn't return
33
44
'E1111': ('Assigning to function call which doesn\'t return',

tests/functional/c/condition_evals_to_constant.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def func(_):
2020
while CONSTANT and False: # [condition-evals-to-constant]
2121
break
2222
1 if CONSTANT or True else 2 # [condition-evals-to-constant]
23-
z = [x for x in range(10) if x or True] # [condition-evals-to-constant]
23+
Z = [x for x in range(10) if x or True] # [condition-evals-to-constant]
2424

2525
# Simplifies recursively
2626
assert True or CONSTANT or OTHER # [condition-evals-to-constant]

tests/functional/c/consider/consider_iterating_dictionary.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ def keys(self):
2626
(key for key in {}.keys()) # [consider-iterating-dictionary]
2727
{key for key in {}.keys()} # [consider-iterating-dictionary]
2828
{key: key for key in {}.keys()} # [consider-iterating-dictionary]
29-
COMP1 = [key for key in {}.keys()] # [consider-iterating-dictionary]
30-
COMP2 = (key for key in {}.keys()) # [consider-iterating-dictionary]
31-
COMP3 = {key for key in {}.keys()} # [consider-iterating-dictionary]
29+
comp1 = [key for key in {}.keys()] # [consider-iterating-dictionary]
30+
comp2 = (key for key in {}.keys()) # [consider-iterating-dictionary]
31+
comp3 = {key for key in {}.keys()} # [consider-iterating-dictionary]
3232
COMP4 = {key: key for key in {}.keys()} # [consider-iterating-dictionary]
3333
for key in {}.keys(): # [consider-iterating-dictionary]
3434
pass
3535

3636
# Issue #1247
3737
DICT = {'a': 1, 'b': 2}
38-
COMP1 = [k * 2 for k in DICT.keys()] + [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
39-
COMP2, COMP3 = [k * 2 for k in DICT.keys()], [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
38+
comp1 = [k * 2 for k in DICT.keys()] + [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
39+
comp2, comp3 = [k * 2 for k in DICT.keys()], [k * 3 for k in DICT.keys()] # [consider-iterating-dictionary,consider-iterating-dictionary]
4040
SOME_TUPLE = ([k * 2 for k in DICT.keys()], [k * 3 for k in DICT.keys()]) # [consider-iterating-dictionary,consider-iterating-dictionary]
4141

4242
# Checks for membership checks
@@ -62,21 +62,21 @@ def keys(self):
6262
pass
6363
if [1] == dict():
6464
pass
65-
VAR = 1 in {}.keys() # [consider-iterating-dictionary]
66-
VAR = 1 in {}
67-
VAR = 1 in dict()
68-
VAR = [1, 2] == {}.keys() in {False}
65+
var = 1 in {}.keys() # [consider-iterating-dictionary]
66+
var = 1 in {}
67+
var = 1 in dict()
68+
var = [1, 2] == {}.keys() in {False}
6969

7070
# Additional membership checks
7171
# See: https://github.com/pylint-dev/pylint/issues/5323
72-
metadata = {}
73-
if "a" not in list(metadata.keys()): # [consider-iterating-dictionary]
72+
METADATA = {}
73+
if "a" not in list(METADATA.keys()): # [consider-iterating-dictionary]
7474
print(1)
75-
if "a" not in metadata.keys(): # [consider-iterating-dictionary]
75+
if "a" not in METADATA.keys(): # [consider-iterating-dictionary]
7676
print(1)
77-
if "a" in list(metadata.keys()): # [consider-iterating-dictionary]
77+
if "a" in list(METADATA.keys()): # [consider-iterating-dictionary]
7878
print(1)
79-
if "a" in metadata.keys(): # [consider-iterating-dictionary]
79+
if "a" in METADATA.keys(): # [consider-iterating-dictionary]
8080
print(1)
8181

8282

@@ -93,24 +93,24 @@ def inner_function():
9393
return inner_function()
9494
return InnerClass().another_function()
9595

96-
a_dict = {"a": 1, "b": 2, "c": 3}
97-
a_set = {"c", "d"}
96+
A_DICT = {"a": 1, "b": 2, "c": 3}
97+
A_SET = {"c", "d"}
9898

9999
# Test bitwise operations. These should not raise msg because removing `.keys()`
100100
# either gives error or ends in a different result
101-
print(a_dict.keys() | a_set)
101+
print(A_DICT.keys() | A_SET)
102102

103-
if "a" in a_dict.keys() | a_set:
103+
if "a" in A_DICT.keys() | A_SET:
104104
pass
105105

106-
if "a" in a_dict.keys() & a_set:
106+
if "a" in A_DICT.keys() & A_SET:
107107
pass
108108

109-
if 1 in a_dict.keys() ^ [1, 2]:
109+
if 1 in A_DICT.keys() ^ [1, 2]:
110110
pass
111111

112-
if "a" in a_dict.keys() or a_set: # [consider-iterating-dictionary]
112+
if "a" in A_DICT.keys() or A_SET: # [consider-iterating-dictionary]
113113
pass
114114

115-
if "a" in a_dict.keys() and a_set: # [consider-iterating-dictionary]
115+
if "a" in A_DICT.keys() and A_SET: # [consider-iterating-dictionary]
116116
pass

tests/functional/c/consider/consider_using_dict_items.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,39 @@ def good():
2020
print(k)
2121

2222

23-
out_of_scope_dict = dict()
23+
OUT_OF_SCOPE_DICT = dict()
2424

2525

2626
def another_bad():
27-
for k in out_of_scope_dict: # [consider-using-dict-items]
28-
print(out_of_scope_dict[k])
27+
for k in OUT_OF_SCOPE_DICT: # [consider-using-dict-items]
28+
print(OUT_OF_SCOPE_DICT[k])
2929

3030

3131
def another_good():
32-
for k in out_of_scope_dict:
32+
for k in OUT_OF_SCOPE_DICT:
3333
k = 1
3434
k = 2
3535
k = 3
36-
print(out_of_scope_dict[k])
36+
print(OUT_OF_SCOPE_DICT[k])
3737

3838

39-
b_dict = {}
40-
for k2 in b_dict: # Should not emit warning, key access necessary
41-
b_dict[k2] = 2
39+
B_DICT = {}
40+
for k2 in B_DICT: # Should not emit warning, key access necessary
41+
B_DICT[k2] = 2
4242

43-
for k2 in b_dict: # Should not emit warning, key access necessary (AugAssign)
44-
b_dict[k2] += 2
43+
for k2 in B_DICT: # Should not emit warning, key access necessary (AugAssign)
44+
B_DICT[k2] += 2
4545

4646
# Warning should be emitted in this case
47-
for k6 in b_dict: # [consider-using-dict-items]
48-
val = b_dict[k6]
49-
b_dict[k6] = 2
47+
for k6 in B_DICT: # [consider-using-dict-items]
48+
val = B_DICT[k6]
49+
B_DICT[k6] = 2
5050

51-
for k3 in b_dict: # [consider-using-dict-items]
52-
val = b_dict[k3]
51+
for k3 in B_DICT: # [consider-using-dict-items]
52+
val = B_DICT[k3]
5353

54-
for k4 in b_dict.keys(): # [consider-iterating-dictionary,consider-using-dict-items]
55-
val = b_dict[k4]
54+
for k4 in B_DICT.keys(): # [consider-iterating-dictionary,consider-using-dict-items]
55+
val = B_DICT[k4]
5656

5757

5858
class Foo:
@@ -67,25 +67,25 @@ class Foo:
6767

6868
# Should NOT emit warning whey key used to access a different dict
6969
for k5 in Foo.c_dict: # This is fine
70-
val = b_dict[k5]
70+
val = B_DICT[k5]
7171

7272
for k5 in Foo.c_dict: # This is fine
7373
val = c_dict[k5]
7474

7575
# Should emit warning within a list/dict comprehension
76-
val = {k9: b_dict[k9] for k9 in b_dict} # [consider-using-dict-items]
77-
val = [(k7, b_dict[k7]) for k7 in b_dict] # [consider-using-dict-items]
76+
val = {k9: B_DICT[k9] for k9 in B_DICT} # [consider-using-dict-items]
77+
val = [(k7, B_DICT[k7]) for k7 in B_DICT] # [consider-using-dict-items]
7878

7979
# Should emit warning even when using dict attribute of a class within comprehension
8080
val = [(k7, Foo.c_dict[k7]) for k7 in Foo.c_dict] # [consider-using-dict-items]
8181
val = any(True for k8 in Foo.c_dict if Foo.c_dict[k8]) # [consider-using-dict-items]
8282

8383
# Should emit warning when dict access done in ``if`` portion of comprehension
84-
val = any(True for k8 in b_dict if b_dict[k8]) # [consider-using-dict-items]
84+
val = any(True for k8 in B_DICT if B_DICT[k8]) # [consider-using-dict-items]
8585

8686
# Should NOT emit warning whey key used to access a different dict
87-
val = [(k7, b_dict[k7]) for k7 in Foo.c_dict]
88-
val = any(True for k8 in Foo.c_dict if b_dict[k8])
87+
val = [(k7, B_DICT[k7]) for k7 in Foo.c_dict]
88+
val = any(True for k8 in Foo.c_dict if B_DICT[k8])
8989

9090
# Should NOT emit warning, essentially same check as above
9191
val = [(k7, c_dict[k7]) for k7 in Foo.c_dict]
@@ -97,20 +97,20 @@ class Foo:
9797
# Test false positive described in #4630
9898
# (https://github.com/pylint-dev/pylint/issues/4630)
9999

100-
d = {"key": "value"}
100+
D = {"key": "value"}
101101

102-
for k in d: # this is fine, with the reassignment of d[k], d[k] is necessary
103-
d[k] += "123"
104-
if "1" in d[k]: # index lookup necessary here, do not emit error
102+
for k in D: # this is fine, with the reassignment of d[k], d[k] is necessary
103+
D[k] += "123"
104+
if "1" in D[k]: # index lookup necessary here, do not emit error
105105
print("found 1")
106106

107-
for k in d: # if this gets rewritten to d.items(), we are back to the above problem
108-
d[k] = d[k] + 1
109-
if "1" in d[k]: # index lookup necessary here, do not emit error
107+
for k in D: # if this gets rewritten to d.items(), we are back to the above problem
108+
D[k] = D[k] + 1
109+
if "1" in D[k]: # index lookup necessary here, do not emit error
110110
print("found 1")
111111

112-
for k in d: # [consider-using-dict-items]
113-
if "1" in d[k]: # index lookup necessary here, do not emit error
112+
for k in D: # [consider-using-dict-items]
113+
if "1" in D[k]: # index lookup necessary here, do not emit error
114114
print("found 1")
115115

116116

tests/functional/c/consider/consider_using_enumerate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class MyClass:
7878
def __init__(self):
7979
self.my_list = []
8080

81-
my_obj = MyClass()
81+
MY_OBJ = MyClass()
8282
def my_function(instance: MyClass):
8383
for i in range(len(instance.my_list)): # [consider-using-enumerate]
8484
var = instance.my_list[i]

0 commit comments

Comments
 (0)