Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid to compare the identical variables
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

We need to find cases like this: `if some_value == some_value:`.
We also need to find other operators: `is`, `is not`, `>`, `<`, `!=`, `>=`, `<=`.

## Reasoning

It clearly does not make any sense. It is a bug in the source code.

Related: https://help.semmle.com/wiki/display/PYTHON/Comparison+of+identical+values



Discussion:
Happy to take a look at this one!
@SheldonNunes your are welcome! 🎉 

Resolving patch:
diff --git a/wemake_python_styleguide/visitors/ast/comparisons.py b/wemake_python_styleguide/visitors/ast/comparisons.py
--- a/wemake_python_styleguide/visitors/ast/comparisons.py
+++ b/wemake_python_styleguide/visitors/ast/comparisons.py
@@ -151,3 +151,32 @@ def visit_Compare(self, node: ast.Compare) -> None:
         """
         self._count_in_comparisons(node)
         self.generic_visit(node)
+
+
+class RedundantComparisonVisitor(BaseNodeVisitor):
+    """Restricts the comparison where always same result."""
+
+    def visit_Compare(self, node: ast.Compare) -> None:
+        """
+        Ensures that compares are not for same variable.
+
+        Raises:
+            ConstantComparisonViolation
+
+        """
+        self._check_redundant_compare(node)
+        self.generic_visit(node)
+
+    def _is_same_variable(self, left: ast.AST, right: ast.AST) -> bool:
+        if isinstance(left, ast.Name) and isinstance(right, ast.Name):
+            if left.id is right.id:
+                return True
+        return False
+
+    def _check_redundant_compare(self, node: ast.Compare) -> None:
+        last_variable = node.left
+        for next_variable in node.comparators:
+            if self._is_same_variable(last_variable, next_variable):
+                self.add_violation(ConstantComparisonViolation(node))
+                break
+            last_variable = next_variable


Test patch:
diff --git a/tests/test_visitors/test_ast/test_comparisons/test_redundant.py b/tests/test_visitors/test_ast/test_comparisons/test_redundant.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_comparisons/test_redundant.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.consistency import (
+    ConstantComparisonViolation,
+)
+from wemake_python_styleguide.visitors.ast.comparisons import (
+    RedundantComparisonVisitor,
+)
+
+create_variables = """
+variable = 1
+another_variable = 2
+{0}
+"""
+
+if_with_is = 'if {0} is {1}: ...'
+if_with_is_not = 'if {0} is not {1}: ...'
+
+if_with_eq = 'if {0} == {1}: ...'
+if_with_not_eq = 'if {0} != {1}: ...'
+if_with_gt = 'if {0} > {1}: ...'
+if_with_lt = 'if {0} < {1}: ...'
+if_with_gte = 'if {0} >= {1}: ...'
+if_with_lte = 'if {0} <= {1}: ...'
+
+if_with_chained_comparisons1 = 'if 0 < {0} < {1}: ...'
+if_with_chained_comparisons2 = 'if {0} > {1} > 0: ...'
+if_with_chained_comparisons3 = 'if -1 > {0} > {1} > 0: ...'
+
+if_with_in = 'if {0} in {1}: ...'
+if_with_not_in = 'if {0} not in {1}: ...'
+
+ternary = 'ternary = 0 if {0} > {1} else 1'
+while_construct = 'while {0} > {1}: ...'
+assert_construct = 'assert {0} == {1}, "message"'
+
+
+@pytest.mark.parametrize('code', [
+    if_with_is,
+    if_with_is_not,
+
+    if_with_eq,
+    if_with_not_eq,
+    if_with_lt,
+    if_with_gt,
+    if_with_lte,
+    if_with_gte,
+
+    ternary,
+    while_construct,
+    assert_construct,
+])
+@pytest.mark.parametrize('comparators', [
+    ('variable', '"test"'),
+    ('variable', 'another_variable'),
+    ('variable', '222'),
+])
+def test_not_redundant(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    comparators,
+    default_options,
+):
+    """Testing that comparisons work well."""
+    tree = parse_ast_tree(create_variables.format(code.format(*comparators)))
+
+    visitor = RedundantComparisonVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+@pytest.mark.parametrize('code', [
+    if_with_is,
+    if_with_is_not,
+
+    if_with_eq,
+    if_with_not_eq,
+    if_with_lt,
+    if_with_gt,
+    if_with_lte,
+    if_with_gte,
+
+    ternary,
+    while_construct,
+    assert_construct,
+])
+@pytest.mark.parametrize('comparators', [
+    ('variable', 'variable'),
+    ('another_variable', 'another_variable'),
+])
+def test_redundant(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    comparators,
+    default_options,
+):
+    """Testing that violations are when comparing identical variable."""
+    tree = parse_ast_tree(create_variables.format(code.format(*comparators)))
+
+    visitor = RedundantComparisonVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [ConstantComparisonViolation])