Skip to content

Commit 2636f8b

Browse files
Implement namespacing for doc comments IDs
1 parent a41a692 commit 2636f8b

File tree

11 files changed

+139
-99
lines changed

11 files changed

+139
-99
lines changed

src/librustdoc/externalfiles.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
1+
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, IdPrefix, Markdown, Playground};
22
use crate::rustc_span::edition::Edition;
33
use std::fs;
44
use std::path::Path;
@@ -47,6 +47,7 @@ impl ExternalHtml {
4747
edition,
4848
playground,
4949
heading_offset: HeadingOffset::H2,
50+
id_prefix: IdPrefix::without_parent(),
5051
}
5152
.into_string()
5253
);
@@ -63,6 +64,7 @@ impl ExternalHtml {
6364
edition,
6465
playground,
6566
heading_offset: HeadingOffset::H2,
67+
id_prefix: IdPrefix::without_parent(),
6668
}
6769
.into_string()
6870
);

src/librustdoc/html/markdown.rs

+83-66
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//! extern crate rustc_span;
99
//!
1010
//! use rustc_span::edition::Edition;
11-
//! use rustdoc::html::markdown::{HeadingOffset, IdMap, Markdown, ErrorCodes};
11+
//! use rustdoc::html::markdown::{HeadingOffset, IdMap, IdPrefix, Markdown, ErrorCodes};
1212
//!
1313
//! let s = "My *markdown* _text_";
1414
//! let mut id_map = IdMap::new();
@@ -20,6 +20,7 @@
2020
//! edition: Edition::Edition2015,
2121
//! playground: &None,
2222
//! heading_offset: HeadingOffset::H2,
23+
//! id_prefix: IdPrefix::none(),
2324
//! };
2425
//! let html = md.into_string();
2526
//! // ... something using html
@@ -40,7 +41,7 @@ use std::fmt::Write;
4041
use std::ops::{ControlFlow, Range};
4142
use std::str;
4243

43-
use crate::clean::RenderedLink;
44+
use crate::clean::{self, RenderedLink};
4445
use crate::doctest;
4546
use crate::html::escape::Escape;
4647
use crate::html::format::Buffer;
@@ -101,6 +102,7 @@ pub struct Markdown<'a> {
101102
/// Offset at which we render headings.
102103
/// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`.
103104
pub heading_offset: HeadingOffset,
105+
pub id_prefix: IdPrefix<'a>,
104106
}
105107
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
106108
crate struct MarkdownWithToc<'a>(
@@ -109,6 +111,7 @@ crate struct MarkdownWithToc<'a>(
109111
crate ErrorCodes,
110112
crate Edition,
111113
crate &'a Option<Playground>,
114+
crate IdPrefix<'a>,
112115
);
113116
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
114117
crate struct MarkdownHtml<'a>(
@@ -117,6 +120,7 @@ crate struct MarkdownHtml<'a>(
117120
crate ErrorCodes,
118121
crate Edition,
119122
crate &'a Option<Playground>,
123+
crate IdPrefix<'a>,
120124
);
121125
/// A tuple struct like `Markdown` that renders only the first paragraph.
122126
crate struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]);
@@ -508,27 +512,36 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for TableWrapper<'a, I> {
508512
type SpannedEvent<'a> = (Event<'a>, Range<usize>);
509513

510514
/// Make headings links with anchor IDs and build up TOC.
511-
struct HeadingLinks<'a, 'b, 'ids, I> {
515+
struct HeadingLinks<'a, 'b, 'ids, 'c, I> {
512516
inner: I,
513517
toc: Option<&'b mut TocBuilder>,
514518
buf: VecDeque<SpannedEvent<'a>>,
515519
id_map: &'ids mut IdMap,
516520
heading_offset: HeadingOffset,
521+
id_prefix: IdPrefix<'c>,
517522
}
518523

519-
impl<'a, 'b, 'ids, I> HeadingLinks<'a, 'b, 'ids, I> {
524+
impl<'a, 'b, 'ids, 'c, I> HeadingLinks<'a, 'b, 'ids, 'c, I> {
520525
fn new(
521526
iter: I,
522527
toc: Option<&'b mut TocBuilder>,
523528
ids: &'ids mut IdMap,
524529
heading_offset: HeadingOffset,
530+
id_prefix: IdPrefix<'c>,
525531
) -> Self {
526-
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids, heading_offset }
532+
HeadingLinks {
533+
inner: iter,
534+
toc,
535+
buf: VecDeque::new(),
536+
id_map: ids,
537+
heading_offset,
538+
id_prefix,
539+
}
527540
}
528541
}
529542

530-
impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
531-
for HeadingLinks<'a, 'b, 'ids, I>
543+
impl<'a, 'b, 'ids, 'c, I: Iterator<Item = SpannedEvent<'a>>> Iterator
544+
for HeadingLinks<'a, 'b, 'ids, 'c, I>
532545
{
533546
type Item = SpannedEvent<'a>;
534547

@@ -551,7 +564,7 @@ impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
551564
_ => self.buf.push_back(event),
552565
}
553566
}
554-
let id = self.id_map.derive(id);
567+
let id = self.id_map.derive(id, self.id_prefix);
555568

556569
if let Some(ref mut builder) = self.toc {
557570
let mut html_header = String::new();
@@ -1044,6 +1057,7 @@ impl Markdown<'_> {
10441057
edition,
10451058
playground,
10461059
heading_offset,
1060+
id_prefix,
10471061
} = self;
10481062

10491063
// This is actually common enough to special-case
@@ -1062,7 +1076,7 @@ impl Markdown<'_> {
10621076

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

1065-
let p = HeadingLinks::new(p, None, &mut ids, heading_offset);
1079+
let p = HeadingLinks::new(p, None, &mut ids, heading_offset, id_prefix);
10661080
let p = Footnotes::new(p);
10671081
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
10681082
let p = TableWrapper::new(p);
@@ -1075,7 +1089,7 @@ impl Markdown<'_> {
10751089

10761090
impl MarkdownWithToc<'_> {
10771091
crate fn into_string(self) -> String {
1078-
let MarkdownWithToc(md, mut ids, codes, edition, playground) = self;
1092+
let MarkdownWithToc(md, mut ids, codes, edition, playground, id_prefix) = self;
10791093

10801094
let p = Parser::new_ext(md, main_body_opts()).into_offset_iter();
10811095

@@ -1084,7 +1098,7 @@ impl MarkdownWithToc<'_> {
10841098
let mut toc = TocBuilder::new();
10851099

10861100
{
1087-
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids, HeadingOffset::H1);
1101+
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids, HeadingOffset::H1, id_prefix);
10881102
let p = Footnotes::new(p);
10891103
let p = TableWrapper::new(p.map(|(ev, _)| ev));
10901104
let p = CodeBlocks::new(p, codes, edition, playground);
@@ -1097,7 +1111,7 @@ impl MarkdownWithToc<'_> {
10971111

10981112
impl MarkdownHtml<'_> {
10991113
crate fn into_string(self) -> String {
1100-
let MarkdownHtml(md, mut ids, codes, edition, playground) = self;
1114+
let MarkdownHtml(md, mut ids, codes, edition, playground, id_prefix) = self;
11011115

11021116
// This is actually common enough to special-case
11031117
if md.is_empty() {
@@ -1113,7 +1127,7 @@ impl MarkdownHtml<'_> {
11131127

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

1116-
let p = HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1);
1130+
let p = HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1, id_prefix);
11171131
let p = Footnotes::new(p);
11181132
let p = TableWrapper::new(p.map(|(ev, _)| ev));
11191133
let p = CodeBlocks::new(p, codes, edition, playground);
@@ -1325,7 +1339,8 @@ crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
13251339
// There's no need to thread an IdMap through to here because
13261340
// the IDs generated aren't going to be emitted anywhere.
13271341
let mut ids = IdMap::new();
1328-
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1));
1342+
let iter =
1343+
Footnotes::new(HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1, IdPrefix::none()));
13291344

13301345
for ev in iter {
13311346
if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {
@@ -1430,68 +1445,70 @@ crate fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<RustCodeB
14301445
code_blocks
14311446
}
14321447

1448+
/// This wrapper exists only to allow the error_index_generator tool to use it.
1449+
#[derive(Debug, Clone, Copy)]
1450+
pub struct IdPrefix<'a>(IdPrefixInner<'a>);
1451+
1452+
impl<'a> IdPrefix<'a> {
1453+
crate fn without_parent() -> Self {
1454+
Self(IdPrefixInner::WithoutParent)
1455+
}
1456+
1457+
pub fn none() -> Self {
1458+
Self(IdPrefixInner::None)
1459+
}
1460+
1461+
crate fn some(item: &'a clean::Item) -> Self {
1462+
Self(IdPrefixInner::Some(item))
1463+
}
1464+
}
1465+
1466+
/// Depending if we're generating a link for a markdown file or on a Rust type, we don't want the
1467+
/// same behaviour:
1468+
///
1469+
/// In Rust, to reduce the number of "conflict" between HTML IDs, we add a prefix to them based on
1470+
/// their parent. However, it is useless if rustdoc is used to render a markdown file.
1471+
#[derive(Debug, Clone, Copy)]
1472+
crate enum IdPrefixInner<'a> {
1473+
/// Contains the "parent" of the ID. It's used to generate the prefix.
1474+
Some(&'a clean::Item),
1475+
/// This is used in case markdown is inserted using `--markdown-before-content` or using
1476+
/// `--markdown-after-content`.
1477+
WithoutParent,
1478+
None,
1479+
}
1480+
14331481
#[derive(Clone, Default, Debug)]
14341482
pub struct IdMap {
14351483
map: FxHashMap<String, usize>,
14361484
}
14371485

1438-
fn init_id_map() -> FxHashMap<String, usize> {
1439-
let mut map = FxHashMap::default();
1440-
// This is the list of IDs used in Javascript.
1441-
map.insert("help".to_owned(), 1);
1442-
// This is the list of IDs used in HTML generated in Rust (including the ones
1443-
// used in tera template files).
1444-
map.insert("mainThemeStyle".to_owned(), 1);
1445-
map.insert("themeStyle".to_owned(), 1);
1446-
map.insert("theme-picker".to_owned(), 1);
1447-
map.insert("theme-choices".to_owned(), 1);
1448-
map.insert("settings-menu".to_owned(), 1);
1449-
map.insert("help-button".to_owned(), 1);
1450-
map.insert("main-content".to_owned(), 1);
1451-
map.insert("search".to_owned(), 1);
1452-
map.insert("crate-search".to_owned(), 1);
1453-
map.insert("render-detail".to_owned(), 1);
1454-
map.insert("toggle-all-docs".to_owned(), 1);
1455-
map.insert("all-types".to_owned(), 1);
1456-
map.insert("default-settings".to_owned(), 1);
1457-
map.insert("rustdoc-vars".to_owned(), 1);
1458-
map.insert("sidebar-vars".to_owned(), 1);
1459-
map.insert("copy-path".to_owned(), 1);
1460-
map.insert("TOC".to_owned(), 1);
1461-
// This is the list of IDs used by rustdoc sections (but still generated by
1462-
// rustdoc).
1463-
map.insert("fields".to_owned(), 1);
1464-
map.insert("variants".to_owned(), 1);
1465-
map.insert("implementors-list".to_owned(), 1);
1466-
map.insert("synthetic-implementors-list".to_owned(), 1);
1467-
map.insert("foreign-impls".to_owned(), 1);
1468-
map.insert("implementations".to_owned(), 1);
1469-
map.insert("trait-implementations".to_owned(), 1);
1470-
map.insert("synthetic-implementations".to_owned(), 1);
1471-
map.insert("blanket-implementations".to_owned(), 1);
1472-
map.insert("associated-types".to_owned(), 1);
1473-
map.insert("associated-const".to_owned(), 1);
1474-
map.insert("required-methods".to_owned(), 1);
1475-
map.insert("provided-methods".to_owned(), 1);
1476-
map.insert("implementors".to_owned(), 1);
1477-
map.insert("synthetic-implementors".to_owned(), 1);
1478-
map.insert("trait-implementations-list".to_owned(), 1);
1479-
map.insert("synthetic-implementations-list".to_owned(), 1);
1480-
map.insert("blanket-implementations-list".to_owned(), 1);
1481-
map.insert("deref-methods".to_owned(), 1);
1482-
map
1483-
}
1484-
14851486
impl IdMap {
14861487
pub fn new() -> Self {
1487-
IdMap { map: init_id_map() }
1488+
IdMap { map: FxHashMap::default() }
14881489
}
14891490

1490-
crate fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
1491-
let id = match self.map.get_mut(candidate.as_ref()) {
1492-
None => candidate.to_string(),
1491+
crate fn derive<S: AsRef<str> + ToString>(
1492+
&mut self,
1493+
candidate: S,
1494+
id_prefix: IdPrefix<'_>,
1495+
) -> String {
1496+
let candidate = match id_prefix.0 {
1497+
IdPrefixInner::None => candidate.to_string(),
1498+
IdPrefixInner::WithoutParent => format!("top.{}", candidate.as_ref()),
1499+
IdPrefixInner::Some(item) => {
1500+
let prefix = if let Some(name) = item.name {
1501+
format!("{}.{}", item.type_(), name)
1502+
} else {
1503+
format!("{}.unknown", item.type_())
1504+
};
1505+
format!("{}.{}", prefix, candidate.as_ref())
1506+
}
1507+
};
1508+
let id = match self.map.get_mut(&candidate) {
1509+
None => candidate,
14931510
Some(a) => {
1494-
let id = format!("{}-{}", candidate.as_ref(), *a);
1511+
let id = format!("{}-{}", candidate, *a);
14951512
*a += 1;
14961513
id
14971514
}

src/librustdoc/html/markdown/tests.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
2-
use super::{ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
2+
use super::{
3+
ErrorCodes, HeadingOffset, IdMap, IdPrefix, Ignore, LangString, Markdown, MarkdownHtml,
4+
};
35
use rustc_span::edition::{Edition, DEFAULT_EDITION};
46

57
#[test]
@@ -28,8 +30,8 @@ fn test_unique_id() {
2830
"examples-2",
2931
"method.into_iter-1",
3032
"foo-1",
31-
"main-content-1",
32-
"search-1",
33+
"main-content",
34+
"search",
3335
"methods",
3436
"examples-3",
3537
"method.into_iter-2",
@@ -38,7 +40,8 @@ fn test_unique_id() {
3840
];
3941

4042
let mut map = IdMap::new();
41-
let actual: Vec<String> = input.iter().map(|s| map.derive(s.to_string())).collect();
43+
let actual: Vec<String> =
44+
input.iter().map(|s| map.derive(s.to_string(), IdPrefix::none())).collect();
4245
assert_eq!(&actual[..], expected);
4346
}
4447

@@ -155,6 +158,7 @@ fn test_header() {
155158
edition: DEFAULT_EDITION,
156159
playground: &None,
157160
heading_offset: HeadingOffset::H2,
161+
id_prefix: IdPrefix::none(),
158162
}
159163
.into_string();
160164
assert_eq!(output, expect, "original: {}", input);
@@ -197,6 +201,7 @@ fn test_header_ids_multiple_blocks() {
197201
edition: DEFAULT_EDITION,
198202
playground: &None,
199203
heading_offset: HeadingOffset::H2,
204+
id_prefix: IdPrefix::none(),
200205
}
201206
.into_string();
202207
assert_eq!(output, expect, "original: {}", input);
@@ -220,7 +225,7 @@ fn test_header_ids_multiple_blocks() {
220225
t(
221226
&mut map,
222227
"# Search",
223-
"<h2 id=\"search-1\" class=\"section-header\"><a href=\"#search-1\">Search</a></h2>",
228+
"<h2 id=\"search\" class=\"section-header\"><a href=\"#search\">Search</a></h2>",
224229
);
225230
t(
226231
&mut map,
@@ -307,8 +312,15 @@ fn test_plain_text_summary() {
307312
fn test_markdown_html_escape() {
308313
fn t(input: &str, expect: &str) {
309314
let mut idmap = IdMap::new();
310-
let output =
311-
MarkdownHtml(input, &mut idmap, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
315+
let output = MarkdownHtml(
316+
input,
317+
&mut idmap,
318+
ErrorCodes::Yes,
319+
DEFAULT_EDITION,
320+
&None,
321+
IdPrefix::none(),
322+
)
323+
.into_string();
312324
assert_eq!(output, expect, "original: {}", input);
313325
}
314326

src/librustdoc/html/render/context.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::formats::item_type::ItemType;
3131
use crate::formats::FormatRenderer;
3232
use crate::html::escape::Escape;
3333
use crate::html::format::Buffer;
34-
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
34+
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap, IdPrefix};
3535
use crate::html::{layout, sources};
3636
use crate::scrape_examples::AllCallLocations;
3737
use crate::try_err;
@@ -168,7 +168,7 @@ impl<'tcx> Context<'tcx> {
168168

169169
pub(super) fn derive_id(&self, id: String) -> String {
170170
let mut map = self.id_map.borrow_mut();
171-
map.derive(id)
171+
map.derive(id, IdPrefix::none())
172172
}
173173

174174
/// String representation of how to get back to the root path of the 'doc/'

0 commit comments

Comments
 (0)