Skip to content

Commit 7bea518

Browse files
Remove global derive_id and reset_ids functions
Previously these functions relied on TLS but we can instead thread the relevant state through explicitly.
1 parent 2216db9 commit 7bea518

File tree

6 files changed

+190
-153
lines changed

6 files changed

+190
-153
lines changed

src/librustdoc/externalfiles.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use std::path::Path;
1313
use std::str;
1414
use errors;
1515
use syntax::feature_gate::UnstableFeatures;
16-
use html::markdown::{ErrorCodes, Markdown};
16+
use html::markdown::{IdMap, ErrorCodes, Markdown};
17+
use std::cell::RefCell;
1718

1819
#[derive(Clone)]
1920
pub struct ExternalHtml {
@@ -30,7 +31,8 @@ pub struct ExternalHtml {
3031

3132
impl ExternalHtml {
3233
pub fn load(in_header: &[String], before_content: &[String], after_content: &[String],
33-
md_before_content: &[String], md_after_content: &[String], diag: &errors::Handler)
34+
md_before_content: &[String], md_after_content: &[String], diag: &errors::Handler,
35+
id_map: &mut IdMap)
3436
-> Option<ExternalHtml> {
3537
let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
3638
load_external_files(in_header, diag)
@@ -40,15 +42,17 @@ impl ExternalHtml {
4042
)
4143
.and_then(|(ih, bc)|
4244
load_external_files(md_before_content, diag)
43-
.map(|m_bc| (ih, format!("{}{}", bc, Markdown(&m_bc, &[], codes))))
45+
.map(|m_bc| (ih,
46+
format!("{}{}", bc, Markdown(&m_bc, &[], RefCell::new(id_map), codes))))
4447
)
4548
.and_then(|(ih, bc)|
4649
load_external_files(after_content, diag)
4750
.map(|ac| (ih, bc, ac))
4851
)
4952
.and_then(|(ih, bc, ac)|
5053
load_external_files(md_after_content, diag)
51-
.map(|m_ac| (ih, bc, format!("{}{}", ac, Markdown(&m_ac, &[], codes))))
54+
.map(|m_ac| (ih, bc,
55+
format!("{}{}", ac, Markdown(&m_ac, &[], RefCell::new(id_map), codes))))
5256
)
5357
.map(|(ih, bc, ac)|
5458
ExternalHtml {

src/librustdoc/html/markdown.rs

Lines changed: 105 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
//! ```
1919
//! #![feature(rustc_private)]
2020
//!
21-
//! use rustdoc::html::markdown::{Markdown, ErrorCodes};
21+
//! use rustdoc::html::markdown::{IdMap, Markdown, ErrorCodes};
22+
//! use std::cell::RefCell;
2223
//!
2324
//! let s = "My *markdown* _text_";
24-
//! let html = format!("{}", Markdown(s, &[], ErrorCodes::Yes));
25+
//! let mut id_map = IdMap::new();
26+
//! let html = format!("{}", Markdown(s, &[], RefCell::new(&mut id_map), ErrorCodes::Yes));
2527
//! // ... something using html
2628
//! ```
2729
@@ -35,7 +37,6 @@ use std::borrow::Cow;
3537
use std::ops::Range;
3638
use std::str;
3739

38-
use html::render::derive_id;
3940
use html::toc::TocBuilder;
4041
use html::highlight;
4142
use test;
@@ -47,12 +48,13 @@ use pulldown_cmark::{Options, OPTION_ENABLE_FOOTNOTES, OPTION_ENABLE_TABLES};
4748
/// formatted, this struct will emit the HTML corresponding to the rendered
4849
/// version of the contained markdown string.
4950
/// The second parameter is a list of link replacements
50-
pub struct Markdown<'a>(pub &'a str, pub &'a [(String, String)], pub ErrorCodes);
51+
pub struct Markdown<'a>(
52+
pub &'a str, pub &'a [(String, String)], pub RefCell<&'a mut IdMap>, pub ErrorCodes);
5153
/// A unit struct like `Markdown`, that renders the markdown with a
5254
/// table of contents.
53-
pub struct MarkdownWithToc<'a>(pub &'a str, pub ErrorCodes);
55+
pub struct MarkdownWithToc<'a>(pub &'a str, pub RefCell<&'a mut IdMap>, pub ErrorCodes);
5456
/// A unit struct like `Markdown`, that renders the markdown escaping HTML tags.
55-
pub struct MarkdownHtml<'a>(pub &'a str, pub ErrorCodes);
57+
pub struct MarkdownHtml<'a>(pub &'a str, pub RefCell<&'a mut IdMap>, pub ErrorCodes);
5658
/// A unit struct like `Markdown`, that renders only the first paragraph.
5759
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
5860

@@ -287,23 +289,25 @@ impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I>
287289
}
288290

289291
/// Make headings links with anchor ids and build up TOC.
290-
struct HeadingLinks<'a, 'b, I: Iterator<Item = Event<'a>>> {
292+
struct HeadingLinks<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> {
291293
inner: I,
292294
toc: Option<&'b mut TocBuilder>,
293295
buf: VecDeque<Event<'a>>,
296+
id_map: &'ids mut IdMap,
294297
}
295298

296-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, I> {
297-
fn new(iter: I, toc: Option<&'b mut TocBuilder>) -> Self {
299+
impl<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> HeadingLinks<'a, 'b, 'ids, I> {
300+
fn new(iter: I, toc: Option<&'b mut TocBuilder>, ids: &'ids mut IdMap) -> Self {
298301
HeadingLinks {
299302
inner: iter,
300303
toc,
301304
buf: VecDeque::new(),
305+
id_map: ids,
302306
}
303307
}
304308
}
305309

306-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I> {
310+
impl<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, 'ids, I> {
307311
type Item = Event<'a>;
308312

309313
fn next(&mut self) -> Option<Self::Item> {
@@ -322,7 +326,7 @@ impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, I>
322326
}
323327
self.buf.push_back(event);
324328
}
325-
let id = derive_id(id);
329+
let id = self.id_map.derive(id);
326330

327331
if let Some(ref mut builder) = self.toc {
328332
let mut html_header = String::new();
@@ -641,7 +645,8 @@ impl LangString {
641645

642646
impl<'a> fmt::Display for Markdown<'a> {
643647
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
644-
let Markdown(md, links, codes) = *self;
648+
let Markdown(md, links, ref ids, codes) = *self;
649+
let mut ids = ids.borrow_mut();
645650

646651
// This is actually common enough to special-case
647652
if md.is_empty() { return Ok(()) }
@@ -661,7 +666,7 @@ impl<'a> fmt::Display for Markdown<'a> {
661666

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

664-
let p = HeadingLinks::new(p, None);
669+
let p = HeadingLinks::new(p, None, &mut ids);
665670
let p = LinkReplacer::new(p, links);
666671
let p = CodeBlocks::new(p, codes);
667672
let p = Footnotes::new(p);
@@ -673,7 +678,8 @@ impl<'a> fmt::Display for Markdown<'a> {
673678

674679
impl<'a> fmt::Display for MarkdownWithToc<'a> {
675680
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
676-
let MarkdownWithToc(md, codes) = *self;
681+
let MarkdownWithToc(md, ref ids, codes) = *self;
682+
let mut ids = ids.borrow_mut();
677683

678684
let mut opts = Options::empty();
679685
opts.insert(OPTION_ENABLE_TABLES);
@@ -686,7 +692,7 @@ impl<'a> fmt::Display for MarkdownWithToc<'a> {
686692
let mut toc = TocBuilder::new();
687693

688694
{
689-
let p = HeadingLinks::new(p, Some(&mut toc));
695+
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
690696
let p = CodeBlocks::new(p, codes);
691697
let p = Footnotes::new(p);
692698
html::push_html(&mut s, p);
@@ -700,7 +706,8 @@ impl<'a> fmt::Display for MarkdownWithToc<'a> {
700706

701707
impl<'a> fmt::Display for MarkdownHtml<'a> {
702708
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
703-
let MarkdownHtml(md, codes) = *self;
709+
let MarkdownHtml(md, ref ids, codes) = *self;
710+
let mut ids = ids.borrow_mut();
704711

705712
// This is actually common enough to special-case
706713
if md.is_empty() { return Ok(()) }
@@ -718,7 +725,7 @@ impl<'a> fmt::Display for MarkdownHtml<'a> {
718725

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

721-
let p = HeadingLinks::new(p, None);
728+
let p = HeadingLinks::new(p, None, &mut ids);
722729
let p = CodeBlocks::new(p, codes);
723730
let p = Footnotes::new(p);
724731
html::push_html(&mut s, p);
@@ -835,7 +842,10 @@ pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
835842
let p = Parser::new_with_broken_link_callback(md, opts,
836843
Some(&push));
837844

838-
let iter = Footnotes::new(HeadingLinks::new(p, None));
845+
// There's no need to thread an IdMap through to here because
846+
// the IDs generated aren't going to be emitted anywhere.
847+
let mut ids = IdMap::new();
848+
let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
839849

840850
for ev in iter {
841851
if let Event::Start(Tag::Link(dest, _)) = ev {
@@ -854,11 +864,67 @@ pub fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
854864
links
855865
}
856866

867+
#[derive(Default)]
868+
pub struct IdMap {
869+
map: HashMap<String, usize>,
870+
}
871+
872+
impl IdMap {
873+
pub fn new() -> Self {
874+
IdMap::default()
875+
}
876+
877+
pub fn populate<I: IntoIterator<Item=String>>(&mut self, ids: I) {
878+
for id in ids {
879+
let _ = self.derive(id);
880+
}
881+
}
882+
883+
pub fn reset(&mut self) {
884+
self.map = HashMap::new();
885+
}
886+
887+
pub fn derive(&mut self, candidate: String) -> String {
888+
let id = match self.map.get_mut(&candidate) {
889+
None => candidate,
890+
Some(a) => {
891+
let id = format!("{}-{}", candidate, *a);
892+
*a += 1;
893+
id
894+
}
895+
};
896+
897+
self.map.insert(id.clone(), 1);
898+
id
899+
}
900+
}
901+
902+
#[cfg(test)]
903+
#[test]
904+
fn test_unique_id() {
905+
let input = ["foo", "examples", "examples", "method.into_iter","examples",
906+
"method.into_iter", "foo", "main", "search", "methods",
907+
"examples", "method.into_iter", "assoc_type.Item", "assoc_type.Item"];
908+
let expected = ["foo", "examples", "examples-1", "method.into_iter", "examples-2",
909+
"method.into_iter-1", "foo-1", "main", "search", "methods",
910+
"examples-3", "method.into_iter-2", "assoc_type.Item", "assoc_type.Item-1"];
911+
912+
let map = RefCell::new(IdMap::new());
913+
let test = || {
914+
let mut map = map.borrow_mut();
915+
let actual: Vec<String> = input.iter().map(|s| map.derive(s.to_string())).collect();
916+
assert_eq!(&actual[..], expected);
917+
};
918+
test();
919+
map.borrow_mut().reset();
920+
test();
921+
}
922+
857923
#[cfg(test)]
858924
mod tests {
859-
use super::{ErrorCodes, LangString, Markdown, MarkdownHtml};
925+
use super::{ErrorCodes, LangString, Markdown, MarkdownHtml, IdMap};
860926
use super::plain_summary_line;
861-
use html::render::reset_ids;
927+
use std::cell::RefCell;
862928

863929
#[test]
864930
fn test_lang_string_parse() {
@@ -901,19 +967,12 @@ mod tests {
901967
t("text,no_run", false, true, false, false, false, false, false, v());
902968
}
903969

904-
#[test]
905-
fn issue_17736() {
906-
let markdown = "# title";
907-
Markdown(markdown, &[], ErrorCodes::Yes).to_string();
908-
reset_ids(true);
909-
}
910-
911970
#[test]
912971
fn test_header() {
913972
fn t(input: &str, expect: &str) {
914-
let output = Markdown(input, &[], ErrorCodes::Yes).to_string();
973+
let mut map = IdMap::new();
974+
let output = Markdown(input, &[], RefCell::new(&mut map), ErrorCodes::Yes).to_string();
915975
assert_eq!(output, expect, "original: {}", input);
916-
reset_ids(true);
917976
}
918977

919978
t("# Foo bar", "<h1 id=\"foo-bar\" class=\"section-header\">\
@@ -932,28 +991,24 @@ mod tests {
932991

933992
#[test]
934993
fn test_header_ids_multiple_blocks() {
935-
fn t(input: &str, expect: &str) {
936-
let output = Markdown(input, &[], ErrorCodes::Yes).to_string();
994+
let mut map = IdMap::new();
995+
fn t(map: &mut IdMap, input: &str, expect: &str) {
996+
let output = Markdown(input, &[], RefCell::new(map), ErrorCodes::Yes).to_string();
937997
assert_eq!(output, expect, "original: {}", input);
938998
}
939999

940-
let test = || {
941-
t("# Example", "<h1 id=\"example\" class=\"section-header\">\
942-
<a href=\"#example\">Example</a></h1>");
943-
t("# Panics", "<h1 id=\"panics\" class=\"section-header\">\
944-
<a href=\"#panics\">Panics</a></h1>");
945-
t("# Example", "<h1 id=\"example-1\" class=\"section-header\">\
946-
<a href=\"#example-1\">Example</a></h1>");
947-
t("# Main", "<h1 id=\"main-1\" class=\"section-header\">\
948-
<a href=\"#main-1\">Main</a></h1>");
949-
t("# Example", "<h1 id=\"example-2\" class=\"section-header\">\
950-
<a href=\"#example-2\">Example</a></h1>");
951-
t("# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
952-
<a href=\"#panics-1\">Panics</a></h1>");
953-
};
954-
test();
955-
reset_ids(true);
956-
test();
1000+
t(&mut map, "# Example", "<h1 id=\"example\" class=\"section-header\">\
1001+
<a href=\"#example\">Example</a></h1>");
1002+
t(&mut map, "# Panics", "<h1 id=\"panics\" class=\"section-header\">\
1003+
<a href=\"#panics\">Panics</a></h1>");
1004+
t(&mut map, "# Example", "<h1 id=\"example-1\" class=\"section-header\">\
1005+
<a href=\"#example-1\">Example</a></h1>");
1006+
t(&mut map, "# Main", "<h1 id=\"main\" class=\"section-header\">\
1007+
<a href=\"#main\">Main</a></h1>");
1008+
t(&mut map, "# Example", "<h1 id=\"example-2\" class=\"section-header\">\
1009+
<a href=\"#example-2\">Example</a></h1>");
1010+
t(&mut map, "# Panics", "<h1 id=\"panics-1\" class=\"section-header\">\
1011+
<a href=\"#panics-1\">Panics</a></h1>");
9571012
}
9581013

9591014
#[test]
@@ -974,7 +1029,8 @@ mod tests {
9741029
#[test]
9751030
fn test_markdown_html_escape() {
9761031
fn t(input: &str, expect: &str) {
977-
let output = MarkdownHtml(input, ErrorCodes::Yes).to_string();
1032+
let mut idmap = IdMap::new();
1033+
let output = MarkdownHtml(input, RefCell::new(&mut idmap), ErrorCodes::Yes).to_string();
9781034
assert_eq!(output, expect, "original: {}", input);
9791035
}
9801036

0 commit comments

Comments
 (0)