diff --git a/src/librustdoc/html/render/sorted_json.rs b/src/librustdoc/html/render/sorted_json.rs
index 3a097733b8b2..e937382f5b0a 100644
--- a/src/librustdoc/html/render/sorted_json.rs
+++ b/src/librustdoc/html/render/sorted_json.rs
@@ -80,3 +80,6 @@ impl fmt::Display for EscapedJson {
write!(f, "{}", json)
}
}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index eaebeadd8817..c2d2b4cd7d9f 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -15,7 +15,6 @@
use std::any::Any;
use std::cell::RefCell;
-use std::collections::hash_map::Entry;
use std::ffi::OsString;
use std::fs::File;
use std::io::BufWriter;
@@ -52,7 +51,7 @@ use crate::html::layout;
use crate::html::render::search_index::build_index;
use crate::html::render::search_index::SerializedSearchIndex;
use crate::html::render::sorted_json::{EscapedJson, SortedJson};
-use crate::html::render::sorted_template::{self, SortedTemplate};
+use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
use crate::html::render::{AssocItemLink, ImplRenderingParameters};
use crate::html::static_files::{self, suffix_path};
use crate::visit::DocVisitor;
@@ -78,33 +77,29 @@ pub(crate) fn write_shared(
let crate_name = krate.name(cx.tcx());
let crate_name = crate_name.as_str(); // rand
let crate_name_json = SortedJson::serialize(crate_name); // "rand"
- let external_crates = hack_get_external_crate_names(cx)?;
+ let external_crates = hack_get_external_crate_names(&cx.dst)?;
let info = CrateInfo {
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
- search_index_js: SearchIndexPart::get(cx, index)?,
+ search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
all_crates: AllCratesPart::get(crate_name_json.clone())?,
crates_index: CratesIndexPart::get(&crate_name, &external_crates)?,
trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
};
- let crates_info = vec![info]; // we have info from just one crate
+ let crates = vec![info]; // we have info from just one crate. rest will found in out dir
write_static_files(cx, &opt)?;
let dst = &cx.dst;
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
if cx.include_sources {
- write_rendered_cci::(SourcesPart::blank, dst, &crates_info)?;
+ write_rendered_cci::(SourcesPart::blank, dst, &crates)?;
}
- write_rendered_cci::(
- SearchIndexPart::blank,
- dst,
- &crates_info,
- )?;
- write_rendered_cci::(AllCratesPart::blank, dst, &crates_info)?;
+ write_rendered_cci::(SearchIndexPart::blank, dst, &crates)?;
+ write_rendered_cci::(AllCratesPart::blank, dst, &crates)?;
}
- write_rendered_cci::(TraitAliasPart::blank, dst, &crates_info)?;
- write_rendered_cci::(TypeAliasPart::blank, dst, &crates_info)?;
+ write_rendered_cci::(TraitAliasPart::blank, dst, &crates)?;
+ write_rendered_cci::(TypeAliasPart::blank, dst, &crates)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
@@ -119,7 +114,7 @@ pub(crate) fn write_shared(
write_rendered_cci::(
|| CratesIndexPart::blank(cx),
dst,
- &crates_info,
+ &crates,
)?;
}
_ => {} // they don't want an index page
@@ -189,7 +184,8 @@ fn write_search_desc(
let path = path.join(filename);
let part = SortedJson::serialize(&part);
let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
- write_create_parents(&path, part)?;
+ create_parents(&path)?;
+ try_err!(fs::write(&path, part), &path);
}
Ok(())
}
@@ -286,8 +282,11 @@ else if (window.initSearch) window.initSearch(searchIndex);",
)
}
- fn get(cx: &Context<'_>, search_index: SortedJson) -> Result, Error> {
- let path = suffix_path("search-index.js", &cx.shared.resource_suffix);
+ fn get(
+ search_index: SortedJson,
+ resource_suffix: &str,
+ ) -> Result, Error> {
+ let path = suffix_path("search-index.js", resource_suffix);
let search_index = EscapedJson::from(search_index);
Ok(PartsAndLocations::with(path, search_index))
}
@@ -319,8 +318,8 @@ impl AllCratesPart {
///
/// This is to match the current behavior of rustdoc, which allows you to get all crates
/// on the index page, even if --enable-index-page is only passed to the last crate.
-fn hack_get_external_crate_names(cx: &Context<'_>) -> Result, Error> {
- let path = cx.dst.join("crates.js");
+fn hack_get_external_crate_names(doc_root: &Path) -> Result, Error> {
+ let path = doc_root.join("crates.js");
let Ok(content) = fs::read_to_string(&path) else {
// they didn't emit invocation specific, so we just say there were no crates
return Ok(Vec::default());
@@ -361,7 +360,7 @@ impl CratesIndexPart {
match SortedTemplate::magic(&template, MAGIC) {
Ok(template) => template,
Err(e) => panic!(
- "{e}: Object Replacement Character (U+FFFC) should not appear in the --index-page"
+ "Object Replacement Character (U+FFFC) should not appear in the --index-page: {e}"
),
}
}
@@ -860,6 +859,21 @@ impl Serialize for AliasSerializableImpl {
}
}
+fn get_path_parts(
+ dst: &Path,
+ crates_info: &[CrateInfo],
+) -> FxHashMap> {
+ let mut templates: FxHashMap> = FxHashMap::default();
+ crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten().for_each(
+ |(path, part)| {
+ let path = dst.join(&path);
+ let part = part.to_string();
+ templates.entry(path).or_default().push(part);
+ },
+ );
+ templates
+}
+
/// Create all parents
fn create_parents(path: &Path) -> Result<(), Error> {
let parent = path.parent().expect("should not have an empty path here");
@@ -867,20 +881,13 @@ fn create_parents(path: &Path) -> Result<(), Error> {
Ok(())
}
-/// Create parents and then write
-fn write_create_parents(path: &Path, content: String) -> Result<(), Error> {
- create_parents(path)?;
- try_err!(fs::write(path, content), path);
- Ok(())
-}
-
/// Returns a blank template unless we could find one to append to
-fn read_template_or_blank(
+fn read_template_or_blank(
mut make_blank: F,
path: &Path,
-) -> Result, Error>
+) -> Result, Error>
where
- F: FnMut() -> SortedTemplate,
+ F: FnMut() -> SortedTemplate,
{
match fs::read_to_string(&path) {
Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
@@ -898,27 +905,14 @@ fn write_rendered_cci(
where
F: FnMut() -> SortedTemplate,
{
- // read parts from disk
- let path_parts =
- crates_info.iter().map(|crate_info| crate_info.get::().parts.iter()).flatten();
- // read previous rendered cci from storage, append to them
- let mut templates: FxHashMap> = Default::default();
- for (path, part) in path_parts {
- let part = format!("{part}");
- let path = dst.join(&path);
- match templates.entry(path.clone()) {
- Entry::Vacant(entry) => {
- let template = read_template_or_blank::<_, T>(&mut make_blank, &path)?;
- let template = entry.insert(template);
- template.append(part);
- }
- Entry::Occupied(mut t) => t.get_mut().append(part),
- }
- }
-
// write the merged cci to disk
- for (path, template) in templates {
+ for (path, parts) in get_path_parts::(dst, crates_info) {
create_parents(&path)?;
+ // read previous rendered cci from storage, append to them
+ let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?;
+ for part in parts {
+ template.append(part);
+ }
let file = try_err!(File::create(&path), &path);
let mut file = BufWriter::new(file);
try_err!(write!(file, "{template}"), &path);
@@ -926,3 +920,6 @@ where
}
Ok(())
}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs
new file mode 100644
index 000000000000..000e233aec00
--- /dev/null
+++ b/src/librustdoc/html/render/write_shared/tests.rs
@@ -0,0 +1,206 @@
+use crate::html::render::sorted_json::{EscapedJson, SortedJson};
+use crate::html::render::sorted_template::{Html, SortedTemplate};
+use crate::html::render::write_shared::*;
+
+#[test]
+fn hack_external_crate_names() {
+ let path = tempfile::TempDir::new().unwrap();
+ let path = path.path();
+ let crates = hack_get_external_crate_names(&path).unwrap();
+ assert!(crates.is_empty());
+ fs::write(path.join("crates.js"), r#"window.ALL_CRATES = ["a","b","c"];"#).unwrap();
+ let crates = hack_get_external_crate_names(&path).unwrap();
+ assert_eq!(crates, ["a".to_string(), "b".to_string(), "c".to_string()]);
+}
+
+fn but_last_line(s: &str) -> &str {
+ let (before, _) = s.rsplit_once("\n").unwrap();
+ before
+}
+
+#[test]
+fn sources_template() {
+ let mut template = SourcesPart::blank();
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r"var srcIndex = new Map(JSON.parse('[]'));
+createSrcSidebar();"
+ );
+ template.append(EscapedJson::from(SortedJson::serialize("u")).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"var srcIndex = new Map(JSON.parse('["u"]'));
+createSrcSidebar();"#
+ );
+ template.append(EscapedJson::from(SortedJson::serialize("v")).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"var srcIndex = new Map(JSON.parse('["u","v"]'));
+createSrcSidebar();"#
+ );
+}
+
+#[test]
+fn sources_parts() {
+ let parts = SearchIndexPart::get(SortedJson::serialize(["foo", "bar"]), "suffix").unwrap();
+ assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js"));
+ assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#);
+}
+
+#[test]
+fn all_crates_template() {
+ let mut template = AllCratesPart::blank();
+ assert_eq!(but_last_line(&template.to_string()), r"window.ALL_CRATES = [];");
+ template.append(EscapedJson::from(SortedJson::serialize("b")).to_string());
+ assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["b"];"#);
+ template.append(EscapedJson::from(SortedJson::serialize("a")).to_string());
+ assert_eq!(but_last_line(&template.to_string()), r#"window.ALL_CRATES = ["a","b"];"#);
+}
+
+#[test]
+fn all_crates_parts() {
+ let parts = AllCratesPart::get(SortedJson::serialize("crate")).unwrap();
+ assert_eq!(&parts.parts[0].0, Path::new("crates.js"));
+ assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#);
+}
+
+#[test]
+fn search_index_template() {
+ let mut template = SearchIndexPart::blank();
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r"var searchIndex = new Map(JSON.parse('[]'));
+if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
+else if (window.initSearch) window.initSearch(searchIndex);"
+ );
+ template.append(EscapedJson::from(SortedJson::serialize([1, 2])).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r"var searchIndex = new Map(JSON.parse('[[1,2]]'));
+if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
+else if (window.initSearch) window.initSearch(searchIndex);"
+ );
+ template.append(EscapedJson::from(SortedJson::serialize([4, 3])).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]'));
+if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
+else if (window.initSearch) window.initSearch(searchIndex);"
+ );
+}
+
+#[test]
+fn crates_index_part() {
+ let external_crates = ["bar".to_string(), "baz".to_string()];
+ let mut parts = CratesIndexPart::get("foo", &external_crates).unwrap();
+ parts.parts.sort_by(|a, b| a.1.to_string().cmp(&b.1.to_string()));
+
+ assert_eq!(&parts.parts[0].0, Path::new("index.html"));
+ assert_eq!(&parts.parts[0].1.to_string(), r#"bar"#);
+
+ assert_eq!(&parts.parts[1].0, Path::new("index.html"));
+ assert_eq!(&parts.parts[1].1.to_string(), r#"baz"#);
+
+ assert_eq!(&parts.parts[2].0, Path::new("index.html"));
+ assert_eq!(&parts.parts[2].1.to_string(), r#"foo"#);
+}
+
+#[test]
+fn trait_alias_template() {
+ let mut template = TraitAliasPart::blank();
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var implementors = Object.fromEntries([]);
+ if (window.register_implementors) {
+ window.register_implementors(implementors);
+ } else {
+ window.pending_implementors = implementors;
+ }
+})()"#,
+ );
+ template.append(SortedJson::serialize(["a"]).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var implementors = Object.fromEntries([["a"]]);
+ if (window.register_implementors) {
+ window.register_implementors(implementors);
+ } else {
+ window.pending_implementors = implementors;
+ }
+})()"#,
+ );
+ template.append(SortedJson::serialize(["b"]).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var implementors = Object.fromEntries([["a"],["b"]]);
+ if (window.register_implementors) {
+ window.register_implementors(implementors);
+ } else {
+ window.pending_implementors = implementors;
+ }
+})()"#,
+ );
+}
+
+#[test]
+fn type_alias_template() {
+ let mut template = TypeAliasPart::blank();
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var type_impls = Object.fromEntries([]);
+ if (window.register_type_impls) {
+ window.register_type_impls(type_impls);
+ } else {
+ window.pending_type_impls = type_impls;
+ }
+})()"#,
+ );
+ template.append(SortedJson::serialize(["a"]).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var type_impls = Object.fromEntries([["a"]]);
+ if (window.register_type_impls) {
+ window.register_type_impls(type_impls);
+ } else {
+ window.pending_type_impls = type_impls;
+ }
+})()"#,
+ );
+ template.append(SortedJson::serialize(["b"]).to_string());
+ assert_eq!(
+ but_last_line(&template.to_string()),
+ r#"(function() {
+ var type_impls = Object.fromEntries([["a"],["b"]]);
+ if (window.register_type_impls) {
+ window.register_type_impls(type_impls);
+ } else {
+ window.pending_type_impls = type_impls;
+ }
+})()"#,
+ );
+}
+
+#[test]
+fn read_template_test() {
+ let path = tempfile::TempDir::new().unwrap();
+ let path = path.path().join("file.html");
+ let make_blank = || SortedTemplate::::before_after("", "
");
+
+ let template = read_template_or_blank(make_blank, &path).unwrap();
+ assert_eq!(but_last_line(&template.to_string()), "");
+ fs::write(&path, template.to_string()).unwrap();
+ let mut template = read_template_or_blank(make_blank, &path).unwrap();
+ template.append("
".to_string());
+ fs::write(&path, template.to_string()).unwrap();
+ let mut template = read_template_or_blank(make_blank, &path).unwrap();
+ template.append("
".to_string());
+ fs::write(&path, template.to_string()).unwrap();
+ let template = read_template_or_blank(make_blank, &path).unwrap();
+
+ assert_eq!(but_last_line(&template.to_string()), "");
+}