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:
codecov[bot]: # [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766?src=pr&el=h1) Report
> Merging [#1766](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766?src=pr&el=desc) (1f9c4d7) into [master](https://codecov.io/gh/wemake-services/wemake-python-styleguide/commit/03460c4ef429ae4e5a3646ae5379c9b3ee329262?el=desc) (03460c4) will **not change** coverage.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff            @@
##            master     #1766   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          107       107           
  Lines         5984      5963   -21     
  Branches      1339      1335    -4     
=========================================
- Hits          5984      5963   -21     
```


| [Impacted Files](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...hon\_styleguide/visitors/ast/complexity/overuses.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL2FzdC9jb21wbGV4aXR5L292ZXJ1c2VzLnB5) | `100.00% <ø> (ø)` | |
| [...ake\_python\_styleguide/logic/complexity/overuses.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL2NvbXBsZXhpdHkvb3ZlcnVzZXMucHk=) | `100.00% <100.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/walk.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3dhbGsucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/nodes.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9ub2Rlcy5weQ==) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/compat/routing.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9yb3V0aW5nLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/tree/ifs.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3RyZWUvaWZzLnB5) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/safe\_eval.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/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/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2NvbXBhdC9wYWNrYWdpbmcucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/tree/calls.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3RyZWUvY2FsbHMucHk=) | `100.00% <0.00%> (ø)` | |
| [wemake\_python\_styleguide/logic/scopes/defs.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL2xvZ2ljL3Njb3Blcy9kZWZzLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [5 more](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766?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/1766?src=pr&el=footer). Last update [03460c4...6b68270](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1766?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 abefd8ff..498b414c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@ Semantic versioning in our case means:
 - Allows to use `_` to declare several unused variables,
   like: `x, _, _ = coordinates()`
 - Fixes that `*'abc'` was not counted as pointless star expression
+- Fixes that `-some` was counted as overused expression
 
 ### Misc
 
diff --git a/wemake_python_styleguide/logic/complexity/overuses.py b/wemake_python_styleguide/logic/complexity/overuses.py
index 446cfe1f..ca1bf34b 100644
--- a/wemake_python_styleguide/logic/complexity/overuses.py
+++ b/wemake_python_styleguide/logic/complexity/overuses.py
@@ -2,6 +2,7 @@ import ast
 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
index e5f8db8e..5199ab71 100644
--- 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
index ef2b8eed..636b2f3f 100644
--- 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 @@ class Context(object):
 
 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 @@ violating_expressions = (
     '{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 @@ ignored_expressions = (
     'dict()',
     # attributes
     'some.prop',
+    # unary operator
+    '-value',
 )