Skip to content

Commit 8c931fe

Browse files
authored
More robust determination of rtype location / fix issue 302 (#304)
Resolves #302
1 parent 7b2f213 commit 8c931fe

File tree

4 files changed

+59
-29
lines changed

4 files changed

+59
-29
lines changed

src/sphinx_autodoc_typehints/__init__.py

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,23 @@ class InsertIndexInfo:
659659
PARAM_SYNONYMS = ("param ", "parameter ", "arg ", "argument ", "keyword ", "kwarg ", "kwparam ")
660660

661661

662-
def line_before_node(node: Node) -> int:
663-
line = node.line
664-
assert line
665-
return line - 2
662+
def node_line_no(node: Node) -> int | None:
663+
"""
664+
Get the 1-indexed line on which the node starts if possible. If not, return
665+
None.
666+
667+
Descend through the first children until we locate one with a line number or
668+
return None if None of them have one.
669+
670+
I'm not aware of any rst on which this returns None, to find out would
671+
require a more detailed analysis of the docutils rst parser source code. An
672+
example where the node doesn't have a line number but the first child does
673+
is all `definition_list` nodes. It seems like bullet_list and option_list
674+
get line numbers, but enum_list also doesn't. *shrug*.
675+
"""
676+
while node.line is None and node.children:
677+
node = node.children[0]
678+
return node.line
666679

667680

668681
def tag_name(node: Node) -> str:
@@ -690,39 +703,29 @@ def get_insert_index(app: Sphinx, lines: list[str]) -> InsertIndexInfo | None:
690703
# Find a top level child which is a field_list that contains a field whose
691704
# name starts with one of the PARAM_SYNONYMS. This is the parameter list. We
692705
# hope there is at most of these.
693-
for idx, child in enumerate(doc.children):
706+
for child in doc.children:
694707
if tag_name(child) != "field_list":
695708
continue
696709

697-
if any(c.children[0].astext().startswith(PARAM_SYNONYMS) for c in child.children):
698-
idx = idx
699-
break
700-
else:
701-
idx = -1
710+
if not any(c.children[0].astext().startswith(PARAM_SYNONYMS) for c in child.children):
711+
continue
702712

703-
if idx == -1:
704-
# No parameters
705-
pass
706-
elif idx + 1 < len(doc.children):
707-
# Unfortunately docutils only tells us the line numbers that nodes start on,
708-
# not the range (boo!). So insert before the line before the next sibling.
709-
at = line_before_node(doc.children[idx + 1])
713+
# Found it! Try to insert before the next sibling. If there is no next
714+
# sibling, insert at end.
715+
# If there is a next sibling but we can't locate a line number, insert
716+
# at end. (I don't know of any input where this happens.)
717+
next_sibling = child.next_node(descend=False, siblings=True)
718+
line_no = node_line_no(next_sibling) if next_sibling else None
719+
at = line_no - 2 if line_no else len(lines)
710720
return InsertIndexInfo(insert_index=at, found_param=True)
711-
else:
712-
# No next sibling, insert at end
713-
return InsertIndexInfo(insert_index=len(lines), found_param=True)
714721

715722
# 4. Insert before examples
716723
# TODO: Maybe adjust which tags to insert ahead of
717-
for idx, child in enumerate(doc.children):
718-
if tag_name(child) not in ["literal_block", "paragraph", "field_list"]:
719-
idx = idx
720-
break
721-
else:
722-
idx = -1
723-
724-
if idx != -1:
725-
at = line_before_node(doc.children[idx])
724+
for child in doc.children:
725+
if tag_name(child) in ["literal_block", "paragraph", "field_list"]:
726+
continue
727+
line_no = node_line_no(child)
728+
at = line_no - 2 if line_no else len(lines)
726729
return InsertIndexInfo(insert_index=at, found_directive=True)
727730

728731
# 5. Otherwise, insert at end

tests/roots/test-dummy/dummy_module.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,15 @@ def func_with_code_block() -> int:
395395
396396
Here are a couple of examples of how to use this function.
397397
"""
398+
399+
400+
def func_with_definition_list() -> int:
401+
"""Some text and then a definition list.
402+
403+
abc
404+
x
405+
406+
xyz
407+
something
408+
"""
409+
# See https://github.com/tox-dev/sphinx-autodoc-typehints/issues/302

tests/roots/test-dummy/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,5 @@ Dummy Module
5555
.. autofunction:: dummy_module.empty_line_between_parameters
5656

5757
.. autofunction:: dummy_module.func_with_code_block
58+
59+
.. autofunction:: dummy_module.func_with_definition_list

tests/test_sphinx_autodoc_typehints.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,19 @@ class dummy_module.TestClassAttributeDocs
880880
-[ Examples ]-
881881
882882
Here are a couple of examples of how to use this function.
883+
884+
dummy_module.func_with_definition_list()
885+
886+
Some text and then a definition list.
887+
888+
Return type:
889+
"int"
890+
891+
abc
892+
x
893+
894+
xyz
895+
something
883896
"""
884897
expected_contents = dedent(expected_contents).format(**format_args).replace("–", "--")
885898
assert text_contents == maybe_fix_py310(expected_contents)

0 commit comments

Comments
 (0)