Skip to content

Commit

Permalink
Stop parsing from overwriting Sphinx configuration (#422)
Browse files Browse the repository at this point in the history
  • Loading branch information
flying-sheep committed Jan 26, 2024
1 parent 0bec1bc commit d2955c6
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 14 deletions.
14 changes: 6 additions & 8 deletions src/sphinx_autodoc_typehints/__init__.py
Expand Up @@ -13,21 +13,21 @@

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__

if TYPE_CHECKING:
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions src/sphinx_autodoc_typehints/attributes_patch.py
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
25 changes: 25 additions & 0 deletions 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
10 changes: 10 additions & 0 deletions 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``
"""
4 changes: 4 additions & 0 deletions tests/roots/test-dummy/simple_default_role.rst
@@ -0,0 +1,4 @@
Simple Module
=============

.. autofunction:: dummy_module_simple_default_role.function
32 changes: 32 additions & 0 deletions tests/test_sphinx_autodoc_typehints.py
Expand Up @@ -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() == "<list_item>"]
foo_param = dedent("\n".join(contents_lines[list_item_idxs[0] : list_item_idxs[1]]))
expected_foo_param = """\
<list_item>
<paragraph>
<literal_strong>
x
(
<inline classes="sphinx_autodoc_typehints-type">
<literal classes="xref py py-class">
bool
)
\N{EN DASH}\N{SPACE}
<literal>
foo
""".rstrip()
expected_foo_param = dedent(expected_foo_param)
assert foo_param == expected_foo_param


@pytest.mark.parametrize(
("defaults_config_val", "expected"),
[
Expand Down

0 comments on commit d2955c6

Please sign in to comment.