Skip to content

Commit 18c14fd

Browse files
committed
Misc cleanup
- Preserve suffixes when displaying - Rename test file to match `intra-link*` - Remove unnecessary .clone()s - Improve comments and naming - Fix more bugs and add tests - Escape intra-doc link example in public documentation
1 parent 9d7e797 commit 18c14fd

File tree

5 files changed

+114
-63
lines changed

5 files changed

+114
-63
lines changed

src/librustdoc/clean/types.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -439,15 +439,17 @@ pub struct ItemLink {
439439
/// The link text displayed in the HTML.
440440
///
441441
/// This may not be the same as `link` if there was a disambiguator
442-
/// in an intra-doc link (e.g. [`fn@f`])
442+
/// in an intra-doc link (e.g. \[`fn@f`\])
443443
pub(crate) link_text: String,
444444
pub(crate) did: Option<DefId>,
445445
/// The url fragment to append to the link
446446
pub(crate) fragment: Option<String>,
447447
}
448448

449449
pub struct RenderedLink {
450-
/// The text the link was original written as
450+
/// The text the link was original written as.
451+
///
452+
/// This could potentially include disambiguators and backticks.
451453
pub(crate) original_text: String,
452454
/// The text to display in the HTML
453455
pub(crate) new_text: String,

src/librustdoc/html/markdown.rs

+42-23
Original file line numberDiff line numberDiff line change
@@ -338,83 +338,102 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
338338
}
339339

340340
/// Make headings links with anchor IDs and build up TOC.
341-
struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
341+
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
342342
inner: I,
343343
links: &'a [RenderedLink],
344-
shortcut_link: Option<&'b RenderedLink>,
344+
shortcut_link: Option<&'a RenderedLink>,
345345
}
346346

347-
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, '_, I> {
347+
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
348348
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
349349
LinkReplacer { inner: iter, links, shortcut_link: None }
350350
}
351351
}
352352

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

356356
fn next(&mut self) -> Option<Self::Item> {
357+
use pulldown_cmark::LinkType;
358+
357359
let mut event = self.inner.next();
358360

359-
// Remove disambiguators from shortcut links (`[fn@f]`)
361+
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
360362
match &mut event {
363+
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
364+
// Remove any disambiguator.
361365
Some(Event::Start(Tag::Link(
362-
pulldown_cmark::LinkType::ShortcutUnknown,
366+
// [fn@f] or [fn@f][]
367+
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
363368
dest,
364369
title,
365370
))) => {
366371
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-
};
372+
// If this is a shortcut link, it was resolved by the broken_link_callback.
373+
// So the URL will already be updated properly.
374+
let link = self.links.iter().find(|&link| *link.href == **dest);
375+
// Since this is an external iterator, we can't replace the inner text just yet.
376+
// Store that we saw a link so we know to replace it later.
376377
if let Some(link) = link {
377378
trace!("it matched");
378379
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
379380
self.shortcut_link = Some(link);
380381
}
381382
}
382-
Some(Event::End(Tag::Link(pulldown_cmark::LinkType::ShortcutUnknown, dest, _))) => {
383+
// Now that we're done with the shortcut link, don't replace any more text.
384+
Some(Event::End(Tag::Link(
385+
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
386+
dest,
387+
_,
388+
))) => {
383389
debug!("saw end of shortcut link to {}", dest);
384-
if let Some(_link) = self.links.iter().find(|&link| *link.href == **dest) {
390+
if self.links.iter().find(|&link| *link.href == **dest).is_some() {
385391
assert!(self.shortcut_link.is_some(), "saw closing link without opening tag");
386392
self.shortcut_link = None;
387393
}
388394
}
389-
// Handle backticks in inline code blocks
395+
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
396+
// [`fn@f`]
390397
Some(Event::Code(text)) => {
391398
trace!("saw code {}", text);
392399
if let Some(link) = self.shortcut_link {
393400
trace!("original text was {}", link.original_text);
401+
// NOTE: this only replaces if the code block is the *entire* text.
402+
// If only part of the link has code highlighting, the disambiguator will not be removed.
403+
// e.g. [fn@`f`]
404+
// This is a limitation from `collect_intra_doc_links`: it passes a full link,
405+
// and does not distinguish at all between code blocks.
406+
// So we could never be sure we weren't replacing too much:
407+
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
408+
//
409+
// NOTE: &[1..len() - 1] is to strip the backticks
394410
if **text == link.original_text[1..link.original_text.len() - 1] {
395411
debug!("replacing {} with {}", text, link.new_text);
396-
*text = link.new_text.clone().into();
412+
*text = CowStr::Borrowed(&link.new_text);
397413
}
398414
}
399415
}
400-
// Replace plain text in links
416+
// Replace plain text in links, but only in the middle of a shortcut link.
417+
// [fn@f]
401418
Some(Event::Text(text)) => {
402419
trace!("saw text {}", text);
403420
if let Some(link) = self.shortcut_link {
404421
trace!("original text was {}", link.original_text);
422+
// NOTE: same limitations as `Event::Code`
405423
if **text == *link.original_text {
406424
debug!("replacing {} with {}", text, link.new_text);
407-
*text = link.new_text.clone().into();
425+
*text = CowStr::Borrowed(&link.new_text);
408426
}
409427
}
410428
}
429+
// If this is a link, but not a shortcut link,
430+
// replace the URL, since the broken_link_callback was not called.
411431
Some(Event::Start(Tag::Link(_, dest, _))) => {
412432
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?
414433
*dest = CowStr::Borrowed(link.href.as_ref());
415434
}
416435
}
417-
// Anything else couldn't have been a valid Rust path
436+
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
418437
_ => {}
419438
}
420439

src/librustdoc/passes/collect_intra_doc_links.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -717,11 +717,11 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
717717
continue;
718718
}
719719

720-
// We stripped ` characters for `path_str`.
721-
// The original link might have had multiple pairs of backticks,
722-
// but we don't handle this case.
723-
//link_text = if had_backticks { format!("`{}`", path_str) } else { path_str.to_owned() };
724-
link_text = path_str.to_owned();
720+
// We stripped `()` and `!` when parsing the disambiguator.
721+
// Add them back to be displayed, but not prefix disambiguators.
722+
link_text = disambiguator
723+
.map(|d| d.display_for(path_str))
724+
.unwrap_or_else(|| path_str.to_owned());
725725

726726
// In order to correctly resolve intra-doc-links we need to
727727
// pick a base AST node to work from. If the documentation for
@@ -1000,6 +1000,18 @@ enum Disambiguator {
10001000
}
10011001

10021002
impl Disambiguator {
1003+
/// The text that should be displayed when the path is rendered as HTML.
1004+
///
1005+
/// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`.
1006+
fn display_for(&self, path: &str) -> String {
1007+
match self {
1008+
// FIXME: this will have different output if the user had `m!()` originally.
1009+
Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path),
1010+
Self::Kind(DefKind::Fn) => format!("{}()", path),
1011+
_ => path.to_owned(),
1012+
}
1013+
}
1014+
10031015
/// (disambiguator, path_str)
10041016
fn from_str(link: &str) -> Result<(Self, &str), ()> {
10051017
use Disambiguator::{Kind, Namespace as NS, Primitive};

src/test/rustdoc/disambiguator_removed.rs

-33
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// ignore-tidy-linelength
2+
#![deny(intra_doc_link_resolution_failure)]
3+
// first try backticks
4+
/// Trait: [`trait@Name`], fn: [`fn@Name`], [`Name`][`macro@Name`]
5+
// @has intra_link_disambiguators_removed/struct.AtDisambiguator.html
6+
// @has - '//a[@href="../intra_link_disambiguators_removed/trait.Name.html"][code]' "Name"
7+
// @has - '//a[@href="../intra_link_disambiguators_removed/fn.Name.html"][code]' "Name"
8+
// @has - '//a[@href="../intra_link_disambiguators_removed/macro.Name.html"][code]' "Name"
9+
pub struct AtDisambiguator;
10+
11+
/// fn: [`Name()`], macro: [`Name!`]
12+
// @has intra_link_disambiguators_removed/struct.SymbolDisambiguator.html
13+
// @has - '//a[@href="../intra_link_disambiguators_removed/fn.Name.html"][code]' "Name()"
14+
// @has - '//a[@href="../intra_link_disambiguators_removed/macro.Name.html"][code]' "Name!"
15+
pub struct SymbolDisambiguator;
16+
17+
// Now make sure that backticks aren't added if they weren't already there
18+
/// [fn@Name]
19+
// @has intra_link_disambiguators_removed/trait.Name.html
20+
// @has - '//a[@href="../intra_link_disambiguators_removed/fn.Name.html"]' "Name"
21+
// @!has - '//a[@href="../intra_link_disambiguators_removed/fn.Name.html"][code]' "Name"
22+
23+
// FIXME: this will turn !() into ! alone
24+
/// [Name!()]
25+
// @has - '//a[@href="../intra_link_disambiguators_removed/macro.Name.html"]' "Name!"
26+
pub trait Name {}
27+
28+
#[allow(non_snake_case)]
29+
30+
// Try collapsed reference links
31+
/// [macro@Name][]
32+
// @has intra_link_disambiguators_removed/fn.Name.html
33+
// @has - '//a[@href="../intra_link_disambiguators_removed/macro.Name.html"]' "Name"
34+
35+
// Try links that have the same text as a generated URL
36+
/// Weird URL aligned [../intra_link_disambiguators_removed/macro.Name.html][trait@Name]
37+
// @has - '//a[@href="../intra_link_disambiguators_removed/trait.Name.html"]' "../intra_link_disambiguators_removed/macro.Name.html"
38+
pub fn Name() {}
39+
40+
#[macro_export]
41+
// Rustdoc doesn't currently handle links that have weird interspersing of inline code blocks.
42+
/// [fn@Na`m`e]
43+
// @has intra_link_disambiguators_removed/macro.Name.html
44+
// @has - '//a[@href="../intra_link_disambiguators_removed/fn.Name.html"]' "fn@Name"
45+
46+
// It also doesn't handle any case where the code block isn't the whole link text:
47+
/// [trait@`Name`]
48+
// @has - '//a[@href="../intra_link_disambiguators_removed/trait.Name.html"]' "trait@Name"
49+
macro_rules! Name {
50+
() => ()
51+
}

0 commit comments

Comments
 (0)