Skip to content

Commit

Permalink
allow starred args and kwargs in docstring params
Browse files Browse the repository at this point in the history
  • Loading branch information
Numerlor committed Sep 18, 2022
1 parent ccc75d2 commit db188c9
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
49 changes: 47 additions & 2 deletions src/sphinx_autodoc_typehints/__init__.py
Expand Up @@ -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)
if len(param_line_without_description) != 3:
return None

split_directive_and_name = param_line_without_description[1].split(maxsplit=1)
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,
Expand All @@ -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

Expand Down
9 changes: 9 additions & 0 deletions tests/roots/test-dummy/dummy_module.py
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions tests/roots/test-dummy/index.rst
Expand Up @@ -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:

Expand Down
9 changes: 9 additions & 0 deletions tests/test_sphinx_autodoc_typehints.py
Expand Up @@ -610,6 +610,15 @@ class InnerClass
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.
Expand Down

0 comments on commit db188c9

Please sign in to comment.