Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid only constants in comparison
# Rule request

## Thesis

We need to forbid constants in some specific cases like:

```python
if 9: ...
if 1 > 1: ...
if False: ...
if 9 > 0 and 4 < 2:
if 3 < 5 < 10: ...
```

It is clear that is does not make any sense. And it was made by mistake. 

Related:
- https://help.semmle.com/wiki/display/PYTHON/Comparison+of+constants
- https://help.semmle.com/wiki/display/PYTHON/Constant+in+conditional+expression+or+statement


Discussion:

Resolving patch:
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
@@ -39,6 +39,7 @@
    FormattedStringViolation
    RequiredBaseClassViolation
    MultipleIfsInComprehensionViolation
+   ConstantComparisonViolation
 
 Consistency checks
 ------------------
@@ -51,6 +52,7 @@
 .. autoclass:: FormattedStringViolation
 .. autoclass:: RequiredBaseClassViolation
 .. autoclass:: MultipleIfsInComprehensionViolation
+.. autoclass:: ConstantComparisonViolation
 
 """
 
@@ -314,3 +316,37 @@ class MultipleIfsInComprehensionViolation(ASTViolation):
     #: Error message shown to the user.
     error_template = 'Found list comprehension with multiple `if`s'
     code = 307
+
+
+class ConstantComparisonViolation(ASTViolation):
+    """
+    Forbids to have comparisons between two literals.
+
+    Reasoning:
+        When two constants are compared it is typically an indication of a
+        mistake, since the Boolean value of the comparison will always be
+        the same.
+
+    Solution:
+        Remove the constant comparison and any associated dead code.
+
+    Example::
+
+        # Wrong:
+        if 60 * 60 < 1000:
+            do_something()
+        else:
+            do_something_else()
+
+        # Correct:
+        do_something_else()
+
+    Note:
+        Returns Z308 as error code
+
+    """
+
+    should_use_text = False
+    #: Error message shown to the user.
+    error_template = 'Found constant comparison'
+    code = 308
diff --git a/wemake_python_styleguide/visitors/ast/comparisons.py b/wemake_python_styleguide/visitors/ast/comparisons.py
new file mode 100644
--- /dev/null
+++ b/wemake_python_styleguide/visitors/ast/comparisons.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import ast
+
+from wemake_python_styleguide.violations.consistency import (
+    ConstantComparisonViolation,
+)
+from wemake_python_styleguide.visitors.base import BaseNodeVisitor
+
+
+class ConstantComparisonVisitor(BaseNodeVisitor):
+    """Restricts the comparison of literals."""
+
+    def _check_is_literal(self, node: ast.AST) -> bool:
+        try:
+            ast.literal_eval(node)
+        except ValueError:
+            return False
+        else:
+            return True
+
+    def visit_Compare(self, node: ast.Compare) -> None:
+        """
+        Restricts comparisons between two literals.
+
+        Raises:
+            ConstantComparisonViolation
+
+        """
+        last_was_literal = self._check_is_literal(node.left)
+        for comparator in node.comparators:
+            next_is_literal = self._check_is_literal(comparator)
+            if last_was_literal and next_is_literal:
+                self.add_violation(ConstantComparisonViolation(node))
+                break
+            last_was_literal = next_is_literal
+        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
@@ -2,6 +2,9 @@
 
 from wemake_python_styleguide.visitors.ast import keywords, naming, numbers
 from wemake_python_styleguide.visitors.ast.classes import WrongClassVisitor
+from wemake_python_styleguide.visitors.ast.comparisons import (
+    ConstantComparisonVisitor,
+)
 from wemake_python_styleguide.visitors.ast.functions import (
     WrongFunctionCallVisitor,
 )
@@ -24,6 +27,7 @@
     numbers.MagicNumberVisitor,
     WrongStringVisitor,
     WrongContentsVisitor,
+    ConstantComparisonVisitor,
 
     # Classes:
     WrongClassVisitor,


Test patch:
diff --git a/tests/test_visitors/test_ast/test_general/test_bad_comparison.py b/tests/test_visitors/test_ast/test_general/test_bad_comparison.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_general/test_bad_comparison.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    ConstantComparisonViolation,
+)
+from wemake_python_styleguide.visitors.ast.comparisons import (
+    ConstantComparisonVisitor,
+)
+
+
+@pytest.mark.parametrize('code', [
+    'foo < bar',
+    '0 < x < 1',
+    '("foo" + "bar") in x',
+    '1 in (1, x)',
+])
+def test_string_normal(
+    assert_errors, parse_ast_tree, code, default_options,
+):
+    """Testing that comparisons work well."""
+    tree = parse_ast_tree(code)
+
+    visitor = ConstantComparisonVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    '1 == 1',
+    '1 != 0',
+    '(1) != 0',
+    '0 < 1 < x',
+    'x < 0 < 1',
+    '[x for x in y if 1 is 2]',
+    '(0 == 1) is x',
+    '"foo" "bar" in "foobarbaz"',
+    'if 1 > 1: pass',
+    'if 9 > x and 4 < 2: pass',
+    'if 3 < 5 < 10: pass',
+])
+def test_wrong_string(assert_errors, parse_ast_tree, code, default_options):
+    """Testing that violations are raised when reaching max value."""
+    tree = parse_ast_tree(code)
+
+    visitor = ConstantComparisonVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [ConstantComparisonViolation])