From 32c5467a8bdb80c691be8669277fc071ff36e36f Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sat, 2 Oct 2021 11:12:49 +0200 Subject: [PATCH] Generalize to disable specific refs as well. --- CHANGES | 8 +++--- doc/usage/extensions/intersphinx.rst | 31 +++++++++++++--------- sphinx/ext/intersphinx.py | 39 +++++++++++++++++----------- tests/test_ext_intersphinx.py | 38 +++++++++++++++++---------- 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/CHANGES b/CHANGES index f5a72b13404..dd602518557 100644 --- a/CHANGES +++ b/CHANGES @@ -22,9 +22,11 @@ Features added * #9691: C, added new info-field ``retval`` for :rst:dir:`c:function` and :rst:dir:`c:macro`. * C++, added new info-field ``retval`` for :rst:dir:`cpp:function`. -* #2068, add :confval:`intersphinx_disabled_domains` for disabling - interphinx resolution of cross-references in specific domains when they - do not have an explicit inventory specification. +* #2068, add :confval:`intersphinx_disabled_refs` for disabling + interphinx resolution of cross-references that do not have an explicit + inventory specification. Specific types of cross-references can be disabled, + e.g., ``std:doc`` or all cross-references in a specific domain, + e.g., ``std``. Bugs fixed ---------- diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst index cbea5ac9e2c..ac68884c9be 100644 --- a/doc/usage/extensions/intersphinx.rst +++ b/doc/usage/extensions/intersphinx.rst @@ -148,21 +148,28 @@ linking: exception is raised if the server has not issued a response for timeout seconds. -.. confval:: intersphinx_disabled_domains +.. confval:: intersphinx_disabled_refs - .. versionadded:: 4.2 + .. versionadded:: 4.3 + + A list of strings being either: + + - the name of a specific reference type, + e.g., ``std:doc``, ``py:func``, or ``cpp:class``, + - the name of a whole domain, e.g., ``std``, ``py``, or ``cpp``, or + - the special name ``all``. - A list of strings being the name of a domain, or the special name ``all``. When a cross-reference without an explicit inventory specification is being - resolved by intersphinx, skip resolution if either the domain of the - cross-reference is in this list or the special name ``all`` is in the list. - - For example, with ``intersphinx_disabled_domains = ['std']`` a cross-reference - ``:doc:`installation``` will not be attempted to be resolved by intersphinx, but - ``:doc:`otherbook:installation``` will be attempted to be resolved in the - inventory named ``otherbook`` in :confval:`intersphinx_mapping`. - At the same time, all cross-references generated in, e.g., Python, declarations - will still be attempted to be resolved by intersphinx. + resolved by intersphinx, skip resolution it matches one of the + specifications in this list. + + For example, with ``intersphinx_disabled_refs = ['std:doc']`` + a cross-reference ``:doc:`installation``` will not be attempted to be + resolved by intersphinx, but ``:doc:`otherbook:installation``` will be + attempted to be resolved in the inventory named ``otherbook`` in + :confval:`intersphinx_mapping`. + At the same time, all cross-references generated in, e.g., Python, + declarations will still be attempted to be resolved by intersphinx. If ``all`` is in the list of domains, then no references without an explicit inventory will be resolved by intersphinx. diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 7764390eebd..9a41421ea99 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -321,7 +321,9 @@ def _resolve_reference_in_domain_by_target( return None -def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory, +def _resolve_reference_in_domain(env: BuildEnvironment, + inv_name: Optional[str], inventory: Inventory, + honor_disabled_refs: bool, domain: Domain, objtypes: List[str], node: pending_xref, contnode: TextElement ) -> Optional[Element]: @@ -336,6 +338,11 @@ def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory, # the inventory contains domain:type as objtype objtypes = ["{}:{}".format(domain.name, t) for t in objtypes] + # now that the objtypes list is complete we can remove the disabled ones + if honor_disabled_refs: + disabled = env.config.intersphinx_disabled_refs + objtypes = [o for o in objtypes if o not in disabled] + # without qualification res = _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes, node['reftarget'], node, contnode) @@ -351,22 +358,23 @@ def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory, def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory, - honor_disabled_domains: bool, + honor_disabled_refs: bool, node: pending_xref, contnode: TextElement) -> Optional[Element]: # disabling should only be done if no inventory is given - honor_disabled_domains = honor_disabled_domains and inv_name is None + honor_disabled_refs = honor_disabled_refs and inv_name is None - if honor_disabled_domains and 'all' in env.config.intersphinx_disabled_domains: + if honor_disabled_refs and 'all' in env.config.intersphinx_disabled_refs: return None typ = node['reftype'] if typ == 'any': for domain_name, domain in env.domains.items(): - if honor_disabled_domains \ - and domain_name in env.config.intersphinx_disabled_domains: + if honor_disabled_refs \ + and domain_name in env.config.intersphinx_disabled_refs: continue objtypes = list(domain.object_types) - res = _resolve_reference_in_domain(inv_name, inventory, + res = _resolve_reference_in_domain(env, inv_name, inventory, + honor_disabled_refs, domain, objtypes, node, contnode) if res is not None: @@ -377,14 +385,15 @@ def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory if not domain_name: # only objects in domains are in the inventory return None - if honor_disabled_domains \ - and domain_name in env.config.intersphinx_disabled_domains: + if honor_disabled_refs \ + and domain_name in env.config.intersphinx_disabled_refs: return None domain = env.get_domain(domain_name) objtypes = domain.objtypes_for_role(typ) if not objtypes: return None - return _resolve_reference_in_domain(inv_name, inventory, + return _resolve_reference_in_domain(env, inv_name, inventory, + honor_disabled_refs, domain, objtypes, node, contnode) @@ -409,7 +418,7 @@ def resolve_reference_in_inventory(env: BuildEnvironment, def resolve_reference_any_inventory(env: BuildEnvironment, - honor_disabled_domains: bool, + honor_disabled_refs: bool, node: pending_xref, contnode: TextElement ) -> Optional[Element]: """Attempt to resolve a missing reference via intersphinx references. @@ -417,12 +426,12 @@ def resolve_reference_any_inventory(env: BuildEnvironment, Resolution is tried with the target as is in any inventory. """ return _resolve_reference(env, None, InventoryAdapter(env).main_inventory, - honor_disabled_domains, + honor_disabled_refs, node, contnode) def resolve_reference_detect_inventory(env: BuildEnvironment, - honor_disabled_domains: bool, + honor_disabled_refs: bool, node: pending_xref, contnode: TextElement ) -> Optional[Element]: """Attempt to resolve a missing reference via intersphinx references. @@ -434,7 +443,7 @@ def resolve_reference_detect_inventory(env: BuildEnvironment, """ # ordinary direct lookup, use data as is - res = resolve_reference_any_inventory(env, honor_disabled_domains, node, contnode) + res = resolve_reference_any_inventory(env, honor_disabled_refs, node, contnode) if res is not None: return res @@ -486,7 +495,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('intersphinx_mapping', {}, True) app.add_config_value('intersphinx_cache_limit', 5, False) app.add_config_value('intersphinx_timeout', None, False) - app.add_config_value('intersphinx_disabled_domains', [], True) + app.add_config_value('intersphinx_disabled_refs', [], True) app.connect('config-inited', normalize_intersphinx_mapping, priority=800) app.connect('builder-inited', load_mappings) app.connect('missing-reference', missing_reference) diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 6856c6e6253..0ffea0d5f1a 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -45,7 +45,7 @@ def reference_check(app, *args, **kwds): def set_config(app, mapping): app.config.intersphinx_mapping = mapping app.config.intersphinx_cache_limit = 0 - app.config.intersphinx_disabled_domains = [] + app.config.intersphinx_disabled_refs = [] @mock.patch('sphinx.ext.intersphinx.InventoryFile') @@ -303,7 +303,7 @@ def test_missing_reference_disabled_domain(tempdir, app, status, warning): normalize_intersphinx_mapping(app, app.config) load_mappings(app) - def case(std_without, std_with, py_without, py_with): + def case(*, term, doc, py): def assert_(rn, expected): if expected is None: assert rn is None @@ -312,34 +312,46 @@ def assert_(rn, expected): kwargs = {} + node, contnode = fake_node('std', 'term', 'a term', 'a term', **kwargs) + rn = missing_reference(app, app.env, node, contnode) + assert_(rn, 'a term' if term else None) + + node, contnode = fake_node('std', 'term', 'inv:a term', 'a term', **kwargs) + rn = missing_reference(app, app.env, node, contnode) + assert_(rn, 'a term') + node, contnode = fake_node('std', 'doc', 'docname', 'docname', **kwargs) rn = missing_reference(app, app.env, node, contnode) - assert_(rn, std_without) + assert_(rn, 'docname' if doc else None) node, contnode = fake_node('std', 'doc', 'inv:docname', 'docname', **kwargs) rn = missing_reference(app, app.env, node, contnode) - assert_(rn, std_with) + assert_(rn, 'docname') # an arbitrary ref in another domain node, contnode = fake_node('py', 'func', 'module1.func', 'func()', **kwargs) rn = missing_reference(app, app.env, node, contnode) - assert_(rn, py_without) + assert_(rn, 'func()' if py else None) node, contnode = fake_node('py', 'func', 'inv:module1.func', 'func()', **kwargs) rn = missing_reference(app, app.env, node, contnode) - assert_(rn, py_with) + assert_(rn, 'func()') # the base case, everything should resolve - assert app.config.intersphinx_disabled_domains == [] - case('docname', 'docname', 'func()', 'func()') + assert app.config.intersphinx_disabled_refs == [] + case(term=True, doc=True, py=True) + + # disabled a single ref type + app.config.intersphinx_disabled_refs = ['std:doc'] + case(term=True, doc=False, py=True) - # disabled one domain - app.config.intersphinx_disabled_domains = ['std'] - case(None, 'docname', 'func()', 'func()') + # disabled a whole domain + app.config.intersphinx_disabled_refs = ['std'] + case(term=False, doc=False, py=True) # disabled all domains - app.config.intersphinx_disabled_domains = ['all'] - case(None, 'docname', None, 'func()') + app.config.intersphinx_disabled_refs = ['all'] + case(term=False, doc=False, py=False) @pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue")