librustdoc: Use correct heading levels.

- Avoid multiple <h1>s on a page.
- The <h#> tags should follow a semantic hierarchy.
- Cap at h6 (no h7)
This commit is contained in:
Mukund Lakshman 2021-10-01 06:17:15 -04:00
parent e737694a4d
commit a8a40ea9a4
65 changed files with 147 additions and 118 deletions

View file

@ -12,7 +12,7 @@
//!
//! let s = "My *markdown* _text_";
//! let mut id_map = IdMap::new();
//! let md = Markdown(s, &[], &mut id_map, ErrorCodes::Yes, Edition::Edition2015, &None);
//! let md = Markdown(s, &[], &mut id_map, ErrorCodes::Yes, Edition::Edition2015, &None, 0);
//! let html = md.into_string();
//! // ... something using html
//! ```
@ -47,6 +47,8 @@ use pulldown_cmark::{
#[cfg(test)]
mod tests;
const MAX_HEADER_LEVEL: u32 = 6;
/// Options for rendering Markdown in the main body of documentation.
pub(crate) fn main_body_opts() -> Options {
Options::ENABLE_TABLES
@ -78,6 +80,7 @@ pub struct Markdown<'a>(
/// Default edition to use when parsing doctests (to add a `fn main`).
pub Edition,
pub &'a Option<Playground>,
pub u32,
);
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
crate struct MarkdownWithToc<'a>(
@ -489,11 +492,12 @@ struct HeadingLinks<'a, 'b, 'ids, I> {
toc: Option<&'b mut TocBuilder>,
buf: VecDeque<SpannedEvent<'a>>,
id_map: &'ids mut IdMap,
level: u32,
}
impl<'a, 'b, 'ids, I> HeadingLinks<'a, 'b, 'ids, I> {
fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap) -> Self {
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids }
fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap, level: u32) -> Self {
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids, level }
}
}
@ -530,6 +534,7 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
self.buf.push_front((Event::Html(format!("{} ", sec).into()), 0..0));
}
let level = std::cmp::min(level + self.level + 1, MAX_HEADER_LEVEL);
self.buf.push_back((Event::Html(format!("</a></h{}>", level).into()), 0..0));
let start_tags = format!(
@ -1005,7 +1010,7 @@ impl LangString {
impl Markdown<'_> {
pub fn into_string(self) -> String {
let Markdown(md, links, mut ids, codes, edition, playground) = self;
let Markdown(md, links, mut ids, codes, edition, playground, level) = self;
// This is actually common enough to special-case
if md.is_empty() {
@ -1026,7 +1031,7 @@ impl Markdown<'_> {
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
let p = HeadingLinks::new(p, None, &mut ids, level);
let p = Footnotes::new(p);
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
let p = TableWrapper::new(p);
@ -1048,7 +1053,7 @@ impl MarkdownWithToc<'_> {
let mut toc = TocBuilder::new();
{
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids, 0);
let p = Footnotes::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = CodeBlocks::new(p, codes, edition, playground);
@ -1077,7 +1082,7 @@ impl MarkdownHtml<'_> {
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
let p = HeadingLinks::new(p, None, &mut ids, 0);
let p = Footnotes::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = CodeBlocks::new(p, codes, edition, playground);
@ -1295,7 +1300,7 @@ crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
// There's no need to thread an IdMap through to here because
// the IDs generated aren't going to be emitted anywhere.
let mut ids = IdMap::new();
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids, 0));
for ev in iter {
if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {

View file

@ -147,33 +147,33 @@ fn test_lang_string_tokenizer() {
fn test_header() {
fn t(input: &str, expect: &str) {
let mut map = IdMap::new();
let output =
Markdown(input, &[], &mut map, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
let output = Markdown(input, &[], &mut map, ErrorCodes::Yes, DEFAULT_EDITION, &None, 0)
.into_string();
assert_eq!(output, expect, "original: {}", input);
}
t(
"# Foo bar",
"<h1 id=\"foo-bar\" class=\"section-header\"><a href=\"#foo-bar\">Foo bar</a></h1>",
"<h2 id=\"foo-bar\" class=\"section-header\"><a href=\"#foo-bar\">Foo bar</a></h2>",
);
t(
"## Foo-bar_baz qux",
"<h2 id=\"foo-bar_baz-qux\" class=\"section-header\">\
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h2>",
"<h3 id=\"foo-bar_baz-qux\" class=\"section-header\">\
<a href=\"#foo-bar_baz-qux\">Foo-bar_baz qux</a></h3>",
);
t(
"### **Foo** *bar* baz!?!& -_qux_-%",
"<h3 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
"<h4 id=\"foo-bar-baz--qux-\" class=\"section-header\">\
<a href=\"#foo-bar-baz--qux-\"><strong>Foo</strong> \
<em>bar</em> baz!?!&amp; -<em>qux</em>-%</a>\
</h3>",
</h4>",
);
t(
"#### **Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
"<h4 id=\"foo--bar--baz--qux\" class=\"section-header\">\
"<h5 id=\"foo--bar--baz--qux\" class=\"section-header\">\
<a href=\"#foo--bar--baz--qux\"><strong>Foo?</strong> &amp; *bar?!* \
<em><code>baz</code></em> #qux</a>\
</h4>",
</h5>",
);
}
@ -182,39 +182,39 @@ fn test_header_ids_multiple_blocks() {
let mut map = IdMap::new();
fn t(map: &mut IdMap, input: &str, expect: &str) {
let output =
Markdown(input, &[], map, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
Markdown(input, &[], map, ErrorCodes::Yes, DEFAULT_EDITION, &None, 0).into_string();
assert_eq!(output, expect, "original: {}", input);
}
t(
&mut map,
"# Example",
"<h1 id=\"example\" class=\"section-header\"><a href=\"#example\">Example</a></h1>",
"<h2 id=\"example\" class=\"section-header\"><a href=\"#example\">Example</a></h2>",
);
t(
&mut map,
"# Panics",
"<h1 id=\"panics\" class=\"section-header\"><a href=\"#panics\">Panics</a></h1>",
"<h2 id=\"panics\" class=\"section-header\"><a href=\"#panics\">Panics</a></h2>",
);
t(
&mut map,
"# Example",
"<h1 id=\"example-1\" class=\"section-header\"><a href=\"#example-1\">Example</a></h1>",
"<h2 id=\"example-1\" class=\"section-header\"><a href=\"#example-1\">Example</a></h2>",
);
t(
&mut map,
"# Main",
"<h1 id=\"main-1\" class=\"section-header\"><a href=\"#main-1\">Main</a></h1>",
"<h2 id=\"main-1\" class=\"section-header\"><a href=\"#main-1\">Main</a></h2>",
);
t(
&mut map,
"# Example",
"<h1 id=\"example-2\" class=\"section-header\"><a href=\"#example-2\">Example</a></h1>",
"<h2 id=\"example-2\" class=\"section-header\"><a href=\"#example-2\">Example</a></h2>",
);
t(
&mut map,
"# Panics",
"<h1 id=\"panics-1\" class=\"section-header\"><a href=\"#panics-1\">Panics</a></h1>",
"<h2 id=\"panics-1\" class=\"section-header\"><a href=\"#panics-1\">Panics</a></h2>",
);
}

View file

@ -471,19 +471,35 @@ fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<Strin
}
fn document(w: &mut Buffer, cx: &Context<'_>, item: &clean::Item, parent: Option<&clean::Item>) {
document_at_level(w, cx, item, parent, 0)
}
fn document_at_level(
w: &mut Buffer,
cx: &Context<'_>,
item: &clean::Item,
parent: Option<&clean::Item>,
level: u32,
) {
if let Some(ref name) = item.name {
info!("Documenting {}", name);
}
document_item_info(w, cx, item, parent);
if parent.is_none() {
document_full_collapsible(w, item, cx);
document_full_collapsible(w, item, cx, level);
} else {
document_full(w, item, cx);
document_full(w, item, cx, level);
}
}
/// Render md_text as markdown.
fn render_markdown(w: &mut Buffer, cx: &Context<'_>, md_text: &str, links: Vec<RenderedLink>) {
fn render_markdown(
w: &mut Buffer,
cx: &Context<'_>,
md_text: &str,
links: Vec<RenderedLink>,
level: u32,
) {
let mut ids = cx.id_map.borrow_mut();
write!(
w,
@ -494,7 +510,8 @@ fn render_markdown(w: &mut Buffer, cx: &Context<'_>, md_text: &str, links: Vec<R
&mut ids,
cx.shared.codes,
cx.shared.edition(),
&cx.shared.playground
&cx.shared.playground,
level
)
.into_string()
)
@ -531,15 +548,21 @@ fn document_short(
}
}
fn document_full_collapsible(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>) {
document_full_inner(w, item, cx, true);
fn document_full_collapsible(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, level: u32) {
document_full_inner(w, item, cx, true, level);
}
fn document_full(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>) {
document_full_inner(w, item, cx, false);
fn document_full(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, level: u32) {
document_full_inner(w, item, cx, false, level);
}
fn document_full_inner(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, is_collapsible: bool) {
fn document_full_inner(
w: &mut Buffer,
item: &clean::Item,
cx: &Context<'_>,
is_collapsible: bool,
level: u32,
) {
if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) {
debug!("Doc block: =====\n{}\n=====", s);
if is_collapsible {
@ -549,10 +572,10 @@ fn document_full_inner(w: &mut Buffer, item: &clean::Item, cx: &Context<'_>, is_
<span>Expand description</span>\
</summary>",
);
render_markdown(w, cx, &s, item.links(cx));
render_markdown(w, cx, &s, item.links(cx), level);
w.write_str("</details>");
} else {
render_markdown(w, cx, &s, item.links(cx));
render_markdown(w, cx, &s, item.links(cx), level);
}
}
}
@ -1321,7 +1344,7 @@ fn render_impl(
// because impls can't have a stability.
if item.doc_value().is_some() {
document_item_info(&mut info_buffer, cx, it, Some(parent));
document_full(&mut doc_buffer, item, cx);
document_full(&mut doc_buffer, item, cx, 0);
short_documented = false;
} else {
// In case the item isn't documented,
@ -1339,7 +1362,7 @@ fn render_impl(
} else {
document_item_info(&mut info_buffer, cx, item, Some(parent));
if rendering_params.show_def_docs {
document_full(&mut doc_buffer, item, cx);
document_full(&mut doc_buffer, item, cx, 3);
short_documented = false;
}
}
@ -1579,7 +1602,8 @@ fn render_impl(
&mut ids,
cx.shared.codes,
cx.shared.edition(),
&cx.shared.playground
&cx.shared.playground,
0
)
.into_string()
);

View file

@ -16,10 +16,10 @@ use rustc_span::symbol::{kw, sym, Symbol};
use rustc_target::abi::{Layout, Primitive, TagEncoding, Variants};
use super::{
collect_paths_for_type, document, ensure_trailing_slash, item_ty_to_strs, notable_traits_decl,
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
render_impl, render_stability_since_raw, write_srclink, AssocItemLink, Context,
ImplRenderingParameters,
collect_paths_for_type, document, document_at_level, ensure_trailing_slash, item_ty_to_strs,
notable_traits_decl, render_assoc_item, render_assoc_items, render_attributes_in_code,
render_attributes_in_pre, render_impl, render_stability_since_raw, write_srclink,
AssocItemLink, Context, ImplRenderingParameters,
};
use crate::clean::{self, GetDefId};
use crate::formats::item_type::ItemType;
@ -626,7 +626,7 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra
let item_type = m.type_();
let id = cx.derive_id(format!("{}.{}", item_type, name));
let mut content = Buffer::empty_from(w);
document(&mut content, cx, m, Some(t));
document_at_level(&mut content, cx, m, Some(t), 3);
let toggled = !content.is_empty();
if toggled {
write!(w, "<details class=\"rustdoc-toggle\" open><summary>");

View file

@ -126,7 +126,7 @@ h2 {
h3 {
font-size: 1.3em;
}
h1, h2, h3, h4 {
h1, h2, h3, h4, h5, h6 {
font-weight: 500;
margin: 20px 0 15px 0;
padding-bottom: 6px;
@ -179,7 +179,7 @@ div.impl-items > div {
padding-left: 0;
}
h1, h2, h3, h4,
h1, h2, h3, h4, h5, h6,
.sidebar, a.source, .search-input, .search-results .result-name,
.content table td:first-child > a,
.item-left > a,
@ -501,21 +501,20 @@ nav.sub {
white-space: pre-wrap;
}
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 {
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5, .docblock h6 {
border-bottom: 1px solid;
}
.top-doc .docblock h1 { font-size: 1.3em; }
.top-doc .docblock h2 { font-size: 1.15em; }
.top-doc .docblock h3,
.top-doc .docblock h2 { font-size: 1.3em; }
.top-doc .docblock h3 { font-size: 1.15em; }
.top-doc .docblock h4,
.top-doc .docblock h5 {
.top-doc .docblock h5,
.top-doc .docblock h6 {
font-size: 1em;
}
.docblock h1 { font-size: 1em; }
.docblock h2 { font-size: 0.95em; }
.docblock h3, .docblock h4, .docblock h5 { font-size: 0.9em; }
.docblock h5 { font-size: 1em; }
.docblock h6 { font-size: 0.95em; }
.docblock {
margin-left: 24px;

View file

@ -136,7 +136,7 @@ pre, .rustdoc.source .example-wrap {
border-right: 1px solid #ffb44c;
}
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 {
.docblock h2, .docblock h3, .docblock h4, .docblock h5, .docblock h6 {
border-bottom-color: #5c6773;
}

View file

@ -93,7 +93,7 @@ pre, .rustdoc.source .example-wrap {
background-color: #0a042f !important;
}
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 {
.docblock h2, .docblock h3, .docblock h4, .docblock h5, .docblock h6 {
border-bottom-color: #DDD;
}

View file

@ -93,7 +93,7 @@ pre, .rustdoc.source .example-wrap {
background-color: #f6fdb0 !important;
}
.docblock h1, .docblock h2, .docblock h3, .docblock h4, .docblock h5 {
.docblock h2, .docblock h3, .docblock h4, .docblock h5, .docblock h6 {
border-bottom-color: #ddd;
}