Skip to content

Commit 9815010

Browse files
committed
Remove disambiguators from link text
Related to rust-lang#65354 - Pass through the replacement text to `markdown.rs` - Add some tests - Add a state machine that actually replaces the text when parsing Markdown
1 parent 31a7b6e commit 9815010

File tree

5 files changed

+141
-22
lines changed

5 files changed

+141
-22
lines changed

src/librustdoc/clean/types.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@ pub struct Attributes {
436436
pub struct ItemLink {
437437
/// The original link written in the markdown
438438
pub(crate) link: String,
439+
/// The link text displayed in the HTML.
440+
///
441+
/// This may not be the same as `link` if there was a disambiguator
442+
/// in an intra-doc link (e.g. [`fn@f`])
443+
pub(crate) link_text: String,
439444
pub(crate) did: Option<DefId>,
440445
/// The url fragment to append to the link
441446
pub(crate) fragment: Option<String>,
@@ -444,6 +449,8 @@ pub struct ItemLink {
444449
pub struct RenderedLink {
445450
/// The text the link was original written as
446451
pub(crate) original_text: String,
452+
/// The text to display in the HTML
453+
pub(crate) new_text: String,
447454
/// The URL to put in the `href`
448455
pub(crate) href: String,
449456
}
@@ -630,15 +637,19 @@ impl Attributes {
630637

631638
self.links
632639
.iter()
633-
.filter_map(|ItemLink { link: s, did, fragment }| {
640+
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
634641
match *did {
635642
Some(did) => {
636643
if let Some((mut href, ..)) = href(did) {
637644
if let Some(ref fragment) = *fragment {
638645
href.push_str("#");
639646
href.push_str(fragment);
640647
}
641-
Some(RenderedLink { original_text: s.clone(), href })
648+
Some(RenderedLink {
649+
original_text: s.clone(),
650+
new_text: link_text.clone(),
651+
href,
652+
})
642653
} else {
643654
None
644655
}
@@ -660,6 +671,7 @@ impl Attributes {
660671
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
661672
Some(RenderedLink {
662673
original_text: s.clone(),
674+
new_text: link_text.clone(),
663675
href: format!(
664676
"{}{}std/primitive.{}.html{}",
665677
url,

src/librustdoc/html/markdown.rs

+73-16
Original file line numberDiff line numberDiff line change
@@ -340,29 +340,86 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
340340
/// Make headings links with anchor IDs and build up TOC.
341341
struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
342342
inner: I,
343-
links: &'b [RenderedLink],
343+
links: &'a [RenderedLink],
344+
shortcut_link: Option<&'b RenderedLink>,
344345
}
345346

346-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
347-
fn new(iter: I, links: &'b [RenderedLink]) -> Self {
348-
LinkReplacer { inner: iter, links }
347+
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, '_, I> {
348+
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
349+
LinkReplacer { inner: iter, links, shortcut_link: None }
349350
}
350351
}
351352

352-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
353+
impl<'a: 'b, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
353354
type Item = Event<'a>;
354355

355356
fn next(&mut self) -> Option<Self::Item> {
356-
let event = self.inner.next();
357-
if let Some(Event::Start(Tag::Link(kind, dest, text))) = event {
358-
if let Some(link) = self.links.iter().find(|link| link.original_text == *dest) {
359-
Some(Event::Start(Tag::Link(kind, link.href.clone().into(), text)))
360-
} else {
361-
Some(Event::Start(Tag::Link(kind, dest, text)))
357+
let mut event = self.inner.next();
358+
359+
// Remove disambiguators from shortcut links (`[fn@f]`)
360+
match &mut event {
361+
Some(Event::Start(Tag::Link(
362+
pulldown_cmark::LinkType::ShortcutUnknown,
363+
dest,
364+
title,
365+
))) => {
366+
debug!("saw start of shortcut link to {} with title {}", dest, title);
367+
let link = if let Some(link) =
368+
self.links.iter().find(|&link| *link.original_text == **dest)
369+
{
370+
// Not sure why this is necessary - maybe the broken_link_callback doesn't always work?
371+
*dest = CowStr::Borrowed(link.href.as_ref());
372+
Some(link)
373+
} else {
374+
self.links.iter().find(|&link| *link.href == **dest)
375+
};
376+
if let Some(link) = link {
377+
trace!("it matched");
378+
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
379+
self.shortcut_link = Some(link);
380+
}
362381
}
363-
} else {
364-
event
382+
Some(Event::End(Tag::Link(pulldown_cmark::LinkType::ShortcutUnknown, dest, _))) => {
383+
debug!("saw end of shortcut link to {}", dest);
384+
if let Some(_link) = self.links.iter().find(|&link| *link.href == **dest) {
385+
assert!(self.shortcut_link.is_some(), "saw closing link without opening tag");
386+
self.shortcut_link = None;
387+
}
388+
}
389+
// Handle backticks in inline code blocks
390+
Some(Event::Code(text)) => {
391+
trace!("saw code {}", text);
392+
if let Some(link) = self.shortcut_link {
393+
trace!("original text was {}", link.original_text);
394+
if **text == link.original_text[1..link.original_text.len() - 1] {
395+
debug!("replacing {} with {}", text, link.new_text);
396+
*text = link.new_text.clone().into();
397+
}
398+
}
399+
}
400+
// Replace plain text in links
401+
Some(Event::Text(text)) => {
402+
trace!("saw text {}", text);
403+
if let Some(link) = self.shortcut_link {
404+
trace!("original text was {}", link.original_text);
405+
if **text == *link.original_text {
406+
debug!("replacing {} with {}", text, link.new_text);
407+
*text = link.new_text.clone().into();
408+
}
409+
}
410+
}
411+
Some(Event::Start(Tag::Link(_, dest, _))) => {
412+
if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) {
413+
// Not sure why this is necessary - maybe the broken_link_callback doesn't always work?
414+
*dest = CowStr::Borrowed(link.href.as_ref());
415+
}
416+
}
417+
// Anything else couldn't have been a valid Rust path
418+
_ => {}
365419
}
420+
421+
// Yield the modified event
422+
event
366423
}
367424
}
368425

@@ -857,7 +914,7 @@ impl Markdown<'_> {
857914
}
858915
let replacer = |_: &str, s: &str| {
859916
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
860-
Some((link.original_text.clone(), link.href.clone()))
917+
Some((link.href.clone(), link.new_text.clone()))
861918
} else {
862919
None
863920
}
@@ -934,8 +991,8 @@ impl MarkdownSummaryLine<'_> {
934991
}
935992

936993
let replacer = |_: &str, s: &str| {
937-
if let Some(rendered_link) = links.iter().find(|link| &*link.original_text == s) {
938-
Some((rendered_link.original_text.clone(), rendered_link.href.clone()))
994+
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
995+
Some((link.href.clone(), link.new_text.clone()))
939996
} else {
940997
None
941998
}

src/librustdoc/html/render/mod.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ use serde::ser::SerializeSeq;
6464
use serde::{Serialize, Serializer};
6565

6666
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, RenderedLink, SelfTy, TypeKind};
67-
use crate::config::RenderInfo;
68-
use crate::config::RenderOptions;
67+
use crate::config::{RenderInfo, RenderOptions};
6968
use crate::docfs::{DocFS, PathError};
7069
use crate::doctree;
7170
use crate::error::Error;

src/librustdoc/passes/collect_intra_doc_links.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
685685
continue;
686686
}
687687

688+
//let had_backticks = ori_link.contains("`");
688689
let link = ori_link.replace("`", "");
689690
let parts = link.split('#').collect::<Vec<_>>();
690691
let (link, extra_fragment) = if parts.len() > 2 {
@@ -700,6 +701,7 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
700701
(parts[0], None)
701702
};
702703
let resolved_self;
704+
let link_text;
703705
let mut path_str;
704706
let disambiguator;
705707
let (mut res, mut fragment) = {
@@ -716,6 +718,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
716718
continue;
717719
}
718720

721+
// We stripped ` characters for `path_str`.
722+
// The original link might have had multiple pairs of backticks,
723+
// but we don't handle this case.
724+
//link_text = if had_backticks { format!("`{}`", path_str) } else { path_str.to_owned() };
725+
link_text = path_str.to_owned();
726+
719727
// In order to correctly resolve intra-doc-links we need to
720728
// pick a base AST node to work from. If the documentation for
721729
// this module came from an inner comment (//!) then we anchor
@@ -904,7 +912,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
904912
if let Res::PrimTy(_) = res {
905913
match disambiguator {
906914
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
907-
item.attrs.links.push(ItemLink { link: ori_link, did: None, fragment });
915+
item.attrs.links.push(ItemLink {
916+
link: ori_link,
917+
link_text: path_str.to_owned(),
918+
did: None,
919+
fragment,
920+
});
908921
}
909922
Some(other) => {
910923
report_mismatch(other, Disambiguator::Primitive);
@@ -955,7 +968,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
955968
}
956969
}
957970
let id = register_res(cx, res);
958-
item.attrs.links.push(ItemLink { link: ori_link, did: Some(id), fragment });
971+
item.attrs.links.push(ItemLink {
972+
link: ori_link,
973+
link_text,
974+
did: Some(id),
975+
fragment,
976+
});
959977
}
960978
}
961979

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![deny(intra_doc_link_resolution_failure)]
2+
// first try backticks
3+
/// Trait: [`trait@Name`], fn: [`fn@Name`], [`Name`][`macro@Name`]
4+
// @has disambiguator_removed/struct.AtDisambiguator.html
5+
// @has - '//a[@href="../disambiguator_removed/trait.Name.html"][code]' "Name"
6+
// @has - '//a[@href="../disambiguator_removed/fn.Name.html"][code]' "Name"
7+
// @has - '//a[@href="../disambiguator_removed/macro.Name.html"][code]' "Name"
8+
pub struct AtDisambiguator;
9+
10+
/// fn: [`Name()`], macro: [`Name!`]
11+
// @has disambiguator_removed/struct.SymbolDisambiguator.html
12+
// @has - '//a[@href="../disambiguator_removed/fn.Name.html"][code]' "Name()"
13+
// @has - '//a[@href="../disambiguator_removed/macro.Name.html"][code]' "Name!"
14+
pub struct SymbolDisambiguator;
15+
16+
// Now make sure that backticks aren't added if they weren't already there
17+
/// [fn@Name]
18+
// @has disambiguator_removed/trait.Name.html
19+
// @has - '//a[@href="../disambiguator_removed/fn.Name.html"]' "Name"
20+
// @!has - '//a[@href="../disambiguator_removed/fn.Name.html"][code]' "Name"
21+
22+
// FIXME: this will turn !() into ! alone
23+
/// [Name!()]
24+
// @has - '//a[@href="../disambiguator_removed/macro.Name.html"]' "Name!"
25+
pub trait Name {}
26+
27+
#[allow(non_snake_case)]
28+
pub fn Name() {}
29+
30+
#[macro_export]
31+
macro_rules! Name {
32+
() => ()
33+
}

0 commit comments

Comments
 (0)