From 350ef53becb7099c7ccd3b10773542d4c673c326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Thu, 11 Mar 2021 15:45:11 +0100 Subject: [PATCH] intersphinx: Allow strict prefix matching Strictly using prefix for intersphinx links gives a better control on external links - only links explicitely declared will point to the external documentation. Fixes #2068 --- doc/usage/extensions/intersphinx.rst | 14 ++++++++++++++ sphinx/ext/intersphinx.py | 4 ++++ tests/test_ext_intersphinx.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/doc/usage/extensions/intersphinx.rst b/doc/usage/extensions/intersphinx.rst index 178655caeba..27c567c9ca1 100644 --- a/doc/usage/extensions/intersphinx.rst +++ b/doc/usage/extensions/intersphinx.rst @@ -148,6 +148,20 @@ linking: exception is raised if the server has not issued a response for timeout seconds. +.. confval:: intersphinx_strict_prefix + + .. versionadded:: 4.0.0 + + Turn on strict matching of references in Sphinx - all intersphinx links + needs to have a prefix. This disables looking up the non-prefixed targets in + intersphinx and will fail the build if such target is not present. + + .. hint:: + + Strictly using prefix for intersphinx links gives a better control on + external links - only links explicitely declared will point to the + external documentation. + Showing all links of an Intersphinx mapping file ------------------------------------------------ diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index a01bcc37ab6..cf187907fb5 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -314,6 +314,8 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, full_qualified_name = env.get_domain(domain).get_full_qualified_name(node) if full_qualified_name: to_try.append((inventories.named_inventory[setname], full_qualified_name)) + elif app.config.intersphinx_strict_prefix: + return None for inventory, target in to_try: for objtype in objtypes: if objtype not in inventory or target not in inventory[objtype]: @@ -379,6 +381,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_strict_prefix', False, True) app.connect('config-inited', normalize_intersphinx_mapping, priority=800) app.connect('builder-inited', load_mappings) app.connect('missing-reference', missing_reference) @@ -399,6 +402,7 @@ def inspect_main(argv: List[str]) -> None: class MockConfig: intersphinx_timeout = None # type: int + intersphinx_strict_prefix = False tls_verify = False user_agent = None diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 523ed2acc32..f4a106d8d8a 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -97,6 +97,7 @@ def test_missing_reference(tempdir, app, status, warning): 'py3krelparent': ('../../py3k', inv_file), # relative path, parent dir } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -165,6 +166,13 @@ def test_missing_reference(tempdir, app, status, warning): rn = reference_check(app, 'std', 'doc', 'docname', 'docname') assert rn['refuri'] == 'https://docs.python.org/docname.html' + # check resolution when strict prefix is enabled + app.config.intersphinx_strict_prefix = True + rn = reference_check(app, 'py', 'func', 'module1.func', 'foo') + assert rn is None + rn = reference_check(app, 'py', 'func', 'py3k:module1.func', 'foo') + assert rn is not None + def test_missing_reference_pydomain(tempdir, app, status, warning): inv_file = tempdir / 'inventory' @@ -173,6 +181,7 @@ def test_missing_reference_pydomain(tempdir, app, status, warning): 'https://docs.python.org/': inv_file, } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -212,6 +221,7 @@ def test_missing_reference_stddomain(tempdir, app, status, warning): 'cmd': ('https://docs.python.org/', inv_file), } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -244,6 +254,7 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning): 'https://docs.python.org/': inv_file, } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -271,6 +282,7 @@ def test_missing_reference_jsdomain(tempdir, app, status, warning): 'https://docs.python.org/': inv_file, } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -297,6 +309,7 @@ def test_inventory_not_having_version(tempdir, app, status, warning): 'https://docs.python.org/': inv_file, } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) @@ -326,6 +339,7 @@ def test_load_mappings_warnings(tempdir, app, status, warning): } app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # load the inventory and check if it's done correctly normalize_intersphinx_mapping(app, app.config) load_mappings(app) @@ -336,6 +350,7 @@ def test_load_mappings_fallback(tempdir, app, status, warning): inv_file = tempdir / 'inventory' inv_file.write_bytes(inventory_v2) app.config.intersphinx_cache_limit = 0 + app.config.intersphinx_strict_prefix = False # connect to invalid path app.config.intersphinx_mapping = {