Repository: wemake-services/wemake-python-styleguide

Issue:
Incorrect line number for Z331
# Bug report

## What's wrong

```python
def foreign_jwt_authentication(token: bytes):  # noqa: Z331
    """
    Goes to the remote provider to validate the token.

    Returns the payload if everything is fine.
    Otherwise raises an exception.
    """
    unverified_payload = jwt.decode(  # type: ignore
        token, key=None, verify=False,
    )
    _make_authentication_request(settings.TOKEN_VERIFICATION_URL, token)
    return unverified_payload
```

Error:

```
./server/rest_app/logics/auth.py:41:1: Z331 Found local variable that are only used in `return` statements
```

## How is that should be

Should be reported on the ` return unverified_payload` line.
Incorrect line number for Z331
# Bug report

## What's wrong

```python
def foreign_jwt_authentication(token: bytes):  # noqa: Z331
    """
    Goes to the remote provider to validate the token.

    Returns the payload if everything is fine.
    Otherwise raises an exception.
    """
    unverified_payload = jwt.decode(  # type: ignore
        token, key=None, verify=False,
    )
    _make_authentication_request(settings.TOKEN_VERIFICATION_URL, token)
    return unverified_payload
```

Error:

```
./server/rest_app/logics/auth.py:41:1: Z331 Found local variable that are only used in `return` statements
```

## How is that should be

Should be reported on the ` return unverified_payload` line.


Discussion:

Resolving patch:
diff --git a/wemake_python_styleguide/visitors/ast/keywords.py b/wemake_python_styleguide/visitors/ast/keywords.py
--- a/wemake_python_styleguide/visitors/ast/keywords.py
+++ b/wemake_python_styleguide/visitors/ast/keywords.py
@@ -2,7 +2,7 @@
 
 import ast
 from collections import defaultdict
-from typing import ClassVar, Dict, List, Type, Union
+from typing import ClassVar, Dict, List, Tuple, Type, Union
 
 from typing_extensions import final
 
@@ -26,6 +26,10 @@
 from wemake_python_styleguide.visitors.decorators import alias
 
 AnyWith = Union[ast.With, ast.AsyncWith]
+NamesAndReturns = Tuple[
+    Dict[str, List[ast.Name]],
+    Dict[str, ast.Return],
+]
 ReturningViolations = Union[
     Type[InconsistentReturnViolation],
     Type[InconsistentYieldViolation],
@@ -252,14 +256,16 @@ def _get_name_nodes_variable(
     def _get_return_node_variables(
         self,
         node: List[ast.AST],
-    ) -> Dict[str, List[ast.Name]]:
+    ) -> NamesAndReturns:
         returns: Dict[str, List[ast.Name]] = defaultdict(list)
+        return_sub_nodes: Dict[str, ast.Return] = defaultdict()
         for sub_node in node:
             if isinstance(sub_node, ast.Return):
                 if isinstance(sub_node.value, ast.Name):
                     variable_name = sub_node.value.id
                     returns[variable_name].append(sub_node.value)
-        return returns
+                    return_sub_nodes[variable_name] = sub_node
+        return returns, return_sub_nodes
 
     def _check_variables_for_return(self, node: AnyFunctionDef) -> None:
         nodes = list(
@@ -270,13 +276,20 @@ def _check_variables_for_return(self, node: AnyFunctionDef) -> None:
         )
         assign = self._get_assign_node_variables(nodes)
         names = self._get_name_nodes_variable(nodes)
-        returns = self._get_return_node_variables(nodes)
+        returns, return_sub_nodes = self._get_return_node_variables(nodes)
 
         returns = {name: returns[name] for name in returns if name in assign}
 
+        self._check_for_violations(names, return_sub_nodes, returns)
+
+    def _check_for_violations(self, names, return_sub_nodes, returns) -> None:
         for variable_name in returns:
             if not set(names[variable_name]) - set(returns[variable_name]):
-                self.add_violation(InconsistentReturnVariableViolation(node))
+                self.add_violation(
+                    InconsistentReturnVariableViolation(
+                        return_sub_nodes[variable_name],
+                    ),
+                )
 
     def visit_return_variable(self, node: AnyFunctionDef) -> None:
         """


Test patch:
diff --git a/tests/fixtures/noqa.py b/tests/fixtures/noqa.py
--- a/tests/fixtures/noqa.py
+++ b/tests/fixtures/noqa.py
@@ -262,9 +262,9 @@ def function_with_wrong_yield():
 except Exception as ex:  # noqa: Z329
     raise ex
 
-def some_function():  # noqa: Z331
+def some_function():
     some_value = 1
-    return some_value
+    return some_value  # noqa: Z331
 
 try:
     anti_z444 = 1