Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Semantic versioning in our case means:

- Fixes false positive `WPS457` for ``while True`` loop with ``await`` expressions, #3753
- Fixes the false positive `WPS617` by assigning a function that receives a lambda expression as a parameter.
- Fixes false positive `WPS430` for whitelisted nested functions, #3589


## 1.5.0
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/noqa/noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ class Nested: # noqa: WPS431
def nested(): # noqa: WPS430
...

if some_condition():
def deep_nested(): # noqa: WPS430
...
else:
async def deep_nested(): # noqa: WPS430
...


del {'a': 1}['a'] # noqa: WPS420
delattr(object, 'some') # noqa: WPS421
Expand Down
2 changes: 1 addition & 1 deletion tests/test_checker/test_noqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
'WPS427': 1,
'WPS428': 0, # disabled since 1.0.0
'WPS429': 1,
'WPS430': 1,
'WPS430': 3,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's revert these changes.

Suggested change
'WPS430': 3,
'WPS430': 1,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added minimal reproducible example as part of integration tests (if I didn't misunderstood what CONTRIBUTING.md states)

'WPS431': 2,
'WPS432': 2,
'WPS433': 0, # disabled since 1.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,34 @@ def test_whitelist_nested_functions(
[
nested_function_in_if,
nested_function_in_if_else,
],
)
def test_deep_whitelist_nested_functions_allowed(
assert_errors,
assert_error_text,
parse_ast_tree,
whitelist_name,
code,
default_options,
mode,
):
"""
Test for allowed whitelisted functions inside single if(/else) block.

See: https://github.com/wemake-services/wemake-python-styleguide/issues/3589
"""
tree = parse_ast_tree(mode(code.format(whitelist_name)))

visitor = NestedComplexityVisitor(default_options, tree=tree)
visitor.run()

assert_errors(visitor, [])


@pytest.mark.parametrize('whitelist_name', NESTED_FUNCTIONS_WHITELIST)
@pytest.mark.parametrize(
'code',
[
nested_function_while_loop,
nested_function_in_for_loop,
nested_function_in_try,
Expand All @@ -251,7 +279,7 @@ def test_deep_whitelist_nested_functions(
default_options,
mode,
):
"""Testing that it is possible to nest whitelisted functions."""
"""Testing that it is restricted to nest even whitelisted functions."""
tree = parse_ast_tree(mode(code.format(whitelist_name)))

visitor = NestedComplexityVisitor(default_options, tree=tree)
Expand Down
26 changes: 21 additions & 5 deletions wemake_python_styleguide/visitors/ast/complexity/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def visit_any_function(self, node: AnyFunctionDef) -> None:
Used to find nested functions.

Uses ``NESTED_FUNCTIONS_WHITELIST`` to respect some nested functions.
Only whitelisted functions are allowed, either directly inside
a function or inside a single function-level ``if`` / ``if-else``.
All other nesting is forbidden.
"""
self._check_nested_function(node)
self.generic_visit(node)
Expand All @@ -56,13 +59,26 @@ def visit_Lambda(self, node: ast.Lambda) -> None:
self.generic_visit(node)

def _check_nested_function(self, node: AnyFunctionDef) -> None:
is_inside_function = isinstance(get_context(node), FunctionNodes)
context = get_context(node)
if not isinstance(context, FunctionNodes):
return

is_direct = isinstance(get_parent(node), FunctionNodes)
is_bad = is_direct and node.name not in NESTED_FUNCTIONS_WHITELIST
parent = get_parent(node)

if is_bad or (is_inside_function and not is_direct):
self.add_violation(NestedFunctionViolation(node, text=node.name))
is_direct = isinstance(parent, FunctionNodes)

is_single_if = (
isinstance(parent, ast.If) and get_parent(parent) is context
)

if node.name in NESTED_FUNCTIONS_WHITELIST and (
is_direct or is_single_if
):
return

self.add_violation(
NestedFunctionViolation(node, text=node.name),
)

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