diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index 95fb1e538a0..a7c799697c5 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -1151,6 +1151,41 @@ def push_item(name: str, rest: List[str]) -> None: items.append((name, list(rest), role)) del rest[:] + def search_inventory(inventory, name, hint=None): + roles = list(inventory.keys()) + if hint is not None: + preferred = [ + role + for role in roles + if role.split(":", 1)[-1].startswith(hint) + ] + roles = preferred + [role for role in roles if role not in preferred] + + for role in roles: + objects = inventory[role] + found = objects.get(name, None) + if found is not None: + domain, role = role.split(":", 1) + return role + + return None + + def translate(func, description, role): + translations = self._config.napoleon_type_aliases + if role is not None or not translations: + return func, description, role + + translated = translations.get(func, func) + match = self._name_rgx.match(translated) + if not match: + return translated, description, role + + groups = match.groupdict() + role = groups["role"] + new_func = groups["name"] or groups["name2"] + + return new_func, description, role + current_func = None rest = [] # type: List[str] @@ -1181,37 +1216,20 @@ def push_item(name: str, rest: List[str]) -> None: if not items: return [] - roles = { - 'method': 'meth', - 'meth': 'meth', - 'function': 'func', - 'func': 'func', - 'class': 'class', - 'exception': 'exc', - 'exc': 'exc', - 'object': 'obj', - 'obj': 'obj', - 'module': 'mod', - 'mod': 'mod', - 'data': 'data', - 'constant': 'const', - 'const': 'const', - 'attribute': 'attr', - 'attr': 'attr' - } - if self._what is None: - func_role = 'obj' - else: - func_role = roles.get(self._what, '') + # apply type aliases + items = [ + translate(func, description, role) + for func, description, role in items + ] + + func_role = 'obj' lines = [] # type: List[str] last_had_desc = True for func, desc, role in items: if role: link = ':%s:`%s`' % (role, func) - elif func_role: - link = ':%s:`%s`' % (func_role, func) else: - link = "`%s`_" % func + link = ':%s:`%s`' % (func_role, func) if desc or last_had_desc: lines += [''] lines += [link] diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index 56812d19390..18a7cc88bf1 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -1372,9 +1372,38 @@ def test_see_also_refs(self): .. seealso:: - :meth:`some`, :meth:`other`, :meth:`funcs` + :obj:`some`, :obj:`other`, :obj:`funcs` + \n\ + :obj:`otherfunc` + relationship +""" + self.assertEqual(expected, actual) + + docstring = """\ +numpy.multivariate_normal(mean, cov, shape=None, spam=None) + +See Also +-------- +some, other, :func:`funcs` +otherfunc : relationship + +""" + translations = { + "other": "MyClass.other", + "otherfunc": ":func:`~my_package.otherfunc`", + } + config = Config(napoleon_type_aliases=translations) + app = mock.Mock() + actual = str(NumpyDocstring(docstring, config, app, "method")) + + expected = """\ +numpy.multivariate_normal(mean, cov, shape=None, spam=None) + +.. seealso:: + + :obj:`some`, :obj:`MyClass.other`, :func:`funcs` \n\ - :meth:`otherfunc` + :func:`~my_package.otherfunc` relationship """ self.assertEqual(expected, actual)