Skip to content

Commit 971827f

Browse files
authored
Merge branch 'master' into issue-3593
2 parents a536c2b + 7d76031 commit 971827f

File tree

6 files changed

+60
-8
lines changed

6 files changed

+60
-8
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
strategy:
2121
fail-fast: false
2222
matrix:
23-
python-version: ['3.10', '3.11', '3.12', '3.13']
23+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
2424
steps:
2525
- uses: actions/checkout@v6
2626
with:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Semantic versioning in our case means:
2727
- Fixes false positive `WPS457` for ``while True`` loop with ``await`` expressions, #3753
2828
- Fixes the false positive `WPS617` by assigning a function that receives a lambda expression as a parameter.
2929
- Fixes `WPS345` to forbid symmetric bitwise operations, #3593
30+
- Fixes false positive `WPS430` for whitelisted nested functions, #3589
3031

3132

3233
## 1.5.0

tests/fixtures/noqa/noqa.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ class Nested: # noqa: WPS431
123123
def nested(): # noqa: WPS430
124124
...
125125

126+
if some_condition():
127+
def deep_nested(): # noqa: WPS430
128+
...
129+
else:
130+
async def deep_nested(): # noqa: WPS430
131+
...
132+
126133

127134
del {'a': 1}['a'] # noqa: WPS420
128135
delattr(object, 'some') # noqa: WPS421

tests/test_checker/test_noqa.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@
197197
'WPS427': 1,
198198
'WPS428': 0, # disabled since 1.0.0
199199
'WPS429': 1,
200-
'WPS430': 1,
200+
'WPS430': 3,
201201
'WPS431': 2,
202202
'WPS432': 2,
203203
'WPS433': 0, # disabled since 1.0.0

tests/test_visitors/test_ast/test_complexity/test_nested/test_nested_functions.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,34 @@ def test_whitelist_nested_functions(
234234
[
235235
nested_function_in_if,
236236
nested_function_in_if_else,
237+
],
238+
)
239+
def test_deep_whitelist_nested_functions_allowed(
240+
assert_errors,
241+
assert_error_text,
242+
parse_ast_tree,
243+
whitelist_name,
244+
code,
245+
default_options,
246+
mode,
247+
):
248+
"""
249+
Test for allowed whitelisted functions inside single if(/else) block.
250+
251+
See: https://github.com/wemake-services/wemake-python-styleguide/issues/3589
252+
"""
253+
tree = parse_ast_tree(mode(code.format(whitelist_name)))
254+
255+
visitor = NestedComplexityVisitor(default_options, tree=tree)
256+
visitor.run()
257+
258+
assert_errors(visitor, [])
259+
260+
261+
@pytest.mark.parametrize('whitelist_name', NESTED_FUNCTIONS_WHITELIST)
262+
@pytest.mark.parametrize(
263+
'code',
264+
[
237265
nested_function_while_loop,
238266
nested_function_in_for_loop,
239267
nested_function_in_try,
@@ -251,7 +279,7 @@ def test_deep_whitelist_nested_functions(
251279
default_options,
252280
mode,
253281
):
254-
"""Testing that it is possible to nest whitelisted functions."""
282+
"""Testing that it is restricted to nest even whitelisted functions."""
255283
tree = parse_ast_tree(mode(code.format(whitelist_name)))
256284

257285
visitor = NestedComplexityVisitor(default_options, tree=tree)

wemake_python_styleguide/visitors/ast/complexity/nested.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def visit_any_function(self, node: AnyFunctionDef) -> None:
4646
Used to find nested functions.
4747
4848
Uses ``NESTED_FUNCTIONS_WHITELIST`` to respect some nested functions.
49+
Only whitelisted functions are allowed, either directly inside
50+
a function or inside a single function-level ``if`` / ``if-else``.
51+
All other nesting is forbidden.
4952
"""
5053
self._check_nested_function(node)
5154
self.generic_visit(node)
@@ -56,13 +59,26 @@ def visit_Lambda(self, node: ast.Lambda) -> None:
5659
self.generic_visit(node)
5760

5861
def _check_nested_function(self, node: AnyFunctionDef) -> None:
59-
is_inside_function = isinstance(get_context(node), FunctionNodes)
62+
context = get_context(node)
63+
if not isinstance(context, FunctionNodes):
64+
return
6065

61-
is_direct = isinstance(get_parent(node), FunctionNodes)
62-
is_bad = is_direct and node.name not in NESTED_FUNCTIONS_WHITELIST
66+
parent = get_parent(node)
6367

64-
if is_bad or (is_inside_function and not is_direct):
65-
self.add_violation(NestedFunctionViolation(node, text=node.name))
68+
is_direct = isinstance(parent, FunctionNodes)
69+
70+
is_single_if = (
71+
isinstance(parent, ast.If) and get_parent(parent) is context
72+
)
73+
74+
if node.name in NESTED_FUNCTIONS_WHITELIST and (
75+
is_direct or is_single_if
76+
):
77+
return
78+
79+
self.add_violation(
80+
NestedFunctionViolation(node, text=node.name),
81+
)
6682

6783
def _check_nested_classes(self, node: ast.ClassDef) -> None:
6884
parent_context = get_context(node)

0 commit comments

Comments
 (0)