Skip to content

Commit 7d6feb4

Browse files
authored
Rollup merge of #89506 - yaymukund:docblock-headings, r=GuillaumeGomez
librustdoc: Use correct heading levels. Closes #89309 This fixes the `<h#>` header tags throughout the docs to reflect a semantic hierarchy. - I ran a script to manually check that we don't have any files with multiple `<h1>` tags. - Also checked that we never incorrectly nest e.g. a `<h2>` under an `<h3>`. - I also spot-checked a bunch of pages (`trait.Read`, `enum.Ordering`, `primitive.isize`, `trait.Iterator`).
2 parents b015940 + 1f86a8e commit 7d6feb4

File tree

17 files changed

+264
-122
lines changed

17 files changed

+264
-122
lines changed

src/librustdoc/externalfiles.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::html::markdown::{ErrorCodes, IdMap, Markdown, Playground};
1+
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
22
use crate::rustc_span::edition::Edition;
33
use std::fs;
44
use std::path::Path;
@@ -39,14 +39,32 @@ impl ExternalHtml {
3939
let bc = format!(
4040
"{}{}",
4141
bc,
42-
Markdown(&m_bc, &[], id_map, codes, edition, playground).into_string()
42+
Markdown {
43+
content: &m_bc,
44+
links: &[],
45+
ids: id_map,
46+
error_codes: codes,
47+
edition,
48+
playground,
49+
heading_offset: HeadingOffset::H2,
50+
}
51+
.into_string()
4352
);
4453
let ac = load_external_files(after_content, diag)?;
4554
let m_ac = load_external_files(md_after_content, diag)?;
4655
let ac = format!(
4756
"{}{}",
4857
ac,
49-
Markdown(&m_ac, &[], id_map, codes, edition, playground).into_string()
58+
Markdown {
59+
content: &m_ac,
60+
links: &[],
61+
ids: id_map,
62+
error_codes: codes,
63+
edition,
64+
playground,
65+
heading_offset: HeadingOffset::H2,
66+
}
67+
.into_string()
5068
);
5169
Some(ExternalHtml { in_header: ih, before_content: bc, after_content: ac })
5270
}

src/librustdoc/html/markdown.rs

+55-17
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@
88
//! extern crate rustc_span;
99
//!
1010
//! use rustc_span::edition::Edition;
11-
//! use rustdoc::html::markdown::{IdMap, Markdown, ErrorCodes};
11+
//! use rustdoc::html::markdown::{HeadingOffset, IdMap, Markdown, ErrorCodes};
1212
//!
1313
//! let s = "My *markdown* _text_";
1414
//! let mut id_map = IdMap::new();
15-
//! let md = Markdown(s, &[], &mut id_map, ErrorCodes::Yes, Edition::Edition2015, &None);
15+
//! let md = Markdown {
16+
//! content: s,
17+
//! links: &[],
18+
//! ids: &mut id_map,
19+
//! error_codes: ErrorCodes::Yes,
20+
//! edition: Edition::Edition2015,
21+
//! playground: &None,
22+
//! heading_offset: HeadingOffset::H2,
23+
//! };
1624
//! let html = md.into_string();
1725
//! // ... something using html
1826
//! ```
@@ -47,6 +55,8 @@ use pulldown_cmark::{
4755
#[cfg(test)]
4856
mod tests;
4957

58+
const MAX_HEADER_LEVEL: u32 = 6;
59+
5060
/// Options for rendering Markdown in the main body of documentation.
5161
pub(crate) fn main_body_opts() -> Options {
5262
Options::ENABLE_TABLES
@@ -65,20 +75,33 @@ pub(crate) fn summary_opts() -> Options {
6575
| Options::ENABLE_SMART_PUNCTUATION
6676
}
6777

78+
#[derive(Debug, Clone, Copy)]
79+
pub enum HeadingOffset {
80+
H1 = 0,
81+
H2,
82+
H3,
83+
H4,
84+
H5,
85+
H6,
86+
}
87+
6888
/// When `to_string` is called, this struct will emit the HTML corresponding to
6989
/// the rendered version of the contained markdown string.
70-
pub struct Markdown<'a>(
71-
pub &'a str,
90+
pub struct Markdown<'a> {
91+
pub content: &'a str,
7292
/// A list of link replacements.
73-
pub &'a [RenderedLink],
93+
pub links: &'a [RenderedLink],
7494
/// The current list of used header IDs.
75-
pub &'a mut IdMap,
95+
pub ids: &'a mut IdMap,
7696
/// Whether to allow the use of explicit error codes in doctest lang strings.
77-
pub ErrorCodes,
97+
pub error_codes: ErrorCodes,
7898
/// Default edition to use when parsing doctests (to add a `fn main`).
79-
pub Edition,
80-
pub &'a Option<Playground>,
81-
);
99+
pub edition: Edition,
100+
pub playground: &'a Option<Playground>,
101+
/// Offset at which we render headings.
102+
/// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`.
103+
pub heading_offset: HeadingOffset,
104+
}
82105
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
83106
crate struct MarkdownWithToc<'a>(
84107
crate &'a str,
@@ -489,11 +512,17 @@ struct HeadingLinks<'a, 'b, 'ids, I> {
489512
toc: Option<&'b mut TocBuilder>,
490513
buf: VecDeque<SpannedEvent<'a>>,
491514
id_map: &'ids mut IdMap,
515+
heading_offset: HeadingOffset,
492516
}
493517

494518
impl<'a, 'b, 'ids, I> HeadingLinks<'a, 'b, 'ids, I> {
495-
fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap) -> Self {
496-
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids }
519+
fn new(
520+
iter: I,
521+
toc: Option<&'b mut TocBuilder>,
522+
ids: &'ids mut IdMap,
523+
heading_offset: HeadingOffset,
524+
) -> Self {
525+
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids, heading_offset }
497526
}
498527
}
499528

@@ -530,6 +559,7 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
530559
self.buf.push_front((Event::Html(format!("{} ", sec).into()), 0..0));
531560
}
532561

562+
let level = std::cmp::min(level + (self.heading_offset as u32), MAX_HEADER_LEVEL);
533563
self.buf.push_back((Event::Html(format!("</a></h{}>", level).into()), 0..0));
534564

535565
let start_tags = format!(
@@ -1005,7 +1035,15 @@ impl LangString {
10051035

10061036
impl Markdown<'_> {
10071037
pub fn into_string(self) -> String {
1008-
let Markdown(md, links, mut ids, codes, edition, playground) = self;
1038+
let Markdown {
1039+
content: md,
1040+
links,
1041+
mut ids,
1042+
error_codes: codes,
1043+
edition,
1044+
playground,
1045+
heading_offset,
1046+
} = self;
10091047

10101048
// This is actually common enough to special-case
10111049
if md.is_empty() {
@@ -1026,7 +1064,7 @@ impl Markdown<'_> {
10261064

10271065
let mut s = String::with_capacity(md.len() * 3 / 2);
10281066

1029-
let p = HeadingLinks::new(p, None, &mut ids);
1067+
let p = HeadingLinks::new(p, None, &mut ids, heading_offset);
10301068
let p = Footnotes::new(p);
10311069
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
10321070
let p = TableWrapper::new(p);
@@ -1048,7 +1086,7 @@ impl MarkdownWithToc<'_> {
10481086
let mut toc = TocBuilder::new();
10491087

10501088
{
1051-
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
1089+
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids, HeadingOffset::H1);
10521090
let p = Footnotes::new(p);
10531091
let p = TableWrapper::new(p.map(|(ev, _)| ev));
10541092
let p = CodeBlocks::new(p, codes, edition, playground);
@@ -1077,7 +1115,7 @@ impl MarkdownHtml<'_> {
10771115

10781116
let mut s = String::with_capacity(md.len() * 3 / 2);
10791117

1080-
let p = HeadingLinks::new(p, None, &mut ids);
1118+
let p = HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1);
10811119
let p = Footnotes::new(p);
10821120
let p = TableWrapper::new(p.map(|(ev, _)| ev));
10831121
let p = CodeBlocks::new(p, codes, edition, playground);
@@ -1295,7 +1333,7 @@ crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
12951333
// There's no need to thread an IdMap through to here because
12961334
// the IDs generated aren't going to be emitted anywhere.
12971335
let mut ids = IdMap::new();
1298-
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
1336+
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1));
12991337

13001338
for ev in iter {
13011339
if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {

src/librustdoc/html/markdown/tests.rs

+34-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
2-
use super::{ErrorCodes, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
2+
use super::{ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
33
use rustc_span::edition::{Edition, DEFAULT_EDITION};
44

55
#[test]
@@ -147,74 +147,90 @@ fn test_lang_string_tokenizer() {
147147
fn test_header() {
148148
fn t(input: &str, expect: &str) {
149149
let mut map = IdMap::new();
150-
let output =
151-
Markdown(input, &[], &mut map, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
150+
let output = Markdown {
151+
content: input,
152+
links: &[],
153+
ids: &mut map,
154+
error_codes: ErrorCodes::Yes,
155+
edition: DEFAULT_EDITION,
156+
playground: &None,
157+
heading_offset: HeadingOffset::H2,
158+
}
159+
.into_string();
152160
assert_eq!(output, expect, "original: {}", input);
153161
}
154162

155163
t(
156164
"# Foo bar",
157-
"<h1 id=\"foo-bar\" class=\"section-header\"><a href=\"#foo-bar\">Foo bar</a></h1>",
165+
"<h2 id=\"foo-bar\" class=\"section-header\"><a href=\"#foo-bar\">Foo bar</a></h2>",
158166
);
159167
t(
160168
"## Foo-bar_baz qux",
161-
"<h2 id=\"foo-bar_baz-qux\" class=\"section-header\">\
162-
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>",
169+
"<h3 id=\"foo-bar_baz-qux\" class=\"section-header\">\
170+
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h3>",
163171
);
164172
t(
165173
"### **Foo** *bar* baz!?!& -_qux_-%",
166-
"<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
174+
"<h4 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
167175
<a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
168176
<em>bar</em> baz!?!&amp; -<em>qux</em>-%</a>\
169-
</h3>",
177+
</h4>",
170178
);
171179
t(
172180
"#### **Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
173-
"<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
181+
"<h5 id=\"foo--bar--baz--qux\" class=\"section-header\">\
174182
<a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!* \
175183
<em><code>baz</code></em> ❤ #qux</a>\
176-
</h4>",
184+
</h5>",
177185
);
178186
}
179187

180188
#[test]
181189
fn test_header_ids_multiple_blocks() {
182190
let mut map = IdMap::new();
183191
fn t(map: &mut IdMap, input: &str, expect: &str) {
184-
let output =
185-
Markdown(input, &[], map, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
192+
let output = Markdown {
193+
content: input,
194+
links: &[],
195+
ids: map,
196+
error_codes: ErrorCodes::Yes,
197+
edition: DEFAULT_EDITION,
198+
playground: &None,
199+
heading_offset: HeadingOffset::H2,
200+
}
201+
.into_string();
186202
assert_eq!(output, expect, "original: {}", input);
187203
}
188204

189205
t(
190206
&mut map,
191207
"# Example",
192-
"<h1 id=\"example\" class=\"section-header\"><a href=\"#example\">Example</a></h1>",
208+
"<h2 id=\"example\" class=\"section-header\"><a href=\"#example\">Example</a></h2>",
193209
);
194210
t(
195211
&mut map,
196212
"# Panics",
197-
"<h1 id=\"panics\" class=\"section-header\"><a href=\"#panics\">Panics</a></h1>",
213+
"<h2 id=\"panics\" class=\"section-header\"><a href=\"#panics\">Panics</a></h2>",
198214
);
199215
t(
200216
&mut map,
201217
"# Example",
202-
"<h1 id=\"example-1\" class=\"section-header\"><a href=\"#example-1\">Example</a></h1>",
218+
"<h2 id=\"example-1\" class=\"section-header\"><a href=\"#example-1\">Example</a></h2>",
203219
);
204220
t(
205221
&mut map,
206222
"# Main",
207-
"<h1 id=\"main-1\" class=\"section-header\"><a href=\"#main-1\">Main</a></h1>",
223+
"<h2 id=\"main-1\" class=\"section-header\"><a href=\"#main-1\">Main</a></h2>",
208224
);
209225
t(
210226
&mut map,
211227
"# Example",
212-
"<h1 id=\"example-2\" class=\"section-header\"><a href=\"#example-2\">Example</a></h1>",
228+
"<h2 id=\"example-2\" class=\"section-header\"><a href=\"#example-2\">Example</a></h2>",
213229
);
214230
t(
215231
&mut map,
216232
"# Panics",
217-
"<h1 id=\"panics-1\" class=\"section-header\"><a href=\"#panics-1\">Panics</a></h1>",
233+
"<h2 id=\"panics-1\" class=\"section-header\"><a href=\"#panics-1\">Panics</a></h2>",
218234
);
219235
}
220236

0 commit comments

Comments
 (0)