Repository: wemake-services/wemake-python-styleguide

Issue:
Stray WPS513 warning.
# Bug report

`WPS513` is emitted on `if`/`else`/`if` chains that contain other statements in the last `if` block.

## What's wrong

The following snippet:

```python
if private_key is not None:
    if isinstance(private_key, PKey):
        key = private_key
    else:
        if isinstance(private_key, str):  # WPS513 Found implicit `elif` condition
            stream = io.StringIO()
            stream.write(private_key)
            stream.seek(0)
            private_key = stream
        key = RSAKey.from_private_key(private_key)
```

triggers a stray WPS513 warning.  Seems like the assignment to `key` is not taken into account.

## How is that should be

`WPS513` should only be emitted if the dangling `if` statement is the only one present in the `else` block.

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670?src=pr&el=h1) Report
> Merging [#1670](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670?src=pr&el=desc) into [master](https://codecov.io/gh/wemake-services/wemake-python-styleguide/commit/259e5d39622bada48cba95dce2eb94d2d77aaecc?el=desc) will **not change** coverage.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff            @@
##            master     #1670   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          105       105           
  Lines         5465      5485   +20     
  Branches      1204      1209    +5     
=========================================
+ Hits          5465      5485   +20     
```


| [Impacted Files](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...\_python\_styleguide/visitors/tokenize/conditions.py](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670/diff?src=pr&el=tree#diff-d2VtYWtlX3B5dGhvbl9zdHlsZWd1aWRlL3Zpc2l0b3JzL3Rva2VuaXplL2NvbmRpdGlvbnMucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670?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/1670?src=pr&el=footer). Last update [259e5d3...4df9b9e](https://codecov.io/gh/wemake-services/wemake-python-styleguide/pull/1670?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

sobolevn: Thanks!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb687639..08bd8160 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,7 @@ Semantic versioning in our case means:
 - Make `WPS326` work when there is comment between string literals
 - Allowed yield statements in call method
 - Allow to use `^` with `1`
+- Fixes false positives in WPS513
 
 ### Misc
 
diff --git a/wemake_python_styleguide/visitors/tokenize/conditions.py b/wemake_python_styleguide/visitors/tokenize/conditions.py
index 1cf272b2..1cbc33ec 100644
--- a/wemake_python_styleguide/visitors/tokenize/conditions.py
+++ b/wemake_python_styleguide/visitors/tokenize/conditions.py
@@ -1,5 +1,5 @@
 import tokenize
-from typing import ClassVar, FrozenSet
+from typing import ClassVar, FrozenSet, Sequence
 
 from typing_extensions import final
 
@@ -74,21 +74,63 @@ class IfElseVisitor(BaseTokenVisitor):
 
         return False
 
-    def _check_implicit_elif(self, token: tokenize.TokenInfo) -> None:
-        if token.string != 'else':
-            return
+    def _if_has_code_below(
+        self,
+        remaining_tokens: Sequence[tokenize.TokenInfo],
+    ) -> bool:
+        """
+        Checks code immediately below an if statement to remove false positives.
+
+        Checks that, below an if that comes immediately after an else, there is
+        more code to be considered so as not to throw an incorrect violation.
+        """
+        index = 1
+
+        while remaining_tokens[index - 1].exact_type != tokenize.INDENT:
+            index += 1
+
+        context_count = 1
 
-        index = self.file_tokens.index(token)
+        while context_count:
+            next_token = remaining_tokens[index]
+            if next_token.exact_type == tokenize.INDENT:
+                context_count += 1
+            if next_token.exact_type == tokenize.DEDENT:
+                context_count -= 1
+            index += 1
+
+        return remaining_tokens[index].exact_type != tokenize.DEDENT
+
+    def _check_complex_else(
+        self,
+        tokens: Sequence[tokenize.TokenInfo],
+        current_token: tokenize.TokenInfo,
+        index: int,
+    ) -> None:
+        complex_else = self._if_has_code_below(tokens[index + 1:])
+        if not complex_else:
+            self.add_violation(ImplicitElifViolation(current_token))
+
+    def _is_invalid_token(self, index: int, token: tokenize.TokenInfo) -> bool:
+        is_not_else = token.string != 'else'
 
         # `else` token can belong also to `for` and `try/except` statement,
         # which can trigger false positive for that violation.
-        if not self._does_else_belong_to_if(index):
+        belongs_to_if = self._does_else_belong_to_if(index)
+
+        return is_not_else or not belongs_to_if
+
+    def _check_implicit_elif(self, token: tokenize.TokenInfo) -> None:
+        token_index = self.file_tokens.index(token)
+
+        if self._is_invalid_token(token_index, token):
             return
 
         # There's a bug in coverage, I am not sure how to make it work.
-        for next_token in self.file_tokens[index + 1:]:  # pragma: no cover
+        next_tokens = self.file_tokens[token_index + 1:]
+        for index, next_token in enumerate(next_tokens):  # pragma: no cover
             if next_token.exact_type in self._allowed_token_types:
                 continue
             elif next_token.string == 'if':
-                self.add_violation(ImplicitElifViolation(next_token))
+                self._check_complex_else(next_tokens, next_token, index)
             return


Test patch:
diff --git a/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py b/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py
index 0da1a2e9..11109a6c 100644
--- a/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py
+++ b/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py
@@ -34,6 +34,15 @@ else:
             ...
 """
 
+correct_else_if = """
+if some:
+    ...
+else:
+    if other:
+        ...
+    ...
+"""
+
 # Wrong:
 
 implicit_elif = """
@@ -44,6 +53,16 @@ else:
         ...
 """
 
+implicit_elif_nested_if = """
+if some:
+    ...
+else:
+    if other:
+        ...
+        if more:
+            ...
+"""
+
 # False positives, triggered by other constructs with `else` clause
 # (see https://github.com/wemake-services/wemake-python-styleguide/issues/835)
 
@@ -80,6 +99,7 @@ technically_correct_token_stream = """
     elif_cases,
     if_expression_in_else,
     not_direct_parent,
+    correct_else_if,
 ])
 def test_correct_if_statements(
     code,
@@ -98,6 +118,7 @@ def test_correct_if_statements(
 
 @pytest.mark.parametrize('code', [
     implicit_elif,
+    implicit_elif_nested_if,
 ])
 def test_implicit_elif_statements(
     code,