Repository: tox-dev/sphinx-autodoc-typehints
Pull request #254 (2022-09-18 21:18:29)

allow starred args and kwargs in docstring params
closes: #253 

Numerlor: Should the changelog go under 1.20?

I'm also not quite sure on the 3.7 test, as the part I added is without differences in the assert

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f6ea3a..2188a3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 - Use hatchling instead of setuptools
 - Add support for typing.ParamSpec
+- Allow star prefixes for parameter names in docstring
 
 ## 1.19.2
 
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 32e8124..cbfa526 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -500,6 +500,49 @@ def process_docstring(
         delattr(app.config, "_annotation_globals")
 
 
+def _get_sphinx_line_keyword_and_argument(line: str) -> tuple[str, str | None] | None:
+    """
+    Extract a keyword, and its optional argument out of a sphinx field option line.
+
+    For example
+    >>> _get_sphinx_line_keyword_and_argument(":param parameter:")
+    ("param", "parameter")
+    >>> _get_sphinx_line_keyword_and_argument(":return:")
+    ("return", None)
+    >>> _get_sphinx_line_keyword_and_argument("some invalid line")
+    None
+    """
+
+    param_line_without_description = line.split(":", maxsplit=2)  # noqa: SC200
+    if len(param_line_without_description) != 3:
+        return None
+
+    split_directive_and_name = param_line_without_description[1].split(maxsplit=1)  # noqa: SC200
+    if len(split_directive_and_name) != 2:
+        return (split_directive_and_name[0], None)
+
+    return tuple(split_directive_and_name)  # type: ignore
+
+
+def _line_is_param_line_for_arg(line: str, arg_name: str) -> bool:
+    """Return True if `line` is a valid parameter line for `arg_name`, false otherwise."""
+    keyword_and_name = _get_sphinx_line_keyword_and_argument(line)
+    if keyword_and_name is None:
+        return False
+
+    keyword, doc_name = keyword_and_name
+    if doc_name is None:
+        return False
+
+    if keyword not in {"param", "parameter", "arg", "argument"}:
+        return False
+
+    for prefix in ("", r"\*", r"\**", r"\*\*"):
+        if doc_name == prefix + arg_name:
+            return True
+    return False
+
+
 def _inject_types_to_docstring(
     type_hints: dict[str, Any],
     signature: inspect.Signature | None,
@@ -521,10 +564,12 @@ def _inject_types_to_docstring(
 
         formatted_annotation = format_annotation(annotation, app.config)
 
-        search_for = {f":{field} {arg_name}:" for field in ("param", "parameter", "arg", "argument")}
         insert_index = None
         for at, line in enumerate(lines):
-            if any(line.startswith(search_string) for search_string in search_for):
+            if _line_is_param_line_for_arg(line, arg_name):
+                # Get the arg_name from the doc to match up for type in case it has a star prefix.
+                # Line is in the correct format so this is guaranteed to return tuple[str, str].
+                _, arg_name = _get_sphinx_line_keyword_and_argument(line)  # type: ignore[assignment, misc]
                 insert_index = at
                 break
 


Test patch:
diff --git a/tests/roots/test-dummy/dummy_module.py b/tests/roots/test-dummy/dummy_module.py
index 0f6dc3c..d94c7dd 100644
--- a/tests/roots/test-dummy/dummy_module.py
+++ b/tests/roots/test-dummy/dummy_module.py
@@ -126,6 +126,15 @@ def function(x: bool, y: int, z_: typing.Optional[str] = None) -> str:  # noqa:
     """
 
 
+def function_with_starred_documentation_param_names(*args: int, **kwargs: str):  # noqa: U100
+    r"""
+    Function docstring.
+
+    :param \*args: foo
+    :param \**kwargs: bar
+    """
+
+
 def function_with_escaped_default(x: str = "\b"):  # noqa: U100
     """
     Function docstring.
diff --git a/tests/roots/test-dummy/index.rst b/tests/roots/test-dummy/index.rst
index b822977..fac0186 100644
--- a/tests/roots/test-dummy/index.rst
+++ b/tests/roots/test-dummy/index.rst
@@ -19,6 +19,8 @@ Dummy Module
 
 .. autofunction:: dummy_module.function_with_typehint_comment
 
+.. autofunction:: dummy_module.function_with_starred_documentation_param_names
+
 .. autoclass:: dummy_module.ClassWithTypehints
    :members:
 
diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py
index 48013ee..2b6fde0 100644
--- a/tests/test_sphinx_autodoc_typehints.py
+++ b/tests/test_sphinx_autodoc_typehints.py
@@ -619,6 +619,15 @@ def test_sphinx_output(
            Return type:
               "None"
 
+        dummy_module.function_with_starred_documentation_param_names(*args, **kwargs)
+
+           Function docstring.
+
+           Parameters:
+              * ***args** ("int") -- foo
+
+              * ****kwargs** ("str") -- bar
+
         class dummy_module.ClassWithTypehints(x)
 
            Class docstring.