Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion myst_parser/mdit_to_docutils/sphinx_.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ def _process_wrap_node(
):
"""Process a wrap node, which is a node that wraps a link."""
self.add_line_and_source_path(wrap_node, token)
self.copy_attributes(token, wrap_node, ("class", "id", "title"))
self.copy_attributes(
token, wrap_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"}
)
self.current_node.append(wrap_node)

if explicit:
Expand Down
2 changes: 2 additions & 0 deletions myst_parser/mdit_to_docutils/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ def apply(self, **kwargs: t.Any) -> None:
)
if labelid:
pending["reflocalid"] = labelid
if "reftitle" in refnode:
pending["reftitle"] = refnode["reftitle"]
inner_node = nodes.inline(
"", "", classes=["xref", "myst"] + refnode["classes"]
)
Expand Down
7 changes: 7 additions & 0 deletions myst_parser/sphinx_ext/myst_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ def run(self, **kwargs: Any) -> None:
elif not newnode.children:
newnode.append(nodes.literal(target, target))

if "reftitle" in node and "reftitle" not in newnode:
# carry the link title, e.g. [text](#target "title"),
# through whichever branch resolved the reference
newnode["reftitle"] = node["reftitle"]

node.replace_self(newnode)

def _std_label_id_in_doc(self, docname: str, ref_id: str) -> str | None:
Expand Down Expand Up @@ -226,6 +231,8 @@ def resolve_myst_ref_doc(self, node: pending_xref):
)
except NoUri:
ref_node = innernode
if "reftitle" in node:
ref_node["reftitle"] = node["reftitle"]
node.replace_self(ref_node)

def resolve_myst_ref_any(
Expand Down
30 changes: 30 additions & 0 deletions tests/test_renderers/test_myst_refs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys

import pytest
from docutils import nodes
from sphinx.util.console import strip_colors
from sphinx_pytest.plugin import CreateDoctree

Expand Down Expand Up @@ -142,3 +143,32 @@ def test_slug_id_stays_secondary_under_sortids(sphinx_doctree: CreateDoctree):
for section in doctree.findall(docutils_nodes.section):
assert section["ids"][0].startswith("id"), section["ids"]
assert section["slug"] in section["ids"][1:], section["ids"]


@pytest.mark.parametrize(
"test_name,text",
[
("doc_root_anchor", '# Title\n\n[a](#index "TT")'),
("heading_anchor", '# Title\n\n## Section\n\n[a](#section "TT")'),
("explicit_target", '(target)=\n# Title\n\n[a](#target "TT")'),
("doc", '# Title\n\n[a](index.md "TT")'),
("doc_with_target_id", '(ref)=\n# Title\n\n[a](index.md#ref "TT")'),
("unresolved", '# Title\n\n[a](#nope "TT")'),
],
)
def test_link_title_is_kept(
test_name: str, text: str, sphinx_doctree: CreateDoctree
) -> None:
"""A CommonMark link title must survive cross-reference resolution.

Only two of these branches used to keep it; the rest built a fresh node
and dropped the title on the way.
"""
sphinx_doctree.set_conf({"extensions": ["myst_parser"]})
doctree = sphinx_doctree(text, "index.md").get_resolved_doctree("index")
titles = [
node["reftitle"]
for node in doctree.findall(nodes.reference)
if "reftitle" in node
]
assert titles == ["TT"], doctree.pformat()