Repository: wemake-services/wemake-python-styleguide

Issue:
WPS210: must not count variables in nested functions / classes
```python
@pytest.fixture
def querybuilder_form_data_factory(  # noqa: WPS210
    querybuilder_consts: HHConsts,
    faker_seed: int,
) -> type[QueryBuilderFormFactory]:
    """Create context-aware fixture for the form data."""
    consts = querybuilder_hh_consts

    def factory(field: Mapping[str, HHMetadata]) -> Callable[[], str]:
        return lambda: QueryBuilderFormFactory.__random__.choice(
            list(field.keys()),
        )

    @final
    class InnerFactory(QueryBuilderFormFactory):  # noqa: WPS431
        __random_seed__ = faker_seed

        area = Use(factory(consts.areas))
        country = Use(factory(consts.countries))
        citizenship = Use(factory(consts.countries))
        professional_role = Use(factory(consts.professional_roles))
        gender = Use(factory(consts.gender))
        employment = Use(factory(consts.employment))
        schedule = Use(factory(consts.schedule))

    return InnerFactory
```

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/wemake-services/wemake-python-styleguide/pull/3159?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`f795ac5`)](https://app.codecov.io/gh/wemake-services/wemake-python-styleguide/commit/f795ac5a03677076c5d0e08c9010b22c333c46db?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services) to head [(`6ef3891`)](https://app.codecov.io/gh/wemake-services/wemake-python-styleguide/commit/6ef3891918ee77f5a1f284f3ebe8fc940d022540?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##            master     #3159   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          119       119           
  Lines         5797      5780   -17     
  Branches       802       801    -1     
=========================================
- Hits          5797      5780   -17     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/wemake-services/wemake-python-styleguide/pull/3159?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=wemake-services).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 733dfce2..a4518623 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -81,6 +81,7 @@ Semantic versioning in our case means:
 
 - Fixes `OverusedStringViolation` not to include `'...'` string
 - Removes `astor` package in favour of `ast.unparse`
+- Fixes `WPS210` to not count nested local variables in nested scopes #3108
 - Fixes `IterableUnpackingViolation` with generic types and `TypeVarTuple`
 - Fixes unnormalized pathes in formatter output
 - Fixes `WPS434` to allow reassignment in a form of `x = [y, *x]`
diff --git a/setup.cfg b/setup.cfg
index cf1a07bd..3eaa1dab 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,13 +16,8 @@ doctests = true
 max-imports = 17
 
 # Excluding some directories:
-exclude =
-  .git
-  __pycache__
+extend-exclude =
   .venv
-  .eggs
-  *.egg
-  dist
   # These folders contain code badly written for reasons:
   # Project specific, do not copy.
   tests/fixtures/**
diff --git a/wemake_python_styleguide/visitors/ast/complexity/function.py b/wemake_python_styleguide/visitors/ast/complexity/function.py
index 207c65aa..3f5818d2 100644
--- a/wemake_python_styleguide/visitors/ast/complexity/function.py
+++ b/wemake_python_styleguide/visitors/ast/complexity/function.py
@@ -13,7 +13,7 @@ from wemake_python_styleguide.logic.complexity.functions import (
     FunctionCounterWithLambda,
 )
 from wemake_python_styleguide.logic.naming import access
-from wemake_python_styleguide.logic.nodes import get_parent
+from wemake_python_styleguide.logic.nodes import get_context, get_parent
 from wemake_python_styleguide.logic.tree import functions
 from wemake_python_styleguide.types import (
     AnyFunctionDef,
@@ -77,6 +77,9 @@ class _ComplexityCounter:
             if access.is_unused(variable_def.id):
                 return
 
+            if get_context(variable_def) is not function:
+                return
+
             parent = get_parent(variable_def)
             no_locals = self._not_contain_locals
             if isinstance(parent, no_locals):


Test patch:
diff --git a/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py b/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py
index 3346aa1e..d38b3fde 100644
--- a/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py
+++ b/tests/test_visitors/test_ast/test_complexity/test_function/test_local_variables.py
@@ -44,19 +44,23 @@ def function():
 """
 
 function_with_nested = """
-def function():  # has two local vars
-    def factory():  # has one local var
+def function():
+    def factory():
         variable1 = 1
+        other = 2
 
     variable2 = 2
+    other = 3
 """
 
 function_with_nested_and_params = """
-def function(param1):  # has two local vars
+def function(param1):
     param1 = param1 + 1
+    other = 2
 
-    def factory(param2):  # has one local var
+    def factory(param2):
         param2 = param2 + 2
+        other = 3
 """
 
 method_with_locals = """
@@ -66,6 +70,18 @@ class Some:
         local_variable2 = 2
 """
 
+# regression 3108
+
+function_nested_class = """
+def test():
+    consts = 0
+    other = 1
+
+    class Nested:
+        area = 2
+        other = 3
+"""
+
 
 @pytest.mark.parametrize(
     'code',
@@ -78,6 +94,7 @@ class Some:
         function_with_nested,
         function_with_nested_and_params,
         method_with_locals,
+        function_nested_class,
     ],
 )
 def test_locals_correct_count(
@@ -113,9 +130,8 @@ def test_locals_correct_count(
         function_with_locals_redefinition,
         function_with_locals_and_params,
         function_with_comprehension,
-        function_with_nested,
-        function_with_nested_and_params,
         method_with_locals,
+        function_nested_class,
     ],
 )
 def test_locals_wrong_count(
@@ -143,3 +159,27 @@ def test_locals_wrong_count(
 
     assert_errors(visitor, [TooManyLocalsViolation])
     assert_error_text(visitor, '2', option_values.max_local_variables)
+
+
+@pytest.mark.parametrize(
+    'code',
+    [
+        function_with_nested,
+        function_with_nested_and_params,
+    ],
+)
+def test_locals_wrong_count_nested(
+    assert_errors,
+    parse_ast_tree,
+    options,
+    code,
+    mode,
+):
+    """Local variables in nested scopes."""
+    option_values = options(max_local_variables=1)
+    tree = parse_ast_tree(mode(code))
+
+    visitor = FunctionComplexityVisitor(option_values, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [TooManyLocalsViolation, TooManyLocalsViolation])