Skip to content

Commit 5aea140

Browse files
committed
Auto merge of #128252 - EtomicBomb:pre-rfc, r=notriddle
modularize rustdoc's write_shared Refactor src/librustdoc/html/render/write_shared.rs to reduce code duplication, adding unit tests * Extract + unit test code for sorting and rendering JSON, which is duplicated 9 times in the current impl * Extract + unit test code for encoding JSON as single quoted strings, which is duplicated twice in the current impl * Unit tests for cross-crate information file formats * Generic interface to add new kinds of cross-crate information files in the future * Intended to match current behavior exactly, except for a merge info comment it adds to the bottom of cci files * This PR is intended to reduce the review burden from my [mergeable rustdoc rfc](rust-lang/rfcs#3662) implementation PR, which is a [small commit based on this branch](https://github.com/EtomicBomb/rust/tree/rfc). This code is agnostic to the RFC and does not include any of the flags discussed there, but cleanly enables the addition of these flags in the future because it is more modular
2 parents 4d5b3b1 + b4f057f commit 5aea140

File tree

9 files changed

+1551
-683
lines changed

9 files changed

+1551
-683
lines changed

src/librustdoc/html/render/context.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use rustc_span::edition::Edition;
1414
use rustc_span::{sym, FileName, Symbol};
1515

1616
use super::print_item::{full_path, item_path, print_item};
17-
use super::search_index::build_index;
1817
use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar};
1918
use super::write_shared::write_shared;
2019
use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
@@ -573,13 +572,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
573572
}
574573

575574
if !no_emit_shared {
576-
// Build our search index
577-
let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
578-
579-
// Write shared runs within a flock; disable thread dispatching of IO temporarily.
580-
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
581-
write_shared(&mut cx, &krate, index, &md_opts)?;
582-
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
575+
write_shared(&mut cx, &krate, &md_opts, tcx)?;
583576
}
584577

585578
Ok((cx, krate))

src/librustdoc/html/render/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ pub(crate) mod search_index;
2929
mod tests;
3030

3131
mod context;
32+
mod ordered_json;
3233
mod print_item;
3334
pub(crate) mod sidebar;
35+
mod sorted_template;
3436
mod span_map;
3537
mod type_layout;
3638
mod write_shared;
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::borrow::Borrow;
2+
use std::fmt;
3+
4+
use itertools::Itertools as _;
5+
use serde::{Deserialize, Serialize};
6+
use serde_json::Value;
7+
8+
/// Prerendered json.
9+
///
10+
/// Both the Display and serde_json::to_string implementations write the serialized json
11+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
12+
#[serde(from = "Value")]
13+
#[serde(into = "Value")]
14+
pub(crate) struct OrderedJson(String);
15+
16+
impl OrderedJson {
17+
/// If you pass in an array, it will not be sorted.
18+
pub(crate) fn serialize<T: Serialize>(item: T) -> Result<Self, serde_json::Error> {
19+
Ok(Self(serde_json::to_string(&item)?))
20+
}
21+
22+
/// Serializes and sorts
23+
pub(crate) fn array_sorted<T: Borrow<Self>, I: IntoIterator<Item = T>>(items: I) -> Self {
24+
let items = items
25+
.into_iter()
26+
.sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow()))
27+
.format_with(",", |item, f| f(item.borrow()));
28+
Self(format!("[{}]", items))
29+
}
30+
31+
pub(crate) fn array_unsorted<T: Borrow<Self>, I: IntoIterator<Item = T>>(items: I) -> Self {
32+
let items = items.into_iter().format_with(",", |item, f| f(item.borrow()));
33+
Self(format!("[{items}]"))
34+
}
35+
}
36+
37+
impl fmt::Display for OrderedJson {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
self.0.fmt(f)
40+
}
41+
}
42+
43+
impl From<Value> for OrderedJson {
44+
fn from(value: Value) -> Self {
45+
let serialized =
46+
serde_json::to_string(&value).expect("Serializing a Value to String should never fail");
47+
Self(serialized)
48+
}
49+
}
50+
51+
impl From<OrderedJson> for Value {
52+
fn from(json: OrderedJson) -> Self {
53+
serde_json::from_str(&json.0).expect("OrderedJson should always store valid JSON")
54+
}
55+
}
56+
57+
/// For use in JSON.parse('{...}').
58+
///
59+
/// Assumes we are going to be wrapped in single quoted strings.
60+
///
61+
/// JSON.parse loads faster than raw JS source,
62+
/// so this is used for large objects.
63+
#[derive(Debug, Clone, Serialize, Deserialize)]
64+
pub(crate) struct EscapedJson(OrderedJson);
65+
66+
impl From<OrderedJson> for EscapedJson {
67+
fn from(json: OrderedJson) -> Self {
68+
Self(json)
69+
}
70+
}
71+
72+
impl fmt::Display for EscapedJson {
73+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74+
// All these `replace` calls are because we have to go through JS string
75+
// for JSON content.
76+
// We need to escape double quotes for the JSON
77+
let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\"");
78+
json.fmt(f)
79+
}
80+
}
81+
82+
#[cfg(test)]
83+
mod tests;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use super::super::ordered_json::*;
2+
3+
fn check(json: OrderedJson, serialized: &str) {
4+
assert_eq!(json.to_string(), serialized);
5+
assert_eq!(serde_json::to_string(&json).unwrap(), serialized);
6+
7+
let json = json.to_string();
8+
let json: OrderedJson = serde_json::from_str(&json).unwrap();
9+
10+
assert_eq!(json.to_string(), serialized);
11+
assert_eq!(serde_json::to_string(&json).unwrap(), serialized);
12+
13+
let json = serde_json::to_string(&json).unwrap();
14+
let json: OrderedJson = serde_json::from_str(&json).unwrap();
15+
16+
assert_eq!(json.to_string(), serialized);
17+
assert_eq!(serde_json::to_string(&json).unwrap(), serialized);
18+
}
19+
20+
// Make sure there is no extra level of string, plus number of escapes.
21+
#[test]
22+
fn escape_json_number() {
23+
let json = OrderedJson::serialize(3).unwrap();
24+
let json = EscapedJson::from(json);
25+
assert_eq!(format!("{json}"), "3");
26+
}
27+
28+
#[test]
29+
fn escape_json_single_quote() {
30+
let json = OrderedJson::serialize("he's").unwrap();
31+
let json = EscapedJson::from(json);
32+
assert_eq!(format!("{json}"), r#""he\'s""#);
33+
}
34+
35+
#[test]
36+
fn escape_json_array() {
37+
let json = OrderedJson::serialize([1, 2, 3]).unwrap();
38+
let json = EscapedJson::from(json);
39+
assert_eq!(format!("{json}"), r#"[1,2,3]"#);
40+
}
41+
42+
#[test]
43+
fn escape_json_string() {
44+
let json = OrderedJson::serialize(r#"he"llo"#).unwrap();
45+
let json = EscapedJson::from(json);
46+
assert_eq!(format!("{json}"), r#""he\\\"llo""#);
47+
}
48+
49+
#[test]
50+
fn escape_json_string_escaped() {
51+
let json = OrderedJson::serialize(r#"he\"llo"#).unwrap();
52+
let json = EscapedJson::from(json);
53+
assert_eq!(format!("{json}"), r#""he\\\\\\\"llo""#);
54+
}
55+
56+
#[test]
57+
fn escape_json_string_escaped_escaped() {
58+
let json = OrderedJson::serialize(r#"he\\"llo"#).unwrap();
59+
let json = EscapedJson::from(json);
60+
assert_eq!(format!("{json}"), r#""he\\\\\\\\\\\"llo""#);
61+
}
62+
63+
// Testing round trip + making sure there is no extra level of string
64+
#[test]
65+
fn number() {
66+
let json = OrderedJson::serialize(3).unwrap();
67+
let serialized = "3";
68+
check(json, serialized);
69+
}
70+
71+
#[test]
72+
fn boolean() {
73+
let json = OrderedJson::serialize(true).unwrap();
74+
let serialized = "true";
75+
check(json, serialized);
76+
}
77+
78+
#[test]
79+
fn string() {
80+
let json = OrderedJson::serialize("he\"llo").unwrap();
81+
let serialized = r#""he\"llo""#;
82+
check(json, serialized);
83+
}
84+
85+
#[test]
86+
fn serialize_array() {
87+
let json = OrderedJson::serialize([3, 1, 2]).unwrap();
88+
let serialized = "[3,1,2]";
89+
check(json, serialized);
90+
}
91+
92+
#[test]
93+
fn sorted_array() {
94+
let items = ["c", "a", "b"];
95+
let serialized = r#"["a","b","c"]"#;
96+
let items: Vec<OrderedJson> =
97+
items.into_iter().map(OrderedJson::serialize).collect::<Result<Vec<_>, _>>().unwrap();
98+
let json = OrderedJson::array_sorted(items);
99+
check(json, serialized);
100+
}
101+
102+
#[test]
103+
fn nested_array() {
104+
let a = OrderedJson::serialize(3).unwrap();
105+
let b = OrderedJson::serialize(2).unwrap();
106+
let c = OrderedJson::serialize(1).unwrap();
107+
let d = OrderedJson::serialize([1, 3, 2]).unwrap();
108+
let json = OrderedJson::array_sorted([a, b, c, d]);
109+
let serialized = r#"[1,2,3,[1,3,2]]"#;
110+
check(json, serialized);
111+
}
112+
113+
#[test]
114+
fn array_unsorted() {
115+
let items = ["c", "a", "b"];
116+
let serialized = r#"["c","a","b"]"#;
117+
let items: Vec<OrderedJson> =
118+
items.into_iter().map(OrderedJson::serialize).collect::<Result<Vec<_>, _>>().unwrap();
119+
let json = OrderedJson::array_unsorted(items);
120+
check(json, serialized);
121+
}

src/librustdoc/html/render/search_index.rs

+15-19
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::formats::cache::{Cache, OrphanImplItem};
1818
use crate::formats::item_type::ItemType;
1919
use crate::html::format::join_with_double_colon;
2020
use crate::html::markdown::short_markdown_summary;
21+
use crate::html::render::ordered_json::OrderedJson;
2122
use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
2223

2324
/// The serialized search description sharded version
@@ -46,7 +47,7 @@ use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, Re
4647
/// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept
4748
/// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features
4849
pub(crate) struct SerializedSearchIndex {
49-
pub(crate) index: String,
50+
pub(crate) index: OrderedJson,
5051
pub(crate) desc: Vec<(usize, String)>,
5152
}
5253

@@ -683,24 +684,19 @@ pub(crate) fn build_index<'tcx>(
683684
// The index, which is actually used to search, is JSON
684685
// It uses `JSON.parse(..)` to actually load, since JSON
685686
// parses faster than the full JavaScript syntax.
686-
let index = format!(
687-
r#"["{}",{}]"#,
688-
krate.name(tcx),
689-
serde_json::to_string(&CrateData {
690-
items: crate_items,
691-
paths: crate_paths,
692-
aliases: &aliases,
693-
associated_item_disambiguators: &associated_item_disambiguators,
694-
desc_index,
695-
empty_desc,
696-
})
697-
.expect("failed serde conversion")
698-
// All these `replace` calls are because we have to go through JS string for JSON content.
699-
.replace('\\', r"\\")
700-
.replace('\'', r"\'")
701-
// We need to escape double quotes for the JSON.
702-
.replace("\\\"", "\\\\\"")
703-
);
687+
let crate_name = krate.name(tcx);
688+
let data = CrateData {
689+
items: crate_items,
690+
paths: crate_paths,
691+
aliases: &aliases,
692+
associated_item_disambiguators: &associated_item_disambiguators,
693+
desc_index,
694+
empty_desc,
695+
};
696+
let index = OrderedJson::array_unsorted([
697+
OrderedJson::serialize(crate_name.as_str()).unwrap(),
698+
OrderedJson::serialize(data).unwrap(),
699+
]);
704700
SerializedSearchIndex { index, desc }
705701
}
706702

0 commit comments

Comments
 (0)