From 00981ae571de6b9375f46592eaa0fda060272844 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 15 Nov 2020 23:15:01 +0900 Subject: [PATCH] Fix #7199: py domain: Add a new confval: python_smart_reference Add a new config variable: python_smart_reference. If enabled, it goes to suppress the module name of the python reference if it can be resolved. --- CHANGES | 2 ++ doc/usage/configuration.rst | 11 +++++++++++ sphinx/domains/python.py | 32 ++++++++++++++++++++++++++++---- sphinx/util/nodes.py | 6 +++--- tests/test_domain_py.py | 19 +++++++++++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index a4382740894..d4f5adf7cb2 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,8 @@ Features added * autodoc: Add ``Documenter.config`` as a shortcut to access the config object * autodoc: Add Optional[t] to annotation of function and method if a default value equal to None is set. +* #7199: py domain: Add :confval:`python_smart_reference` to suppress the module + name of the python reference if it can be resolved (experimental) * #6914: Add a new event :event:`warn-missing-reference` to custom warning messages when failed to resolve a cross-reference * #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 4881b86295c..8ec1a497028 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2615,6 +2615,17 @@ Options for the C++ domain .. versionadded:: 1.5 +Options for the Python domain +----------------------------- + +.. confval:: python_smart_reference + + If true, suppress the module name of the python reference if it can be + resolved. The default is ``False``. + + .. versionadded:: 3.4 + + .. note:: This configuration is still in experimental Example of configuration file ============================= diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 79d7e4f466d..0012067d1e3 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -37,7 +37,7 @@ from sphinx.util.docfields import Field, GroupedField, TypedField from sphinx.util.docutils import SphinxDirective from sphinx.util.inspect import signature_from_str -from sphinx.util.nodes import make_id, make_refnode +from sphinx.util.nodes import find_pending_xref_condition, make_id, make_refnode from sphinx.util.typing import TextlikeNode if False: @@ -91,7 +91,14 @@ def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xr else: kwargs = {} - return pending_xref('', nodes.Text(text), + if env.config.python_smart_reference: + shortname = text.split('.')[-1] + contnodes = [addnodes.pending_xref_condition('', shortname, condition='resolved'), + addnodes.pending_xref_condition('', text, condition='*')] + else: + contnodes = [nodes.Text(text)] + + return pending_xref('', *contnodes, refdomain='py', reftype=reftype, reftarget=text, **kwargs) @@ -1313,7 +1320,15 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: - return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name) + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = contnode + + return make_refnode(builder, fromdocname, obj[0], obj[1], children, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element @@ -1330,9 +1345,17 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui self._make_module_refnode(builder, fromdocname, name, contnode))) else: + # determine the content of the reference by conditions + content = find_pending_xref_condition(node, 'resolved') + if content: + children = content.children + else: + # if not found, use contnode + children = contnode + results.append(('py:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], - contnode, name))) + children, name))) return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, @@ -1395,6 +1418,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) + app.add_config_value('python_smart_reference', False, 'env') app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index ef64ddfd0f3..8e703d136e7 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -11,7 +11,7 @@ import re import unicodedata import warnings -from typing import Any, Callable, Iterable, List, Set, Tuple, cast +from typing import Any, Callable, Iterable, List, Set, Tuple, Union, cast from docutils import nodes from docutils.nodes import Element, Node @@ -549,7 +549,7 @@ def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: str, - child: Node, title: str = None) -> nodes.reference: + child: Union[Node, List[Node]], title: str = None) -> nodes.reference: """Shortcut to create a reference node.""" node = nodes.reference('', '', internal=True) if fromdocname == todocname and targetid: @@ -562,7 +562,7 @@ def make_refnode(builder: "Builder", fromdocname: str, todocname: str, targetid: node['refuri'] = builder.get_relative_uri(fromdocname, todocname) if title: node['reftitle'] = title - node.append(child) + node += child return node diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 2dc97bed99e..23611347c88 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -859,6 +859,25 @@ def test_noindexentry(app): assert_node(doctree[2], addnodes.index, entries=[]) +@pytest.mark.sphinx('html', testroot='domain-py-smart_reference') +def test_python_smart_reference(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('' + 'Name' in content) + assert 'foo.Age' in content + + +@pytest.mark.sphinx('html', testroot='domain-py-smart_reference', + confoverrides={'python_smart_reference': False}) +def test_python_smart_reference_disabled(app, status, warning): + app.build() + content = (app.outdir / 'index.html').read_text() + assert ('' + 'foo.Name' in content) + assert 'foo.Age' in content + + @pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning') def test_warn_missing_reference(app, status, warning): app.build()