Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid to `raise` exception from itself
# Rule request

We need to forbid code like `raise exc from exc`, because it does not make much sense and works the same way:

```python
>>> e = Exception('m')
>>> raise e
Traceback (most recent call last):
  File "ex.py", line 1, in <module>
    e = Exception("m"); raise e
Exception: m
```

And with `from`:

```python
>>> e = Exception('m')
>>> raise e from e
Traceback (most recent call last):
  File "ex.py", line 1, in <module>
    e = Exception("m"); raise e
Exception: m
```

We need to explicitly test two cases:
1. New `raise`
2. Re-raising inside `except` 


Discussion:
Hi! Could you please assign me this? Im new and would like to know more!
Hi! @shruthi223 thanks a lot! Take a look at https://github.com/wemake-services/wemake-python-styleguide/blob/master/CONTRIBUTING.md
Hi! Been looking into the resources but since I'm a beginner, I haven't been able to make a lot of sense with it. Would it be possible to guide me on how to proceed and maybe what python file I can look at? Thanks!
@shruthi223 sure! You need to modify this place: https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/visitors/ast/keywords.py#L54-L63

Write new `_check_raise_from_itself` method and a new violation inheriting `ASTViolation`
Hi, I am an aspiring Hacktoberfest an OpenSource newbie. Can I take on this? and is this eligible for hacktoberfest ?
@singhaditya28 yes, sure! Thank you!

Resolving patch:
diff --git a/wemake_python_styleguide/logic/tree/exceptions.py b/wemake_python_styleguide/logic/tree/exceptions.py
--- a/wemake_python_styleguide/logic/tree/exceptions.py
+++ b/wemake_python_styleguide/logic/tree/exceptions.py
@@ -20,6 +20,11 @@ def get_exception_name(node: ast.Raise) -> Optional[str]:
     return getattr(exception, 'id', None)
 
 
+def get_cause_name(node: ast.Raise) -> Optional[str]:
+    """Returns the cause name or ``None`` if node has not it."""
+    return getattr(node.cause, 'id', None)
+
+
 def get_all_exception_names(node: ast.Try) -> List[str]:
     """Returns a list of all exceptions names in ``ast.Try``."""
     exceptions: List[str] = []
diff --git a/wemake_python_styleguide/violations/best_practices.py b/wemake_python_styleguide/violations/best_practices.py
--- a/wemake_python_styleguide/violations/best_practices.py
+++ b/wemake_python_styleguide/violations/best_practices.py
@@ -85,6 +85,7 @@
    NewStyledDecoratorViolation
    BareRaiseViolation
    RedundantEnumerateViolation
+   RaiseFromItselfViolation
 
 Best practices
 --------------
@@ -158,6 +159,7 @@
 .. autoclass:: NewStyledDecoratorViolation
 .. autoclass:: BareRaiseViolation
 .. autoclass:: RedundantEnumerateViolation
+.. autoclass:: RaiseFromItselfViolation
 
 """
 
@@ -2640,3 +2642,33 @@ class RedundantEnumerateViolation(ASTViolation):
 
     error_template = 'Found redundant use of `enumerate`'
     code = 468
+
+
+@final
+class RaiseFromItselfViolation(ASTViolation):
+    """
+    Forbid raising an exception from itself.
+
+    Reasoning:
+        It doesn't make sense to raise an exception from it self,
+        since the final behavior will be the same.
+
+    Solution:
+        Don't raise an exeception from itself.
+
+    Example::
+
+        # Correct:
+        ex = Exception('Some Exception')
+        raise ex
+
+        # Wrong:
+        ex = Exception('Some Exception')
+        raise ex from ex
+
+    .. versionadded:: 0.16.0
+
+    """
+
+    error_template = 'Found error raising from itself'
+    code = 469
diff --git a/wemake_python_styleguide/visitors/ast/keywords.py b/wemake_python_styleguide/visitors/ast/keywords.py
--- a/wemake_python_styleguide/visitors/ast/keywords.py
+++ b/wemake_python_styleguide/visitors/ast/keywords.py
@@ -12,7 +12,10 @@
 from wemake_python_styleguide.logic.naming import name_nodes
 from wemake_python_styleguide.logic.nodes import get_parent
 from wemake_python_styleguide.logic.tree import keywords, operators
-from wemake_python_styleguide.logic.tree.exceptions import get_exception_name
+from wemake_python_styleguide.logic.tree.exceptions import (
+    get_cause_name,
+    get_exception_name,
+)
 from wemake_python_styleguide.logic.tree.variables import (
     is_valid_block_variable_definition,
 )
@@ -21,6 +24,7 @@
     BareRaiseViolation,
     BaseExceptionRaiseViolation,
     ContextManagerVariableDefinitionViolation,
+    RaiseFromItselfViolation,
     RaiseNotImplementedViolation,
     WrongKeywordConditionViolation,
     WrongKeywordViolation,
@@ -56,6 +60,7 @@ def visit_Raise(self, node: ast.Raise) -> None:
         """Checks how ``raise`` keyword is used."""
         self._check_exception_type(node)
         self._check_bare_raise(node)
+        self._check_raise_from_itself(node)
         self.generic_visit(node)
 
     def _check_exception_type(self, node: ast.Raise) -> None:
@@ -74,6 +79,13 @@ def _check_bare_raise(self, node: ast.Raise) -> None:
             if not parent_except:
                 self.add_violation(BareRaiseViolation(node))
 
+    def _check_raise_from_itself(self, node: ast.Raise) -> None:
+        if node.exc and node.cause:
+            names_are_same = get_exception_name(node) == get_cause_name(node)
+
+            if names_are_same:
+                self.add_violation(RaiseFromItselfViolation(node))
+
 
 @final
 @alias('visit_any_function', (


Test patch:
diff --git a/tests/fixtures/noqa/noqa.py b/tests/fixtures/noqa/noqa.py
--- a/tests/fixtures/noqa/noqa.py
+++ b/tests/fixtures/noqa/noqa.py
@@ -801,4 +801,8 @@ def bare_raise_function():
     for _, definitely_something in enumerate(collection) # noqa: WPS468
 }
 
-
+try:  # noqa: WPS328
+    raise TypeError('Type Error')
+except TypeError as raise_from_itself:
+    my_print(raise_from_itself)
+    raise raise_from_itself from raise_from_itself  # noqa: WPS469
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
@@ -143,7 +143,7 @@
     'WPS325': 1,
     'WPS326': 1,
     'WPS327': 1,
-    'WPS328': 2,
+    'WPS328': 3,
     'WPS329': 1,
     'WPS330': 1,
     'WPS331': 1,
@@ -248,6 +248,7 @@
     'WPS466': 0,  # defined in version specific table.
     'WPS467': 1,
     'WPS468': 2,
+    'WPS469': 1,
 
     'WPS500': 1,
     'WPS501': 1,
diff --git a/tests/test_visitors/test_ast/test_keywords/test_raise.py b/tests/test_visitors/test_ast/test_keywords/test_raise.py
deleted file mode 100644
--- a/tests/test_visitors/test_ast/test_keywords/test_raise.py
+++ /dev/null
@@ -1,188 +0,0 @@
-import pytest
-
-from wemake_python_styleguide.violations.best_practices import (
-    BareRaiseViolation,
-    BaseExceptionRaiseViolation,
-    RaiseNotImplementedViolation,
-)
-from wemake_python_styleguide.visitors.ast.keywords import WrongRaiseVisitor
-
-raise_exception_method = """
-class CheckAbstractMethods():
-    def check_exception(self):
-        raise {0}
-"""
-
-raise_exception_function = """
-def check_exception_without_call():
-    raise {0}
-"""
-
-raise_exception_property = """
-class CheckAbstractMethods():
-    @property
-    def check_exception(self):
-        raise {0}
-"""
-
-raise_exception_raw = 'raise {0}'
-
-
-@pytest.mark.parametrize('code', [
-    raise_exception_method,
-    raise_exception_function,
-    raise_exception_raw,
-    raise_exception_property,
-])
-@pytest.mark.parametrize('exception', [
-    'NotImplemented',
-    'NotImplemented()',
-])
-def test_raise_not_implemented(
-    assert_errors,
-    parse_ast_tree,
-    code,
-    exception,
-    default_options,
-    mode,
-):
-    """Testing that `raise NotImplemented` is restricted."""
-    tree = parse_ast_tree(mode(code.format(exception)))
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [RaiseNotImplementedViolation])
-
-
-@pytest.mark.parametrize('code', [
-    raise_exception_method,
-    raise_exception_function,
-    raise_exception_raw,
-    raise_exception_property,
-])
-@pytest.mark.parametrize('exception', [
-    'BaseException',
-    'BaseException()',
-    'Exception',
-    'Exception()',
-])
-def test_raise_base_exception(
-    assert_errors,
-    parse_ast_tree,
-    code,
-    exception,
-    default_options,
-    mode,
-):
-    """Testing `raise BaseException` and `raise Exception` are restricted."""
-    tree = parse_ast_tree(mode(code.format(exception)))
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [BaseExceptionRaiseViolation])
-
-
-@pytest.mark.parametrize('code', [
-    raise_exception_method,
-    raise_exception_function,
-    raise_exception_raw,
-    raise_exception_property,
-])
-@pytest.mark.parametrize('exception', [
-    'NotImplementedError',
-    'NotImplementedError()',
-    'UserDefinedError',
-    'UserDefinedError()',
-])
-def test_raise_good_errors(
-    assert_errors,
-    parse_ast_tree,
-    code,
-    exception,
-    default_options,
-    mode,
-):
-    """Testing that good `raise` usages are allowed."""
-    tree = parse_ast_tree(mode(code.format(exception)))
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [])
-
-
-def test_bare_raise_wrong_visitor(
-    assert_errors,
-    parse_ast_tree,
-    default_options,
-):
-    """Testing that bare `raise` is allowed."""
-    code = """
-    try:
-        1 / 0
-    except Exception:
-        raise
-    """
-    tree = parse_ast_tree(code)
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [])
-
-
-bare_raise_except_function = """
-def bare_raise_with_except():
-    try:
-        print('test')
-    except:
-        raise
-"""
-
-bare_raise_if_function = """
-def bare_raise_with_if():
-    try:
-        print('test')
-    except:
-        if 1 == 1:
-            raise
-"""
-
-
-def test_bare_raise(
-    assert_errors,
-    parse_ast_tree,
-    default_options,
-):
-    """Testing bare raise without except block."""
-    code = """
-    def bare_raise():
-        raise
-    """
-    tree = parse_ast_tree(code)
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [BareRaiseViolation])
-
-
-@pytest.mark.parametrize('code', [
-    bare_raise_except_function,
-    bare_raise_if_function,
-])
-def test_bare_raise_except(
-    assert_errors,
-    parse_ast_tree,
-    code,
-    default_options,
-):
-    """Testing that bare `raise` is only allowed in except blocks."""
-    tree = parse_ast_tree(code)
-
-    visitor = WrongRaiseVisitor(default_options, tree=tree)
-    visitor.run()
-
-    assert_errors(visitor, [])
diff --git a/tests/test_visitors/test_ast/test_keywords/test_raise/test_bare_raise_violation.py b/tests/test_visitors/test_ast/test_keywords/test_raise/test_bare_raise_violation.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_keywords/test_raise/test_bare_raise_violation.py
@@ -0,0 +1,81 @@
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    BareRaiseViolation,
+)
+from wemake_python_styleguide.visitors.ast.keywords import WrongRaiseVisitor
+
+
+def test_bare_raise(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+):
+    """Testing bare raise without except block."""
+    code = """
+    def bare_raise():
+        raise
+    """
+    tree = parse_ast_tree(code)
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [BareRaiseViolation])
+
+
+def test_bare_raise_wrong_visitor(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+):
+    """Testing that bare `raise` is allowed."""
+    code = """
+    try:
+        1 / 0
+    except Exception:
+        raise
+    """
+    tree = parse_ast_tree(code)
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
+
+
+bare_raise_except_function = """
+def bare_raise_with_except():
+    try:
+        print('test')
+    except:
+        raise
+"""
+
+bare_raise_if_function = """
+def bare_raise_with_if():
+    try:
+        print('test')
+    except:
+        if 1 == 1:
+            raise
+"""
+
+
+@pytest.mark.parametrize('code', [
+    bare_raise_except_function,
+    bare_raise_if_function,
+])
+def test_bare_raise_except(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+):
+    """Testing that bare `raise` is only allowed in except blocks."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
diff --git a/tests/test_visitors/test_ast/test_keywords/test_raise/test_exception_raise_violation.py b/tests/test_visitors/test_ast/test_keywords/test_raise/test_exception_raise_violation.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_keywords/test_raise/test_exception_raise_violation.py
@@ -0,0 +1,84 @@
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    BaseExceptionRaiseViolation,
+)
+from wemake_python_styleguide.visitors.ast.keywords import WrongRaiseVisitor
+
+raise_exception_method = """
+class CheckAbstractMethods():
+    def check_exception(self):
+        raise {0}
+"""
+
+raise_exception_function = """
+def check_exception_without_call():
+    raise {0}
+"""
+
+raise_exception_raw = 'raise {0}'
+
+raise_exception_property = """
+class CheckAbstractMethods():
+    @property
+    def check_exception(self):
+        raise {0}
+"""
+
+
+@pytest.mark.parametrize('code', [
+    raise_exception_method,
+    raise_exception_function,
+    raise_exception_raw,
+    raise_exception_property,
+])
+@pytest.mark.parametrize('exception', [
+    'BaseException',
+    'BaseException()',
+    'Exception',
+    'Exception()',
+])
+def test_raise_base_exception(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    exception,
+    default_options,
+    mode,
+):
+    """Testing `raise BaseException` and `raise Exception` are restricted."""
+    tree = parse_ast_tree(mode(code.format(exception)))
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [BaseExceptionRaiseViolation])
+
+
+@pytest.mark.parametrize('code', [
+    raise_exception_method,
+    raise_exception_function,
+    raise_exception_raw,
+    raise_exception_property,
+])
+@pytest.mark.parametrize('exception', [
+    'NotImplementedError',
+    'NotImplementedError()',
+    'UserDefinedError',
+    'UserDefinedError()',
+])
+def test_raise_good_errors(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    exception,
+    default_options,
+    mode,
+):
+    """Testing that good `raise` usages are allowed."""
+    tree = parse_ast_tree(mode(code.format(exception)))
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
diff --git a/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_from_itself_violation.py b/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_from_itself_violation.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_from_itself_violation.py
@@ -0,0 +1,71 @@
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    RaiseFromItselfViolation,
+)
+from wemake_python_styleguide.visitors.ast.keywords import WrongRaiseVisitor
+
+raise_from_itself_outside_try = """
+e = Exception('Some Exception')
+raise e from e
+"""
+
+raise_from_itself_inside_try = """
+def raise_from_itself():
+    try:
+        print('test')
+    except Exeception as ex:
+        raise ex from ex
+"""
+
+
+@pytest.mark.parametrize('code', [
+    raise_from_itself_outside_try,
+    raise_from_itself_inside_try,
+])
+def test_raise_from_itself(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Testing that are not allowed to raise an exception from itself."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongRaiseVisitor(default_options, tree)
+    visitor.run()
+
+    assert_errors(visitor, [RaiseFromItselfViolation])
+
+
+raise_from_other_exception_outside_try = """
+first_exception = Exception('First Exception')
+second_exception = Exception('Second Exception')
+raise second_exception from first_exception
+"""
+
+raise_from_other_exception_inside_try = """
+try:
+    raise TypeError('Type Error')
+except TypeError as ex:
+    raise NameError('New Exception') from ex
+"""
+
+
+@pytest.mark.parametrize('code', [
+    raise_from_other_exception_outside_try,
+    raise_from_other_exception_inside_try,
+])
+def test_raise_from_other_exception(
+    assert_errors,
+    parse_ast_tree,
+    default_options,
+    code,
+):
+    """Testing that are allowed to raise an exception from other exception."""
+    tree = parse_ast_tree(code)
+
+    visitor = WrongRaiseVisitor(default_options, tree)
+    visitor.run()
+
+    assert_errors(visitor, [])
diff --git a/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_not_implemented_violation.py b/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_not_implemented_violation.py
new file mode 100644
--- /dev/null
+++ b/tests/test_visitors/test_ast/test_keywords/test_raise/test_raise_not_implemented_violation.py
@@ -0,0 +1,53 @@
+import pytest
+
+from wemake_python_styleguide.violations.best_practices import (
+    RaiseNotImplementedViolation,
+)
+from wemake_python_styleguide.visitors.ast.keywords import WrongRaiseVisitor
+
+raise_exception_method = """
+class CheckAbstractMethods():
+    def check_exception(self):
+        raise {0}
+"""
+
+raise_exception_function = """
+def check_exception_without_call():
+    raise {0}
+"""
+
+raise_exception_raw = 'raise {0}'
+
+raise_exception_property = """
+class CheckAbstractMethods():
+    @property
+    def check_exception(self):
+        raise {0}
+"""
+
+
+@pytest.mark.parametrize('code', [
+    raise_exception_method,
+    raise_exception_function,
+    raise_exception_raw,
+    raise_exception_property,
+])
+@pytest.mark.parametrize('exception', [
+    'NotImplemented',
+    'NotImplemented()',
+])
+def test_raise_not_implemented(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    exception,
+    default_options,
+    mode,
+):
+    """Testing that `raise NotImplemented` is restricted."""
+    tree = parse_ast_tree(mode(code.format(exception)))
+
+    visitor = WrongRaiseVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [RaiseNotImplementedViolation])