Skip to content

Commit 62dad45

Browse files
committed
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!
2 parents 3edf11c + 98232ec commit 62dad45

File tree

12 files changed

+152
-82
lines changed

12 files changed

+152
-82
lines changed

library/core/src/fmt/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ pub trait Write {
168168
/// This method should generally not be invoked manually, but rather through
169169
/// the [`write!`] macro itself.
170170
///
171-
/// [`write!`]: ../../std/macro.write.html
172-
///
173171
/// # Examples
174172
///
175173
/// ```

library/core/src/iter/traits/double_ended.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ pub trait DoubleEndedIterator: Iterator {
148148
/// This is the reverse version of [`try_fold()`]: it takes elements
149149
/// starting from the back of the iterator.
150150
///
151-
/// [`try_fold()`]: trait.Iterator.html#method.try_fold
151+
/// [`try_fold()`]: Iterator::try_fold
152152
///
153153
/// # Examples
154154
///
@@ -213,7 +213,7 @@ pub trait DoubleEndedIterator: Iterator {
213213
/// Folding is useful whenever you have a collection of something, and want
214214
/// to produce a single value from it.
215215
///
216-
/// [`fold()`]: trait.Iterator.html#method.fold
216+
/// [`fold()`]: Iterator::fold
217217
///
218218
/// # Examples
219219
///

library/core/src/slice/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3319,7 +3319,7 @@ pub unsafe trait SliceIndex<T: ?Sized>: private_slice_index::Sealed {
33193319
/// Calling this method with an out-of-bounds index or a dangling `slice` pointer
33203320
/// is *[undefined behavior]* even if the resulting reference is not used.
33213321
///
3322-
/// [undefined behavior]: ../../reference/behavior-considered-undefined.html
3322+
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
33233323
#[unstable(feature = "slice_index_methods", issue = "none")]
33243324
unsafe fn get_unchecked(self, slice: *const T) -> *const Self::Output;
33253325

@@ -3328,7 +3328,7 @@ pub unsafe trait SliceIndex<T: ?Sized>: private_slice_index::Sealed {
33283328
/// Calling this method with an out-of-bounds index or a dangling `slice` pointer
33293329
/// is *[undefined behavior]* even if the resulting reference is not used.
33303330
///
3331-
/// [undefined behavior]: ../../reference/behavior-considered-undefined.html
3331+
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
33323332
#[unstable(feature = "slice_index_methods", issue = "none")]
33333333
unsafe fn get_unchecked_mut(self, slice: *mut T) -> *mut Self::Output;
33343334

library/std/src/os/linux/fs.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ pub trait MetadataExt {
196196
fn st_atime(&self) -> i64;
197197
/// Returns the last access time of the file, in nanoseconds since [`st_atime`].
198198
///
199-
/// [`st_atime`]: #tymethod.st_atime
199+
/// [`st_atime`]: Self::st_atime
200200
///
201201
/// # Examples
202202
///
@@ -232,7 +232,7 @@ pub trait MetadataExt {
232232
fn st_mtime(&self) -> i64;
233233
/// Returns the last modification time of the file, in nanoseconds since [`st_mtime`].
234234
///
235-
/// [`st_mtime`]: #tymethod.st_mtime
235+
/// [`st_mtime`]: Self::st_mtime
236236
///
237237
/// # Examples
238238
///
@@ -268,7 +268,7 @@ pub trait MetadataExt {
268268
fn st_ctime(&self) -> i64;
269269
/// Returns the last status change time of the file, in nanoseconds since [`st_ctime`].
270270
///
271-
/// [`st_ctime`]: #tymethod.st_ctime
271+
/// [`st_ctime`]: Self::st_ctime
272272
///
273273
/// # Examples
274274
///

library/std/src/os/redox/fs.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ pub trait MetadataExt {
200200
fn st_atime(&self) -> i64;
201201
/// Returns the last access time of the file, in nanoseconds since [`st_atime`].
202202
///
203-
/// [`st_atime`]: #tymethod.st_atime
203+
/// [`st_atime`]: Self::st_atime
204204
///
205205
/// # Examples
206206
///
@@ -236,7 +236,7 @@ pub trait MetadataExt {
236236
fn st_mtime(&self) -> i64;
237237
/// Returns the last modification time of the file, in nanoseconds since [`st_mtime`].
238238
///
239-
/// [`st_mtime`]: #tymethod.st_mtime
239+
/// [`st_mtime`]: Self::st_mtime
240240
///
241241
/// # Examples
242242
///
@@ -272,7 +272,7 @@ pub trait MetadataExt {
272272
fn st_ctime(&self) -> i64;
273273
/// Returns the last status change time of the file, in nanoseconds since [`st_ctime`].
274274
///
275-
/// [`st_ctime`]: #tymethod.st_ctime
275+
/// [`st_ctime`]: Self::st_ctime
276276
///
277277
/// # Examples
278278
///

src/librustdoc/formats/cache.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::formats::item_type::ItemType;
1616
use crate::formats::Impl;
1717
use crate::html::render::cache::{extern_location, get_index_search_type, ExternalLocation};
1818
use crate::html::render::IndexItem;
19-
use crate::html::render::{plain_summary_line, shorten};
19+
use crate::html::render::{plain_text_summary, shorten};
2020

2121
thread_local!(crate static CACHE_KEY: RefCell<Arc<Cache>> = Default::default());
2222

@@ -313,7 +313,7 @@ impl DocFolder for Cache {
313313
ty: item.type_(),
314314
name: s.to_string(),
315315
path: path.join("::"),
316-
desc: shorten(plain_summary_line(item.doc_value())),
316+
desc: shorten(plain_text_summary(item.doc_value())),
317317
parent,
318318
parent_idx: None,
319319
search_type: get_index_search_type(&item),

src/librustdoc/html/markdown.rs

+22-33
Original file line numberDiff line numberDiff line change
@@ -954,44 +954,33 @@ impl MarkdownSummaryLine<'_> {
954954
}
955955
}
956956

957-
pub fn plain_summary_line(md: &str) -> String {
958-
struct ParserWrapper<'a> {
959-
inner: Parser<'a>,
960-
is_in: isize,
961-
is_first: bool,
957+
/// Renders the first paragraph of the provided markdown as plain text.
958+
///
959+
/// - Headings, links, and formatting are stripped.
960+
/// - Inline code is rendered as-is, surrounded by backticks.
961+
/// - HTML and code blocks are ignored.
962+
pub fn plain_text_summary(md: &str) -> String {
963+
if md.is_empty() {
964+
return String::new();
962965
}
963966

964-
impl<'a> Iterator for ParserWrapper<'a> {
965-
type Item = String;
966-
967-
fn next(&mut self) -> Option<String> {
968-
let next_event = self.inner.next()?;
969-
let (ret, is_in) = match next_event {
970-
Event::Start(Tag::Paragraph) => (None, 1),
971-
Event::Start(Tag::Heading(_)) => (None, 1),
972-
Event::Code(code) => (Some(format!("`{}`", code)), 0),
973-
Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0),
974-
Event::End(Tag::Paragraph | Tag::Heading(_)) => (None, -1),
975-
_ => (None, 0),
976-
};
977-
if is_in > 0 || (is_in < 0 && self.is_in > 0) {
978-
self.is_in += is_in;
979-
}
980-
if ret.is_some() {
981-
self.is_first = false;
982-
ret
983-
} else {
984-
Some(String::new())
967+
let mut s = String::with_capacity(md.len() * 3 / 2);
968+
969+
for event in Parser::new_ext(md, Options::ENABLE_STRIKETHROUGH) {
970+
match &event {
971+
Event::Text(text) => s.push_str(text),
972+
Event::Code(code) => {
973+
s.push('`');
974+
s.push_str(code);
975+
s.push('`');
985976
}
977+
Event::HardBreak | Event::SoftBreak => s.push(' '),
978+
Event::Start(Tag::CodeBlock(..)) => break,
979+
Event::End(Tag::Paragraph) => break,
980+
_ => (),
986981
}
987982
}
988-
let mut s = String::with_capacity(md.len() * 3 / 2);
989-
let p = ParserWrapper {
990-
inner: Parser::new_ext(md, Options::ENABLE_STRIKETHROUGH),
991-
is_in: 0,
992-
is_first: true,
993-
};
994-
p.filter(|t| !t.is_empty()).for_each(|i| s.push_str(&i));
983+
995984
s
996985
}
997986

src/librustdoc/html/markdown/tests.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::plain_summary_line;
1+
use super::plain_text_summary;
22
use super::{ErrorCodes, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
33
use rustc_span::edition::{Edition, DEFAULT_EDITION};
44
use std::cell::RefCell;
@@ -205,18 +205,25 @@ fn test_header_ids_multiple_blocks() {
205205
}
206206

207207
#[test]
208-
fn test_plain_summary_line() {
208+
fn test_plain_text_summary() {
209209
fn t(input: &str, expect: &str) {
210-
let output = plain_summary_line(input);
210+
let output = plain_text_summary(input);
211211
assert_eq!(output, expect, "original: {}", input);
212212
}
213213

214214
t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
215+
t("**bold**", "bold");
216+
t("Multi-line\nsummary", "Multi-line summary");
217+
t("Hard-break \nsummary", "Hard-break summary");
218+
t("hello [Rust] :)\n\n[Rust]: https://www.rust-lang.org", "hello Rust :)");
215219
t("hello [Rust](https://www.rust-lang.org \"Rust\") :)", "hello Rust :)");
216220
t("code `let x = i32;` ...", "code `let x = i32;` ...");
217221
t("type `Type<'static>` ...", "type `Type<'static>` ...");
218222
t("# top header", "top header");
219223
t("## header", "header");
224+
t("first paragraph\n\nsecond paragraph", "first paragraph");
225+
t("```\nfn main() {}\n```", "");
226+
t("<div>hello</div>", "");
220227
}
221228

222229
#[test]

src/librustdoc/html/render/cache.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::clean::types::GetDefId;
99
use crate::clean::{self, AttributesExt};
1010
use crate::formats::cache::Cache;
1111
use crate::formats::item_type::ItemType;
12-
use crate::html::render::{plain_summary_line, shorten};
12+
use crate::html::render::{plain_text_summary, shorten};
1313
use crate::html::render::{Generic, IndexItem, IndexItemFunctionType, RenderType, TypeWithKind};
1414

1515
/// Indicates where an external crate can be found.
@@ -78,7 +78,7 @@ pub fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
7878
ty: item.type_(),
7979
name: item.name.clone().unwrap(),
8080
path: fqp[..fqp.len() - 1].join("::"),
81-
desc: shorten(plain_summary_line(item.doc_value())),
81+
desc: shorten(plain_text_summary(item.doc_value())),
8282
parent: Some(did),
8383
parent_idx: None,
8484
search_type: get_index_search_type(&item),
@@ -127,7 +127,7 @@ pub fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String {
127127
let crate_doc = krate
128128
.module
129129
.as_ref()
130-
.map(|module| shorten(plain_summary_line(module.doc_value())))
130+
.map(|module| shorten(plain_text_summary(module.doc_value())))
131131
.unwrap_or(String::new());
132132

133133
#[derive(Serialize)]

src/librustdoc/html/render/mod.rs

+33-29
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,7 @@ impl Context {
15081508
}
15091509
}
15101510

1511+
/// Construct a map of items shown in the sidebar to a plain-text summary of their docs.
15111512
fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> {
15121513
// BTreeMap instead of HashMap to get a sorted output
15131514
let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
@@ -1524,7 +1525,7 @@ impl Context {
15241525
let short = short.to_string();
15251526
map.entry(short)
15261527
.or_default()
1527-
.push((myname, Some(plain_summary_line(item.doc_value()))));
1528+
.push((myname, Some(plain_text_summary(item.doc_value()))));
15281529
}
15291530

15301531
if self.shared.sort_modules_alphabetically {
@@ -1730,22 +1731,15 @@ fn full_path(cx: &Context, item: &clean::Item) -> String {
17301731
s
17311732
}
17321733

1734+
/// Renders the first paragraph of the given markdown as plain text, making it suitable for
1735+
/// contexts like alt-text or the search index.
1736+
///
1737+
/// If no markdown is supplied, the empty string is returned.
1738+
///
1739+
/// See [`markdown::plain_text_summary`] for further details.
17331740
#[inline]
1734-
crate fn plain_summary_line(s: Option<&str>) -> String {
1735-
let s = s.unwrap_or("");
1736-
// This essentially gets the first paragraph of text in one line.
1737-
let mut line = s
1738-
.lines()
1739-
.skip_while(|line| line.chars().all(|c| c.is_whitespace()))
1740-
.take_while(|line| line.chars().any(|c| !c.is_whitespace()))
1741-
.fold(String::new(), |mut acc, line| {
1742-
acc.push_str(line);
1743-
acc.push(' ');
1744-
acc
1745-
});
1746-
// remove final whitespace
1747-
line.pop();
1748-
markdown::plain_summary_line(&line[..])
1741+
crate fn plain_text_summary(s: Option<&str>) -> String {
1742+
s.map(markdown::plain_text_summary).unwrap_or_default()
17491743
}
17501744

17511745
crate fn shorten(s: String) -> String {
@@ -1802,25 +1796,35 @@ fn render_markdown(
18021796
)
18031797
}
18041798

1799+
/// Writes a documentation block containing only the first paragraph of the documentation. If the
1800+
/// docs are longer, a "Read more" link is appended to the end.
18051801
fn document_short(
18061802
w: &mut Buffer,
1807-
cx: &Context,
18081803
item: &clean::Item,
18091804
link: AssocItemLink<'_>,
18101805
prefix: &str,
18111806
is_hidden: bool,
18121807
) {
18131808
if let Some(s) = item.doc_value() {
1814-
let markdown = if s.contains('\n') {
1815-
format!(
1816-
"{} [Read more]({})",
1817-
&plain_summary_line(Some(s)),
1818-
naive_assoc_href(item, link)
1819-
)
1820-
} else {
1821-
plain_summary_line(Some(s))
1822-
};
1823-
render_markdown(w, cx, &markdown, item.links(), prefix, is_hidden);
1809+
let mut summary_html = MarkdownSummaryLine(s, &item.links()).into_string();
1810+
1811+
if s.contains('\n') {
1812+
let link = format!(r#" <a href="{}">Read more</a>"#, naive_assoc_href(item, link));
1813+
1814+
if let Some(idx) = summary_html.rfind("</p>") {
1815+
summary_html.insert_str(idx, &link);
1816+
} else {
1817+
summary_html.push_str(&link);
1818+
}
1819+
}
1820+
1821+
write!(
1822+
w,
1823+
"<div class='docblock{}'>{}{}</div>",
1824+
if is_hidden { " hidden" } else { "" },
1825+
prefix,
1826+
summary_html,
1827+
);
18241828
} else if !prefix.is_empty() {
18251829
write!(
18261830
w,
@@ -3677,7 +3681,7 @@ fn render_impl(
36773681
} else if show_def_docs {
36783682
// In case the item isn't documented,
36793683
// provide short documentation from the trait.
3680-
document_short(w, cx, it, link, "", is_hidden);
3684+
document_short(w, it, link, "", is_hidden);
36813685
}
36823686
}
36833687
} else {
@@ -3689,7 +3693,7 @@ fn render_impl(
36893693
} else {
36903694
document_stability(w, cx, item, is_hidden);
36913695
if show_def_docs {
3692-
document_short(w, cx, item, link, "", is_hidden);
3696+
document_short(w, item, link, "", is_hidden);
36933697
}
36943698
}
36953699
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![crate_type = "lib"]
2+
#![crate_name = "summaries"]
3+
4+
//! This summary has a [link] and `code`.
5+
//!
6+
//! This is the second paragraph.
7+
//!
8+
//! [link]: https://example.com
9+
10+
// @has search-index.js 'This summary has a link and `code`.'
11+
// @!has - 'second paragraph'
12+
13+
/// This `code` should be in backticks.
14+
///
15+
/// This text should not be rendered.
16+
pub struct Sidebar;
17+
18+
// @has summaries/sidebar-items.js 'This `code` should be in backticks.'
19+
// @!has - 'text should not be rendered'
20+
21+
/// ```text
22+
/// this block should not be rendered
23+
/// ```
24+
pub struct Sidebar2;
25+
26+
// @!has summaries/sidebar-items.js 'block should not be rendered'

0 commit comments

Comments
 (0)