From 4534d2d1a5755c8cbc9ef4327eab7e34a85a7de8 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 1 May 2021 13:46:51 +0200 Subject: [PATCH] fields, call roles instead of making a pending_xref Except for py which has mixins that assumes a single pending_xref --- sphinx/domains/python.py | 26 +++++++++---- sphinx/util/docfields.py | 79 ++++++++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 733909769..5c2acf18b 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -20,6 +20,7 @@ from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tu from docutils import nodes from docutils.nodes import Element, Node from docutils.parsers.rst import directives +from docutils.parsers.rst.states import Inliner from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition @@ -284,9 +285,13 @@ def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None: class PyXrefMixin: def make_xref(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = nodes.emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> Node: + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> Node: + # we use inliner=None to make sure we get the old behaviour with a single + # pending_xref node result = super().make_xref(rolename, domain, target, # type: ignore - innernode, contnode, env) + innernode, contnode, + env, inliner=None, location=None) result['refspecific'] = True result['py:module'] = env.ref_context.get('py:module') result['py:class'] = env.ref_context.get('py:class') @@ -313,7 +318,8 @@ class PyXrefMixin: def make_xrefs(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = nodes.emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> List[Node]: delims = r'(\s*[\[\]\(\),](?:\s*or\s)?\s*|\s+or\s+|\s*\|\s*|\.\.\.)' delims_re = re.compile(delims) sub_targets = re.split(delims, target) @@ -329,7 +335,7 @@ class PyXrefMixin: results.append(contnode or innernode(sub_target, sub_target)) else: results.append(self.make_xref(rolename, domain, sub_target, - innernode, contnode, env)) + innernode, contnode, env, inliner, location)) return results @@ -337,12 +343,14 @@ class PyXrefMixin: class PyField(PyXrefMixin, Field): def make_xref(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = nodes.emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> Node: + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. rolename = 'obj' - return super().make_xref(rolename, domain, target, innernode, contnode, env) + return super().make_xref(rolename, domain, target, innernode, contnode, + env, inliner, location) class PyGroupedField(PyXrefMixin, GroupedField): @@ -352,12 +360,14 @@ class PyGroupedField(PyXrefMixin, GroupedField): class PyTypedField(PyXrefMixin, TypedField): def make_xref(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = nodes.emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> Node: + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> Node: if rolename == 'class' and target == 'None': # None is not a type, so use obj role instead. rolename = 'obj' - return super().make_xref(rolename, domain, target, innernode, contnode, env) + return super().make_xref(rolename, domain, target, innernode, contnode, + env, inliner, location) class PyObject(ObjectDescription[Tuple[str, str]]): diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 6d48e910c..39199b3b1 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -8,19 +8,22 @@ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ - from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union, cast from docutils import nodes from docutils.nodes import Node +from docutils.parsers.rst.states import Inliner from sphinx import addnodes from sphinx.environment import BuildEnvironment +from sphinx.util import logging from sphinx.util.typing import TextlikeNode if TYPE_CHECKING: from sphinx.directive import ObjectDescription +logger = logging.getLogger(__name__) + def _is_single_paragraph(node: nodes.field_body) -> bool: """True if the node only contains one paragraph (and system messages).""" @@ -62,39 +65,62 @@ class Field: def make_xref(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = addnodes.literal_emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> Node: + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> Node: + # note: for backwards compatibility env is last, but not optional + assert env is not None + assert (inliner is None) == (location is None), (inliner, location) if not rolename: return contnode or innernode(target, target) - refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False, - reftype=rolename, reftarget=target) - refnode += contnode or innernode(target, target) - if env: - env.get_domain(domain).process_field_xref(refnode) - return refnode + role = env.get_domain(domain).role(rolename) + if role is None or inliner is None: + if role is None and inliner is not None: + msg = "Problem in %s domain: field is supposed " + msg += "to use role '%s', but that role is not in the domain." + logger.warning(msg, domain, rolename, location=location) + if inliner is None: + # msg = "Field for %s domain using role '%s' does not run the role." + # msg += " No inliner provided." + # logger.warning(msg, domain, rolename) + pass + refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False, + reftype=rolename, reftarget=target) + refnode += contnode or innernode(target, target) + if env: + env.get_domain(domain).process_field_xref(refnode) + return refnode + lineno = logging.get_source_line(location)[1] + ns, messages = role(rolename, target, target, lineno, inliner, {}, []) + return nodes.inline(target, '', *ns) def make_xrefs(self, rolename: str, domain: str, target: str, innernode: Type[TextlikeNode] = addnodes.literal_emphasis, - contnode: Node = None, env: BuildEnvironment = None) -> List[Node]: - return [self.make_xref(rolename, domain, target, innernode, contnode, env)] + contnode: Node = None, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> List[Node]: + return [self.make_xref(rolename, domain, target, innernode, contnode, + env, inliner, location)] def make_entry(self, fieldarg: str, content: List[Node]) -> Tuple[str, List[Node]]: return (fieldarg, content) def make_field(self, types: Dict[str, List[Node]], domain: str, - item: Tuple, env: BuildEnvironment = None) -> nodes.field: + item: Tuple, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> nodes.field: fieldarg, content = item fieldname = nodes.field_name('', self.label) if fieldarg: fieldname += nodes.Text(' ') fieldname.extend(self.make_xrefs(self.rolename, domain, - fieldarg, nodes.Text, env=env)) + fieldarg, nodes.Text, + env=env, inliner=inliner, location=location)) if len(content) == 1 and ( isinstance(content[0], nodes.Text) or (isinstance(content[0], nodes.inline) and len(content[0]) == 1 and isinstance(content[0][0], nodes.Text))): content = self.make_xrefs(self.bodyrolename, domain, - content[0].astext(), contnode=content[0], env=env) + content[0].astext(), contnode=content[0], + env=env, inliner=inliner, location=location) fieldbody = nodes.field_body('', nodes.paragraph('', '', *content)) return nodes.field('', fieldname, fieldbody) @@ -121,13 +147,15 @@ class GroupedField(Field): self.can_collapse = can_collapse def make_field(self, types: Dict[str, List[Node]], domain: str, - items: Tuple, env: BuildEnvironment = None) -> nodes.field: + items: Tuple, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> nodes.field: fieldname = nodes.field_name('', self.label) listnode = self.list_type() for fieldarg, content in items: par = nodes.paragraph() par.extend(self.make_xrefs(self.rolename, domain, fieldarg, - addnodes.literal_strong, env=env)) + addnodes.literal_strong, + env=env, inliner=inliner, location=location)) par += nodes.Text(' -- ') par += content listnode += nodes.list_item('', par) @@ -170,7 +198,8 @@ class TypedField(GroupedField): self.typerolename = typerolename def make_field(self, types: Dict[str, List[Node]], domain: str, - items: Tuple, env: BuildEnvironment = None) -> nodes.field: + items: Tuple, env: BuildEnvironment = None, + inliner: Inliner = None, location: Node = None) -> nodes.field: def handle_item(fieldarg: str, content: str) -> nodes.paragraph: par = nodes.paragraph() par.extend(self.make_xrefs(self.rolename, domain, fieldarg, @@ -184,7 +213,8 @@ class TypedField(GroupedField): if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): typename = fieldtype[0].astext() par.extend(self.make_xrefs(self.typerolename, domain, typename, - addnodes.literal_emphasis, env=env)) + addnodes.literal_emphasis, env=env, + inliner=inliner, location=location)) else: par += fieldtype par += nodes.Text(')') @@ -227,7 +257,7 @@ class DocFieldTransformer: """Transform a single field list *node*.""" typemap = self.typemap - entries: List[Union[nodes.field, Tuple[Field, Any]]] = [] + entries: List[Union[nodes.field, Tuple[Field, Any, Node]]] = [] groupindices: Dict[str, int] = {} types: Dict[str, Dict] = {} @@ -317,16 +347,16 @@ class DocFieldTransformer: # get one entry per field if typedesc.is_grouped: if typename in groupindices: - group = cast(Tuple[Field, List], entries[groupindices[typename]]) + group = cast(Tuple[Field, List, Node], entries[groupindices[typename]]) else: groupindices[typename] = len(entries) - group = (typedesc, []) + group = (typedesc, [], field) entries.append(group) new_entry = typedesc.make_entry(fieldarg, [translatable_content]) group[1].append(new_entry) else: new_entry = typedesc.make_entry(fieldarg, [translatable_content]) - entries.append((typedesc, new_entry)) + entries.append((typedesc, new_entry, field)) # step 2: all entries are collected, construct the new field list new_list = nodes.field_list() @@ -335,10 +365,11 @@ class DocFieldTransformer: # pass-through old field new_list += entry else: - fieldtype, items = entry + fieldtype, items, location = entry fieldtypes = types.get(fieldtype.name, {}) env = self.directive.state.document.settings.env - new_list += fieldtype.make_field(fieldtypes, self.directive.domain, - items, env=env) + inliner = self.directive.state.inliner + new_list += fieldtype.make_field(fieldtypes, self.directive.domain, items, + env=env, inliner=inliner, location=location) node.replace_self(new_list)