Skip to content

Commit f0e1fb4

Browse files
authored
Fix: Use relative URIs for backreference links (#190)
1 parent 92d3a63 commit f0e1fb4

File tree

5 files changed

+83
-8
lines changed

5 files changed

+83
-8
lines changed

docs/src/release_notes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ These release notes are based on
88
sphinx-codeautolink adheres to
99
`Semantic Versioning <https://semver.org>`_.
1010

11+
Unreleased
12+
----------
13+
- Fix backreference links using relative URIs (:issue:`190`)
14+
1115
0.17.3 (2025-03-06)
1216
-------------------
1317
- Fix Sphinx InventoryItem deprecation warning (:issue:`173`)

src/sphinx_codeautolink/extension/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ def generate_backref_tables(self, app, doctree, docname):
271271
visitor = CodeRefsVisitor(
272272
doctree,
273273
code_refs=self.code_refs,
274-
builder=app.builder.name,
274+
docname=docname,
275+
builder=app.builder,
275276
warn_no_backreference=self.warn_no_backreference,
276277
)
277278
doctree.walk(visitor)

src/sphinx_codeautolink/extension/backref.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Backreference tables implementation."""
22

33
from dataclasses import dataclass
4-
from pathlib import Path
54

65
from docutils import nodes
6+
from sphinx.builders import Builder
77

88
from sphinx_codeautolink.warn import logger, warn_type
99

@@ -63,12 +63,14 @@ def __init__(
6363
self,
6464
*args,
6565
code_refs: dict[str, list[CodeExample]],
66-
builder: str,
66+
docname: str,
67+
builder: Builder,
6768
warn_no_backreference: bool = False,
6869
**kwargs,
6970
) -> None:
7071
super().__init__(*args, **kwargs)
7172
self.code_refs = code_refs
73+
self.docname = docname
7274
self.builder = builder
7375
self.warn_no_backreference = warn_no_backreference
7476

@@ -82,10 +84,7 @@ def unknown_visit(self, node) -> None:
8284

8385
items = []
8486
for ref in self.code_refs.get(node.ref, []):
85-
if self.builder == "dirhtml" and Path(ref.document).name != "index":
86-
link = ref.document + "/index.html"
87-
else:
88-
link = ref.document + ".html"
87+
link = self.builder.get_relative_uri(self.docname, ref.document)
8988
if ref.ref_id is not None:
9089
link += f"#{ref.ref_id}"
9190
items.append((link, " / ".join(ref.headings)))

tests/extension/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from bs4 import BeautifulSoup
1010
from sphinx.cmd.build import main as sphinx_main
1111

12-
from ._check import check_link_targets
12+
from ._check import check_link_targets, check_reference_targets_exist
1313

1414
# Insert test package root to path for all tests
1515
sys.path.insert(0, str(Path(__file__).parent / "src"))
@@ -365,6 +365,58 @@ def test_dirhtml_builder(tmp_path: Path):
365365
assert_links(result_dir / "subdir/page3/index.html", links)
366366

367367
assert check_link_targets(result_dir) == len(links) * 4
368+
check_reference_targets_exist(result_dir)
369+
370+
371+
def test_html_subdir_reference(tmp_path: Path):
372+
index = """
373+
Test project
374+
============
375+
376+
.. toctree::
377+
378+
subdir/page1
379+
subdir/subdir2/page2
380+
381+
Index Page
382+
----------
383+
384+
.. code:: python
385+
386+
import test_project
387+
test_project.bar()
388+
389+
.. automodule:: test_project
390+
"""
391+
392+
page = """
393+
Page {idx}
394+
===========
395+
396+
.. code:: python
397+
398+
import test_project
399+
test_project.bar()
400+
401+
.. autolink-examples:: test_project.bar
402+
"""
403+
404+
files = {
405+
"conf.py": default_conf,
406+
"index.rst": index,
407+
"subdir/page1.rst": page.format(idx=1),
408+
"subdir/subdir2/page2.rst": page.format(idx=2),
409+
}
410+
links = ["test_project", "test_project.bar"]
411+
412+
result_dir = _sphinx_build(tmp_path, "html", files)
413+
414+
assert_links(result_dir / "index.html", links)
415+
assert_links(result_dir / "subdir/page1.html", links)
416+
assert_links(result_dir / "subdir/subdir2/page2.html", links)
417+
418+
assert check_link_targets(result_dir) == len(links) * 3
419+
check_reference_targets_exist(result_dir)
368420

369421

370422
def _sphinx_build(

tests/extension/_check.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ def check_link_targets(root: Path) -> int:
4444
return total
4545

4646

47+
def check_reference_targets_exist(root: Path):
48+
site_docs = {
49+
p: BeautifulSoup(p.read_text("utf-8"), "html.parser")
50+
for p in root.glob("**/*.html")
51+
}
52+
for doc, soup in site_docs.items():
53+
for link in soup.find_all("a", attrs={"class": "reference internal"}):
54+
base = link["href"].split("#")[0]
55+
if any(base.startswith(s) for s in ("http://", "https://")):
56+
continue
57+
target_path = doc if base == "" else (doc.parent / base).resolve()
58+
if target_path.is_dir():
59+
target_path /= "index.html"
60+
assert target_path.exists(), (
61+
f"Target path {target_path!s} not found while validating"
62+
f" link for `{link.string}` in {doc.relative_to(root)!s}!"
63+
)
64+
65+
4766
def gather_ids(soup: BeautifulSoup) -> set:
4867
"""Gather all HTML IDs from a given page."""
4968
return {tag["id"] for tag in soup.find_all(id=True)}

0 commit comments

Comments
 (0)