Repository: wemake-services/wemake-python-styleguide

Issue:
False positive ImplicitEnumerateViolation on range with step
# Bug report

<!--
Hi, thanks for submitting a bug. We appreciate that.

But, we will need some information about what's wrong to help you.
-->

## What's wrong
A simple function that splits a list into chunks 
```python
def chunks(lst, amount):
    for index in range(0, len(lst), amount):
        yield lst[index:index + amount]
```
will raise `ImplicitEnumerateViolation`. 

Documentation suggests using `enumerate(lst)` instead of `range(0, len(list))` but there is no `step`
<!-- Describe what is not working. Please, attach a traceback. -->

## How it should be
`ImplicitEnumerateViolation` should not raise if `step > 0` in range arguments.
<!-- Describe how it should work. -->

<!--

  Love wemake-python-styleguide? ❤️
  Do you want your bugs to be solved faster?

  Please consider supporting our collective:
👉  https://opencollective.com/wemake-python-styleguide/donate

-->

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=h1) Report
> Merging [#1792](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=desc) (fff4dc0) into [master](https://codecov.io/gh/wemake-services/wemake-python-styleguide/commit/6791154f19b32fe1153ae973138e113520f66cbc?el=desc) (6791154) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/graphs/tree.svg?width=650&height=150&src=pr&token=yMZCKw0D02)](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1792   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          107       107           
  Lines         5867      5850   -17     
  Branches      1312      1308    -4     
=========================================
- Hits          5867      5850   -17     
```


| [Impacted Files](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [wemake\_python\_styleguide/visitors/ast/functions.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9mdW5jdGlvbnMucHk=) | `100.00% <100.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/nodes.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9ub2Rlcy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/walrus.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3dhbHJ1cy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/routing.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9yb3V0aW5nLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/safe\_eval.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3NhZmVfZXZhbC5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/packaging.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9wYWNrYWdpbmcucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/visitors/ast/operators.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9vcGVyYXRvcnMucHk=) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=footer). Last update [6791154...fff4dc0](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1792?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b39d1f3..79edc4ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,7 @@ Semantic versioning in our case means:
 - Fixes false positives in WPS513 and WPS323
 - Fixes false positive WPS426 if `lambda` in loop uses only its arguments
 - Fixes false negative WPS421 with `pprint.pprint`
+- Fixes false positive ImplicitEnumerateViolation on range with step #1742
 
 ### Misc
 
diff --git a/wemake_python_styleguide/visitors/ast/functions.py b/wemake_python_styleguide/visitors/ast/functions.py
index 54232f7e..23c2be28 100644
--- a/wemake_python_styleguide/visitors/ast/functions.py
+++ b/wemake_python_styleguide/visitors/ast/functions.py
@@ -240,22 +240,38 @@ class WrongFunctionCallContextVisitior(base.BaseNodeVisitor):
             self.add_violation(TypeCompareViolation(node))
 
     def _check_range_len(self, node: ast.Call) -> None:
-        function_name = functions.given_function_called(node, {'range'})
-        if not function_name:
+        if not functions.given_function_called(node, {'range'}):
             return
 
-        is_one_argument_range = (
-            len(node.args) == 1 and
+        args_len = len(node.args)
+
+        is_one_arg_range = (
+            args_len == 1 and
             isinstance(node.args[0], ast.Call) and
             functions.given_function_called(node.args[0], {'len'})
         )
-        is_two_arguments_range = (
+        is_two_args_range = (
+            self._is_multiple_args_range_with_len(node) and
+            args_len == 2
+        )
+        # for three args add violation
+        # only if `step` arg do not equals 1 or -1
+        step_arg = args_len == 3 and operators.unwrap_unary_node(node.args[2])
+        is_three_args_range = (
+            self._is_multiple_args_range_with_len(node) and
+            args_len == 3 and
+            isinstance(step_arg, ast.Num) and
+            abs(step_arg.n) == 1
+        )
+        if any([is_one_arg_range, is_two_args_range, is_three_args_range]):
+            self.add_violation(ImplicitEnumerateViolation(node))
+
+    def _is_multiple_args_range_with_len(self, node: ast.Call) -> bool:
+        return bool(
             len(node.args) in {2, 3} and
             isinstance(node.args[1], ast.Call) and
-            functions.given_function_called(node.args[1], {'len'})
+            functions.given_function_called(node.args[1], {'len'}),
         )
-        if is_one_argument_range or is_two_arguments_range:
-            self.add_violation(ImplicitEnumerateViolation(node))
 
 
 @final


Test patch:
diff --git a/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py b/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py
index dda30860..9c569098 100644
--- a/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py
+++ b/tests/test_visitors/test_ast/test_functions/test_call_context/test_range_len.py
@@ -17,6 +17,8 @@ from wemake_python_styleguide.visitors.ast.functions import (
     'len([])',
     'len(some)',
     'len(range(10))',
+    'range(0, len(some), 2)',
+    'range(0, len(some), -2)',
 ])
 def test_correct_range_len(
     assert_errors,