Repository: wemake-services/wemake-python-styleguide

Issue:
Detect redundant enumerate
# Rule request

## Thesis

```python
# bad
for _, item in enumerate(items):
  ...

# good
for item in items:
  ...
```

## Reasoning

Simplification.

P.S. I'm not making it, have seen it in the wild 🙃 


Discussion:
The same can be applied to `dict.items` but it apparently requires to know the right type for the iterator. Probably, one more good candidate for typed-linter.

```python
dctnr = {1: 2, 3: 4}
for _, value in dctnr.items():
  ...
for key, _ in dctnr.items():
  ...
```
I will leave this one for `typed-linter`.
It will be false-positive only if you have shadowed `enumerate` function, and I'd say it is a bad practice on its own and you deserve this false-positive then 🙃 
Yes, my bad! 

I was confused by:

```python
dctnr = {1: 2, 3: 4}
for _, value in dctnr.items():
  ...
for key, _ in dctnr.items():
  ...
```

This should go to `typed-linter` rule.
Hello, I am a beginner in the Open source world and I found this issue to be good first so I would like to take this issue. So please guide me.
Hi @ujju20!

Make sure to read [our contributing guides](https://github.com/wemake-services/wemake-python-styleguide/blob/master/CONTRIBUTING.md)

Next, you will need to create a new `violation` and a new `visitor`, see our glossary what these things mean.

I am here to help: ask anything you need.
If this is not taken by anybody, can I take it over?
Sure, go on :)

Resolving patch:
diff --git a/wemake_python_styleguide/presets/types/tree.py b/wemake_python_styleguide/presets/types/tree.py
--- a/wemake_python_styleguide/presets/types/tree.py
+++ b/wemake_python_styleguide/presets/types/tree.py
@@ -18,6 +18,7 @@
     loops,
     modules,
     operators,
+    redundancy,
     statements,
     subscripts,
 )
@@ -103,6 +104,8 @@
 
     decorators.WrongDecoratorVisitor,
 
+    redundancy.RedundantEnumerateVisitor,
+
     # Modules:
     modules.EmptyModuleContentsVisitor,
     modules.MagicModuleFunctionsVisitor,
diff --git a/wemake_python_styleguide/violations/best_practices.py b/wemake_python_styleguide/violations/best_practices.py
--- a/wemake_python_styleguide/violations/best_practices.py
+++ b/wemake_python_styleguide/violations/best_practices.py
@@ -84,6 +84,7 @@
    BitwiseAndBooleanMixupViolation
    NewStyledDecoratorViolation
    BareRaiseViolation
+   RedundantEnumerateViolation
 
 Best practices
 --------------
@@ -156,6 +157,7 @@
 .. autoclass:: BitwiseAndBooleanMixupViolation
 .. autoclass:: NewStyledDecoratorViolation
 .. autoclass:: BareRaiseViolation
+.. autoclass:: RedundantEnumerateViolation
 
 """
 
@@ -2608,3 +2610,33 @@ def smth():
 
     error_template = 'Found bare raise keyword'
     code = 467
+
+
+@final
+class RedundantEnumerateViolation(ASTViolation):
+    """
+    Forbid using a placeholder (``_``) with ``enumerate``.
+
+    Reasoning:
+       This adds no value and introduces additional complexity.
+
+    Solution:
+        Only use ``enumerate`` when you are going to do something with the
+        index it returns.
+
+    Example::
+
+        # Correct:
+        for item in items:
+          ...
+
+        # Wrong:
+        for _, item in enumerate(items):
+          ...
+
+    .. versionadded:: 0.18.0
+
+    """
+
+    error_template = 'Found redundant use of `enumerate`'
+    code = 468
diff --git a/wemake_python_styleguide/visitors/ast/redundancy.py b/wemake_python_styleguide/visitors/ast/redundancy.py
new file mode 100644
--- /dev/null
+++ b/wemake_python_styleguide/visitors/ast/redundancy.py
@@ -0,0 +1,55 @@
+import ast
+from typing import Union
+
+from typing_extensions import final
+
+from wemake_python_styleguide.types import AnyComprehension, AnyFor
+from wemake_python_styleguide.violations.best_practices import (
+    RedundantEnumerateViolation,
+)
+from wemake_python_styleguide.visitors import decorators
+from wemake_python_styleguide.visitors.base import BaseNodeVisitor
+
+
+@final
+@decorators.alias('visit_any_for', (
+    'visit_For',
+    'visit_AsyncFor',
+))
+@decorators.alias('visit_any_comprehension', (
+    'visit_ListComp',
+    'visit_DictComp',
+    'visit_SetComp',
+    'visit_GeneratorExp',
+))
+class RedundantEnumerateVisitor(BaseNodeVisitor):
+    """Responsible for detecting redundant usages of ``enumerate`` function."""
+
+    def visit_any_comprehension(self, node: AnyComprehension) -> None:
+        """Finds incorrect patterns inside comprehensions."""
+        for generator_node in node.generators:
+            self._check_for_redundant_enumerate(generator_node)
+        self.generic_visit(node)
+
+    def visit_any_for(self, node: AnyFor) -> None:
+        """Used to find redundant usages of ``enumerate`` function."""
+        self._check_for_redundant_enumerate(node)
+        self.generic_visit(node)
+
+    def _check_for_redundant_enumerate(self, node: Union[AnyFor, ast.comprehension]) -> None:  # noqa: E501
+        if not isinstance(node.iter, ast.Call):
+            return
+
+        if not isinstance(node.iter.func, ast.Name):
+            return
+
+        if node.iter.func.id != 'enumerate':
+            return
+
+        if isinstance(node.target, ast.Tuple):
+            index_receiver = node.target.elts[0]
+        else:
+            index_receiver = node.target
+
+        if isinstance(index_receiver, ast.Name) and index_receiver.id == '_':
+            self.add_violation(RedundantEnumerateViolation(index_receiver))


Test patch:
diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py
--- a/tests/fixtures/noqa/noqa.py
+++ b/tests/fixtures/noqa/noqa.py
@@ -792,3 +792,13 @@ def get_item():  # noqa: WPS463
 
 def bare_raise_function():
     raise # noqa: WPS467
+
+for _, something in enumerate(collection): # noqa: WPS468
+    report(something)
+
+variable_to_store_things = {
+    definitely_something
+    for _, definitely_something in enumerate(collection) # noqa: WPS468
+}
+
+
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
@@ -247,6 +247,7 @@
     'WPS465': 1,
     'WPS466': 0,  # defined in version specific table.
     'WPS467': 1,
+    'WPS468': 2,
 
     'WPS500': 1,
     'WPS501': 1,
diff --git a/tests/test_visitors/test_ast/test_redundancy/test_redundant_enumerate.py b/tests/test_visitors/test_ast/test_redundancy/test_redundant_enumerate.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_redundancy/test_redundant_enumerate.py
@@ -0,0 +1,165 @@
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    RedundantEnumerateViolation,
+)
+from wemake_python_styleguide.visitors.ast.redundancy import (
+    RedundantEnumerateVisitor,
+)
+
+# Correct:
+
+correct_iteration_without_enumerate = """
+def container():
+    for item in items:
+      ...
+"""
+
+correct_iteration_without_enumerate_raw = """
+def container():
+    for item in (1, 2, 3):
+      ...
+"""
+
+correct_iteration_enumerate_receiver = """
+def container():
+    for result in enumerate(items):
+      ...
+"""
+
+correct_iteration_enumerate_tuple = """
+def container():
+    for i, value in enumerate(items):
+      ...
+"""
+
+correct_iteration_enumerate_comprehension = """
+def container():
+    some_value = {
+        something
+        for index, something in enumerate(items)
+    }
+"""
+
+correct_iteration_anonymous_func = """
+def container():
+    for item in (lambda a: [a])(42):
+      ...
+"""
+
+# Wrong:
+
+wrong_iteration_enumerate_tuple_placeholder = """
+def container():
+    for _, item in enumerate(items):
+      ...
+"""
+
+wrong_iteration_with_receiver_placeholder = """
+def container():
+    for _ in enumerate(items):
+        ...
+"""
+
+wrong_iteration_set_comprehension = """
+def container():
+    some_value = {
+        something
+        for _, something in enumerate(items)
+    }
+"""
+
+wrong_iteration_dict_comprehension = """
+def container():
+    some_value = {
+        something: something
+        for _, something in enumerate(items)
+    }
+"""
+
+wrong_iteration_generator_expression = """
+def container():
+    some_value = (
+        something
+        for _, something in enumerate(items)
+    )
+"""
+
+wrong_iteration_multiple_generators = """
+def container():
+    some_value = (
+        something
+        for _, something in enumerate(items)
+        for i, v in enumerate([1, 2, 3])
+        for _, another in enumerate(stuff)
+    )
+"""
+
+
+@pytest.mark.parametrize('code', [
+    wrong_iteration_enumerate_tuple_placeholder,
+    wrong_iteration_with_receiver_placeholder,
+    wrong_iteration_set_comprehension,
+    wrong_iteration_dict_comprehension,
+    wrong_iteration_generator_expression,
+])
+def test_wrong_usage_of_enumerate(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+    mode,
+):
+    """Ensures that using incorrect return annotations is forbidden."""
+    tree = parse_ast_tree(mode(code))
+
+    visitor = RedundantEnumerateVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [RedundantEnumerateViolation])
+
+
+@pytest.mark.parametrize('code', [
+    wrong_iteration_multiple_generators,
+])
+def test_wrong_multiple_generators(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+    mode,
+):
+    """Ensures that using incorrect return annotations is forbidden."""
+    tree = parse_ast_tree(mode(code))
+
+    visitor = RedundantEnumerateVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [
+        RedundantEnumerateViolation,
+        RedundantEnumerateViolation,
+    ])
+
+
+@pytest.mark.parametrize('code', [
+    correct_iteration_without_enumerate,
+    correct_iteration_without_enumerate_raw,
+    correct_iteration_anonymous_func,
+    correct_iteration_enumerate_receiver,
+    correct_iteration_enumerate_tuple,
+    correct_iteration_enumerate_comprehension,
+])
+def test_correct_usage_of_enumerate(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+    mode,
+):
+    """Ensures that it is possible to use correct type annotations."""
+    tree = parse_ast_tree(mode(code))
+
+    visitor = RedundantEnumerateVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])