Skip to content

Stop parsing from overwriting Sphinx configuration #422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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