Repository: wemake-services/wemake-python-styleguide

Issue:
WPS204: Overused expression should not count unary minus operator
# Bug report

Using unary minus operator often breaks rule WPS204

## What's wrong

Using unary minus operator even with named variable violates rule WPS204.
Example:
```python
my_value = 5
position = (0, -my_value)
```

## How it should be

No violation should be raised.

## System information

### flake8 information

Contents of `flake8 --bug-report`:

### pip information

Contents of `pip freeze`:



Discussion:
@pawelarybak are you interested in providing a fix?   🙂 
I can give it a shot.
Thanks! @pawelarybak 

Resolving patch:
diff --git a/wemake_python_styleguide/logic/complexity/overuses.py b/wemake_python_styleguide/logic/complexity/overuses.py
--- a/wemake_python_styleguide/logic/complexity/overuses.py
+++ b/wemake_python_styleguide/logic/complexity/overuses.py
@@ -2,6 +2,7 @@
 from typing import Union
 
 from wemake_python_styleguide.compat.aliases import FunctionNodes
+from wemake_python_styleguide.compat.nodes import Constant
 from wemake_python_styleguide.constants import SPECIAL_ARGUMENT_NAMES_WHITELIST
 from wemake_python_styleguide.logic import nodes, walk
 from wemake_python_styleguide.logic.arguments import call_args
@@ -121,3 +122,21 @@ def is_primitive(node: ast.AST) -> bool:
     elif isinstance(node, ast.Call):
         return not call_args.get_all_args(node)  # we do allow `call()`
     return False
+
+
+def is_unary_minus(node: ast.AST) -> bool:
+    """
+    Detects if node is unary minus operator.
+
+    We use this predicate to allow values
+    like ``-some_value`` to be overused.
+
+    Although negative constants like ``-1``
+    should raise violation to force naming them.
+    """
+    if isinstance(node, ast.UnaryOp):
+        return (
+            isinstance(node.op, ast.USub) and
+            not isinstance(node.operand, (Constant, ast.Num))
+        )
+    return False
diff --git a/wemake_python_styleguide/visitors/ast/complexity/overuses.py b/wemake_python_styleguide/visitors/ast/complexity/overuses.py
--- a/wemake_python_styleguide/visitors/ast/complexity/overuses.py
+++ b/wemake_python_styleguide/visitors/ast/complexity/overuses.py
@@ -101,10 +101,10 @@ class ExpressionOveruseVisitor(base.BaseNodeVisitor):
         ast.Assert,
         ast.BoolOp,
         ast.BinOp,
+        ast.UnaryOp,
         ast.Call,
         ast.Compare,
         ast.Subscript,
-        ast.UnaryOp,
         ast.Lambda,
 
         ast.DictComp,
@@ -124,6 +124,7 @@ class ExpressionOveruseVisitor(base.BaseNodeVisitor):
         overuses.is_class_context,
         overuses.is_super_call,
         overuses.is_primitive,
+        overuses.is_unary_minus,
     )
 
     _msg: ClassVar[str] = '{0}; used {1}'


Test patch:
diff --git a/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_expressions.py b/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_expressions.py
--- a/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_expressions.py
+++ b/tests/test_visitors/test_ast/test_complexity/test_overuses/test_overused_expressions.py
@@ -118,12 +118,11 @@ def method2(self, arg2: "List[int]") -> 'type.Any':
 
 violating_expressions = (
     # Nodes:
-    'assert 1',
+    'assert 1',  # statement, but still
     'a and b',
     'b + a',
     'call(1, None)',
     'a >= 1',
-    '-item.attr',
     'lambda x: x.set',
     '{a: 1 for a in "123"}',
     '{"1": a}',
@@ -134,6 +133,10 @@ def method2(self, arg2: "List[int]") -> 'type.Any':
     '{1, 2, 3}',
     '{a for a in other}',
 
+    # Unary nodes:
+    '-1',  # unary minus operator is raising when used with literal
+    '+some',  # unary plus always raises
+
     # Special cases for self:
     'self.method([star])',  # `[star]` is raising
     'self[1, 2, 3]',  # tuple is raising
@@ -164,6 +167,8 @@ def method2(self, arg2: "List[int]") -> 'type.Any':
     'dict()',
     # attributes
     'some.prop',
+    # unary operator
+    '-value',
 )