Auto merge of #73819 - euclio:rustdoc-summaries, r=jyn514,GuillaumeGomez
rustdoc: do not use plain summary for trait impls Fixes #38386. Fixes #48332. Fixes #49430. Fixes #62741. Fixes #73474. Unfortunately this is not quite ready to go because the newly-working links trigger a bunch of linkcheck failures. The failures are tough to fix because the links are resolved relative to the implementor, which could be anywhere in the module hierarchy. (In the current docs, these links end up rendering as uninterpreted markdown syntax, so I don't think these failures are any worse than the status quo. It might be acceptable to just add them to the linkchecker whitelist.) Ideally this could be fixed with intra-doc links ~~but it isn't working for me: I am currently investigating if it's possible to solve it this way.~~ Opened #73829. EDIT: This is now ready!
This commit is contained in:
commit
62dad457bc
@ -168,8 +168,6 @@ pub trait Write {
|
||||
/// This method should generally not be invoked manually, but rather through
|
||||
/// the [`write!`] macro itself.
|
||||
///
|
||||
/// [`write!`]: ../../std/macro.write.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
|
@ -148,7 +148,7 @@ pub trait DoubleEndedIterator: Iterator {
|
||||
/// This is the reverse version of [`try_fold()`]: it takes elements
|
||||
/// starting from the back of the iterator.
|
||||
///
|
||||
/// [`try_fold()`]: trait.Iterator.html#method.try_fold
|
||||
/// [`try_fold()`]: Iterator::try_fold
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -213,7 +213,7 @@ pub trait DoubleEndedIterator: Iterator {
|
||||
/// Folding is useful whenever you have a collection of something, and want
|
||||
/// to produce a single value from it.
|
||||
///
|
||||
/// [`fold()`]: trait.Iterator.html#method.fold
|
||||
/// [`fold()`]: Iterator::fold
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -3319,7 +3319,7 @@ pub unsafe trait SliceIndex<T: ?Sized>: private_slice_index::Sealed {
|
||||
/// Calling this method with an out-of-bounds index or a dangling `slice` pointer
|
||||
/// is *[undefined behavior]* even if the resulting reference is not used.
|
||||
///
|
||||
/// [undefined behavior]: ../../reference/behavior-considered-undefined.html
|
||||
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
|
||||
#[unstable(feature = "slice_index_methods", issue = "none")]
|
||||
unsafe fn get_unchecked(self, slice: *const T) -> *const Self::Output;
|
||||
|
||||
@ -3328,7 +3328,7 @@ pub unsafe trait SliceIndex<T: ?Sized>: private_slice_index::Sealed {
|
||||
/// Calling this method with an out-of-bounds index or a dangling `slice` pointer
|
||||
/// is *[undefined behavior]* even if the resulting reference is not used.
|
||||
///
|
||||
/// [undefined behavior]: ../../reference/behavior-considered-undefined.html
|
||||
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
|
||||
#[unstable(feature = "slice_index_methods", issue = "none")]
|
||||
unsafe fn get_unchecked_mut(self, slice: *mut T) -> *mut Self::Output;
|
||||
|
||||
|
@ -196,7 +196,7 @@ pub trait MetadataExt {
|
||||
fn st_atime(&self) -> i64;
|
||||
/// Returns the last access time of the file, in nanoseconds since [`st_atime`].
|
||||
///
|
||||
/// [`st_atime`]: #tymethod.st_atime
|
||||
/// [`st_atime`]: Self::st_atime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -232,7 +232,7 @@ pub trait MetadataExt {
|
||||
fn st_mtime(&self) -> i64;
|
||||
/// Returns the last modification time of the file, in nanoseconds since [`st_mtime`].
|
||||
///
|
||||
/// [`st_mtime`]: #tymethod.st_mtime
|
||||
/// [`st_mtime`]: Self::st_mtime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -268,7 +268,7 @@ pub trait MetadataExt {
|
||||
fn st_ctime(&self) -> i64;
|
||||
/// Returns the last status change time of the file, in nanoseconds since [`st_ctime`].
|
||||
///
|
||||
/// [`st_ctime`]: #tymethod.st_ctime
|
||||
/// [`st_ctime`]: Self::st_ctime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -200,7 +200,7 @@ pub trait MetadataExt {
|
||||
fn st_atime(&self) -> i64;
|
||||
/// Returns the last access time of the file, in nanoseconds since [`st_atime`].
|
||||
///
|
||||
/// [`st_atime`]: #tymethod.st_atime
|
||||
/// [`st_atime`]: Self::st_atime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -236,7 +236,7 @@ pub trait MetadataExt {
|
||||
fn st_mtime(&self) -> i64;
|
||||
/// Returns the last modification time of the file, in nanoseconds since [`st_mtime`].
|
||||
///
|
||||
/// [`st_mtime`]: #tymethod.st_mtime
|
||||
/// [`st_mtime`]: Self::st_mtime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -272,7 +272,7 @@ pub trait MetadataExt {
|
||||
fn st_ctime(&self) -> i64;
|
||||
/// Returns the last status change time of the file, in nanoseconds since [`st_ctime`].
|
||||
///
|
||||
/// [`st_ctime`]: #tymethod.st_ctime
|
||||
/// [`st_ctime`]: Self::st_ctime
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -16,7 +16,7 @@ use crate::formats::item_type::ItemType;
|
||||
use crate::formats::Impl;
|
||||
use crate::html::render::cache::{extern_location, get_index_search_type, ExternalLocation};
|
||||
use crate::html::render::IndexItem;
|
||||
use crate::html::render::{plain_summary_line, shorten};
|
||||
use crate::html::render::{plain_text_summary, shorten};
|
||||
|
||||
thread_local!(crate static CACHE_KEY: RefCell<Arc<Cache>> = Default::default());
|
||||
|
||||
@ -313,7 +313,7 @@ impl DocFolder for Cache {
|
||||
ty: item.type_(),
|
||||
name: s.to_string(),
|
||||
path: path.join("::"),
|
||||
desc: shorten(plain_summary_line(item.doc_value())),
|
||||
desc: shorten(plain_text_summary(item.doc_value())),
|
||||
parent,
|
||||
parent_idx: None,
|
||||
search_type: get_index_search_type(&item),
|
||||
|
@ -954,44 +954,33 @@ impl MarkdownSummaryLine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plain_summary_line(md: &str) -> String {
|
||||
struct ParserWrapper<'a> {
|
||||
inner: Parser<'a>,
|
||||
is_in: isize,
|
||||
is_first: bool,
|
||||
/// Renders the first paragraph of the provided markdown as plain text.
|
||||
///
|
||||
/// - Headings, links, and formatting are stripped.
|
||||
/// - Inline code is rendered as-is, surrounded by backticks.
|
||||
/// - HTML and code blocks are ignored.
|
||||
pub fn plain_text_summary(md: &str) -> String {
|
||||
if md.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ParserWrapper<'a> {
|
||||
type Item = String;
|
||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||
|
||||
fn next(&mut self) -> Option<String> {
|
||||
let next_event = self.inner.next()?;
|
||||
let (ret, is_in) = match next_event {
|
||||
Event::Start(Tag::Paragraph) => (None, 1),
|
||||
Event::Start(Tag::Heading(_)) => (None, 1),
|
||||
Event::Code(code) => (Some(format!("`{}`", code)), 0),
|
||||
Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
|
||||
Event::End(Tag::Paragraph | Tag::Heading(_)) => (None, -1),
|
||||
_ => (None, 0),
|
||||
};
|
||||
if is_in > 0 || (is_in < 0 && self.is_in > 0) {
|
||||
self.is_in += is_in;
|
||||
}
|
||||
if ret.is_some() {
|
||||
self.is_first = false;
|
||||
ret
|
||||
} else {
|
||||
Some(String::new())
|
||||
for event in Parser::new_ext(md, Options::ENABLE_STRIKETHROUGH) {
|
||||
match &event {
|
||||
Event::Text(text) => s.push_str(text),
|
||||
Event::Code(code) => {
|
||||
s.push('`');
|
||||
s.push_str(code);
|
||||
s.push('`');
|
||||
}
|
||||
Event::HardBreak | Event::SoftBreak => s.push(' '),
|
||||
Event::Start(Tag::CodeBlock(..)) => break,
|
||||
Event::End(Tag::Paragraph) => break,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let mut s = String::with_capacity(md.len() * 3 / 2);
|
||||
let p = ParserWrapper {
|
||||
inner: Parser::new_ext(md, Options::ENABLE_STRIKETHROUGH),
|
||||
is_in: 0,
|
||||
is_first: true,
|
||||
};
|
||||
p.filter(|t| !t.is_empty()).for_each(|i| s.push_str(&i));
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::plain_summary_line;
|
||||
use super::plain_text_summary;
|
||||
use super::{ErrorCodes, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
|
||||
use rustc_span::edition::{Edition, DEFAULT_EDITION};
|
||||
use std::cell::RefCell;
|
||||
@ -205,18 +205,25 @@ fn test_header_ids_multiple_blocks() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_plain_summary_line() {
|
||||
fn test_plain_text_summary() {
|
||||
fn t(input: &str, expect: &str) {
|
||||
let output = plain_summary_line(input);
|
||||
let output = plain_text_summary(input);
|
||||
assert_eq!(output, expect, "original: {}", input);
|
||||
}
|
||||
|
||||
t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
|
||||
t("**bold**", "bold");
|
||||
t("Multi-line\nsummary", "Multi-line 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("code `let x = i32;` ...", "code `let x = i32;` ...");
|
||||
t("type `Type<'static>` ...", "type `Type<'static>` ...");
|
||||
t("# top header", "top header");
|
||||
t("## header", "header");
|
||||
t("first paragraph\n\nsecond paragraph", "first paragraph");
|
||||
t("```\nfn main() {}\n```", "");
|
||||
t("<div>hello</div>", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -9,7 +9,7 @@ use crate::clean::types::GetDefId;
|
||||
use crate::clean::{self, AttributesExt};
|
||||
use crate::formats::cache::Cache;
|
||||
use crate::formats::item_type::ItemType;
|
||||
use crate::html::render::{plain_summary_line, shorten};
|
||||
use crate::html::render::{plain_text_summary, shorten};
|
||||
use crate::html::render::{Generic, IndexItem, IndexItemFunctionType, RenderType, TypeWithKind};
|
||||
|
||||
/// Indicates where an external crate can be found.
|
||||
@ -78,7 +78,7 @@ pub fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
|
||||
ty: item.type_(),
|
||||
name: item.name.clone().unwrap(),
|
||||
path: fqp[..fqp.len() - 1].join("::"),
|
||||
desc: shorten(plain_summary_line(item.doc_value())),
|
||||
desc: shorten(plain_text_summary(item.doc_value())),
|
||||
parent: Some(did),
|
||||
parent_idx: None,
|
||||
search_type: get_index_search_type(&item),
|
||||
@ -127,7 +127,7 @@ pub fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
|
||||
let crate_doc = krate
|
||||
.module
|
||||
.as_ref()
|
||||
.map(|module| shorten(plain_summary_line(module.doc_value())))
|
||||
.map(|module| shorten(plain_text_summary(module.doc_value())))
|
||||
.unwrap_or(String::new());
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -1508,6 +1508,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
|
||||
fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
|
||||
// BTreeMap instead of HashMap to get a sorted output
|
||||
let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
|
||||
@ -1524,7 +1525,7 @@ impl Context {
|
||||
let short = short.to_string();
|
||||
map.entry(short)
|
||||
.or_default()
|
||||
.push((myname, Some(plain_summary_line(item.doc_value()))));
|
||||
.push((myname, Some(plain_text_summary(item.doc_value()))));
|
||||
}
|
||||
|
||||
if self.shared.sort_modules_alphabetically {
|
||||
@ -1730,22 +1731,15 @@ fn full_path(cx: &Context, item: &clean::Item) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
/// Renders the first paragraph of the given markdown as plain text, making it suitable for
|
||||
/// contexts like alt-text or the search index.
|
||||
///
|
||||
/// If no markdown is supplied, the empty string is returned.
|
||||
///
|
||||
/// See [`markdown::plain_text_summary`] for further details.
|
||||
#[inline]
|
||||
crate fn plain_summary_line(s: Option<&str>) -> String {
|
||||
let s = s.unwrap_or("");
|
||||
// This essentially gets the first paragraph of text in one line.
|
||||
let mut line = s
|
||||
.lines()
|
||||
.skip_while(|line| line.chars().all(|c| c.is_whitespace()))
|
||||
.take_while(|line| line.chars().any(|c| !c.is_whitespace()))
|
||||
.fold(String::new(), |mut acc, line| {
|
||||
acc.push_str(line);
|
||||
acc.push(' ');
|
||||
acc
|
||||
});
|
||||
// remove final whitespace
|
||||
line.pop();
|
||||
markdown::plain_summary_line(&line[..])
|
||||
crate fn plain_text_summary(s: Option<&str>) -> String {
|
||||
s.map(markdown::plain_text_summary).unwrap_or_default()
|
||||
}
|
||||
|
||||
crate fn shorten(s: String) -> String {
|
||||
@ -1802,25 +1796,35 @@ fn render_markdown(
|
||||
)
|
||||
}
|
||||
|
||||
/// Writes a documentation block containing only the first paragraph of the documentation. If the
|
||||
/// docs are longer, a "Read more" link is appended to the end.
|
||||
fn document_short(
|
||||
w: &mut Buffer,
|
||||
cx: &Context,
|
||||
item: &clean::Item,
|
||||
link: AssocItemLink<'_>,
|
||||
prefix: &str,
|
||||
is_hidden: bool,
|
||||
) {
|
||||
if let Some(s) = item.doc_value() {
|
||||
let markdown = if s.contains('\n') {
|
||||
format!(
|
||||
"{} [Read more]({})",
|
||||
&plain_summary_line(Some(s)),
|
||||
naive_assoc_href(item, link)
|
||||
)
|
||||
} else {
|
||||
plain_summary_line(Some(s))
|
||||
};
|
||||
render_markdown(w, cx, &markdown, item.links(), prefix, is_hidden);
|
||||
let mut summary_html = MarkdownSummaryLine(s, &item.links()).into_string();
|
||||
|
||||
if s.contains('\n') {
|
||||
let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link));
|
||||
|
||||
if let Some(idx) = summary_html.rfind("</p>") {
|
||||
summary_html.insert_str(idx, &link);
|
||||
} else {
|
||||
summary_html.push_str(&link);
|
||||
}
|
||||
}
|
||||
|
||||
write!(
|
||||
w,
|
||||
"<div class='docblock{}'>{}{}</div>",
|
||||
if is_hidden { " hidden" } else { "" },
|
||||
prefix,
|
||||
summary_html,
|
||||
);
|
||||
} else if !prefix.is_empty() {
|
||||
write!(
|
||||
w,
|
||||
@ -3677,7 +3681,7 @@ fn render_impl(
|
||||
} else if show_def_docs {
|
||||
// In case the item isn't documented,
|
||||
// provide short documentation from the trait.
|
||||
document_short(w, cx, it, link, "", is_hidden);
|
||||
document_short(w, it, link, "", is_hidden);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -3689,7 +3693,7 @@ fn render_impl(
|
||||
} else {
|
||||
document_stability(w, cx, item, is_hidden);
|
||||
if show_def_docs {
|
||||
document_short(w, cx, item, link, "", is_hidden);
|
||||
document_short(w, item, link, "", is_hidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
src/test/rustdoc/plain-text-summaries.rs
Normal file
26
src/test/rustdoc/plain-text-summaries.rs
Normal file
@ -0,0 +1,26 @@
|
||||
#![crate_type = "lib"]
|
||||
#![crate_name = "summaries"]
|
||||
|
||||
//! This summary has a [link] and `code`.
|
||||
//!
|
||||
//! This is the second paragraph.
|
||||
//!
|
||||
//! [link]: https://example.com
|
||||
|
||||
// @has search-index.js 'This summary has a link and `code`.'
|
||||
// @!has - 'second paragraph'
|
||||
|
||||
/// This `code` should be in backticks.
|
||||
///
|
||||
/// This text should not be rendered.
|
||||
pub struct Sidebar;
|
||||
|
||||
// @has summaries/sidebar-items.js 'This `code` should be in backticks.'
|
||||
// @!has - 'text should not be rendered'
|
||||
|
||||
/// ```text
|
||||
/// this block should not be rendered
|
||||
/// ```
|
||||
pub struct Sidebar2;
|
||||
|
||||
// @!has summaries/sidebar-items.js 'block should not be rendered'
|
46
src/test/rustdoc/trait-impl.rs
Normal file
46
src/test/rustdoc/trait-impl.rs
Normal file
@ -0,0 +1,46 @@
|
||||
pub trait Trait {
|
||||
/// Some long docs here.
|
||||
///
|
||||
/// These docs are long enough that a link will be added to the end.
|
||||
fn a();
|
||||
|
||||
/// These docs contain a [reference link].
|
||||
///
|
||||
/// [reference link]: https://example.com
|
||||
fn b();
|
||||
|
||||
/// ```
|
||||
/// This code block should not be in the output, but a Read more link should be generated
|
||||
/// ```
|
||||
fn c();
|
||||
|
||||
/// Escaped formatting a\*b\*c\* works
|
||||
fn d();
|
||||
}
|
||||
|
||||
pub struct Struct;
|
||||
|
||||
impl Trait for Struct {
|
||||
// @has trait_impl/struct.Struct.html '//*[@id="method.a"]/../div/p' 'Some long docs'
|
||||
// @!has - '//*[@id="method.a"]/../div/p' 'link will be added'
|
||||
// @has - '//*[@id="method.a"]/../div/p/a' 'Read more'
|
||||
// @has - '//*[@id="method.a"]/../div/p/a/@href' 'trait.Trait.html'
|
||||
fn a() {}
|
||||
|
||||
// @has trait_impl/struct.Struct.html '//*[@id="method.b"]/../div/p' 'These docs contain'
|
||||
// @has - '//*[@id="method.b"]/../div/p/a' 'reference link'
|
||||
// @has - '//*[@id="method.b"]/../div/p/a/@href' 'https://example.com'
|
||||
// @has - '//*[@id="method.b"]/../div/p/a' 'Read more'
|
||||
// @has - '//*[@id="method.b"]/../div/p/a/@href' 'trait.Trait.html'
|
||||
fn b() {}
|
||||
|
||||
// @!has trait_impl/struct.Struct.html '//*[@id="method.c"]/../div/p' 'code block'
|
||||
// @has - '//*[@id="method.c"]/../div/p/a' 'Read more'
|
||||
// @has - '//*[@id="method.c"]/../div/p/a/@href' 'trait.Trait.html'
|
||||
fn c() {}
|
||||
|
||||
// @has trait_impl/struct.Struct.html '//*[@id="method.d"]/../div/p' \
|
||||
// 'Escaped formatting a*b*c* works'
|
||||
// @!has trait_impl/struct.Struct.html '//*[@id="method.d"]/../div/p/em'
|
||||
fn d() {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user