Repository: tox-dev/sphinx-autodoc-typehints (python)

Issue:
v1.12.7 shows "Content block expected for the "note" directive; none found" with multiple lines of notes
Hello maintainers.

I have a function which has multiple lines of notes, like this:

```py
def func(param1: str) -> None:
    """Do something.

    Args:
        param1: A parameter.

    Note:
        Some notes.
        Some more notes.
    """
    print("Do something")

```

Using v1.12.7, running `sphinx-build` throws an error and a warning like this:

```
ERROR: Content block expected for the "note" directive; none found.
WARNING: Explicit markup ends without a blank line; unexpected unindent.
```

Debugging with `-vv` option, logs show how the method is converted. Seems `:rtype:` is inserted in a wrong place.

```
.. py:function:: func(param1)
   :module: pykit.grpc

   Do something.

   :type param1: :py:class:`str`
   :param param1: A parameter.

   .. note::
   :rtype: :py:obj:`None`

      Some notes.
      Some more notes.
```

This code works fine with v1.12.1, but fails with v1.12.7.
A function with one line of notes works fine with both versions.
Is this an expected or unexpected behavior?

Pull request:
Patch docutils rst parser to add line numbers to more nodes
This resolves #312. 

The missing line numbers make it impossible for us to correctly calculate where to place the rtype.

See upstream issue:
https://sourceforge.net/p/docutils/bugs/465/

I submitted a patch to docutils to fix these things but it is currently waiting to get through a spam filter. The docutils test suite has no coverage checking whether this field is filled correctly.

Patch:
diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py
index 94cd45e..3176651 100644
--- a/src/sphinx_autodoc_typehints/__init__.py
+++ b/src/sphinx_autodoc_typehints/__init__.py
@@ -678,7 +678,7 @@ def node_line_no(node: Node) -> int | None:
 
 
 def tag_name(node: Node) -> str:
-    return node.tagname  # type:ignore[attr-defined,no-any-return] # noqa: SC200
+    return node.tagname  # type:ignore[attr-defined,no-any-return]
 
 
 def get_insert_index(app: Sphinx, lines: list[str]) -> InsertIndexInfo | None:
diff --git a/src/sphinx_autodoc_typehints/patches.py b/src/sphinx_autodoc_typehints/patches.py
index b91f839..4cc6e96 100644
--- a/src/sphinx_autodoc_typehints/patches.py
+++ b/src/sphinx_autodoc_typehints/patches.py
@@ -3,6 +3,8 @@ from __future__ import annotations
 from functools import lru_cache
 from typing import Any
 
+from docutils.parsers.rst.directives.admonitions import BaseAdmonition
+from docutils.parsers.rst.states import Text
 from sphinx.application import Sphinx
 from sphinx.ext.autodoc import Options
 from sphinx.ext.napoleon.docstring import GoogleDocstring
@@ -86,11 +88,43 @@ def patch_google_docstring_lookup_annotation() -> None:
     GoogleDocstring._lookup_annotation = patched_lookup_annotation  # type: ignore[assignment]
 
 
+orig_base_admonition_run = BaseAdmonition.run
+
+
+def patched_base_admonition_run(self: BaseAdmonition) -> Any:
+    result = orig_base_admonition_run(self)
+    result[0].line = self.lineno
+    return result
+
+
+orig_text_indent = Text.indent
+
+
+def patched_text_indent(self: Text, *args: Any) -> Any:
+    _, line = self.state_machine.get_source_and_line()
+    result = orig_text_indent(self, *args)
+    node = self.parent[-1]
+    if node.tagname == "system_message":
+        node = self.parent[-2]
+    node.line = line
+    return result
+
+
+def patch_line_numbers() -> None:
+    """Make the rst parser put line numbers on more nodes.
+
+    When the line numbers are missing, we have a hard time placing the :rtype:.
+    """
+    Text.indent = patched_text_indent
+    BaseAdmonition.run = patched_base_admonition_run
+
+
 def install_patches(app: Sphinx) -> None:
     fix_autodoc_typehints_for_overloaded_methods()
     patch_attribute_handling(app)
     patch_google_docstring_lookup_annotation()
     fix_napoleon_numpy_docstring_return_type(app)
+    patch_line_numbers()
 
 
 ___all__ = ["install_patches"]
diff --git a/whitelist.txt b/whitelist.txt
index 7a2e21d..0e748bd 100644
--- a/whitelist.txt
+++ b/whitelist.txt
@@ -39,6 +39,7 @@ isfunction
 iterdir
 kwonlyargs
 libs
+lineno
 lru
 metaclass
 ModuleType
@@ -73,6 +74,7 @@ stmt
 stringify
 subclasses
 supertype
+tagname
 tempdir
 testroot
 textwrap


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 0413022..ace4609 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -999,6 +999,174 @@ def google_docstrings(arg1: CodeType, arg2: ModuleType) -> CodeType:  # noqa: U1
     """
 
 
+@expected(
+    """
+    mod.docstring_with_multiline_note_after_params(param)
+
+       Do something.
+
+       Parameters:
+          **param** ("int") -- A parameter.
+
+       Return type:
+          "None"
+
+       Note:
+
+         Some notes. More notes
+
+    """
+)
+def docstring_with_multiline_note_after_params(param: int) -> None:  # noqa: U100
+    """Do something.
+
+    Args:
+        param: A parameter.
+
+    Note:
+
+        Some notes.
+        More notes
+    """
+
+
+@expected(
+    """
+    mod.docstring_with_bullet_list_after_params(param)
+
+       Do something.
+
+       Parameters:
+          **param** ("int") -- A parameter.
+
+       Return type:
+          "None"
+
+       * A: B
+
+       * C: D
+
+    """
+)
+def docstring_with_bullet_list_after_params(param: int) -> None:  # noqa: U100
+    """Do something.
+
+    Args:
+        param: A parameter.
+
+    * A: B
+    * C: D
+    """
+
+
+@expected(
+    """
+    mod.docstring_with_definition_list_after_params(param)
+
+       Do something.
+
+       Parameters:
+          **param** ("int") -- A parameter.
+
+       Return type:
+          "None"
+
+       Term
+          A description
+
+          maybe multiple lines
+
+       Next Term
+          Somethign about it
+
+    """
+)
+def docstring_with_definition_list_after_params(param: int) -> None:  # noqa: U100
+    """Do something.
+
+    Args:
+        param: A parameter.
+
+    Term
+        A description
+
+        maybe multiple lines
+
+    Next Term
+        Somethign about it
+    """
+
+
+@expected(
+    """
+    mod.docstring_with_enum_list_after_params(param)
+
+       Do something.
+
+       Parameters:
+          **param** ("int") -- A parameter.
+
+       Return type:
+          "None"
+
+       1. A: B
+
+       2. C: D
+
+    """
+)
+def docstring_with_enum_list_after_params(param: int) -> None:  # noqa: U100
+    """Do something.
+
+    Args:
+        param: A parameter.
+
+    1. A: B
+    2. C: D
+    """
+
+
+@warns("Definition list ends without a blank line")
+@expected(
+    """
+    mod.docstring_with_definition_list_after_params_no_blank_line(param)
+
+       Do something.
+
+       Parameters:
+          **param** ("int") -- A parameter.
+
+       Return type:
+          "None"
+
+       Term
+          A description
+
+          maybe multiple lines
+
+       Next Term
+          Somethign about it
+
+       -[ Example ]-
+    """
+)
+def docstring_with_definition_list_after_params_no_blank_line(param: int) -> None:  # noqa: U100
+    """Do something.
+
+    Args:
+        param: A parameter.
+
+    Term
+        A description
+
+        maybe multiple lines
+
+    Next Term
+        Somethign about it
+    .. rubric:: Example
+    """
+
+
 AUTO_FUNCTION = ".. autofunction:: mod.{}"
 AUTO_CLASS = """\
 .. autoclass:: mod.{}