Repository: wemake-services/wemake-python-styleguide

Issue:
`if` after `else` in `try` raises false positive WPS513
# Bug report

<!--
Hi, thanks for submitting a bug. We appreciate that.

But, we will need some information about what's wrong to help you.
-->

## What's wrong

Code:

```python
        try:
            return_value = ast.literal_eval(node.body)
        except ValueError:
            # The value was not literal, it's ok.
            return
        else:
            if return_value in self._primitive_values:
                self.add_violation(ImplicitPrimitiveViolation(node))
```

Output:

```
329:13   WPS513 Found implicit `elif` condition
  if return_value in self._primitive_values:
```

<!-- Describe what is not working. Please, attach a traceback. -->

## How is that should be

It should not raise this violation. It is not a part of `if` / `else` case.



Discussion:
Can I take this?
@lensvol sure! Do you have any ideas how to implement it? Because, I don't 😞 
I do have an idea and it will sound _weird_. Here it is:

The cause of the problem is the assumption that `else` token can only be applied to `if` statement, but `except` and `for` can also be followed by this token. So to prevent this error we should introduce an additional check that `else` is part of the previous `if` statement.

Here it gets tricky. 

To achieve this I propose we use a heuristic that relies on a fact that Python uses indents to distinguish between different code blocks. What we need is to find a closest preceding token with the same level of indentation and see if it is either `if` or `elif`. If it is `except` or `for`, then just carry on.

That's the idea in a nutshell. Can't say for sure that it will work (corner cases etc), but worth exploring in my opinion. 
Awesome, let's do it!

Resolving patch:
diff --git a/wemake_python_styleguide/visitors/tokenize/conditions.py b/wemake_python_styleguide/visitors/tokenize/conditions.py
--- a/wemake_python_styleguide/visitors/tokenize/conditions.py
+++ b/wemake_python_styleguide/visitors/tokenize/conditions.py
@@ -57,11 +57,36 @@ def visit_name(self, token: tokenize.TokenInfo) -> None:
         """
         self._check_implicit_elif(token)
 
+    def _does_else_belong_to_if(self, start_index: int) -> bool:
+        previous_token = self.file_tokens[start_index - 1]
+
+        if previous_token.type != tokenize.DEDENT:
+            # This is not the first token on the line, which means that it can
+            # also be "embedded" else: x if A else B
+            return False
+
+        for token in reversed(self.file_tokens[:start_index - 1]):
+            if token.type != tokenize.NAME:
+                continue
+
+            # Here we rely upon an intuition that in Python else have to be
+            # on the same level (same indentation) as parent statement.
+            if token.start[1] == previous_token.start[1]:
+                return token.string in {'if', 'elif'}
+
+        return False
+
     def _check_implicit_elif(self, token: tokenize.TokenInfo) -> None:
         if token.string != 'else':
             return
 
         index = self.file_tokens.index(token)
+
+        # `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):
+            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
             if next_token.exact_type in self._allowed_token_types:


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
--- a/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py
+++ b/tests/test_visitors/test_tokenize/test_conditions/test_implicit_elif.py
@@ -46,6 +46,37 @@
         ...
 """
 
+# False positives, triggered by other constructs with `else` clause
+# (see https://github.com/wemake-services/wemake-python-styleguide/issues/835)
+
+for_else = """
+for a in b:
+   ...
+else:
+   if some:
+       ...
+"""
+
+try_except_else = """
+try:
+   ...
+except:
+   ...
+else:
+   if some:
+       ...
+"""
+
+embedded_else = """
+... if ... else ...
+"""
+
+technically_correct_token_stream = """
+    42
+        a
+    else
+"""
+
 
 @pytest.mark.parametrize('code', [
     elif_cases,
@@ -83,3 +114,24 @@ def test_implicit_elif_statements(
     visitor.run()
 
     assert_errors(visitor, [ImplicitElifViolation])
+
+
+@pytest.mark.parametrize('code', [
+    for_else,
+    try_except_else,
+    embedded_else,
+    technically_correct_token_stream,
+])
+def test_false_positives_are_ignored(
+    code,
+    assert_errors,
+    parse_tokens,
+    default_options,
+):
+    """Testing regular conditions."""
+    file_tokens = parse_tokens(code)
+
+    visitor = IfElseVisitor(default_options, file_tokens=file_tokens)
+    visitor.run()
+
+    assert_errors(visitor, [])