Repository: wemake-services/wemake-python-styleguide

Issue:
Forbid to use `Literal[None]`
# Rule request

## Thesis

`Literal[None]` is just the same as `None`. There's no need to use the first version.
It is not type related, it is a consistency rule.


Discussion:
Related to #792 
I can take this.
@lensvol glad to see you again! Assigned.

Resolving patch:
diff --git a/wemake_python_styleguide/violations/annotations.py b/wemake_python_styleguide/violations/annotations.py
new file mode 100644
--- /dev/null
+++ b/wemake_python_styleguide/violations/annotations.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+"""
+These checks help to prevent incorrect usage of Python 3 typing annotations.
+
+While they maybe of a great help in writing clear
+and concise code, they still can be abused.
+
+Once again, these rules are highly subjective. But, we love them.
+
+.. currentmodule:: wemake_python_styleguide.violations.annotations
+
+Summary
+-------
+
+.. autosummary::
+   :nosignatures:
+
+   LiteralNoneViolation
+
+Annotation checks
+------------------
+
+.. autoclass:: LiteralNoneViolation
+
+"""
+
+from typing_extensions import final
+
+from wemake_python_styleguide.violations.base import ASTViolation
+
+
+@final
+class LiteralNoneViolation(ASTViolation):
+    """
+    Forbids to use ``Literal[None]`` typing annotation.
+
+    Reasoning:
+        Literal[None] is just the same as None.
+        There's no need to use the first version.
+        It is not type related, it is a consistency rule.
+
+    Solution:
+        Replace ``Literal[None]`` with ``None``.
+
+    Example::
+
+        # Correct:
+        def func(empty: None):
+            '''Empty function.'''
+
+        # Wrong:
+        def func(empty: Literal[None]):
+            '''Empty function.'''
+
+    .. versionadded:: 0.13.0
+
+    """
+
+    code = 701
+    error_template = 'Found useless `Literal[None]` typing annotation'
diff --git a/wemake_python_styleguide/visitors/ast/annotations.py b/wemake_python_styleguide/visitors/ast/annotations.py
--- a/wemake_python_styleguide/visitors/ast/annotations.py
+++ b/wemake_python_styleguide/visitors/ast/annotations.py
@@ -1,10 +1,14 @@
 # -*- coding: utf-8 -*-
 
 import ast
+from typing import cast
 
 from typing_extensions import final
 
 from wemake_python_styleguide.types import AnyFunctionDef
+from wemake_python_styleguide.violations.annotations import (
+    LiteralNoneViolation,
+)
 from wemake_python_styleguide.violations.consistency import (
     MultilineFunctionAnnotationViolation,
 )
@@ -42,6 +46,19 @@ def visit_arg(self, node: ast.arg) -> None:
         self._check_arg_annotation(node)
         self.generic_visit(node)
 
+    def _check_for_literal_none(self, node: ast.Subscript) -> None:
+        annotation_name = cast(ast.Name, node.value)
+        slice_index = cast(ast.Index, node.slice)
+
+        if annotation_name.id != 'Literal':
+            return
+
+        if not isinstance(slice_index.value, ast.NameConstant):
+            return
+
+        if slice_index.value.value is None:
+            self.add_violation(LiteralNoneViolation(node))
+
     def _check_arg_annotation(self, node: ast.arg) -> None:
         for sub_node in ast.walk(node):
             lineno = getattr(sub_node, 'lineno', None)
@@ -49,6 +66,9 @@ def _check_arg_annotation(self, node: ast.arg) -> None:
                 self.add_violation(MultilineFunctionAnnotationViolation(node))
                 return
 
+            if isinstance(sub_node, ast.Subscript):
+                self._check_for_literal_none(sub_node)
+
     def _check_return_annotation(self, node: AnyFunctionDef) -> None:
         if not node.returns:
             return
@@ -58,3 +78,6 @@ def _check_return_annotation(self, node: AnyFunctionDef) -> None:
             if lineno and lineno != node.returns.lineno:
                 self.add_violation(MultilineFunctionAnnotationViolation(node))
                 return
+
+            if isinstance(sub_node, ast.Subscript):
+                self._check_for_literal_none(sub_node)


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,6 +9,7 @@
 
 from wemake_python_styleguide.options.config import Configuration
 from wemake_python_styleguide.violations import (
+    annotations,
     best_practices,
     complexity,
     consistency,
@@ -47,6 +48,7 @@ def _load_all_violation_classes():
         best_practices,
         refactoring,
         oop,
+        annotations,
     ]
 
     classes = {}
diff --git a/tests/fixtures/noqa.py b/tests/fixtures/noqa.py
--- a/tests/fixtures/noqa.py
+++ b/tests/fixtures/noqa.py
@@ -560,3 +560,10 @@ async def async_gen(self):
 unhashable = {[]}  # noqa: WPS443
 assert []  # noqa: WPS444
 unhashable = [] * 2  # noqa: WPS435
+
+
+def literal_none_func(arg: Literal[None]):  # noqa: WPS701
+    '''Literal[None]'''
+
+def literal_none_return_func() -> Literal[None]:  # noqa: WPS701
+    '''Literal[None]'''
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
@@ -205,6 +205,8 @@
     'WPS610': 1,
     'WPS611': 1,
     'WPS612': 1,
+
+    'WPS701': 2,
 })
 
 
diff --git a/tests/test_violations/test_codes.py b/tests/test_violations/test_codes.py
--- a/tests/test_violations/test_codes.py
+++ b/tests/test_violations/test_codes.py
@@ -12,7 +12,7 @@ def test_all_unique_violation_codes(all_violations):
 
 def test_all_violations_correct_numbers(all_module_violations):
     """Ensures that all violations has correct violation code numbers."""
-    assert len(all_module_violations) == 6
+    assert len(all_module_violations) == 7
 
     for index, module in enumerate(all_module_violations.keys()):
         classes = all_module_violations[module]
diff --git a/tests/test_visitors/test_ast/test_annotations/test_argument_annotations.py b/tests/test_visitors/test_ast/test_annotations/test_argument_annotations.py
--- a/tests/test_visitors/test_ast/test_annotations/test_argument_annotations.py
+++ b/tests/test_visitors/test_ast/test_annotations/test_argument_annotations.py
@@ -3,6 +3,7 @@
 import pytest
 
 from wemake_python_styleguide.visitors.ast.annotations import (
+    LiteralNoneViolation,
     MultilineFunctionAnnotationViolation,
     WrongAnnotationVisitor,
 )
@@ -32,6 +33,14 @@ def function(
 ): ...
 """
 
+correct_literal_annotation = """
+def function(arg: Literal[True]) -> Literal["foo"]: ...
+"""
+
+correct_arg_none_annotation = """
+def function(empty_arg: None): ...
+"""
+
 # Wrong:
 
 wrong_multiline_arguments = """
@@ -51,6 +60,34 @@ def function(
 ): ...
 """
 
+wrong_arg_none_annotation = """
+def function(empty_arg: Literal[None]): ...
+"""
+
+wrong_embedded_arg_none_annotation = """
+def function(empty_arg: Union[Literal[None], Optional[int]]): ...
+"""
+
+
+@pytest.mark.parametrize('code', [
+    wrong_arg_none_annotation,
+    wrong_embedded_arg_none_annotation,
+])
+def test_forbidden_literal_none_annotation(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+    mode,
+):
+    """Ensures that using incorrect argument annotations is forbiden."""
+    tree = parse_ast_tree(mode(code))
+
+    visitor = WrongAnnotationVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [LiteralNoneViolation])
+
 
 @pytest.mark.parametrize('code', [
     wrong_multiline_arguments,
@@ -78,6 +115,8 @@ def test_wrong_argument_annotation(
     correct_simple_argument,
     correct_compound_argument,
     correct_multiline_arguments,
+    correct_arg_none_annotation,
+    correct_literal_annotation,
 ])
 def test_correct_argument_annotation(
     assert_errors,
diff --git a/tests/test_visitors/test_ast/test_annotations/test_return_annotations.py b/tests/test_visitors/test_ast/test_annotations/test_return_annotations.py
--- a/tests/test_visitors/test_ast/test_annotations/test_return_annotations.py
+++ b/tests/test_visitors/test_ast/test_annotations/test_return_annotations.py
@@ -3,6 +3,7 @@
 import pytest
 
 from wemake_python_styleguide.visitors.ast.annotations import (
+    LiteralNoneViolation,
     MultilineFunctionAnnotationViolation,
     WrongAnnotationVisitor,
 )
@@ -27,8 +28,24 @@ def function(
 ) -> Optional[Union[int, str]]: ...
 """
 
+correct_return_none_annotation = """
+def function(arg) -> None: ...
+"""
+
+correct_embedded_return_none_annotation = """
+def function(arg) -> Union[None, Optional[int]]: ...
+"""
+
 # Wrong:
 
+wrong_embedded_return_none_annotation = """
+def function(arg) -> Union[Literal[None], Optional[int]]: ...
+"""
+
+wrong_return_none_annotation = """
+def function(arg) -> Literal[None]: ...
+"""
+
 wrong_multiline_return1 = """
 def function(
     arg
@@ -80,11 +97,33 @@ def test_wrong_return_annotation(
     assert_errors(visitor, [MultilineFunctionAnnotationViolation])
 
 
+@pytest.mark.parametrize('code', [
+    wrong_embedded_return_none_annotation,
+    wrong_return_none_annotation,
+])
+def test_wrong_literal_none_return_annotation(
+    assert_errors,
+    parse_ast_tree,
+    code,
+    default_options,
+    mode,
+):
+    """Ensures that using incorrect return annotations is forbiden."""
+    tree = parse_ast_tree(mode(code))
+
+    visitor = WrongAnnotationVisitor(default_options, tree=tree)
+    visitor.run()
+
+    assert_errors(visitor, [LiteralNoneViolation])
+
+
 @pytest.mark.parametrize('code', [
     correct_function_without_annotations,
     correct_simple_return,
     correct_compound_return,
     correct_multiline_return,
+    correct_embedded_return_none_annotation,
+    correct_return_none_annotation,
 ])
 def test_correct_return_annotation(
     assert_errors,