Repository: wemake-services/wemake-python-styleguide

Issue:
WPS472 only detecting one pure variable assignment
### What's wrong

WPS472 detects `name, *_ = get_address()`. 
When it comes to slightly more complex value assignments, it does not work:
1. `names[0], *_ = get_address()` 
2. `self.name, *_ = get_address()`
3. `(name, street), *_ = get_address()`

Also when extracting a second (or more values) the detection fails:
1. `name, street, *_ = get_address()`
2. `name, _, city, *_ = get_address()`
3. `name, _, self.city, *_ = get_address()`

### How it should be

It would be nice if all of the examples would also be detected as WPS472 violations.

### Flake8 version and plugins

{
  "dependencies": [],
  "platform": {
    "python_implementation": "CPython",
    "python_version": "3.9.6",
    "system": "Darwin"
  },
  "plugins": [
    {
      "is_local": false,
      "plugin": "flake8-bandit",
      "version": "2.1.2"
    },
    {
      "is_local": false,
      "plugin": "flake8-broken-line",
      "version": "0.3.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-bugbear",
      "version": "20.11.1"
    },
    {
      "is_local": false,
      "plugin": "flake8-comprehensions",
      "version": "3.4.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-darglint",
      "version": "1.8.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-debugger",
      "version": "4.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-docstrings",
      "version": "1.6.0, pydocstyle: 6.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-eradicate",
      "version": "1.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8-string-format",
      "version": "0.3.0"
    },
    {
      "is_local": false,
      "plugin": "flake8.datetimez",
      "version": "20.10.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_commas",
      "version": "2.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_isort",
      "version": "4.0.0"
    },
    {
      "is_local": false,
      "plugin": "flake8_quotes",
      "version": "3.2.0"
    },
    {
      "is_local": false,
      "plugin": "mccabe",
      "version": "0.6.1"
    },
    {
      "is_local": false,
      "plugin": "naming",
      "version": "0.11.1"
    },
    {
      "is_local": false,
      "plugin": "pycodestyle",
      "version": "2.7.0"
    },
    {
      "is_local": false,
      "plugin": "pyflakes",
      "version": "2.3.0"
    },
    {
      "is_local": false,
      "plugin": "rst-docstrings",
      "version": "0.2.3"
    },
    {
      "is_local": false,
      "plugin": "wemake_python_styleguide",
      "version": "0.16.0"
    }
  ],
  "version": "3.9.2"
}

### pip information

pip 21.3.1

### OS information

macOS Monterey
Version 12.0.1


Discussion:
Thanks! @Clemens-Kialo I hope to fix it in `0.16.1`
Just to clarify, cases like `name, street, *_ = get_address()` are ok, because we only forbid getting first and last items implicitly.

Resolving patch:
diff --git a/wemake_python_styleguide/logic/tree/variables.py b/wemake_python_styleguide/logic/tree/variables.py
--- a/wemake_python_styleguide/logic/tree/variables.py
+++ b/wemake_python_styleguide/logic/tree/variables.py
@@ -62,12 +62,10 @@ def is_getting_element_by_unpacking(targets: List[ast.expr]) -> bool:
     if len(targets) != 2:
         return False
     first_item = (
-        isinstance(targets[0], ast.Name) and
         isinstance(targets[1], ast.Starred) and
         _is_unused_variable_name(targets[1].value)
     )
     last_item = (
-        isinstance(targets[1], ast.Name) and
         isinstance(targets[0], ast.Starred) and
         _is_unused_variable_name(targets[0].value)
     )


Test patch:
diff --git a/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py b/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
--- a/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
+++ b/tests/test_visitors/test_ast/test_builtins/test_assign/test_unpacking_rules.py
@@ -21,6 +21,8 @@
 spread_assignment1 = '{0}, *second = [1, 2, 3]'
 spread_assignment2 = 'first, *{0} = [1, 2, 3]'
 spread_assignment3 = '*{0}, second = [1, 2, 3]'
+spread_assignment4 = '(first, second), *{0} = ...'
+spread_assignment5 = '*{0}, self.second = [1, 2, 3]'
 
 for_assignment = """
 def wrapper():
@@ -281,6 +283,8 @@ def test_single_element_destructing(
 @pytest.mark.parametrize('code', [
     spread_assignment2,
     spread_assignment3,
+    spread_assignment4,
+    spread_assignment5,
 ])
 @pytest.mark.parametrize('definition', [
     '_',
@@ -299,4 +303,8 @@ def test_element_getting_by_unpacking(
     visitor = WrongAssignmentVisitor(default_options, tree=tree)
     visitor.run()
 
-    assert_errors(visitor, [GettingElementByUnpackingViolation])
+    assert_errors(
+        visitor,
+        [GettingElementByUnpackingViolation],
+        ignored_types=WrongUnpackingViolation,
+    )