Skip to content

Commit eda25f6

Browse files
authored
Fix spacing between :rtype: and directives (#294)
Fix #293
1 parent 0f42705 commit eda25f6

File tree

2 files changed

+87
-59
lines changed

2 files changed

+87
-59
lines changed

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 85 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -588,74 +588,100 @@ def _inject_types_to_docstring(
588588
name: str,
589589
lines: list[str],
590590
) -> None:
591-
592591
if signature is not None:
593-
for arg_name in signature.parameters:
594-
annotation = type_hints.get(arg_name, None)
592+
_inject_signature(type_hints, signature, app, lines)
593+
if "return" in type_hints:
594+
_inject_rtype(type_hints, original_obj, app, what, name, lines)
595595

596-
default = signature.parameters[arg_name].default
597596

598-
if arg_name.endswith("_"):
599-
arg_name = f"{arg_name[:-1]}\\_"
597+
def _inject_signature(
598+
type_hints: dict[str, Any],
599+
signature: inspect.Signature,
600+
app: Sphinx,
601+
lines: list[str],
602+
) -> None:
603+
for arg_name in signature.parameters:
604+
annotation = type_hints.get(arg_name, None)
600605

601-
insert_index = None
602-
for at, line in enumerate(lines):
603-
if _line_is_param_line_for_arg(line, arg_name):
604-
# Get the arg_name from the doc to match up for type in case it has a star prefix.
605-
# Line is in the correct format so this is guaranteed to return tuple[str, str].
606-
_, arg_name = _get_sphinx_line_keyword_and_argument(line) # type: ignore[assignment, misc]
607-
insert_index = at
608-
break
609-
610-
if annotation is not None and insert_index is None and app.config.always_document_param_types:
611-
lines.append(f":param {arg_name}:")
612-
insert_index = len(lines)
613-
614-
if insert_index is not None:
615-
if annotation is None:
616-
type_annotation = f":type {arg_name}: "
617-
else:
618-
formatted_annotation = format_annotation(annotation, app.config)
619-
type_annotation = f":type {arg_name}: {formatted_annotation}"
620-
621-
if app.config.typehints_defaults:
622-
formatted_default = format_default(app, default, annotation is not None)
623-
if formatted_default:
624-
if app.config.typehints_defaults.endswith("after"):
625-
lines[insert_index] += formatted_default
626-
else: # add to last param doc line
627-
type_annotation += formatted_default
628-
629-
lines.insert(insert_index, type_annotation)
630-
631-
if "return" in type_hints and not inspect.isclass(original_obj) and not inspect.isdatadescriptor(original_obj):
632-
if what == "method" and name.endswith(".__init__"): # avoid adding a return type for data class __init__
633-
return
634-
formatted_annotation = format_annotation(type_hints["return"], app.config)
635-
insert_index = len(lines)
606+
default = signature.parameters[arg_name].default
607+
608+
if arg_name.endswith("_"):
609+
arg_name = f"{arg_name[:-1]}\\_"
610+
611+
insert_index = None
636612
for at, line in enumerate(lines):
637-
if line.startswith(":rtype:"):
638-
insert_index = None
639-
break
640-
elif line.startswith(":return:") or line.startswith(":returns:"):
641-
if " -- " in line and not app.config.typehints_use_rtype:
642-
insert_index = None
643-
break
644-
insert_index = at
645-
elif line.startswith(".."):
646-
# Make sure that rtype comes before any usage or examples section
613+
if _line_is_param_line_for_arg(line, arg_name):
614+
# Get the arg_name from the doc to match up for type in case it has a star prefix.
615+
# Line is in the correct format so this is guaranteed to return tuple[str, str].
616+
_, arg_name = _get_sphinx_line_keyword_and_argument(line) # type: ignore[assignment, misc]
647617
insert_index = at
648618
break
649619

650-
if insert_index is not None and app.config.typehints_document_rtype:
651-
if insert_index == len(lines): # ensure that :rtype: doesn't get joined with a paragraph of text
652-
lines.append("")
653-
insert_index += 1
654-
if app.config.typehints_use_rtype or insert_index == len(lines):
655-
lines.insert(insert_index, f":rtype: {formatted_annotation}")
620+
if annotation is not None and insert_index is None and app.config.always_document_param_types:
621+
lines.append(f":param {arg_name}:")
622+
insert_index = len(lines)
623+
624+
if insert_index is not None:
625+
if annotation is None:
626+
type_annotation = f":type {arg_name}: "
627+
else:
628+
formatted_annotation = format_annotation(annotation, app.config)
629+
type_annotation = f":type {arg_name}: {formatted_annotation}"
630+
631+
if app.config.typehints_defaults:
632+
formatted_default = format_default(app, default, annotation is not None)
633+
if formatted_default:
634+
if app.config.typehints_defaults.endswith("after"):
635+
lines[insert_index] += formatted_default
636+
else: # add to last param doc line
637+
type_annotation += formatted_default
638+
639+
lines.insert(insert_index, type_annotation)
640+
641+
642+
def _inject_rtype(
643+
type_hints: dict[str, Any],
644+
original_obj: Any,
645+
app: Sphinx,
646+
what: str,
647+
name: str,
648+
lines: list[str],
649+
) -> None:
650+
if inspect.isclass(original_obj) or inspect.isdatadescriptor(original_obj):
651+
return
652+
if what == "method" and name.endswith(".__init__"): # avoid adding a return type for data class __init__
653+
return
654+
formatted_annotation = format_annotation(type_hints["return"], app.config)
655+
insert_index: int | None = len(lines)
656+
extra_newline = False
657+
for at, line in enumerate(lines):
658+
if line.startswith(":rtype:"):
659+
insert_index = None
660+
break
661+
if line.startswith(":return:") or line.startswith(":returns:"):
662+
if " -- " in line and not app.config.typehints_use_rtype:
663+
insert_index = None
664+
break
665+
insert_index = at
666+
elif line.startswith(".."):
667+
# Make sure that rtype comes before any usage or examples section, with a blank line between.
668+
insert_index = at
669+
extra_newline = True
670+
break
671+
672+
if insert_index is not None and app.config.typehints_document_rtype:
673+
if insert_index == len(lines): # ensure that :rtype: doesn't get joined with a paragraph of text
674+
lines.append("")
675+
insert_index += 1
676+
if app.config.typehints_use_rtype or insert_index == len(lines):
677+
line = f":rtype: {formatted_annotation}"
678+
if extra_newline:
679+
lines[insert_index:insert_index] = [line, "\n"]
656680
else:
657-
line = lines[insert_index]
658-
lines[insert_index] = f":return: {formatted_annotation} --{line[line.find(' '):]}"
681+
lines.insert(insert_index, line)
682+
else:
683+
line = lines[insert_index]
684+
lines[insert_index] = f":return: {formatted_annotation} --{line[line.find(' '):]}"
659685

660686

661687
def validate_config(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: # noqa: U100

tests/test_sphinx_autodoc_typehints.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ def test_sphinx_output(
470470
# There should be a warning about an unresolved forward reference
471471
warnings = warning.getvalue().strip()
472472
assert "Cannot resolve forward reference in type annotations of " in warnings, warnings
473+
# There should not be warnings about incorrect block endings.
474+
assert "Field list ends without a blank line; unexpected unindent." not in warnings, warnings
473475

474476
format_args = {}
475477
for indentation_level in range(2):

0 commit comments

Comments
 (0)