Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid to use `*[]`, `*()`, and `**{}` in calls
# Rule request

<!-- Thanks for coming up with a proposal! -->

## Thesis

Using code like:

```python
print(*[], **{})
```

Does not make any sense. It should be forbidden.
There's no need in using explicit `*[]` or `*{}` or `*()` since call that expect `*args` will just receive empty tuple. There's also no point in using `**{}` in your code.

## Reasoning

<!--
Why do you think this is a good idea?

Please, include some strong points and considerations.
-->

This is a refactoring rule. This kind of calls is just pointless. 

<!--

  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:
Hello, Nikita! I would like to work on this issue.
Awesome, @ea-volkov! You are assigned. Please, feel free to ask any questions, show your progress with pull requests, and ask for help. 👍 

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
@@ -27,6 +27,7 @@
     # General:
     statements.StatementsWithBodiesVisitor,
     statements.WrongParametersIndentationVisitor,
+    statements.PointlessStarredVisitor,
 
     keywords.WrongRaiseVisitor,
     keywords.WrongKeywordVisitor,
diff --git a/wemake_python_styleguide/violations/refactoring.py b/wemake_python_styleguide/violations/refactoring.py
--- a/wemake_python_styleguide/violations/refactoring.py
+++ b/wemake_python_styleguide/violations/refactoring.py
@@ -32,6 +32,7 @@
    ImplicitInConditionViolation
    OpenWithoutContextManagerViolation
    TypeCompareViolation
+   PointlessStarredViolation
 
 Refactoring opportunities
 -------------------------
@@ -53,6 +54,7 @@
 .. autoclass:: ImplicitInConditionViolation
 .. autoclass:: OpenWithoutContextManagerViolation
 .. autoclass:: TypeCompareViolation
+.. autoclass:: PointlessStarredViolation
 
 """
 
@@ -608,6 +610,7 @@ class ImplicitInConditionViolation(ASTViolation):
     Solution:
         Refactor compares to use ``in`` or ``not in`` clauses.
 
+
     Example::
 
         # Correct:
@@ -687,3 +690,34 @@ class TypeCompareViolation(ASTViolation):
 
     code = 516
     error_template = 'Found `type()` used to compare types'
+
+
+@final
+class PointlessStarredViolation(ASTViolation):
+    """
+    Forbids to have useless starred expressions.
+
+    Reasoning:
+        Using starred expression with constants is useless.
+        This piece of code can be rewritten to be flat.
+        Eg.: ``print(*[1, 2, 3])`` is ``print(1, 2, 3)``.
+
+    Solution:
+        Refactor your code not to use starred expressions
+        with ``list``, ``dict``, ``tuple``, and ``set`` constants.
+        Use regular argument passing instead.
+
+    Example::
+
+        # Correct:
+        my_list = [1, 2, 3, *other_iterable]
+
+        # Wrong:
+        print(*[1, 2, 3], **{{}})
+
+    .. versionadded:: 0.12.0
+
+    """
+
+    code = 517
+    error_template = 'Found pointless starred expression'
diff --git a/wemake_python_styleguide/visitors/ast/statements.py b/wemake_python_styleguide/visitors/ast/statements.py
--- a/wemake_python_styleguide/visitors/ast/statements.py
+++ b/wemake_python_styleguide/visitors/ast/statements.py
@@ -24,6 +24,9 @@
     ParametersIndentationViolation,
     UselessNodeViolation,
 )
+from wemake_python_styleguide.violations.refactoring import (
+    PointlessStarredViolation,
+)
 from wemake_python_styleguide.visitors.base import BaseNodeVisitor
 from wemake_python_styleguide.visitors.decorators import alias
 
@@ -286,3 +289,41 @@ def _check_indentation(
                     elements[index - 1].lineno,
                     multi_line_mode,
                 )
+
+
+@final
+class PointlessStarredVisitor(BaseNodeVisitor):
+    """Responsible for absence of useless starred expressions."""
+
+    _pointless_star_nodes: ClassVar[AnyNodes] = (
+        ast.Dict,
+        ast.List,
+        ast.Set,
+        ast.Tuple,
+    )
+
+    def visit_Call(self, node: ast.Call) -> None:
+        """Checks useless call arguments."""
+        self._check_starred_args(node.args)
+        self._check_double_starred_dict(node.keywords)
+        self.generic_visit(node)
+
+    def _is_pointless_star(self, node: ast.AST) -> bool:
+        return isinstance(node, self._pointless_star_nodes)
+
+    def _check_starred_args(
+        self,
+        args: Sequence[ast.AST],
+    ) -> None:
+        for node in args:
+            if isinstance(node, ast.Starred):
+                if self._is_pointless_star(node.value):
+                    self.add_violation(PointlessStarredViolation(node))
+
+    def _check_double_starred_dict(
+        self,
+        keywords: Sequence[ast.keyword],
+    ) -> None:
+        for keyword in keywords:
+            if keyword.arg is None and self._is_pointless_star(keyword.value):
+                self.add_violation(PointlessStarredViolation(keyword.value))


Test patch:
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
@@ -184,6 +184,7 @@
     'WPS514': 1,
     'WPS515': 1,
     'WPS516': 1,
+    'WPS517': 0,
 
     'WPS600': 1,
     'WPS601': 1,
diff --git a/tests/test_visitors/test_ast/test_statements/test_pointless_starred.py b/tests/test_visitors/test_ast/test_statements/test_pointless_starred.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_statements/test_pointless_starred.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from wemake_python_styleguide.violations.refactoring import (
+    PointlessStarredViolation,
+)
+from wemake_python_styleguide.visitors.ast.statements import (
+    PointlessStarredVisitor,
+)
+
+
+@pytest.mark.parametrize('code', [
+    'print(*[])',
+    'print(*())',
+    'print(*{})',  # noqa: P103
+    'print(**{})',  # noqa: P103
+    'print(*[1, 2])',
+    'print(*(1, 2))',
+    'print(*{1, 2})',
+    'print(**{"end": " "})',
+])
+def test_pointless_starred_arg(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Testing that pointless starred expression is detected."""
+    tree = parse_ast_tree(code)
+
+    visitor = PointlessStarredVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [PointlessStarredViolation])
+
+
+@pytest.mark.parametrize('code', [
+    'print(*[], **{})',  # noqa: P103
+    'print(*[], **{"1": 1})',  # noqa: P103
+    'print(*[1], **{})',  # noqa: P103
+    'print(*[1], **{"end": " "})',
+])
+def test_pointless_starred_arg_and_keyword(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Testing that pointless starred expression is detected."""
+    tree = parse_ast_tree(code)
+
+    visitor = PointlessStarredVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(
+        visitor,
+        [PointlessStarredViolation, PointlessStarredViolation],
+    )
+
+
+@pytest.mark.parametrize('code', [
+    """
+    _list = [1, 2]
+    _dict = {"end": " "}
+    print(*_list, **_dict)
+    """,
+])
+def test_useful_starred_arg_and_keyword(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Testing that pointless starred expression is detected."""
+    tree = parse_ast_tree(code)
+
+    visitor = PointlessStarredVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])