From e3da9476b4a9313ec5a111408c134717f9155eb0 Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Fri, 26 Dec 2025 18:19:23 +0000 Subject: [PATCH 1/2] feat(python): add docstring variable xref toggle --- sphinx/domains/python.py | 12 +++++++ tests/roots/test-variable-xrefs/conf.py | 4 +++ tests/roots/test-variable-xrefs/index.rst | 7 ++++ tests/test_domain_py.py | 40 +++++++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 tests/roots/test-variable-xrefs/conf.py create mode 100644 tests/roots/test-variable-xrefs/index.rst diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 000e2e8d34f..6ebee8ab42c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -369,6 +369,17 @@ class PyObject(ObjectDescription): allow_nesting = False + def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + for field in self.doc_field_types: + if isinstance(field, TypedField) and field.name == 'variable': + if self.config.python_docstring_variable_xrefs: + field.rolename = 'obj' + else: + field.rolename = None + break + + return super().get_field_type_map() + def get_signature_prefix(self, sig: str) -> str: """May return a prefix to put before the object name in the signature. @@ -1287,6 +1298,7 @@ def istyping(s: str) -> bool: def setup(app: Sphinx) -> Dict[str, Any]: app.setup_extension('sphinx.directives') + app.add_config_value('python_docstring_variable_xrefs', True, 'env') app.add_domain(PythonDomain) app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) diff --git a/tests/roots/test-variable-xrefs/conf.py b/tests/roots/test-variable-xrefs/conf.py new file mode 100644 index 00000000000..2752437974d --- /dev/null +++ b/tests/roots/test-variable-xrefs/conf.py @@ -0,0 +1,4 @@ +project = 'variable-xrefs' +extensions = [] +master_doc = 'index' +html_theme = 'alabaster' diff --git a/tests/roots/test-variable-xrefs/index.rst b/tests/roots/test-variable-xrefs/index.rst new file mode 100644 index 00000000000..352109fb58d --- /dev/null +++ b/tests/roots/test-variable-xrefs/index.rst @@ -0,0 +1,7 @@ +.. py:function:: foo() + + +.. py:class:: Sample + + :ivar foo: link me if enabled + :vartype foo: int diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index e0c690518f6..1f0ac1e1c42 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -208,6 +208,46 @@ def find_obj(modname, prefix, obj_name, obj_type, searchmode=0): ('roles', 'NestedParentA.NestedChildA.subchild_1', 'method', False))]) +@pytest.mark.sphinx('dummy', testroot='variable-xrefs') +def test_variable_docfield_uses_xref_by_default(app, status, warning): + app.builder.build_all() + + doctree = app.env.get_doctree('index') + variable_field = next(iter(doctree.traverse(nodes.field))) + xrefs = list(variable_field.traverse(pending_xref)) + + assert len(xrefs) == 2 + assert_node(xrefs[0], pending_xref, refdomain='py', reftype='obj', reftarget='foo') + assert_node(xrefs[1], pending_xref, refdomain='py', reftype='class', reftarget='int') + + +@pytest.mark.sphinx('dummy', testroot='variable-xrefs', + confoverrides={'python_docstring_variable_xrefs': False}) +def test_variable_docfield_renders_literal_when_disabled(app, status, warning): + app.builder.build_all() + + doctree = app.env.get_doctree('index') + variable_field = next(iter(doctree.traverse(nodes.field))) + xrefs = list(variable_field.traverse(pending_xref)) + + assert len(xrefs) == 1 + assert_node(xrefs[0], pending_xref, refdomain='py', reftype='class', reftarget='int') + + literal = variable_field.traverse(addnodes.literal_strong)[0] + assert literal.astext() == 'foo' + assert not isinstance(literal.parent, pending_xref) + + +@pytest.mark.sphinx('html', testroot='variable-xrefs', + confoverrides={'python_docstring_variable_xrefs': False}) +def test_variable_docfield_html_has_no_links_when_disabled(app, status, warning): + app.builder.build_all() + + html = (app.outdir / 'index.html').read_text() + assert '

foo' in html + assert '

Date: Fri, 26 Dec 2025 18:36:42 +0000 Subject: [PATCH 2/2] docs(python): document docstring xrefs toggle --- CHANGES | 2 ++ doc/usage/configuration.rst | 18 ++++++++++++++++++ doc/usage/extensions/autodoc.rst | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGES b/CHANGES index 9c889420fd6..31c405d88da 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,8 @@ Features added * #8070: html search: Support searching for 2characters word * #7830: Add debug logs for change detection of sources and templates * #8201: Emit a warning if toctree contains duplicated entries +* #66: py domain: add :confval:`python_docstring_variable_xrefs` to control + automatic cross-references for ``:var:``, ``:ivar:`` and ``:cvar:`` fields Bugs fixed ---------- diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 2cfc7d9ff33..79831d456e0 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -598,6 +598,24 @@ General configuration If the value is a fully-qualified name of a custom Pygments style class, this is then used as custom style. +.. confval:: python_docstring_variable_xrefs + + A boolean that decides whether variable names documented with the Python + domain docstring fields ``:var:``, ``:ivar:`` and ``:cvar:`` are turned into + cross-references. The default is ``True`` which preserves the historical + behaviour of linking these names to the closest matching Python object. If + you prefer them to render as plain literal text, set the value to + ``False``:: + + python_docstring_variable_xrefs = False + + Turning this off still allows the corresponding ``:vartype:`` entries to use + the ``class`` role so that type information remains linked. Explicit roles + (for example ``:py:attr:``) inside docstrings continue to work regardless of + the setting. + + .. versionadded:: 4.0 + .. confval:: add_function_parentheses A boolean that decides whether parentheses are appended to function and diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 2bd220c6d90..b21e8718c78 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -192,6 +192,12 @@ inserting them into the page source under a suitable :rst:dir:`py:module`, .. versionadded:: 3.5 + * Variable docstring fields ``:var:``, ``:ivar:`` and ``:cvar:`` link to the + nearest matching Python object by default. Set + :confval:`python_docstring_variable_xrefs` to ``False`` if you would prefer + these names to render as literal text while still allowing ``:vartype:`` + entries and explicit roles such as ``:py:attr:`` to resolve normally. + * Python "special" members (that is, those named like ``__special__``) will be included if the ``special-members`` flag option is given::