Skip to content

Commit c3aa8c9

Browse files
authored
Merge pull request #15 from PashaWNN/BANG_225__FIX_MULTIPLE_ASSIGNMENT_VALIDATION
BANG-225: fix validation of multiple assignments
2 parents 3db7cd9 + 30cfd19 commit c3aa8c9

File tree

6 files changed

+63
-22
lines changed

6 files changed

+63
-22
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,5 @@ venv.bak/
102102

103103
# mypy
104104
.mypy_cache/
105+
106+
.idea

flake8_variables_names/ast_helpers.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,46 @@
11
import ast
2+
import functools
23
from typing import List, Tuple, Union
34

45
from flake8_variables_names.list_helpers import flat
56

67

8+
@functools.singledispatch
9+
def extract_names_from_node(node) -> List[ast.Name]:
10+
return []
11+
12+
13+
@extract_names_from_node.register
14+
def _extract_names_from_name_node(node: ast.Name):
15+
return [node]
16+
17+
18+
@extract_names_from_node.register
19+
def _extract_names_from_assign_node(node: ast.Assign):
20+
return flat([extract_names_from_node(target) for target in node.targets])
21+
22+
23+
# in some versions of Python, singledispatch does not support `Union` in type annotations
24+
@extract_names_from_node.register(ast.AnnAssign)
25+
@extract_names_from_node.register(ast.For)
26+
def _extract_names_from_annassign_node(node):
27+
return extract_names_from_node(node.target)
28+
29+
30+
@extract_names_from_node.register
31+
def _extract_names_from_starred_node(node: ast.Starred):
32+
return extract_names_from_node(node.value)
33+
34+
35+
@extract_names_from_node.register
36+
def _extract_names_from_tuple_node(node: ast.Tuple):
37+
return flat([extract_names_from_node(elt) for elt in node.elts])
38+
39+
740
def get_var_names_from_assignment(
8-
assignment_node: Union[ast.Assign, ast.AnnAssign],
41+
assignment_node: Union[ast.Assign, ast.AnnAssign, ast.For],
942
) -> List[Tuple[str, ast.AST]]:
10-
if isinstance(assignment_node, ast.AnnAssign):
11-
if isinstance(assignment_node.target, ast.Name):
12-
return [(assignment_node.target.id, assignment_node.target)]
13-
elif isinstance(assignment_node, ast.Assign):
14-
names = [t for t in assignment_node.targets if isinstance(t, ast.Name)]
15-
return [(n.id, n) for n in names]
16-
return []
43+
return [(n.id, n) for n in extract_names_from_node(assignment_node)]
1744

1845

1946
def get_var_names_from_funcdef(funcdef_node: ast.FunctionDef) -> List[Tuple[str, ast.arg]]:
@@ -25,22 +52,13 @@ def get_var_names_from_funcdef(funcdef_node: ast.FunctionDef) -> List[Tuple[str,
2552
return vars_info
2653

2754

28-
def get_var_names_from_for(for_node: ast.For) -> List[Tuple[str, ast.AST]]:
29-
if isinstance(for_node.target, ast.Name):
30-
return [(for_node.target.id, for_node.target)]
31-
elif isinstance(for_node.target, ast.Tuple):
32-
return [(n.id, n) for n in for_node.target.elts if isinstance(n, ast.Name)]
33-
return []
34-
35-
3655
def extract_all_variable_names(ast_tree: ast.AST) -> List[Tuple[str, ast.AST]]:
3756
var_info: List[Tuple[str, ast.AST]] = []
38-
assignments = [n for n in ast.walk(ast_tree) if isinstance(n, ast.Assign)]
57+
assignments = [
58+
n for n in ast.walk(ast_tree)
59+
if isinstance(n, (ast.Assign, ast.AnnAssign, ast.For))
60+
]
3961
var_info += flat([get_var_names_from_assignment(a) for a in assignments])
40-
ann_assignments = [n for n in ast.walk(ast_tree) if isinstance(n, ast.AnnAssign)]
41-
var_info += flat([get_var_names_from_assignment(a) for a in ann_assignments])
4262
funcdefs = [n for n in ast.walk(ast_tree) if isinstance(n, ast.FunctionDef)]
4363
var_info += flat([get_var_names_from_funcdef(f) for f in funcdefs])
44-
fors = [n for n in ast.walk(ast_tree) if isinstance(n, ast.For)]
45-
var_info += flat([get_var_names_from_for(f) for f in fors])
4664
return var_info

tests/test_files/loops_names.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
for result in get_some_data():
2+
pass
3+
4+
for a, *b in zip(data, goosebumps):
5+
print(a + b)

tests/test_files/ok_names.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
user_id = 23
22
some_email = '[email protected]'
3+
some_normal_name, _ = -1, -2
4+
some_other_name, *_ = -1, -2, -3
5+
some_different_name, *other_different_name = -1, -2, -3

tests/test_files/short_names.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
b = 1
33
c = 3
44
i = 4
5+
x, y, z = 5, 6, 7
6+
w, *q = 1, 2, 3

tests/test_variables_names.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,20 @@ def test_ok_good_names():
1313

1414
def test_ok_for_short_names_file():
1515
errors = run_validator_for_test_file('short_names.py', use_strict_mode=True)
16-
assert len(errors) == 4
16+
assert len(errors) == 9
1717
errors = run_validator_for_test_file('short_names.py', use_strict_mode=False)
18+
assert len(errors) == 8
19+
assert (
20+
get_error_message(errors[0])
21+
== "VNE001 single letter variable names like 'a' are not allowed"
22+
)
23+
24+
25+
def test_ok_for_names_in_loops_file():
26+
errors = run_validator_for_test_file('loops_names.py', use_strict_mode=True)
1827
assert len(errors) == 3
28+
errors = run_validator_for_test_file('loops_names.py', use_strict_mode=False)
29+
assert len(errors) == 2
1930
assert (
2031
get_error_message(errors[0])
2132
== "VNE001 single letter variable names like 'a' are not allowed"

0 commit comments

Comments
 (0)