Repository: wemake-services/wemake-python-styleguide

Issue:
Limit how `_` variable can be used
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

Today I saw this code:

```python
[redis_db.delete(symbol_lock_key(_, endpoint_name)) for _ in symbols if True or redis_db.get(symbol_lock_key(_, endpoint_name)) == upid]
```

The thing about it is that we are using `_` as a regular variable.
We also need to list this special case in our `naming.rst`.

## Reasoning

`_` is a special case that indicates that this variable is not used.
So, when it is used we need to alert the user.



Discussion:
I have seen it used like
```
for _ in range(10):
    pass  # do something ten times
```
I think this is legit 
Yes, it is. But it is not actually used. It is just defined. 
let me add this rule
@nixphix with pleasure! 

Resolving patch:
diff --git a/wemake_python_styleguide/violations/naming.py b/wemake_python_styleguide/violations/naming.py
--- a/wemake_python_styleguide/violations/naming.py
+++ b/wemake_python_styleguide/violations/naming.py
@@ -118,6 +118,7 @@
    UpperCaseAttributeViolation
    ConsecutiveUnderscoresInNameViolation
    ReservedArgumentNameViolation
+   AnonymousVariableUseViolation
 
 
 Module names
@@ -138,6 +139,7 @@
 .. autoclass:: UpperCaseAttributeViolation
 .. autoclass:: ConsecutiveUnderscoresInNameViolation
 .. autoclass:: ReservedArgumentNameViolation
+.. autoclass:: AnonymousVariableUseViolation
 
 """
 
@@ -510,3 +512,35 @@ def __init__(self):
 
     error_template = 'Found name reserved for first argument: {0}'
     code = 117
+
+
+@final
+class AnonymousVariableUseViolation(ASTViolation):
+    """
+    Forbids to use ``_`` anonymous variable.
+
+    Reasoning:
+        Use ``_`` anonymous variable only to discard values.
+
+    Example::
+
+        # Correct:
+        key, _ = a_tuple
+        a_list = [random_fun() for _ in range(10)]
+
+        # Wrong:
+        print(_)
+        x = 42 * _
+
+    This rule checks: variables.
+
+    .. versionadded:: 0.5.0
+
+    Note:
+        Returns Z119 as error code
+
+    """
+
+    #: Error message shown to the user.
+    error_template = 'Found using anonymous variable to store value: {0}'
+    code = 119
diff --git a/wemake_python_styleguide/visitors/ast/naming.py b/wemake_python_styleguide/visitors/ast/naming.py
--- a/wemake_python_styleguide/visitors/ast/naming.py
+++ b/wemake_python_styleguide/visitors/ast/naming.py
@@ -281,3 +281,24 @@ def visit_Assign(self, node: ast.Assign) -> None:
         """
         self._check_assignment(node)
         self.generic_visit(node)
+
+
+@final
+class WrongVariableUseVisitor(BaseNodeVisitor):
+    """Finds wrong use of variables."""
+
+    def _check_variable_use(self, node: ast.Name) -> None:
+        """Add violation when the anonymous variable is used to load value."""
+        if node.id == '_' and isinstance(node.ctx, ast.Load):
+            self.add_violation(naming.AnonymousVariableUseViolation(node))
+
+    def visit_Name(self, node: ast.Name) -> None:
+        """
+        Check variable usage.
+
+        Raises:
+            AnonymousVariableUseViolation
+
+        """
+        self._check_variable_use(node)
+        self.generic_visit(node)
diff --git a/wemake_python_styleguide/visitors/presets/general.py b/wemake_python_styleguide/visitors/presets/general.py
--- a/wemake_python_styleguide/visitors/presets/general.py
+++ b/wemake_python_styleguide/visitors/presets/general.py
@@ -36,6 +36,7 @@
     naming.WrongNameVisitor,
     naming.WrongModuleMetadataVisitor,
     naming.WrongVariableAssignmentVisitor,
+    naming.WrongVariableUseVisitor,
 
     builtins.MagicNumberVisitor,
     builtins.WrongStringVisitor,


Test patch:
diff --git a/tests/fixtures/noqa.py b/tests/fixtures/noqa.py
--- a/tests/fixtures/noqa.py
+++ b/tests/fixtures/noqa.py
@@ -188,3 +188,6 @@ def function(first, second):  # noqa: Z443
 
 def function(_first):  # noqa: Z444
     return _first + 1
+
+_ = 1 + 6
+print(_ * 2)  # noqa: Z119
diff --git a/tests/test_checker/test_noqa.py b/tests/test_checker/test_noqa.py
--- a/tests/test_checker/test_noqa.py
+++ b/tests/test_checker/test_noqa.py
@@ -41,6 +41,7 @@ def test_noqa_fixture_disabled(absolute_path, all_violations):
         'Z115': 1,
         'Z116': 1,
         'Z117': 1,
+        'Z119': 1,
 
         'Z200': 0,
         'Z201': 0,
diff --git a/tests/test_visitors/test_ast/test_naming/test_variable_usages.py b/tests/test_visitors/test_ast/test_naming/test_variable_usages.py
--- a/tests/test_visitors/test_ast/test_naming/test_variable_usages.py
+++ b/tests/test_visitors/test_ast/test_naming/test_variable_usages.py
@@ -4,9 +4,13 @@
 
 import pytest
 
+from wemake_python_styleguide.violations.naming import (
+    AnonymousVariableUseViolation,
+)
 from wemake_python_styleguide.visitors.ast.naming import (
     VARIABLE_NAMES_BLACKLIST,
     WrongNameVisitor,
+    WrongVariableUseVisitor,
 )
 
 import_name = 'import {0}'
@@ -36,6 +40,23 @@ def function():
     yield {0}
 """
 
+anonymous_variable_store_one = """
+_ = 4 + 5
+"""
+
+anonymous_variable_store_two = """
+_, var = 'foo', 'bar'
+"""
+
+anonymous_variable_store_load_one = """
+_ = 4 + 5
+print(_)
+"""
+
+anonymous_variable_store_load_two = """
+[_ for _ in range(5)]
+"""
+
 
 @pytest.mark.parametrize('bad_name', [
     *VARIABLE_NAMES_BLACKLIST,
@@ -75,3 +96,41 @@ def test_wrong_variable_names(
     visitor.run()
 
     assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    anonymous_variable_store_one,
+    anonymous_variable_store_two,
+])
+def test_anonymous_variable_use_correct(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Ensures that anonymous variables are used to discard values."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongVariableUseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    anonymous_variable_store_load_one,
+    anonymous_variable_store_load_two,
+])
+def test_anonymous_variable_use_wrong(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Ensures that anonymous variables are not used to store values."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongVariableUseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [AnonymousVariableUseViolation])