diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index a4f735a..69e1489 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -13,14 +13,13 @@ from docutils import nodes from docutils.frontend import OptionParser -from docutils.parsers.rst import Parser as RstParser -from docutils.parsers.rst import states -from docutils.utils import new_document from sphinx.ext.autodoc.mock import mock +from sphinx.parsers import RSTParser from sphinx.util import logging, rst from sphinx.util.inspect import signature as sphinx_signature from sphinx.util.inspect import stringify_signature +from .parser import parse from .patches import install_patches from .version import __version__ @@ -28,6 +27,7 @@ from ast import FunctionDef, Module, stmt from docutils.nodes import Node + from docutils.parsers.rst import states from sphinx.application import Sphinx from sphinx.config import Config from sphinx.environment import BuildEnvironment @@ -793,10 +793,9 @@ def get_insert_index(app: Sphinx, lines: list[str]) -> InsertIndexInfo | None: # 3. Insert after the parameters. # To find the parameters, parse as a docutils tree. - settings = OptionParser(components=(RstParser,)).get_default_values() + settings = OptionParser(components=(RSTParser,)).get_default_values() settings.env = app.env - doc = new_document("", settings=settings) - RstParser().parse("\n".join(lines), doc) + doc = parse("\n".join(lines), settings) # Find a top level child which is a field_list that contains a field whose # name starts with one of the PARAM_SYNONYMS. This is the parameter list. We @@ -915,8 +914,7 @@ def sphinx_autodoc_typehints_type_role( """ unescaped = unescape(text) # the typestubs for docutils don't have any info about Inliner - doc = new_document("", inliner.document.settings) # type: ignore[attr-defined] - RstParser().parse(unescaped, doc) + doc = parse(unescaped, inliner.document.settings) # type: ignore[attr-defined] n = nodes.inline(text) n["classes"].append("sphinx_autodoc_typehints-type") n += doc.children[0].children diff --git a/src/sphinx_autodoc_typehints/attributes_patch.py b/src/sphinx_autodoc_typehints/attributes_patch.py index 807fab3..e091955 100644 --- a/src/sphinx_autodoc_typehints/attributes_patch.py +++ b/src/sphinx_autodoc_typehints/attributes_patch.py @@ -7,14 +7,13 @@ import sphinx.domains.python import sphinx.ext.autodoc -from docutils.parsers.rst import Parser as RstParser -from docutils.utils import new_document from sphinx.domains.python import PyAttribute from sphinx.ext.autodoc import AttributeDocumenter -if TYPE_CHECKING: - from optparse import Values +from .parser import parse +if TYPE_CHECKING: + from docutils.frontend import Values from sphinx.addnodes import desc_signature from sphinx.application import Sphinx @@ -62,8 +61,7 @@ def add_directive_header(*args: Any, **kwargs: Any) -> Any: def rst_to_docutils(settings: Values, rst: str) -> Any: """Convert rst to a sequence of docutils nodes.""" - doc = new_document("", settings) - RstParser().parse(rst, doc) + doc = parse(rst, settings) # Remove top level paragraph node so that there is no line break. return doc.children[0].children diff --git a/src/sphinx_autodoc_typehints/parser.py b/src/sphinx_autodoc_typehints/parser.py new file mode 100644 index 0000000..ea38444 --- /dev/null +++ b/src/sphinx_autodoc_typehints/parser.py @@ -0,0 +1,25 @@ +"""Utilities for side-effect-free rST parsing.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docutils.utils import new_document +from sphinx.parsers import RSTParser +from sphinx.util.docutils import sphinx_domains + +if TYPE_CHECKING: + import optparse + + from docutils import nodes + from docutils.frontend import Values + + +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.set_application(settings.env.app) + parser.parse(inputstr, doc) + return doc diff --git a/tests/roots/test-dummy/dummy_module_simple_default_role.py b/tests/roots/test-dummy/dummy_module_simple_default_role.py new file mode 100644 index 0000000..758196f --- /dev/null +++ b/tests/roots/test-dummy/dummy_module_simple_default_role.py @@ -0,0 +1,10 @@ +from __future__ import annotations + + +def function(x: bool, y: int) -> str: # noqa: ARG001 + """ + Function docstring. + + :param x: `foo` + :param y: ``bar`` + """ diff --git a/tests/roots/test-dummy/simple_default_role.rst b/tests/roots/test-dummy/simple_default_role.rst new file mode 100644 index 0000000..c3148a7 --- /dev/null +++ b/tests/roots/test-dummy/simple_default_role.rst @@ -0,0 +1,4 @@ +Simple Module +============= + +.. autofunction:: dummy_module_simple_default_role.function diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 10b22d3..2c05c99 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -567,6 +567,38 @@ def test_sphinx_output_future_annotations(app: SphinxTestApp, status: StringIO) assert contents == expected_contents +@pytest.mark.sphinx("pseudoxml", testroot="dummy") +@patch("sphinx.writers.text.MAXWIDTH", 2000) +def test_sphinx_output_default_role(app: SphinxTestApp, status: StringIO) -> None: + set_python_path() + + app.config.master_doc = "simple_default_role" # type: ignore[attr-defined] # create flag + app.config.default_role = "literal" # type: ignore[attr-defined] + app.build() + + assert "build succeeded" in status.getvalue() # Build succeeded + + contents_lines = (Path(app.srcdir) / "_build/pseudoxml/simple_default_role.pseudoxml").read_text().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 = """\ + + + + x + ( + + + bool + ) + \N{EN DASH}\N{SPACE} + + foo + """.rstrip() + expected_foo_param = dedent(expected_foo_param) + assert foo_param == expected_foo_param + + @pytest.mark.parametrize( ("defaults_config_val", "expected"), [