Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid `[0:x]` and `[1:None]`, prefer `[:x]` and `[1:]`
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

We need to enforce consistency in several cases:
- `[0:x]` which copies all elements from the first one to `x`. We need to use `[:x]` instead. Because it is way more readable.
- `[1:None]` which copies all elements from the second to the very last one. We need to use `[1:]` instead. It is also more readable.

So, the rule is: we must omit the starting `0` and the leading `None`. Other values are fine.
Step can also be specified:  `[:0:2]` and `[1::2]`

## Reasoning

<!--
Why do you think this is a good idea?

Please, include some strong points and considerations.
-->

We do it for consistency reasons.

<!--

  Love wemake-python-styleguide? ❤️
  Do you want your features to be implemented faster?

  Please consider supporting our collective:
👉  https://opencollective.com/wemake-python-styleguide/donate

-->



Discussion:
Can I take this?
Sure, @cronos2! Thanks a lot!
Great! I believe I have my local setup complete and I'm ready to implement, but I'm not quite sure as to how to approach it.

I think I need to create a new violation as well as a new (AST) visitor (since the only visitor regarding subscripts is about levels/depth in the complexity section, which is not really related to this). Where should I put it, though? Does `operators.py` make sense? Should I create a new file for this?

Any kind of help is appreciated. Thanks!
Yes, you are 100% correct. That's exactly how it should be done. 



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
@@ -20,6 +20,7 @@
     naming,
     operators,
     statements,
+    subscripts,
 )
 
 #: Used to store all general visitors to be later passed to checker:
@@ -96,4 +97,7 @@
 
     # Complexity:
     *complexity.PRESET,
+
+    # Subscripts:
+    subscripts.SubscriptVisitor,
 )
diff --git a/wemake_python_styleguide/violations/consistency.py b/wemake_python_styleguide/violations/consistency.py
--- a/wemake_python_styleguide/violations/consistency.py
+++ b/wemake_python_styleguide/violations/consistency.py
@@ -74,6 +74,7 @@
    OperationSignNegationViolation
    VagueImportViolation
    AdditionAssignmentOnListViolation
+   RedundantSubscriptViolation
 
 Consistency checks
 ------------------
@@ -127,6 +128,7 @@
 .. autoclass:: OperationSignNegationViolation
 .. autoclass:: VagueImportViolation
 .. autoclass:: AdditionAssignmentOnListViolation
+.. autoclass:: RedundantSubscriptViolation
 
 """
 
@@ -1788,7 +1790,6 @@ class VagueImportViolation(ASTViolation):
     code = 347
 
 
-@final
 class AdditionAssignmentOnListViolation(ASTViolation):
     """
     Forbids usage of += with list arguments.
@@ -1811,3 +1812,29 @@ class AdditionAssignmentOnListViolation(ASTViolation):
 
     error_template = 'Found addition assignment with list argument'
     code = 348
+
+
+@final
+class RedundantSubscriptViolation(ASTViolation):
+    """
+    Forbids the use of redundant components in a subscript's slice.
+
+    Reasoning:
+        We do it for consistency reasons.
+
+    Example::
+
+        # Correct:
+        array[:7]
+        array[3:]
+
+        # Wrong:
+        x[0:7]
+        x[3:None]
+
+    .. versionadded:: 0.13.0
+
+    """
+
+    error_template = 'Found redundant subscript slice: {0}'
+    code = 349
diff --git a/wemake_python_styleguide/visitors/ast/subscripts.py b/wemake_python_styleguide/visitors/ast/subscripts.py
new file mode 100644
--- /dev/null
+++ b/wemake_python_styleguide/visitors/ast/subscripts.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+import ast
+
+from typing_extensions import final
+
+from wemake_python_styleguide.violations import consistency
+from wemake_python_styleguide.visitors import base
+
+
+@final
+class SubscriptVisitor(base.BaseNodeVisitor):
+    """Checks subscripts used in the code."""
+
+    def visit_Subscript(self, node: ast.Subscript) -> None:
+        """
+        Visits subscript.
+
+        Raises:
+            RedundantSubscriptViolation
+
+        """
+        self._check_redundant_subscript(node)
+        self.generic_visit(node)
+
+    def _check_redundant_subscript(self, node: ast.Subscript) -> None:
+        if not isinstance(node.slice, ast.Slice):
+            return
+
+        lower_ok = (
+            (node.slice.lower is None) or (
+                not self._is_zero(node.slice.lower) and
+                not self._is_none(node.slice.lower)
+            )
+        )
+
+        upper_ok = (
+            (node.slice.upper is None) or
+            not self._is_none(node.slice.upper)
+        )
+
+        step_ok = (
+            (node.slice.step is None) or (
+                not self._is_one(node.slice.step) and
+                not self._is_none(node.slice.step)
+            )
+        )
+
+        if not (lower_ok and upper_ok and step_ok):
+            self.add_violation(
+                consistency.RedundantSubscriptViolation(
+                    node, text=str(node),
+                ),
+            )
+
+    def _is_none(self, component_value: ast.expr) -> bool:
+        return (
+            isinstance(component_value, ast.NameConstant) and
+            component_value.value is None
+        )
+
+    def _is_zero(self, component_value: ast.expr) -> bool:
+        return isinstance(component_value, ast.Num) and component_value.n == 0
+
+    def _is_one(self, component_value: ast.expr) -> bool:
+        return isinstance(component_value, ast.Num) and component_value.n == 1


Test patch:
diff --git a/tests/fixtures/noqa.py b/tests/fixtures/noqa.py
--- a/tests/fixtures/noqa.py
+++ b/tests/fixtures/noqa.py
@@ -587,3 +587,5 @@ def nested_annotation_return_func() -> Union[str, Union[int, float]]: # noqa: WP
 
 swap_a = swap_b
 swap_b = swap_a  # noqa: WPS448
+
+print(constant[0:7])  # noqa: WPS349
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
@@ -122,6 +122,7 @@
     'WPS346': 1,
     'WPS347': 3,
     'WPS348': 1,
+    'WPS349': 1,
 
     'WPS400': 0,
     'WPS401': 0,
diff --git a/tests/test_visitors/test_ast/test_subscripts/test_redundant_subscripts.py b/tests/test_visitors/test_ast/test_subscripts/test_redundant_subscripts.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_subscripts/test_redundant_subscripts.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    RedundantSubscriptViolation,
+)
+from wemake_python_styleguide.visitors.ast.subscripts import SubscriptVisitor
+
+usage_template = 'constant{0}'
+
+
+@pytest.mark.parametrize('expression', [
+    '[0:7]',
+    '[0:7:1]',
+    '[None:7]',
+    '[3:None]',
+    '[3:None:2]',
+    '[3:7:None]',
+    '[3:7:1]',
+])
+def test_redundant_subscript(
+    assert_errors,
+    parse_ast_tree,
+    expression,
+    default_options,
+):
+    """Testing that redundant subscripts are forbidden."""
+    tree = parse_ast_tree(usage_template.format(expression))
+
+    visitor = SubscriptVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [RedundantSubscriptViolation])
+
+
+@pytest.mark.parametrize('expression', [
+    '[5]',
+    '[3:7]',
+    '[3:7:2]',
+    '[3:]',
+    '[:7]',
+    '[3::2]',
+    '[3:7:]',
+    '[:7:2]',
+    '[3::]',
+    '[:7:]',
+    '[::2]',
+    '[:]',
+    '[::]',
+])
+def test_correct_subscripts(
+    assert_errors,
+    parse_ast_tree,
+    expression,
+    default_options,
+):
+    """Testing that non-redundant subscripts are allowed."""
+    tree = parse_ast_tree(usage_template.format(expression))
+
+    visitor = SubscriptVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])