From a79c4c11d778d6472bed6d22f48beb4928615ff0 Mon Sep 17 00:00:00 2001 From: caffeinepills Date: Wed, 10 Apr 2024 12:52:06 -0500 Subject: [PATCH] Return injection fix (#445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bernát Gábor --- src/sphinx_autodoc_typehints/__init__.py | 3 + src/sphinx_autodoc_typehints/parser.py | 9 ++- tests/test_integration.py | 97 ++++++++++++++++++++++-- tests/test_sphinx_autodoc_typehints.py | 4 +- 4 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 8c814d1..4961fd8 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -779,6 +779,9 @@ def node_line_no(node: Node) -> int | None: docutils rst parser source code. An example where the node doesn't have a line number but the first child does is all `definition_list` nodes. It seems like bullet_list and option_list get line numbers, but enum_list also doesn't. """ + if node is None: + return None + while node.line is None and node.children: node = node.children[0] return node.line diff --git a/src/sphinx_autodoc_typehints/parser.py b/src/sphinx_autodoc_typehints/parser.py index ea38444..1f93881 100644 --- a/src/sphinx_autodoc_typehints/parser.py +++ b/src/sphinx_autodoc_typehints/parser.py @@ -13,13 +13,20 @@ from docutils import nodes from docutils.frontend import Values + from docutils.statemachine import StringList + + +class _RstSnippetParser(RSTParser): + @staticmethod + def decorate(_content: StringList) -> None: + """Override to skip processing rst_epilog/rst_prolog for typing.""" def parse(inputstr: str, settings: Values | optparse.Values) -> nodes.document: """Parse inputstr and return a docutils document.""" doc = new_document("", settings=settings) with sphinx_domains(settings.env): - parser = RSTParser() + parser = _RstSnippetParser() parser.set_application(settings.env.app) parser.parse(inputstr, doc) return doc diff --git a/tests/test_integration.py b/tests/test_integration.py index 773f884..8d732d9 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1268,14 +1268,100 @@ def typehints_use_signature(a: AsyncGenerator) -> AsyncGenerator: return a +prolog = """ +.. |test_node_start| replace:: {test_node_start} +""".format(test_node_start="test_start") + + +@expected( + """ + mod.docstring_with_multiline_note_after_params_prolog_replace(param) + + Do something. + + Parameters: + **param** ("int") -- A parameter. + + Return type: + "None" + + Note: + + Some notes. test_start More notes + + """, + rst_prolog=prolog, +) +def docstring_with_multiline_note_after_params_prolog_replace(param: int) -> None: # noqa: ARG001 + """Do something. + + Args: + param: A parameter. + + Note: + + Some notes. |test_node_start| + More notes + """ + + +epilog = """ +.. |test_node_end| replace:: {test_node_end} +""".format(test_node_end="test_end") + + +@expected( + """ + mod.docstring_with_multiline_note_after_params_epilog_replace(param) + + Do something. + + Parameters: + **param** ("int") -- A parameter. + + Return type: + "None" + + Note: + + Some notes. test_end More notes + + """, + rst_epilog=epilog, +) +def docstring_with_multiline_note_after_params_epilog_replace(param: int) -> None: # noqa: ARG001 + """Do something. + + Args: + param: A parameter. + + Note: + + Some notes. |test_node_end| + More notes + """ + + +# Config settings for each test run. +# Config Name: Sphinx Options as Dict. +configs = { + "default_conf": {}, + "prolog_conf": {"rst_prolog": prolog}, + "epilog_conf": { + "rst_epilog": epilog, + }, + "bothlog_conf": { + "rst_prolog": prolog, + "rst_epilog": epilog, + }, +} + + @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")]) +@pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"]) @pytest.mark.sphinx("text", testroot="integration") def test_integration( - app: SphinxTestApp, - status: StringIO, - warning: StringIO, - monkeypatch: pytest.MonkeyPatch, - val: Any, + app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str ) -> None: if isclass(val) and issubclass(val, BaseException): template = AUTO_EXCEPTION @@ -1285,6 +1371,7 @@ def test_integration( template = AUTO_FUNCTION (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__)) + app.config.__dict__.update(configs[conf_run]) app.config.__dict__.update(val.OPTIONS) monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__]) app.build() diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 6cef7ca..3c706e5 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -607,7 +607,9 @@ def test_sphinx_output_default_role(app: SphinxTestApp, status: StringIO) -> Non assert "build succeeded" in status.getvalue() # Build succeeded - contents_lines = (Path(app.srcdir) / "_build/pseudoxml/simple_default_role.pseudoxml").read_text().splitlines() + contents_lines = ( + (Path(app.srcdir) / "_build/pseudoxml/simple_default_role.pseudoxml").read_text(encoding="utf-8").splitlines() + ) list_item_idxs = [i for i, line in enumerate(contents_lines) if line.strip() == ""] foo_param = dedent("\n".join(contents_lines[list_item_idxs[0] : list_item_idxs[1]])) expected_foo_param = """\