Skip to content

Commit d144e3e

Browse files
committed
initial implementation of mergable rustdoc cci
1 parent 9bad7ba commit d144e3e

File tree

41 files changed

+1360
-1002
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1360
-1002
lines changed

src/librustdoc/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ minifier = "0.3.0"
1616
pulldown-cmark-old = { version = "0.9.6", package = "pulldown-cmark", default-features = false }
1717
regex = "1"
1818
rustdoc-json-types = { path = "../rustdoc-json-types" }
19-
serde_json = "1.0"
19+
serde_json = { version = "1.0", features = ["preserve_order"] }
2020
serde = { version = "1.0", features = ["derive"] }
2121
smallvec = "1.8.1"
2222
tempfile = "3"

src/librustdoc/clean/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub(crate) struct ExternalCrate {
128128
}
129129

130130
impl ExternalCrate {
131-
const LOCAL: Self = Self { crate_num: LOCAL_CRATE };
131+
pub(crate) const LOCAL: Self = Self { crate_num: LOCAL_CRATE };
132132

133133
#[inline]
134134
pub(crate) fn def_id(&self) -> DefId {

src/librustdoc/config.rs

-1
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,6 @@ impl Options {
730730
let extern_html_root_takes_precedence =
731731
matches.opt_present("extern-html-root-takes-precedence");
732732
let html_no_source = matches.opt_present("html-no-source");
733-
734733
if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
735734
dcx.fatal(
736735
"--generate-link-to-definition option can only be used with HTML output format",

src/librustdoc/html/render/context.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ 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+
<<<<<<< HEAD
1718
use super::search_index::build_index;
1819
use super::sidebar::{print_sidebar, sidebar_module_like, Sidebar};
20+
=======
21+
>>>>>>> 164844f3f807 (initial implementation of mergable rustdoc cci)
1922
use super::write_shared::write_shared;
2023
use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
2124
use crate::clean::types::ExternalLocation;
@@ -573,13 +576,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
573576
}
574577

575578
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);
579+
write_shared(&mut cx, &krate, &md_opts, tcx)?;
583580
}
584581

585582
Ok((cx, krate))
@@ -729,6 +726,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
729726
);
730727
shared.fs.write(help_file, v)?;
731728

729+
// if to avoid writing files to doc root unless we're on the final invocation
732730
if shared.layout.scrape_examples_extension {
733731
page.title = "About scraped examples";
734732
page.description = "How the scraped examples feature works in Rustdoc";

src/librustdoc/html/render/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ mod tests;
3131
mod context;
3232
mod print_item;
3333
pub(crate) mod sidebar;
34+
mod sorted_json;
35+
mod sorted_template;
3436
mod span_map;
3537
mod type_layout;
3638
mod write_shared;

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::sorted_json::SortedJson;
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: SortedJson,
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 = SortedJson::array_unsorted([
697+
SortedJson::serialize(crate_name.as_str()),
698+
SortedJson::serialize(data),
699+
]);
704700
SerializedSearchIndex { index, desc }
705701
}
706702

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use itertools::Itertools as _;
2+
use serde::{Deserialize, Serialize};
3+
use serde_json::Value;
4+
use std::borrow::Borrow;
5+
use std::fmt;
6+
7+
/// Prerenedered json.
8+
///
9+
/// Arrays are sorted by their stringified entries, and objects are sorted by their stringified
10+
/// keys.
11+
///
12+
/// Must use serde_json with the preserve_order feature.
13+
///
14+
/// Both the Display and serde_json::to_string implementations write the serialized json
15+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
16+
#[serde(from = "Value")]
17+
#[serde(into = "Value")]
18+
pub(crate) struct SortedJson(String);
19+
20+
impl SortedJson {
21+
/// If you pass in an array, it will not be sorted.
22+
pub(crate) fn serialize<T: Serialize>(item: T) -> Self {
23+
SortedJson(serde_json::to_string(&item).unwrap())
24+
}
25+
26+
/// Serializes and sorts
27+
pub(crate) fn array<T: Borrow<SortedJson>, I: IntoIterator<Item = T>>(items: I) -> Self {
28+
let items = items
29+
.into_iter()
30+
.sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow()))
31+
.format_with(",", |item, f| f(item.borrow()));
32+
SortedJson(format!("[{}]", items))
33+
}
34+
35+
pub(crate) fn array_unsorted<T: Borrow<SortedJson>, I: IntoIterator<Item = T>>(
36+
items: I,
37+
) -> Self {
38+
let items = items.into_iter().format_with(",", |item, f| f(item.borrow()));
39+
SortedJson(format!("[{items}]"))
40+
}
41+
}
42+
43+
impl fmt::Display for SortedJson {
44+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45+
write!(f, "{}", self.0)
46+
}
47+
}
48+
49+
impl From<Value> for SortedJson {
50+
fn from(value: Value) -> Self {
51+
SortedJson(serde_json::to_string(&value).unwrap())
52+
}
53+
}
54+
55+
impl From<SortedJson> for Value {
56+
fn from(json: SortedJson) -> Self {
57+
serde_json::from_str(&json.0).unwrap()
58+
}
59+
}
60+
61+
/// For use in JSON.parse('{...}').
62+
///
63+
/// JSON.parse supposedly loads faster than raw JS source,
64+
/// so this is used for large objects.
65+
#[derive(Debug, Clone, Serialize, Deserialize)]
66+
pub(crate) struct EscapedJson(SortedJson);
67+
68+
impl From<SortedJson> for EscapedJson {
69+
fn from(json: SortedJson) -> Self {
70+
EscapedJson(json)
71+
}
72+
}
73+
74+
impl fmt::Display for EscapedJson {
75+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+
// All these `replace` calls are because we have to go through JS string
77+
// for JSON content.
78+
// We need to escape double quotes for the JSON
79+
let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\"");
80+
write!(f, "{}", json)
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use std::collections::BTreeSet;
2+
use std::fmt;
3+
use std::marker::PhantomData;
4+
use std::str::FromStr;
5+
6+
use serde::{Deserialize, Serialize};
7+
8+
/// Append-only templates for sorted, deduplicated lists of items.
9+
///
10+
/// Last line of the rendered output is a comment encoding the next insertion point.
11+
#[derive(Debug, Clone)]
12+
pub(crate) struct SortedTemplate<F> {
13+
format: PhantomData<F>,
14+
before: String,
15+
after: String,
16+
contents: BTreeSet<String>,
17+
}
18+
19+
/// Written to last line of file to specify the location of each fragment
20+
#[derive(Serialize, Deserialize, Debug, Clone)]
21+
struct Offset {
22+
/// Index of the first byte in the template
23+
start: usize,
24+
/// The length of each fragment in the encoded template, including the separator
25+
delta: Vec<usize>,
26+
}
27+
28+
impl<F> SortedTemplate<F> {
29+
/// Generate this template from arbitary text.
30+
/// Will insert wherever the substring `magic` can be found.
31+
/// Errors if it does not appear exactly once.
32+
pub(crate) fn magic(template: &str, magic: &str) -> Result<Self, Error> {
33+
let mut split = template.split(magic);
34+
let before = split.next().ok_or(Error)?;
35+
let after = split.next().ok_or(Error)?;
36+
if split.next().is_some() {
37+
return Err(Error);
38+
}
39+
Ok(Self::before_after(before, after))
40+
}
41+
42+
/// Template will insert contents between `before` and `after`
43+
pub(crate) fn before_after<S: ToString, T: ToString>(before: S, after: T) -> Self {
44+
let before = before.to_string();
45+
let after = after.to_string();
46+
SortedTemplate { format: PhantomData, before, after, contents: Default::default() }
47+
}
48+
}
49+
50+
impl<F: FileFormat> SortedTemplate<F> {
51+
/// Adds this text to the template
52+
pub(crate) fn append(&mut self, insert: String) {
53+
self.contents.insert(insert);
54+
}
55+
}
56+
57+
impl<F: FileFormat> fmt::Display for SortedTemplate<F> {
58+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59+
let mut delta = Vec::default();
60+
write!(f, "{}", self.before)?;
61+
let contents: Vec<_> = self.contents.iter().collect();
62+
let mut sep = "";
63+
for content in contents {
64+
delta.push(sep.len() + content.len());
65+
write!(f, "{}{}", sep, content)?;
66+
sep = F::SEPARATOR;
67+
}
68+
let offset = Offset { start: self.before.len(), delta };
69+
let offset = serde_json::to_string(&offset).unwrap();
70+
write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?;
71+
Ok(())
72+
}
73+
}
74+
75+
fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> {
76+
s.is_char_boundary(index).then(|| s.split_at(index))
77+
}
78+
79+
impl<F: FileFormat> FromStr for SortedTemplate<F> {
80+
type Err = Error;
81+
fn from_str(s: &str) -> Result<Self, Self::Err> {
82+
let (s, offset) = s.rsplit_once("\n").ok_or(Error)?;
83+
let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?;
84+
let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?;
85+
let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?;
86+
let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?;
87+
let mut contents = BTreeSet::default();
88+
let mut sep = "";
89+
for &index in offset.delta.iter() {
90+
let (content, rest) = checked_split_at(s, index).ok_or(Error)?;
91+
s = rest;
92+
let content = content.strip_prefix(sep).ok_or(Error)?;
93+
contents.insert(content.to_string());
94+
sep = F::SEPARATOR;
95+
}
96+
Ok(SortedTemplate {
97+
format: PhantomData,
98+
before: before.to_string(),
99+
after: s.to_string(),
100+
contents,
101+
})
102+
}
103+
}
104+
105+
pub(crate) trait FileFormat {
106+
const COMMENT_START: &'static str;
107+
const COMMENT_END: &'static str;
108+
const SEPARATOR: &'static str;
109+
}
110+
111+
#[derive(Debug, Clone)]
112+
pub(crate) struct Html;
113+
114+
impl FileFormat for Html {
115+
const COMMENT_START: &'static str = "<!--";
116+
const COMMENT_END: &'static str = "-->";
117+
const SEPARATOR: &'static str = "";
118+
}
119+
120+
#[derive(Debug, Clone)]
121+
pub(crate) struct Js;
122+
123+
impl FileFormat for Js {
124+
const COMMENT_START: &'static str = "//";
125+
const COMMENT_END: &'static str = "";
126+
const SEPARATOR: &'static str = ",";
127+
}
128+
129+
#[derive(Debug, Clone)]
130+
pub(crate) struct Error;
131+
132+
impl fmt::Display for Error {
133+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134+
write!(f, "invalid template")
135+
}
136+
}

0 commit comments

Comments
 (0)