Skip to content

Commit

Permalink
Implement namespacing for doc comments IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Dec 19, 2021
1 parent a41a692 commit 2636f8b
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 99 deletions.
4 changes: 3 additions & 1 deletion src/librustdoc/externalfiles.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, IdPrefix, Markdown, Playground};
use crate::rustc_span::edition::Edition;
use std::fs;
use std::path::Path;
Expand Down Expand Up @@ -47,6 +47,7 @@ impl ExternalHtml {
edition,
playground,
heading_offset: HeadingOffset::H2,
id_prefix: IdPrefix::without_parent(),
}
.into_string()
);
Expand All @@ -63,6 +64,7 @@ impl ExternalHtml {
edition,
playground,
heading_offset: HeadingOffset::H2,
id_prefix: IdPrefix::without_parent(),
}
.into_string()
);
Expand Down
149 changes: 83 additions & 66 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! extern crate rustc_span;
//!
//! use rustc_span::edition::Edition;
//! use rustdoc::html::markdown::{HeadingOffset, IdMap, Markdown, ErrorCodes};
//! use rustdoc::html::markdown::{HeadingOffset, IdMap, IdPrefix, Markdown, ErrorCodes};
//!
//! let s = "My *markdown* _text_";
//! let mut id_map = IdMap::new();
Expand All @@ -20,6 +20,7 @@
//! edition: Edition::Edition2015,
//! playground: &None,
//! heading_offset: HeadingOffset::H2,
//! id_prefix: IdPrefix::none(),
//! };
//! let html = md.into_string();
//! // ... something using html
Expand All @@ -40,7 +41,7 @@ use std::fmt::Write;
use std::ops::{ControlFlow, Range};
use std::str;

use crate::clean::RenderedLink;
use crate::clean::{self, RenderedLink};
use crate::doctest;
use crate::html::escape::Escape;
use crate::html::format::Buffer;
Expand Down Expand Up @@ -101,6 +102,7 @@ pub struct Markdown<'a> {
/// Offset at which we render headings.
/// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`.
pub heading_offset: HeadingOffset,
pub id_prefix: IdPrefix<'a>,
}
/// A tuple struct like `Markdown` that renders the markdown with a table of contents.
crate struct MarkdownWithToc<'a>(
Expand All @@ -109,6 +111,7 @@ crate struct MarkdownWithToc<'a>(
crate ErrorCodes,
crate Edition,
crate &'a Option<Playground>,
crate IdPrefix<'a>,
);
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
crate struct MarkdownHtml<'a>(
Expand All @@ -117,6 +120,7 @@ crate struct MarkdownHtml<'a>(
crate ErrorCodes,
crate Edition,
crate &'a Option<Playground>,
crate IdPrefix<'a>,
);
/// A tuple struct like `Markdown` that renders only the first paragraph.
crate struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]);
Expand Down Expand Up @@ -508,27 +512,36 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for TableWrapper<'a, I> {
type SpannedEvent<'a> = (Event<'a>, Range<usize>);

/// Make headings links with anchor IDs and build up TOC.
struct HeadingLinks<'a, 'b, 'ids, I> {
struct HeadingLinks<'a, 'b, 'ids, 'c, I> {
inner: I,
toc: Option<&'b mut TocBuilder>,
buf: VecDeque<SpannedEvent<'a>>,
id_map: &'ids mut IdMap,
heading_offset: HeadingOffset,
id_prefix: IdPrefix<'c>,
}

impl<'a, 'b, 'ids, I> HeadingLinks<'a, 'b, 'ids, I> {
impl<'a, 'b, 'ids, 'c, I> HeadingLinks<'a, 'b, 'ids, 'c, I> {
fn new(
iter: I,
toc: Option<&'b mut TocBuilder>,
ids: &'ids mut IdMap,
heading_offset: HeadingOffset,
id_prefix: IdPrefix<'c>,
) -> Self {
HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids, heading_offset }
HeadingLinks {
inner: iter,
toc,
buf: VecDeque::new(),
id_map: ids,
heading_offset,
id_prefix,
}
}
}

impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
for HeadingLinks<'a, 'b, 'ids, I>
impl<'a, 'b, 'ids, 'c, I: Iterator<Item = SpannedEvent<'a>>> Iterator
for HeadingLinks<'a, 'b, 'ids, 'c, I>
{
type Item = SpannedEvent<'a>;

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

if let Some(ref mut builder) = self.toc {
let mut html_header = String::new();
Expand Down Expand Up @@ -1044,6 +1057,7 @@ impl Markdown<'_> {
edition,
playground,
heading_offset,
id_prefix,
} = self;

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

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

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

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

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

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

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

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

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

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

let p = HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1);
let p = HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1, id_prefix);
let p = Footnotes::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = CodeBlocks::new(p, codes, edition, playground);
Expand Down Expand Up @@ -1325,7 +1339,8 @@ 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, HeadingOffset::H1));
let iter =
Footnotes::new(HeadingLinks::new(p, None, &mut ids, HeadingOffset::H1, IdPrefix::none()));

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

/// This wrapper exists only to allow the error_index_generator tool to use it.
#[derive(Debug, Clone, Copy)]
pub struct IdPrefix<'a>(IdPrefixInner<'a>);

impl<'a> IdPrefix<'a> {
crate fn without_parent() -> Self {
Self(IdPrefixInner::WithoutParent)
}

pub fn none() -> Self {
Self(IdPrefixInner::None)
}

crate fn some(item: &'a clean::Item) -> Self {
Self(IdPrefixInner::Some(item))
}
}

/// Depending if we're generating a link for a markdown file or on a Rust type, we don't want the
/// same behaviour:
///
/// In Rust, to reduce the number of "conflict" between HTML IDs, we add a prefix to them based on
/// their parent. However, it is useless if rustdoc is used to render a markdown file.
#[derive(Debug, Clone, Copy)]
crate enum IdPrefixInner<'a> {
/// Contains the "parent" of the ID. It's used to generate the prefix.
Some(&'a clean::Item),
/// This is used in case markdown is inserted using `--markdown-before-content` or using
/// `--markdown-after-content`.
WithoutParent,
None,
}

#[derive(Clone, Default, Debug)]
pub struct IdMap {
map: FxHashMap<String, usize>,
}

fn init_id_map() -> FxHashMap<String, usize> {
let mut map = FxHashMap::default();
// This is the list of IDs used in Javascript.
map.insert("help".to_owned(), 1);
// This is the list of IDs used in HTML generated in Rust (including the ones
// used in tera template files).
map.insert("mainThemeStyle".to_owned(), 1);
map.insert("themeStyle".to_owned(), 1);
map.insert("theme-picker".to_owned(), 1);
map.insert("theme-choices".to_owned(), 1);
map.insert("settings-menu".to_owned(), 1);
map.insert("help-button".to_owned(), 1);
map.insert("main-content".to_owned(), 1);
map.insert("search".to_owned(), 1);
map.insert("crate-search".to_owned(), 1);
map.insert("render-detail".to_owned(), 1);
map.insert("toggle-all-docs".to_owned(), 1);
map.insert("all-types".to_owned(), 1);
map.insert("default-settings".to_owned(), 1);
map.insert("rustdoc-vars".to_owned(), 1);
map.insert("sidebar-vars".to_owned(), 1);
map.insert("copy-path".to_owned(), 1);
map.insert("TOC".to_owned(), 1);
// This is the list of IDs used by rustdoc sections (but still generated by
// rustdoc).
map.insert("fields".to_owned(), 1);
map.insert("variants".to_owned(), 1);
map.insert("implementors-list".to_owned(), 1);
map.insert("synthetic-implementors-list".to_owned(), 1);
map.insert("foreign-impls".to_owned(), 1);
map.insert("implementations".to_owned(), 1);
map.insert("trait-implementations".to_owned(), 1);
map.insert("synthetic-implementations".to_owned(), 1);
map.insert("blanket-implementations".to_owned(), 1);
map.insert("associated-types".to_owned(), 1);
map.insert("associated-const".to_owned(), 1);
map.insert("required-methods".to_owned(), 1);
map.insert("provided-methods".to_owned(), 1);
map.insert("implementors".to_owned(), 1);
map.insert("synthetic-implementors".to_owned(), 1);
map.insert("trait-implementations-list".to_owned(), 1);
map.insert("synthetic-implementations-list".to_owned(), 1);
map.insert("blanket-implementations-list".to_owned(), 1);
map.insert("deref-methods".to_owned(), 1);
map
}

impl IdMap {
pub fn new() -> Self {
IdMap { map: init_id_map() }
IdMap { map: FxHashMap::default() }
}

crate fn derive<S: AsRef<str> + ToString>(&mut self, candidate: S) -> String {
let id = match self.map.get_mut(candidate.as_ref()) {
None => candidate.to_string(),
crate fn derive<S: AsRef<str> + ToString>(
&mut self,
candidate: S,
id_prefix: IdPrefix<'_>,
) -> String {
let candidate = match id_prefix.0 {
IdPrefixInner::None => candidate.to_string(),
IdPrefixInner::WithoutParent => format!("top.{}", candidate.as_ref()),
IdPrefixInner::Some(item) => {
let prefix = if let Some(name) = item.name {
format!("{}.{}", item.type_(), name)
} else {
format!("{}.unknown", item.type_())
};
format!("{}.{}", prefix, candidate.as_ref())
}
};
let id = match self.map.get_mut(&candidate) {
None => candidate,
Some(a) => {
let id = format!("{}-{}", candidate.as_ref(), *a);
let id = format!("{}-{}", candidate, *a);
*a += 1;
id
}
Expand Down
26 changes: 19 additions & 7 deletions src/librustdoc/html/markdown/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
use super::{ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownHtml};
use super::{
ErrorCodes, HeadingOffset, IdMap, IdPrefix, Ignore, LangString, Markdown, MarkdownHtml,
};
use rustc_span::edition::{Edition, DEFAULT_EDITION};

#[test]
Expand Down Expand Up @@ -28,8 +30,8 @@ fn test_unique_id() {
"examples-2",
"method.into_iter-1",
"foo-1",
"main-content-1",
"search-1",
"main-content",
"search",
"methods",
"examples-3",
"method.into_iter-2",
Expand All @@ -38,7 +40,8 @@ fn test_unique_id() {
];

let mut map = IdMap::new();
let actual: Vec<String> = input.iter().map(|s| map.derive(s.to_string())).collect();
let actual: Vec<String> =
input.iter().map(|s| map.derive(s.to_string(), IdPrefix::none())).collect();
assert_eq!(&actual[..], expected);
}

Expand Down Expand Up @@ -155,6 +158,7 @@ fn test_header() {
edition: DEFAULT_EDITION,
playground: &None,
heading_offset: HeadingOffset::H2,
id_prefix: IdPrefix::none(),
}
.into_string();
assert_eq!(output, expect, "original: {}", input);
Expand Down Expand Up @@ -197,6 +201,7 @@ fn test_header_ids_multiple_blocks() {
edition: DEFAULT_EDITION,
playground: &None,
heading_offset: HeadingOffset::H2,
id_prefix: IdPrefix::none(),
}
.into_string();
assert_eq!(output, expect, "original: {}", input);
Expand All @@ -220,7 +225,7 @@ fn test_header_ids_multiple_blocks() {
t(
&mut map,
"# Search",
"<h2 id=\"search-1\" class=\"section-header\"><a href=\"#search-1\">Search</a></h2>",
"<h2 id=\"search\" class=\"section-header\"><a href=\"#search\">Search</a></h2>",
);
t(
&mut map,
Expand Down Expand Up @@ -307,8 +312,15 @@ fn test_plain_text_summary() {
fn test_markdown_html_escape() {
fn t(input: &str, expect: &str) {
let mut idmap = IdMap::new();
let output =
MarkdownHtml(input, &mut idmap, ErrorCodes::Yes, DEFAULT_EDITION, &None).into_string();
let output = MarkdownHtml(
input,
&mut idmap,
ErrorCodes::Yes,
DEFAULT_EDITION,
&None,
IdPrefix::none(),
)
.into_string();
assert_eq!(output, expect, "original: {}", input);
}

Expand Down
4 changes: 2 additions & 2 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::formats::item_type::ItemType;
use crate::formats::FormatRenderer;
use crate::html::escape::Escape;
use crate::html::format::Buffer;
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap, IdPrefix};
use crate::html::{layout, sources};
use crate::scrape_examples::AllCallLocations;
use crate::try_err;
Expand Down Expand Up @@ -168,7 +168,7 @@ impl<'tcx> Context<'tcx> {

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

/// String representation of how to get back to the root path of the 'doc/'
Expand Down
Loading

0 comments on commit 2636f8b

Please sign in to comment.