Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove disambiguators from intra doc link text #76078

Merged
merged 6 commits into from
Sep 5, 2020
Merged
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
53 changes: 43 additions & 10 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl Item {
self.attrs.collapsed_doc_value()
}

pub fn links(&self) -> Vec<(String, String)> {
pub fn links(&self) -> Vec<RenderedLink> {
self.attrs.links(&self.def_id.krate)
}

Expand Down Expand Up @@ -425,10 +425,38 @@ pub struct Attributes {
pub cfg: Option<Arc<Cfg>>,
pub span: Option<rustc_span::Span>,
/// map from Rust paths to resolved defs and potential URL fragments
pub links: Vec<(String, Option<DefId>, Option<String>)>,
pub links: Vec<ItemLink>,
pub inner_docs: bool,
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
/// A link that has not yet been rendered.
///
/// This link will be turned into a rendered link by [`Attributes::links`]
pub struct ItemLink {
/// The original link written in the markdown
jyn514 marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) link: String,
/// The link text displayed in the HTML.
///
/// This may not be the same as `link` if there was a disambiguator
/// in an intra-doc link (e.g. \[`fn@f`\])
pub(crate) link_text: String,
pub(crate) did: Option<DefId>,
/// The url fragment to append to the link
pub(crate) fragment: Option<String>,
}

pub struct RenderedLink {
/// The text the link was original written as.
///
/// This could potentially include disambiguators and backticks.
pub(crate) original_text: String,
/// The text to display in the HTML
pub(crate) new_text: String,
/// The URL to put in the `href`
pub(crate) href: String,
}

impl Attributes {
/// Extracts the content from an attribute `#[doc(cfg(content))]`.
pub fn extract_cfg(mi: &ast::MetaItem) -> Option<&ast::MetaItem> {
Expand Down Expand Up @@ -605,21 +633,25 @@ impl Attributes {
/// Gets links as a vector
///
/// Cache must be populated before call
pub fn links(&self, krate: &CrateNum) -> Vec<(String, String)> {
pub fn links(&self, krate: &CrateNum) -> Vec<RenderedLink> {
use crate::html::format::href;
use crate::html::render::CURRENT_DEPTH;

self.links
.iter()
.filter_map(|&(ref s, did, ref fragment)| {
match did {
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
match *did {
Some(did) => {
if let Some((mut href, ..)) = href(did) {
if let Some(ref fragment) = *fragment {
href.push_str("#");
href.push_str(fragment);
}
Some((s.clone(), href))
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href,
})
} else {
None
}
Expand All @@ -639,16 +671,17 @@ impl Attributes {
};
// This is a primitive so the url is done "by hand".
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
Some((
s.clone(),
format!(
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: format!(
"{}{}std/primitive.{}.html{}",
url,
if !url.ends_with('/') { "/" } else { "" },
&fragment[..tail],
&fragment[tail..]
),
))
})
} else {
panic!("This isn't a primitive?!");
}
Expand Down
117 changes: 97 additions & 20 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use std::fmt::Write;
use std::ops::Range;
use std::str;

use crate::clean::RenderedLink;
use crate::doctest;
use crate::html::highlight;
use crate::html::toc::TocBuilder;
Expand All @@ -52,7 +53,7 @@ fn opts() -> Options {
pub struct Markdown<'a>(
pub &'a str,
/// A list of link replacements.
pub &'a [(String, String)],
pub &'a [RenderedLink],
/// The current list of used header IDs.
pub &'a mut IdMap,
/// Whether to allow the use of explicit error codes in doctest lang strings.
Expand All @@ -78,7 +79,7 @@ pub struct MarkdownHtml<'a>(
pub &'a Option<Playground>,
);
/// A tuple struct like `Markdown` that renders only the first paragraph.
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]);

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ErrorCodes {
Expand Down Expand Up @@ -337,31 +338,107 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
}

/// Make headings links with anchor IDs and build up TOC.
struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
inner: I,
links: &'b [(String, String)],
links: &'a [RenderedLink],
shortcut_link: Option<&'a RenderedLink>,
}

impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
fn new(iter: I, links: &'b [(String, String)]) -> Self {
LinkReplacer { inner: iter, links }
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
LinkReplacer { inner: iter, links, shortcut_link: None }
}
}

impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
type Item = Event<'a>;

fn next(&mut self) -> Option<Self::Item> {
let event = self.inner.next();
if let Some(Event::Start(Tag::Link(kind, dest, text))) = event {
if let Some(&(_, ref replace)) = self.links.iter().find(|link| link.0 == *dest) {
Some(Event::Start(Tag::Link(kind, replace.to_owned().into(), text)))
} else {
Some(Event::Start(Tag::Link(kind, dest, text)))
use pulldown_cmark::LinkType;

let mut event = self.inner.next();

// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
match &mut event {
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
// Remove any disambiguator.
Some(Event::Start(Tag::Link(
// [fn@f] or [fn@f][]
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
dest,
title,
))) => {
debug!("saw start of shortcut link to {} with title {}", dest, title);
// If this is a shortcut link, it was resolved by the broken_link_callback.
// So the URL will already be updated properly.
let link = self.links.iter().find(|&link| *link.href == **dest);
// Since this is an external iterator, we can't replace the inner text just yet.
// Store that we saw a link so we know to replace it later.
if let Some(link) = link {
trace!("it matched");
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
self.shortcut_link = Some(link);
}
}
} else {
event
// Now that we're done with the shortcut link, don't replace any more text.
Some(Event::End(Tag::Link(
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
dest,
_,
))) => {
debug!("saw end of shortcut link to {}", dest);
if self.links.iter().find(|&link| *link.href == **dest).is_some() {
assert!(self.shortcut_link.is_some(), "saw closing link without opening tag");
self.shortcut_link = None;
}
}
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
// [`fn@f`]
Some(Event::Code(text)) => {
trace!("saw code {}", text);
if let Some(link) = self.shortcut_link {
trace!("original text was {}", link.original_text);
// NOTE: this only replaces if the code block is the *entire* text.
// If only part of the link has code highlighting, the disambiguator will not be removed.
// e.g. [fn@`f`]
// This is a limitation from `collect_intra_doc_links`: it passes a full link,
// and does not distinguish at all between code blocks.
// So we could never be sure we weren't replacing too much:
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
//
// NOTE: &[1..len() - 1] is to strip the backticks
if **text == link.original_text[1..link.original_text.len() - 1] {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
}
}
// Replace plain text in links, but only in the middle of a shortcut link.
// [fn@f]
Some(Event::Text(text)) => {
trace!("saw text {}", text);
if let Some(link) = self.shortcut_link {
trace!("original text was {}", link.original_text);
// NOTE: same limitations as `Event::Code`
if **text == *link.original_text {
debug!("replacing {} with {}", text, link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
}
}
// If this is a link, but not a shortcut link,
// replace the URL, since the broken_link_callback was not called.
Some(Event::Start(Tag::Link(_, dest, _))) => {
if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) {
*dest = CowStr::Borrowed(link.href.as_ref());
}
}
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
_ => {}
}

// Yield the modified event
event
}
}

Expand Down Expand Up @@ -855,8 +932,8 @@ impl Markdown<'_> {
return String::new();
}
let replacer = |_: &str, s: &str| {
if let Some(&(_, ref replace)) = links.iter().find(|link| &*link.0 == s) {
Some((replace.clone(), s.to_owned()))
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
Some((link.href.clone(), link.new_text.clone()))
} else {
None
}
Expand Down Expand Up @@ -933,8 +1010,8 @@ impl MarkdownSummaryLine<'_> {
}

let replacer = |_: &str, s: &str| {
if let Some(&(_, ref replace)) = links.iter().find(|link| &*link.0 == s) {
Some((replace.clone(), s.to_owned()))
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
Some((link.href.clone(), link.new_text.clone()))
} else {
None
}
Expand Down
7 changes: 3 additions & 4 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ use rustc_span::symbol::{sym, Symbol};
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};

use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, TypeKind};
use crate::config::RenderInfo;
use crate::config::RenderOptions;
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, RenderedLink, SelfTy, TypeKind};
use crate::config::{RenderInfo, RenderOptions};
use crate::docfs::{DocFS, PathError};
use crate::doctree;
use crate::error::Error;
Expand Down Expand Up @@ -1780,7 +1779,7 @@ fn render_markdown(
w: &mut Buffer,
cx: &Context,
md_text: &str,
links: Vec<(String, String)>,
links: Vec<RenderedLink>,
prefix: &str,
is_hidden: bool,
) {
Expand Down
41 changes: 35 additions & 6 deletions src/librustdoc/passes/collect_intra_doc_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,11 +695,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
// This is an anchor to an element of the current page, nothing to do in here!
continue;
}
(parts[0].to_owned(), Some(parts[1].to_owned()))
(parts[0], Some(parts[1].to_owned()))
} else {
(parts[0].to_owned(), None)
(parts[0], None)
};
let resolved_self;
let link_text;
let mut path_str;
let disambiguator;
let (mut res, mut fragment) = {
Expand All @@ -716,6 +717,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
continue;
}

// We stripped `()` and `!` when parsing the disambiguator.
// Add them back to be displayed, but not prefix disambiguators.
link_text = disambiguator
.map(|d| d.display_for(path_str))
.unwrap_or_else(|| path_str.to_owned());

// In order to correctly resolve intra-doc-links we need to
// pick a base AST node to work from. If the documentation for
// this module came from an inner comment (//!) then we anchor
Expand Down Expand Up @@ -904,7 +911,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
if let Res::PrimTy(_) = res {
match disambiguator {
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
item.attrs.links.push((ori_link, None, fragment))
item.attrs.links.push(ItemLink {
link: ori_link,
link_text: path_str.to_owned(),
did: None,
fragment,
});
}
Some(other) => {
report_mismatch(other, Disambiguator::Primitive);
Expand Down Expand Up @@ -955,7 +967,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
}
}
let id = register_res(cx, res);
item.attrs.links.push((ori_link, Some(id), fragment));
item.attrs.links.push(ItemLink {
link: ori_link,
link_text,
did: Some(id),
fragment,
});
}
}

Expand Down Expand Up @@ -983,6 +1000,18 @@ enum Disambiguator {
}

impl Disambiguator {
/// The text that should be displayed when the path is rendered as HTML.
///
/// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`.
fn display_for(&self, path: &str) -> String {
match self {
// FIXME: this will have different output if the user had `m!()` originally.
Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path),
Self::Kind(DefKind::Fn) => format!("{}()", path),
_ => path.to_owned(),
}
}

/// (disambiguator, path_str)
fn from_str(link: &str) -> Result<(Self, &str), ()> {
use Disambiguator::{Kind, Namespace as NS, Primitive};
Expand Down Expand Up @@ -1035,7 +1064,7 @@ impl Disambiguator {
}

/// Return (description of the change, suggestion)
fn display_for(self, path_str: &str) -> (&'static str, String) {
fn suggestion_for(self, path_str: &str) -> (&'static str, String) {
const PREFIX: &str = "prefix with the item kind";
const FUNCTION: &str = "add parentheses";
const MACRO: &str = "add an exclamation mark";
Expand Down Expand Up @@ -1290,7 +1319,7 @@ fn suggest_disambiguator(
sp: Option<rustc_span::Span>,
link_range: &Option<Range<usize>>,
) {
let (action, mut suggestion) = disambiguator.display_for(path_str);
let (action, mut suggestion) = disambiguator.suggestion_for(path_str);
let help = format!("to link to the {}, {}", disambiguator.descr(), action);

if let Some(sp) = sp {
Expand Down
Loading