diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 6a7c3f8caa49f..66f62d97b047d 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -523,6 +523,33 @@ impl Item { .collect() } + /// Find a list of all link names, without finding their href. + /// + /// This is used for generating summary text, which does not include + /// the link text, but does need to know which `[]`-bracketed names + /// are actually links. + crate fn link_names(&self, cache: &Cache) -> Vec { + cache + .intra_doc_links + .get(&self.def_id) + .map_or(&[][..], |v| v.as_slice()) + .iter() + .filter_map(|ItemLink { link: s, link_text, did, fragment }| { + // FIXME(83083): using fragments as a side-channel for + // primitive names is very unfortunate + if did.is_some() || fragment.is_some() { + Some(RenderedLink { + original_text: s.clone(), + new_text: link_text.clone(), + href: String::new(), + }) + } else { + None + } + }) + .collect() + } + crate fn is_crate(&self) -> bool { self.is_mod() && self.def_id.as_real().map_or(false, |did| did.index == CRATE_DEF_INDEX) } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 5734a4a98e2b5..811f682920107 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -292,13 +292,14 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // which should not be indexed. The crate-item itself is // inserted later on when serializing the search-index. if item.def_id.index().map_or(false, |idx| idx != CRATE_DEF_INDEX) { + let desc = item.doc_value().map_or_else(String::new, |x| { + short_markdown_summary(&x.as_str(), &item.link_names(&self.cache)) + }); self.cache.search_index.push(IndexItem { ty: item.type_(), name: s.to_string(), path: path.join("::"), - desc: item - .doc_value() - .map_or_else(String::new, |x| short_markdown_summary(&x.as_str())), + desc, parent, parent_idx: None, search_type: get_index_search_type(&item, &self.empty_cache, self.tcx), diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 00a91e07d65e3..bafb522f36338 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1051,7 +1051,11 @@ impl MarkdownSummaryLine<'_> { /// /// Returns a tuple of the rendered HTML string and whether the output was shortened /// due to the provided `length_limit`. -fn markdown_summary_with_limit(md: &str, length_limit: usize) -> (String, bool) { +fn markdown_summary_with_limit( + md: &str, + link_names: &[RenderedLink], + length_limit: usize, +) -> (String, bool) { if md.is_empty() { return (String::new(), false); } @@ -1065,7 +1069,20 @@ fn markdown_summary_with_limit(md: &str, length_limit: usize) -> (String, bool) *text_length += text.len(); } - 'outer: for event in Parser::new_ext(md, summary_opts()) { + let mut replacer = |broken_link: BrokenLink<'_>| { + if let Some(link) = + link_names.iter().find(|link| &*link.original_text == broken_link.reference) + { + Some((link.href.as_str().into(), link.new_text.as_str().into())) + } else { + None + } + }; + + let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut replacer)); + let p = LinkReplacer::new(p, link_names); + + 'outer: for event in p { match &event { Event::Text(text) => { for word in text.split_inclusive(char::is_whitespace) { @@ -1121,8 +1138,8 @@ fn markdown_summary_with_limit(md: &str, length_limit: usize) -> (String, bool) /// Will shorten to 59 or 60 characters, including an ellipsis (…) if it was shortened. /// /// See [`markdown_summary_with_limit`] for details about what is rendered and what is not. -crate fn short_markdown_summary(markdown: &str) -> String { - let (mut s, was_shortened) = markdown_summary_with_limit(markdown, 59); +crate fn short_markdown_summary(markdown: &str, link_names: &[RenderedLink]) -> String { + let (mut s, was_shortened) = markdown_summary_with_limit(markdown, link_names, 59); if was_shortened { s.push('…'); diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index ac3ea4c8c5f6f..d10da64ccfaa5 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -221,7 +221,7 @@ fn test_header_ids_multiple_blocks() { #[test] fn test_short_markdown_summary() { fn t(input: &str, expect: &str) { - let output = short_markdown_summary(input); + let output = short_markdown_summary(input, &[][..]); assert_eq!(output, expect, "original: {}", input); } @@ -232,6 +232,7 @@ fn test_short_markdown_summary() { t("Hard-break \nsummary", "Hard-break summary"); t("hello [Rust] :)\n\n[Rust]: https://www.rust-lang.org", "hello Rust :)"); t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)"); + t("dud [link]", "dud [link]"); t("code `let x = i32;` ...", "code let x = i32; …"); t("type `Type<'static>` ...", "type Type<'static> …"); t("# top header", "top header"); @@ -259,6 +260,7 @@ fn test_plain_text_summary() { t("Hard-break \nsummary", "Hard-break summary"); t("hello [Rust] :)\n\n[Rust]: https://www.rust-lang.org", "hello Rust :)"); t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)"); + t("dud [link]", "dud [link]"); t("code `let x = i32;` ...", "code `let x = i32;` …"); t("type `Type<'static>` ...", "type `Type<'static>` …"); t("# top header", "top header"); diff --git a/src/librustdoc/html/render/cache.rs b/src/librustdoc/html/render/cache.rs index 3e056c4b67a70..5b3c445013b54 100644 --- a/src/librustdoc/html/render/cache.rs +++ b/src/librustdoc/html/render/cache.rs @@ -34,11 +34,14 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< // has since been learned. for &(did, ref item) in &cache.orphan_impl_items { if let Some(&(ref fqp, _)) = cache.paths.get(&did) { + let desc = item + .doc_value() + .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(&cache))); cache.search_index.push(IndexItem { ty: item.type_(), name: item.name.unwrap().to_string(), path: fqp[..fqp.len() - 1].join("::"), - desc: item.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)), + desc, parent: Some(did.into()), parent_idx: None, search_type: get_index_search_type(&item, cache, tcx), @@ -47,6 +50,11 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< } } + let crate_doc = krate + .module + .doc_value() + .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(&cache))); + let Cache { ref mut search_index, ref paths, .. } = *cache; // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias, @@ -100,9 +108,6 @@ crate fn build_index<'tcx>(krate: &clean::Crate, cache: &mut Cache, tcx: TyCtxt< crate_items.push(&*item); } - let crate_doc = - krate.module.doc_value().map_or_else(String::new, |s| short_markdown_summary(&s)); - struct CrateData<'a> { doc: String, items: Vec<&'a IndexItem>, diff --git a/src/test/rustdoc-js/summaries.js b/src/test/rustdoc-js/summaries.js index f175e47342df6..dfb11e80414c1 100644 --- a/src/test/rustdoc-js/summaries.js +++ b/src/test/rustdoc-js/summaries.js @@ -5,7 +5,7 @@ const QUERY = ['summaries', 'summaries::Sidebar', 'summaries::Sidebar2']; const EXPECTED = [ { 'others': [ - { 'path': '', 'name': 'summaries', 'desc': 'This summary has a link and code.' }, + { 'path': '', 'name': 'summaries', 'desc': 'This summary has a link, [code], and Sidebar2 intra-doc.' }, ], }, { diff --git a/src/test/rustdoc-js/summaries.rs b/src/test/rustdoc-js/summaries.rs index beb91e286b610..418c9f8d0edd0 100644 --- a/src/test/rustdoc-js/summaries.rs +++ b/src/test/rustdoc-js/summaries.rs @@ -1,9 +1,11 @@ #![crate_type = "lib"] #![crate_name = "summaries"] -//! This *summary* has a [link] and `code`. +//! This *summary* has a [link], [`code`], and [`Sidebar2`] intra-doc. //! -//! This is the second paragraph. +//! This is the second paragraph. It should not be rendered. +//! To test that intra-doc links are resolved properly, [`code`] should render +//! the square brackets, and [`Sidebar2`] should not. //! //! [link]: https://example.com