Skip to content

Commit 92a9bb6

Browse files
authored
FIX Put a slash space between xref and type args (#399)
1 parent 9735dc6 commit 92a9bb6

File tree

3 files changed

+107
-54
lines changed

3 files changed

+107
-54
lines changed

Diff for: src/sphinx_autodoc_typehints/__init__.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
211211
fully_qualified: bool = getattr(config, "typehints_fully_qualified", False)
212212
prefix = "" if fully_qualified or full_name == class_name else "~"
213213
role = "data" if module == "typing" and class_name in _PYDATA_ANNOTATIONS else "class"
214-
args_format = "\\ \\[{}]"
214+
args_format = "\\[{}]"
215215
formatted_args: str | None = ""
216216

217217
# Some types require special handling
@@ -244,9 +244,9 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
244244
args = tuple(x for x in args if x is not type(None))
245245
elif full_name in ("typing.Callable", "collections.abc.Callable") and args and args[0] is not ...:
246246
fmt = [format_annotation(arg, config) for arg in args]
247-
formatted_args = f"\\ \\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]"
247+
formatted_args = f"\\[\\[{', '.join(fmt[:-1])}], {fmt[-1]}]"
248248
elif full_name == "typing.Literal":
249-
formatted_args = f"\\ \\[{', '.join(f'``{arg!r}``' for arg in args)}]"
249+
formatted_args = f"\\[{', '.join(f'``{arg!r}``' for arg in args)}]"
250250
elif full_name == "types.UnionType":
251251
return " | ".join([format_annotation(arg, config) for arg in args])
252252

@@ -258,6 +258,8 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL
258258
else:
259259
fmt = [format_annotation(arg, config) for arg in args]
260260
formatted_args = args_format.format(", ".join(fmt))
261+
if formatted_args:
262+
formatted_args = "\\ " + formatted_args
261263

262264
return f":py:{role}:`{prefix}{full_name}`{formatted_args}"
263265

Diff for: tests/test_integration.py

+52-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from inspect import isclass
77
from pathlib import Path
88
from textwrap import dedent, indent
9-
from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union, overload # no type comments
9+
from typing import TYPE_CHECKING, Any, Callable, NewType, Optional, TypeVar, Union, overload # no type comments
1010

1111
import pytest
1212

@@ -18,6 +18,7 @@
1818
from sphinx.testing.util import SphinxTestApp
1919

2020
T = TypeVar("T")
21+
W = NewType("W", str)
2122

2223

2324
def expected(expected: str) -> Callable[[T], T]:
@@ -1174,6 +1175,52 @@ def docstring_with_definition_list_after_params_no_blank_line(param: int) -> Non
11741175
"""
11751176

11761177

1178+
@expected(
1179+
"""
1180+
mod.has_typevar(param)
1181+
1182+
Do something.
1183+
1184+
Parameters:
1185+
**param** ("TypeVar"("T")) -- A parameter.
1186+
1187+
Return type:
1188+
"TypeVar"("T")
1189+
1190+
""",
1191+
)
1192+
def has_typevar(param: T) -> T:
1193+
"""Do something.
1194+
1195+
Args:
1196+
param: A parameter.
1197+
"""
1198+
return param
1199+
1200+
1201+
@expected(
1202+
"""
1203+
mod.has_newtype(param)
1204+
1205+
Do something.
1206+
1207+
Parameters:
1208+
**param** ("NewType"("W", "str")) -- A parameter.
1209+
1210+
Return type:
1211+
"NewType"("W", "str")
1212+
1213+
""",
1214+
)
1215+
def has_newtype(param: W) -> W:
1216+
"""Do something.
1217+
1218+
Args:
1219+
param: A parameter.
1220+
"""
1221+
return param
1222+
1223+
11771224
AUTO_FUNCTION = ".. autofunction:: mod.{}"
11781225
AUTO_CLASS = """\
11791226
.. autoclass:: mod.{}
@@ -1184,6 +1231,8 @@ def docstring_with_definition_list_after_params_no_blank_line(param: int) -> Non
11841231
:members:
11851232
"""
11861233

1234+
LT_PY310 = sys.version_info < (3, 10)
1235+
11871236

11881237
@pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
11891238
@pytest.mark.sphinx("text", testroot="integration")
@@ -1217,6 +1266,8 @@ def test_integration(
12171266
result = (Path(app.srcdir) / "_build/text/index.txt").read_text()
12181267

12191268
expected = val.EXPECTED
1269+
if LT_PY310:
1270+
expected = expected.replace("NewType", "NewType()")
12201271
try:
12211272
assert result.strip() == dedent(expected).strip()
12221273
except Exception:

Diff for: tests/test_sphinx_autodoc_typehints.py

+50-50
Original file line numberDiff line numberDiff line change
@@ -201,122 +201,122 @@ def test_parse_annotation(annotation: Any, module: str, class_name: str, args: t
201201
(type, ":py:class:`type`"),
202202
(collections.abc.Callable, ":py:class:`~collections.abc.Callable`"),
203203
(Type, ":py:class:`~typing.Type`"),
204-
(Type[A], ":py:class:`~typing.Type`\\ \\[:py:class:`~%s.A`]" % __name__),
204+
(Type[A], r":py:class:`~typing.Type`\ \[:py:class:`~%s.A`]" % __name__),
205205
(Any, ":py:data:`~typing.Any`"),
206206
(AnyStr, ":py:data:`~typing.AnyStr`"),
207-
(Generic[T], ":py:class:`~typing.Generic`\\ \\[:py:class:`~typing.TypeVar`\\(``T``)]"),
207+
(Generic[T], r":py:class:`~typing.Generic`\ \[:py:class:`~typing.TypeVar`\ \(``T``)]"),
208208
(Mapping, ":py:class:`~typing.Mapping`"),
209209
(
210210
Mapping[T, int], # type: ignore[valid-type]
211-
":py:class:`~typing.Mapping`\\ \\[:py:class:`~typing.TypeVar`\\(``T``), :py:class:`int`]",
211+
r":py:class:`~typing.Mapping`\ \[:py:class:`~typing.TypeVar`\ \(``T``), :py:class:`int`]",
212212
),
213213
(
214214
Mapping[str, V_contra], # type: ignore[valid-type]
215-
":py:class:`~typing.Mapping`\\ \\[:py:class:`str`, :py:class:`~typing.TypeVar`\\("
215+
r":py:class:`~typing.Mapping`\ \[:py:class:`str`, :py:class:`~typing.TypeVar`\ \("
216216
"``V_contra``, contravariant=True)]",
217217
),
218218
(
219219
Mapping[T, U_co], # type: ignore[valid-type]
220-
":py:class:`~typing.Mapping`\\ \\[:py:class:`~typing.TypeVar`\\(``T``), "
221-
":py:class:`~typing.TypeVar`\\(``U_co``, covariant=True)]",
220+
r":py:class:`~typing.Mapping`\ \[:py:class:`~typing.TypeVar`\ \(``T``), "
221+
r":py:class:`~typing.TypeVar`\ \(``U_co``, covariant=True)]",
222222
),
223-
(Mapping[str, bool], ":py:class:`~typing.Mapping`\\ \\[:py:class:`str`, :py:class:`bool`]"),
223+
(Mapping[str, bool], r":py:class:`~typing.Mapping`\ \[:py:class:`str`, :py:class:`bool`]"),
224224
(Dict, ":py:class:`~typing.Dict`"),
225225
(
226226
Dict[T, int], # type: ignore[valid-type]
227-
":py:class:`~typing.Dict`\\ \\[:py:class:`~typing.TypeVar`\\(``T``), :py:class:`int`]",
227+
r":py:class:`~typing.Dict`\ \[:py:class:`~typing.TypeVar`\ \(``T``), :py:class:`int`]",
228228
),
229229
(
230230
Dict[str, V_contra], # type: ignore[valid-type]
231-
":py:class:`~typing.Dict`\\ \\[:py:class:`str`, :py:class:`~typing.TypeVar`\\(``V_contra``, contravariant=True)]", # noqa: E501
231+
r":py:class:`~typing.Dict`\ \[:py:class:`str`, :py:class:`~typing.TypeVar`\ \(``V_contra``, contravariant=True)]", # noqa: E501
232232
),
233233
(
234234
Dict[T, U_co], # type: ignore[valid-type]
235-
":py:class:`~typing.Dict`\\ \\[:py:class:`~typing.TypeVar`\\(``T``),"
236-
" :py:class:`~typing.TypeVar`\\(``U_co``, covariant=True)]",
235+
r":py:class:`~typing.Dict`\ \[:py:class:`~typing.TypeVar`\ \(``T``),"
236+
r" :py:class:`~typing.TypeVar`\ \(``U_co``, covariant=True)]",
237237
),
238-
(Dict[str, bool], ":py:class:`~typing.Dict`\\ \\[:py:class:`str`, :py:class:`bool`]"),
238+
(Dict[str, bool], r":py:class:`~typing.Dict`\ \[:py:class:`str`, :py:class:`bool`]"),
239239
(Tuple, ":py:data:`~typing.Tuple`"),
240-
(Tuple[str, bool], ":py:data:`~typing.Tuple`\\ \\[:py:class:`str`, :py:class:`bool`]"),
241-
(Tuple[int, int, int], ":py:data:`~typing.Tuple`\\ \\[:py:class:`int`, :py:class:`int`, :py:class:`int`]"),
242-
(Tuple[str, ...], ":py:data:`~typing.Tuple`\\ \\[:py:class:`str`, :py:data:`...<Ellipsis>`]"),
240+
(Tuple[str, bool], r":py:data:`~typing.Tuple`\ \[:py:class:`str`, :py:class:`bool`]"),
241+
(Tuple[int, int, int], r":py:data:`~typing.Tuple`\ \[:py:class:`int`, :py:class:`int`, :py:class:`int`]"),
242+
(Tuple[str, ...], r":py:data:`~typing.Tuple`\ \[:py:class:`str`, :py:data:`...<Ellipsis>`]"),
243243
(Union, ":py:data:`~typing.Union`"),
244-
(Union[str, bool], ":py:data:`~typing.Union`\\ \\[:py:class:`str`, :py:class:`bool`]"),
245-
(Union[str, bool, None], ":py:data:`~typing.Union`\\ \\[:py:class:`str`, :py:class:`bool`, :py:obj:`None`]"),
246-
pytest.param(Union[str, Any], ":py:data:`~typing.Union`\\ \\[:py:class:`str`, :py:data:`~typing.Any`]"),
247-
(Optional[str], ":py:data:`~typing.Optional`\\ \\[:py:class:`str`]"),
248-
(Union[str, None], ":py:data:`~typing.Optional`\\ \\[:py:class:`str`]"),
244+
(Union[str, bool], r":py:data:`~typing.Union`\ \[:py:class:`str`, :py:class:`bool`]"),
245+
(Union[str, bool, None], r":py:data:`~typing.Union`\ \[:py:class:`str`, :py:class:`bool`, :py:obj:`None`]"),
246+
pytest.param(Union[str, Any], r":py:data:`~typing.Union`\ \[:py:class:`str`, :py:data:`~typing.Any`]"),
247+
(Optional[str], r":py:data:`~typing.Optional`\ \[:py:class:`str`]"),
248+
(Union[str, None], r":py:data:`~typing.Optional`\ \[:py:class:`str`]"),
249249
(
250250
Optional[Union[str, bool]],
251-
":py:data:`~typing.Union`\\ \\[:py:class:`str`, :py:class:`bool`, :py:obj:`None`]",
251+
r":py:data:`~typing.Union`\ \[:py:class:`str`, :py:class:`bool`, :py:obj:`None`]",
252252
),
253253
(Callable, ":py:data:`~typing.Callable`"),
254-
(Callable[..., int], ":py:data:`~typing.Callable`\\ \\[:py:data:`...<Ellipsis>`, :py:class:`int`]"),
255-
(Callable[[int], int], ":py:data:`~typing.Callable`\\ \\[\\[:py:class:`int`], :py:class:`int`]"),
254+
(Callable[..., int], r":py:data:`~typing.Callable`\ \[:py:data:`...<Ellipsis>`, :py:class:`int`]"),
255+
(Callable[[int], int], r":py:data:`~typing.Callable`\ \[\[:py:class:`int`], :py:class:`int`]"),
256256
(
257257
Callable[[int, str], bool],
258-
":py:data:`~typing.Callable`\\ \\[\\[:py:class:`int`, :py:class:`str`], :py:class:`bool`]",
258+
r":py:data:`~typing.Callable`\ \[\[:py:class:`int`, :py:class:`str`], :py:class:`bool`]",
259259
),
260260
(
261261
Callable[[int, str], None],
262-
":py:data:`~typing.Callable`\\ \\[\\[:py:class:`int`, :py:class:`str`], :py:obj:`None`]",
262+
r":py:data:`~typing.Callable`\ \[\[:py:class:`int`, :py:class:`str`], :py:obj:`None`]",
263263
),
264264
(
265265
Callable[[T], T],
266-
":py:data:`~typing.Callable`\\ \\[\\[:py:class:`~typing.TypeVar`\\(``T``)],"
267-
" :py:class:`~typing.TypeVar`\\(``T``)]",
266+
r":py:data:`~typing.Callable`\ \[\[:py:class:`~typing.TypeVar`\ \(``T``)],"
267+
r" :py:class:`~typing.TypeVar`\ \(``T``)]",
268268
),
269269
(
270270
AbcCallable[[int, str], bool], # type: ignore[valid-type,misc,type-arg]
271-
":py:class:`~collections.abc.Callable`\\ \\[\\[:py:class:`int`, :py:class:`str`], :py:class:`bool`]",
271+
r":py:class:`~collections.abc.Callable`\ \[\[:py:class:`int`, :py:class:`str`], :py:class:`bool`]",
272272
),
273273
(Pattern, ":py:class:`~typing.Pattern`"),
274-
(Pattern[str], ":py:class:`~typing.Pattern`\\ \\[:py:class:`str`]"),
274+
(Pattern[str], r":py:class:`~typing.Pattern`\ \[:py:class:`str`]"),
275275
(IO, ":py:class:`~typing.IO`"),
276-
(IO[str], ":py:class:`~typing.IO`\\ \\[:py:class:`str`]"),
276+
(IO[str], r":py:class:`~typing.IO`\ \[:py:class:`str`]"),
277277
(Metaclass, ":py:class:`~%s.Metaclass`" % __name__),
278278
(A, ":py:class:`~%s.A`" % __name__),
279279
(B, ":py:class:`~%s.B`" % __name__),
280-
(B[int], ":py:class:`~%s.B`\\ \\[:py:class:`int`]" % __name__),
280+
(B[int], r":py:class:`~%s.B`\ \[:py:class:`int`]" % __name__),
281281
(C, ":py:class:`~%s.C`" % __name__),
282282
(D, ":py:class:`~%s.D`" % __name__),
283283
(E, ":py:class:`~%s.E`" % __name__),
284-
(E[int], ":py:class:`~%s.E`\\ \\[:py:class:`int`]" % __name__),
285-
(W, f":py:{'class' if PY310_PLUS else 'func'}:`~typing.NewType`\\(``W``, :py:class:`str`)"),
286-
(T, ":py:class:`~typing.TypeVar`\\(``T``)"),
287-
(U_co, ":py:class:`~typing.TypeVar`\\(``U_co``, covariant=True)"),
288-
(V_contra, ":py:class:`~typing.TypeVar`\\(``V_contra``, contravariant=True)"),
289-
(X, ":py:class:`~typing.TypeVar`\\(``X``, :py:class:`str`, :py:class:`int`)"),
290-
(Y, ":py:class:`~typing.TypeVar`\\(``Y``, bound= :py:class:`str`)"),
291-
(Z, ":py:class:`~typing.TypeVar`\\(``Z``, bound= A)"),
292-
(S, ":py:class:`~typing.TypeVar`\\(``S``, bound= miss)"),
284+
(E[int], r":py:class:`~%s.E`\ \[:py:class:`int`]" % __name__),
285+
(W, rf":py:{'class' if PY310_PLUS else 'func'}:`~typing.NewType`\ \(``W``, :py:class:`str`)"),
286+
(T, r":py:class:`~typing.TypeVar`\ \(``T``)"),
287+
(U_co, r":py:class:`~typing.TypeVar`\ \(``U_co``, covariant=True)"),
288+
(V_contra, r":py:class:`~typing.TypeVar`\ \(``V_contra``, contravariant=True)"),
289+
(X, r":py:class:`~typing.TypeVar`\ \(``X``, :py:class:`str`, :py:class:`int`)"),
290+
(Y, r":py:class:`~typing.TypeVar`\ \(``Y``, bound= :py:class:`str`)"),
291+
(Z, r":py:class:`~typing.TypeVar`\ \(``Z``, bound= A)"),
292+
(S, r":py:class:`~typing.TypeVar`\ \(``S``, bound= miss)"),
293293
# ParamSpec should behave like TypeVar, except for missing constraints
294-
(P, f":py:class:`~typing.ParamSpec`\\(``P``{', bound= :py:obj:`None`' if PY312_PLUS else ''})"),
294+
(P, rf":py:class:`~typing.ParamSpec`\ \(``P``{', bound= :py:obj:`None`' if PY312_PLUS else ''})"),
295295
(
296296
P_co,
297-
f":py:class:`~typing.ParamSpec`\\(``P_co``{', bound= :py:obj:`None`' if PY312_PLUS else ''}, covariant=True)",
297+
rf":py:class:`~typing.ParamSpec`\ \(``P_co``{', bound= :py:obj:`None`' if PY312_PLUS else ''}, covariant=True)",
298298
),
299299
(
300300
P_contra,
301-
f":py:class:`~typing.ParamSpec`\\(``P_contra``{', bound= :py:obj:`None`' if PY312_PLUS else ''}"
301+
rf":py:class:`~typing.ParamSpec`\ \(``P_contra``{', bound= :py:obj:`None`' if PY312_PLUS else ''}"
302302
", contravariant=True)",
303303
),
304-
(P_bound, ":py:class:`~typing.ParamSpec`\\(``P_bound``, bound= :py:class:`str`)"),
304+
(P_bound, r":py:class:`~typing.ParamSpec`\ \(``P_bound``, bound= :py:class:`str`)"),
305305
# ## These test for correct internal tuple rendering, even if not all are valid Tuple types
306306
# Zero-length tuple remains
307307
(Tuple[()], ":py:data:`~typing.Tuple`"),
308308
# Internal single tuple with simple types is flattened in the output
309-
(Tuple[(int,)], ":py:data:`~typing.Tuple`\\ \\[:py:class:`int`]"),
310-
(Tuple[(int, int)], ":py:data:`~typing.Tuple`\\ \\[:py:class:`int`, :py:class:`int`]"),
309+
(Tuple[(int,)], r":py:data:`~typing.Tuple`\ \[:py:class:`int`]"),
310+
(Tuple[(int, int)], r":py:data:`~typing.Tuple`\ \[:py:class:`int`, :py:class:`int`]"),
311311
# Ellipsis in single tuple also gets flattened
312-
(Tuple[(int, ...)], ":py:data:`~typing.Tuple`\\ \\[:py:class:`int`, :py:data:`...<Ellipsis>`]"),
312+
(Tuple[(int, ...)], r":py:data:`~typing.Tuple`\ \[:py:class:`int`, :py:data:`...<Ellipsis>`]"),
313313
(
314314
RecList,
315-
":py:data:`~typing.Union`\\ \\[:py:class:`int`, :py:class:`~typing.List`\\ \\[RecList]]",
315+
r":py:data:`~typing.Union`\ \[:py:class:`int`, :py:class:`~typing.List`\ \[RecList]]",
316316
),
317317
(
318318
MutualRecA,
319-
":py:data:`~typing.Union`\\ \\[:py:class:`bool`, :py:class:`~typing.List`\\ \\[MutualRecB]]",
319+
r":py:data:`~typing.Union`\ \[:py:class:`bool`, :py:class:`~typing.List`\ \[MutualRecB]]",
320320
),
321321
]
322322

@@ -425,7 +425,7 @@ def test_format_annotation(inv: Inventory, annotation: Any, expected_result: str
425425
("NoReturn", None, ":py:data:`~typing.NoReturn`"),
426426
("Literal", ("a", 1), ":py:data:`~typing.Literal`\\ \\[``'a'``, ``1``]"),
427427
("Type", None, ":py:class:`~typing.Type`"),
428-
("Type", (A,), f":py:class:`~typing.Type`\\ \\[:py:class:`~{__name__}.A`]"),
428+
("Type", (A,), rf":py:class:`~typing.Type`\ \[:py:class:`~{__name__}.A`]"),
429429
],
430430
)
431431
def test_format_annotation_both_libs(library: ModuleType, annotation: str, params: Any, expected_result: str) -> None:

0 commit comments

Comments
 (0)