Skip to content

Commit 31c736c

Browse files
committed
feat: forbid initializing multiple variables on one line (fixes #1901)
1 parent 60834f3 commit 31c736c

5 files changed

Lines changed: 134 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Semantic versioning in our case means:
2121
### Features
2222

2323
- Adds `WPS367`: forbid redundant trailing colon in slice, #1071
24+
- Adds `WPS482`: forbid initializing multiple variables on one line, #1901
2425

2526
## 1.6.2
2627

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import pytest
2+
3+
from wemake_python_styleguide.violations.best_practices import (
4+
MultipleVariablesInitializationViolation,
5+
)
6+
from wemake_python_styleguide.visitors.ast.builtins import (
7+
MultipleVariablesInitializationVisitor,
8+
)
9+
10+
# Correct usages:
11+
12+
function_unpacking = 'first, second = some_tuple()'
13+
swap_assignment = 'first, second = second, first'
14+
single_assignment = 'constant = 1'
15+
spread_assignment = 'first, *_, second = [1, 2, 4, 3]'
16+
17+
# Wrong usages:
18+
19+
tuple_assignment = 'first, second = (1, 2)'
20+
list_unpacking = 'first, second = [], []'
21+
mixed_unpacking = 'first, second = (1, [])'
22+
23+
24+
@pytest.mark.parametrize(
25+
'code',
26+
[
27+
single_assignment,
28+
spread_assignment,
29+
function_unpacking,
30+
swap_assignment,
31+
],
32+
)
33+
def test_correct_assignments(
34+
assert_errors,
35+
parse_ast_tree,
36+
code,
37+
default_options,
38+
):
39+
"""Testing that correct assignments work."""
40+
tree = parse_ast_tree(code)
41+
42+
visitor = MultipleVariablesInitializationVisitor(
43+
default_options, tree=tree,
44+
)
45+
visitor.run()
46+
47+
assert_errors(visitor, [])
48+
49+
50+
@pytest.mark.parametrize(
51+
'code',
52+
[
53+
tuple_assignment,
54+
list_unpacking,
55+
mixed_unpacking,
56+
],
57+
)
58+
def test_multiple_variables_initialization(
59+
assert_errors,
60+
parse_ast_tree,
61+
code,
62+
default_options,
63+
):
64+
"""Testing that multiple variables initialization is restricted."""
65+
tree = parse_ast_tree(code)
66+
67+
visitor = MultipleVariablesInitializationVisitor(
68+
default_options, tree=tree,
69+
)
70+
visitor.run()
71+
72+
assert_errors(visitor, [MultipleVariablesInitializationViolation])

wemake_python_styleguide/presets/types/tree.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
builtins.WrongStringVisitor,
5757
builtins.WrongFormatStringVisitor,
5858
builtins.WrongAssignmentVisitor,
59+
builtins.MultipleVariablesInitializationVisitor,
5960
builtins.WrongCollectionVisitor,
6061
operators.UselessOperatorsVisitor,
6162
operators.WrongMathOperatorVisitor,

wemake_python_styleguide/violations/best_practices.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3019,3 +3019,35 @@ class Some:
30193019

30203020
error_template = 'Found a leaking ``for`` loop in a class or module body'
30213021
code = 481
3022+
3023+
3024+
@final
3025+
class MultipleVariablesInitializationViolation(ASTViolation):
3026+
"""
3027+
Forbid initializing multiple variables on a single line.
3028+
3029+
Reasoning:
3030+
Assigning multiple variables in one line reduces readability.
3031+
It is harder to see where each variable is initialized.
3032+
3033+
Solution:
3034+
Use separate assignment statements for each variable.
3035+
3036+
Example::
3037+
3038+
# Correct:
3039+
ab = []
3040+
cd = []
3041+
a, b = some_tuple()
3042+
x, y = y, x
3043+
3044+
# Wrong:
3045+
ab, cd = [], []
3046+
x, y = (1, 2)
3047+
3048+
.. versionadded:: 1.7.0
3049+
3050+
"""
3051+
3052+
error_template = 'Found multiple variables initialized on one line'
3053+
code = 482

wemake_python_styleguide/visitors/ast/builtins.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,34 @@ def _check_unpacking_target_types(self, node: ast.AST | None) -> None:
374374
)
375375

376376

377+
@final
378+
class MultipleVariablesInitializationVisitor(base.BaseNodeVisitor):
379+
"""Ensures that multiple variables are not initialized on one line."""
380+
381+
def visit_Assign(self, node: ast.Assign) -> None:
382+
"""Checks assignments for multiple variables initialization."""
383+
self._check_multiple_variables_initialization(node)
384+
self.generic_visit(node)
385+
386+
def _check_multiple_variables_initialization(
387+
self,
388+
node: ast.Assign,
389+
) -> None:
390+
if len(node.targets) != 1:
391+
return
392+
target = node.targets[0]
393+
if not isinstance(target, ast.Tuple):
394+
return
395+
if not isinstance(node.value, ast.Tuple):
396+
return
397+
# Allow swapping: x, y = y, x (all elements in value are ast.Name)
398+
if all(isinstance(elt, ast.Name) for elt in node.value.elts):
399+
return
400+
self.add_violation(
401+
best_practices.MultipleVariablesInitializationViolation(node),
402+
)
403+
404+
377405
@final
378406
class WrongCollectionVisitor(base.BaseNodeVisitor):
379407
"""Ensures that collection definitions are correct."""

0 commit comments

Comments
 (0)