Repository: tox-dev/sphinx-autodoc-typehints (python)

Issue:
Don't add `:rtype: None`
No return type should be added to the documentation of this function:

```python
def foo() -> None:
    """Foo."""
```

Note that ruff mandates a return type: https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function

Pull request:
Don't add :rtype: None
Closes #483

Patch:
diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml
index c028c36..f546111 100644
--- a/.github/workflows/check.yaml
+++ b/.github/workflows/check.yaml
@@ -32,7 +32,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: Install the latest version of uv
-        uses: astral-sh/setup-uv@v5
+        uses: astral-sh/setup-uv@v6
         with:
           enable-cache: true
           cache-dependency-glob: "pyproject.toml"
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 8662416..4ff55fb 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -14,7 +14,7 @@ jobs:
         with:
           fetch-depth: 0
       - name: Install the latest version of uv
-        uses: astral-sh/setup-uv@v5
+        uses: astral-sh/setup-uv@v6
         with:
           enable-cache: true
           cache-dependency-glob: "pyproject.toml"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d10536d..edf68af 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
     hooks:
       - id: pyproject-fmt
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: "v0.11.5"
+    rev: "v0.11.6"
     hooks:
       - id: ruff-format
       - id: ruff
diff --git a/README.md b/README.md
index 7ddbf09..4da9476 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ The following configuration options are accepted:
   or `Optional[int]`)
 - `typehints_document_rtype` (default: `True`): If `False`, never add an `:rtype:` directive. If `True`, add the
   `:rtype:` directive if no existing `:rtype:` is found.
+- `typehints_document_rtype_none` (default: `True`): If `False`, never add an `:rtype: None` directive. If `True`, add the `:rtype: None`.
 - `typehints_use_rtype` (default: `True`): Controls behavior when `typehints_document_rtype` is set to `True`. If
   `True`, document return type in the `:rtype:` directive. If `False`, document return type as part of the `:return:`
   directive, if present, otherwise fall back to using `:rtype:`. Use in conjunction with
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 432fdf4..7e1d994 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -10,14 +10,14 @@ import sys
 import textwrap
 import types
 from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any, AnyStr, ForwardRef, NewType, TypeVar, get_type_hints
+from typing import TYPE_CHECKING, Any, AnyStr, ForwardRef, NewType, TypeVar, Union, get_type_hints
 
 from docutils import nodes
 from docutils.frontend import get_default_settings
 from sphinx.ext.autodoc.mock import mock
 from sphinx.parsers import RSTParser
 from sphinx.util import logging, rst
-from sphinx.util.inspect import TypeAliasForwardRef, TypeAliasNamespace, stringify_signature
+from sphinx.util.inspect import TypeAliasForwardRef, stringify_signature
 from sphinx.util.inspect import signature as sphinx_signature
 
 from ._parser import parse
@@ -61,6 +61,11 @@ _TYPES_DICT = {getattr(types, name): name for name in types.__all__}
 _TYPES_DICT[types.FunctionType] = "FunctionType"
 
 
+class MyTypeAliasForwardRef(TypeAliasForwardRef):
+    def __or__(self, value: Any) -> Any:
+        return Union[self, value]  # noqa: UP007
+
+
 def _get_types_type(obj: Any) -> str | None:
     try:
         return _TYPES_DICT.get(obj)
@@ -446,7 +451,7 @@ def _future_annotations_imported(obj: Any) -> bool:
 
 
 def get_all_type_hints(
-    autodoc_mock_imports: list[str], obj: Any, name: str, localns: TypeAliasNamespace
+    autodoc_mock_imports: list[str], obj: Any, name: str, localns: dict[Any, MyTypeAliasForwardRef]
 ) -> dict[str, Any]:
     result = _get_type_hint(autodoc_mock_imports, name, obj, localns)
     if not result:
@@ -517,7 +522,9 @@ def _resolve_type_guarded_imports(autodoc_mock_imports: list[str], obj: Any) ->
             _execute_guarded_code(autodoc_mock_imports, obj, module_code)
 
 
-def _get_type_hint(autodoc_mock_imports: list[str], name: str, obj: Any, localns: TypeAliasNamespace) -> dict[str, Any]:
+def _get_type_hint(
+    autodoc_mock_imports: list[str], name: str, obj: Any, localns: dict[Any, MyTypeAliasForwardRef]
+) -> dict[str, Any]:
     _resolve_type_guarded_imports(autodoc_mock_imports, obj)
     try:
         result = get_type_hints(obj, None, localns)
@@ -689,7 +696,7 @@ def process_docstring(  # noqa: PLR0913, PLR0917
     except (ValueError, TypeError):
         signature = None
 
-    localns = TypeAliasNamespace(app.config["autodoc_type_aliases"])
+    localns = {key: MyTypeAliasForwardRef(value) for key, value in app.config["autodoc_type_aliases"].items()}
     type_hints = get_all_type_hints(app.config.autodoc_mock_imports, obj, name, localns)
     app.config._annotation_globals = getattr(obj, "__globals__", {})  # noqa: SLF001
     try:
@@ -905,7 +912,7 @@ def get_insert_index(app: Sphinx, lines: list[str]) -> InsertIndexInfo | None:
     return InsertIndexInfo(insert_index=len(lines))
 
 
-def _inject_rtype(  # noqa: PLR0913, PLR0917
+def _inject_rtype(  # noqa: C901, PLR0913, PLR0917
     type_hints: dict[str, Any],
     original_obj: Any,
     app: Sphinx,
@@ -919,6 +926,8 @@ def _inject_rtype(  # noqa: PLR0913, PLR0917
         return
     if not app.config.typehints_document_rtype:
         return
+    if not app.config.typehints_document_rtype_none and type_hints["return"] is types.NoneType:
+        return
 
     r = get_insert_index(app, lines)
     if r is None:
@@ -1004,6 +1013,7 @@ def setup(app: Sphinx) -> dict[str, bool]:
     app.add_config_value("always_document_param_types", False, "html")  # noqa: FBT003
     app.add_config_value("typehints_fully_qualified", False, "env")  # noqa: FBT003
     app.add_config_value("typehints_document_rtype", True, "env")  # noqa: FBT003
+    app.add_config_value("typehints_document_rtype_none", True, "env")  # noqa: FBT003
     app.add_config_value("typehints_use_rtype", True, "env")  # noqa: FBT003
     app.add_config_value("typehints_defaults", None, "env")
     app.add_config_value("simplify_optional_unions", True, "env")  # noqa: FBT003


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 900d585..2b53940 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -1319,6 +1319,19 @@ def typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator:
     return a
 
 
+@expected(
+    """
+    mod.typehints_no_rtype_none()
+
+       Do something.
+
+    """,
+    typehints_document_rtype_none=False,
+)
+def typehints_no_rtype_none() -> None:
+    """Do something."""
+
+
 prolog = """
 .. |test_node_start| replace:: {test_node_start}
 """.format(test_node_start="test_start")
diff --git a/tests/test_integration_autodoc_type_aliases.py b/tests/test_integration_autodoc_type_aliases.py
index 9d5a951..f964042 100644
--- a/tests/test_integration_autodoc_type_aliases.py
+++ b/tests/test_integration_autodoc_type_aliases.py
@@ -103,7 +103,7 @@ mod.function(x, y)
    Function docstring.
 
    Parameters:
-      * **x** (Array) -- foo
+      * **x** ("Optional"[Array]) -- foo
 
       * **y** ("Schema") -- boo
 
@@ -115,7 +115,7 @@ mod.function(x, y)
 
 """,
 )
-def function(x: ArrayLike, y: Schema) -> str:
+def function(x: ArrayLike | None, y: Schema) -> str:
     """
     Function docstring.