diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 0d208c018..df1782934 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -22,12 +22,13 @@ from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _, __ from sphinx.util import logging -logger = logging.getLogger(__name__) - if False: # For type annotation from typing import Type # for python3.5.1 + +logger = logging.getLogger(__name__) + _directive_regex = re.compile(r'\.\. \S+::') _google_section_regex = re.compile(r'^(\s|\w)+:\s*$') _google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*\)') @@ -1075,14 +1076,24 @@ class NumpyDocstring(GoogleDocstring): super().__init__(docstring, config, app, what, name, obj, options) def _get_location(self) -> str: - filepath = inspect.getfile(self._obj) if self._obj is not None else "" + filepath = inspect.getfile(self._obj) if self._obj is not None else None name = self._name if filepath is None and name is None: return None + elif filepath is None: + filepath = "" return ":".join([filepath, "docstring of %s" % name]) + def _escape_args_and_kwargs(self, name: str) -> str: + func = super()._escape_args_and_kwargs + + if ", " in name: + return ", ".join(func(param) for param in name.split(", ")) + else: + return func(name) + def _consume_field(self, parse_type: bool = True, prefer_type: bool = False ) -> Tuple[str, str, List[str]]: line = next(self._line_iter) diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index a21ec7562..23935925b 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -16,6 +16,8 @@ from inspect import cleandoc from textwrap import dedent from unittest import TestCase, mock +import pytest + from sphinx.ext.napoleon import Config from sphinx.ext.napoleon.docstring import GoogleDocstring, NumpyDocstring from sphinx.ext.napoleon.docstring import ( @@ -1218,6 +1220,23 @@ class NumpyDocstringTest(BaseDocstringTest): """ Single line summary + Parameters + ---------- + arg1:str + Extended description of arg1 + *args, **kwargs: + Variable length argument list and arbitrary keyword arguments. + """, + """ + Single line summary + + :Parameters: * **arg1** (*str*) -- Extended description of arg1 + * **\\*args, \\*\\*kwargs** -- Variable length argument list and arbitrary keyword arguments. + """ + ), ( + """ + Single line summary + Yield ----- str @@ -2182,6 +2201,7 @@ definition_after_normal_text : int actual = str(NumpyDocstring(docstring, config)) self.assertEqual(expected, actual) + @contextmanager def warns(warning, match): match_re = re.compile(match) @@ -2216,3 +2236,17 @@ class TestNumpyDocstring: for token, error in zip(tokens, errors): with warns(warning, match=error): _token_type(token) + + @pytest.mark.parametrize( + ("name", "expected"), + ( + ("x, y, z", "x, y, z"), + ("*args, **kwargs", r"\*args, \*\*kwargs"), + ("*x, **y", r"\*x, \*\*y"), + ), + ) + def test_escape_args_and_kwargs(self, name, expected): + numpy_docstring = NumpyDocstring("") + actual = numpy_docstring._escape_args_and_kwargs(name) + + assert actual == expected