rustdoc-search: shard the search result descriptions

The descriptions are, on almost all crates[^1], the majority
of the size of the search index, even though they aren't really
used for searching. This makes it relatively easy to separate
them into their own files.

This commit also bumps us to ES8. Out of the browsers we support,
all of them support async functions according to caniuse.

https://caniuse.com/async-functions

[^1]:
    <https://microsoft.github.io/windows-docs-rs/>, a crate with
    44MiB of pure names and no descriptions for them, is an outlier
    and should not be counted.
This commit is contained in:
Michael Howell 2024-03-16 17:50:44 -07:00
parent 351890d682
commit 5b44bfda7f
11 changed files with 427 additions and 228 deletions

View File

@ -56,7 +56,7 @@ ENV SCRIPT python3 ../x.py --stage 2 test src/tools/expand-yaml-anchors && \
/scripts/validate-error-codes.sh && \ /scripts/validate-error-codes.sh && \
reuse --include-submodules lint && \ reuse --include-submodules lint && \
# Runs checks to ensure that there are no ES5 issues in our JS code. # Runs checks to ensure that there are no ES5 issues in our JS code.
es-check es6 ../src/librustdoc/html/static/js/*.js && \ es-check es8 ../src/librustdoc/html/static/js/*.js && \
eslint -c ../src/librustdoc/html/static/.eslintrc.js ../src/librustdoc/html/static/js/*.js && \ eslint -c ../src/librustdoc/html/static/.eslintrc.js ../src/librustdoc/html/static/js/*.js && \
eslint -c ../src/tools/rustdoc-js/.eslintrc.js ../src/tools/rustdoc-js/tester.js && \ eslint -c ../src/tools/rustdoc-js/.eslintrc.js ../src/tools/rustdoc-js/tester.js && \
eslint -c ../src/tools/rustdoc-gui/.eslintrc.js ../src/tools/rustdoc-gui/tester.js eslint -c ../src/tools/rustdoc-gui/.eslintrc.js ../src/tools/rustdoc-gui/tester.js

View File

@ -184,40 +184,15 @@ pub(crate) enum RenderTypeId {
impl RenderTypeId { impl RenderTypeId {
pub fn write_to_string(&self, string: &mut String) { pub fn write_to_string(&self, string: &mut String) {
// (sign, value) let id: i32 = match &self {
let (sign, id): (bool, u32) = match &self {
// 0 is a sentinel, everything else is one-indexed // 0 is a sentinel, everything else is one-indexed
// concrete type // concrete type
RenderTypeId::Index(idx) if *idx >= 0 => (false, (idx + 1isize).try_into().unwrap()), RenderTypeId::Index(idx) if *idx >= 0 => (idx + 1isize).try_into().unwrap(),
// generic type parameter // generic type parameter
RenderTypeId::Index(idx) => (true, (-*idx).try_into().unwrap()), RenderTypeId::Index(idx) => (*idx).try_into().unwrap(),
_ => panic!("must convert render types to indexes before serializing"), _ => panic!("must convert render types to indexes before serializing"),
}; };
// zig-zag encoding search_index::write_vlqhex_to_string(id, string);
let value: u32 = (id << 1) | (if sign { 1 } else { 0 });
// Self-terminating hex use capital letters for everything but the
// least significant digit, which is lowercase. For example, decimal 17
// would be `` Aa `` if zig-zag encoding weren't used.
//
// Zig-zag encoding, however, stores the sign bit as the last bit.
// This means, in the last hexit, 1 is actually `c`, -1 is `b`
// (`a` is the imaginary -0), and, because all the bits are shifted
// by one, `` A` `` is actually 8 and `` Aa `` is -8.
//
// https://rust-lang.github.io/rustc-dev-guide/rustdoc-internals/search.html
// describes the encoding in more detail.
let mut shift: u32 = 28;
let mut mask: u32 = 0xF0_00_00_00;
while shift < 32 {
let hexit = (value & mask) >> shift;
if hexit != 0 || shift == 0 {
let hex =
char::try_from(if shift == 0 { '`' } else { '@' } as u32 + hexit).unwrap();
string.push(hex);
}
shift = shift.wrapping_sub(4);
mask = mask >> 4;
}
} }
} }

View File

@ -17,12 +17,25 @@ use crate::html::format::join_with_double_colon;
use crate::html::markdown::short_markdown_summary; use crate::html::markdown::short_markdown_summary;
use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId}; use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
/// The serialized search description sharded version
///
/// The `index` is a JSON-encoded list of names and other information.
///
/// The desc has newlined descriptions, split up by size into 1MiB shards.
/// For example, `(4, "foo\nbar\nbaz\nquux")`.
pub(crate) struct SerializedSearchIndex {
pub(crate) index: String,
pub(crate) desc: Vec<(usize, String)>,
}
const DESC_INDEX_SHARD_LEN: usize = 1024 * 1024;
/// Builds the search index from the collected metadata /// Builds the search index from the collected metadata
pub(crate) fn build_index<'tcx>( pub(crate) fn build_index<'tcx>(
krate: &clean::Crate, krate: &clean::Crate,
cache: &mut Cache, cache: &mut Cache,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
) -> String { ) -> SerializedSearchIndex {
let mut itemid_to_pathid = FxHashMap::default(); let mut itemid_to_pathid = FxHashMap::default();
let mut primitives = FxHashMap::default(); let mut primitives = FxHashMap::default();
let mut associated_types = FxHashMap::default(); let mut associated_types = FxHashMap::default();
@ -318,7 +331,6 @@ pub(crate) fn build_index<'tcx>(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
struct CrateData<'a> { struct CrateData<'a> {
doc: String,
items: Vec<&'a IndexItem>, items: Vec<&'a IndexItem>,
paths: Vec<(ItemType, Vec<Symbol>)>, paths: Vec<(ItemType, Vec<Symbol>)>,
// The String is alias name and the vec is the list of the elements with this alias. // The String is alias name and the vec is the list of the elements with this alias.
@ -327,6 +339,9 @@ pub(crate) fn build_index<'tcx>(
aliases: &'a BTreeMap<String, Vec<usize>>, aliases: &'a BTreeMap<String, Vec<usize>>,
// Used when a type has more than one impl with an associated item with the same name. // Used when a type has more than one impl with an associated item with the same name.
associated_item_disambiguators: &'a Vec<(usize, String)>, associated_item_disambiguators: &'a Vec<(usize, String)>,
// A list of shard lengths encoded as vlqhex. See the comment in write_vlqhex_to_string
// for information on the format.
descindex: String,
} }
struct Paths { struct Paths {
@ -408,7 +423,6 @@ pub(crate) fn build_index<'tcx>(
let mut names = Vec::with_capacity(self.items.len()); let mut names = Vec::with_capacity(self.items.len());
let mut types = String::with_capacity(self.items.len()); let mut types = String::with_capacity(self.items.len());
let mut full_paths = Vec::with_capacity(self.items.len()); let mut full_paths = Vec::with_capacity(self.items.len());
let mut descriptions = Vec::with_capacity(self.items.len());
let mut parents = Vec::with_capacity(self.items.len()); let mut parents = Vec::with_capacity(self.items.len());
let mut functions = String::with_capacity(self.items.len()); let mut functions = String::with_capacity(self.items.len());
let mut deprecated = Vec::with_capacity(self.items.len()); let mut deprecated = Vec::with_capacity(self.items.len());
@ -431,7 +445,6 @@ pub(crate) fn build_index<'tcx>(
parents.push(item.parent_idx.map(|x| x + 1).unwrap_or(0)); parents.push(item.parent_idx.map(|x| x + 1).unwrap_or(0));
names.push(item.name.as_str()); names.push(item.name.as_str());
descriptions.push(&item.desc);
if !item.path.is_empty() { if !item.path.is_empty() {
full_paths.push((index, &item.path)); full_paths.push((index, &item.path));
@ -454,14 +467,12 @@ pub(crate) fn build_index<'tcx>(
let has_aliases = !self.aliases.is_empty(); let has_aliases = !self.aliases.is_empty();
let mut crate_data = let mut crate_data =
serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?; serializer.serialize_struct("CrateData", if has_aliases { 9 } else { 8 })?;
crate_data.serialize_field("doc", &self.doc)?;
crate_data.serialize_field("t", &types)?; crate_data.serialize_field("t", &types)?;
crate_data.serialize_field("n", &names)?; crate_data.serialize_field("n", &names)?;
// Serialize as an array of item indices and full paths
crate_data.serialize_field("q", &full_paths)?; crate_data.serialize_field("q", &full_paths)?;
crate_data.serialize_field("d", &descriptions)?;
crate_data.serialize_field("i", &parents)?; crate_data.serialize_field("i", &parents)?;
crate_data.serialize_field("f", &functions)?; crate_data.serialize_field("f", &functions)?;
crate_data.serialize_field("D", &self.descindex)?;
crate_data.serialize_field("c", &deprecated)?; crate_data.serialize_field("c", &deprecated)?;
crate_data.serialize_field("p", &paths)?; crate_data.serialize_field("p", &paths)?;
crate_data.serialize_field("b", &self.associated_item_disambiguators)?; crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
@ -472,16 +483,46 @@ pub(crate) fn build_index<'tcx>(
} }
} }
// Collect the index into a string let desc = {
format!( let mut result = Vec::new();
let mut set = String::new();
let mut len: usize = 0;
for desc in std::iter::once(&crate_doc).chain(crate_items.iter().map(|item| &item.desc)) {
if set.len() >= DESC_INDEX_SHARD_LEN {
result.push((len, std::mem::replace(&mut set, String::new())));
len = 0;
} else if len != 0 {
set.push('\n');
}
set.push_str(&desc);
len += 1;
}
result.push((len, std::mem::replace(&mut set, String::new())));
result
};
let descindex = {
let mut descindex = String::with_capacity(desc.len() * 4);
for &(len, _) in desc.iter() {
write_vlqhex_to_string(len.try_into().unwrap(), &mut descindex);
}
descindex
};
assert_eq!(crate_items.len() + 1, desc.iter().map(|(len, _)| *len).sum::<usize>());
// The index, which is actually used to search, is JSON
// It uses `JSON.parse(..)` to actually load, since JSON
// parses faster than the full JavaScript syntax.
let index = format!(
r#"["{}",{}]"#, r#"["{}",{}]"#,
krate.name(tcx), krate.name(tcx),
serde_json::to_string(&CrateData { serde_json::to_string(&CrateData {
doc: crate_doc,
items: crate_items, items: crate_items,
paths: crate_paths, paths: crate_paths,
aliases: &aliases, aliases: &aliases,
associated_item_disambiguators: &associated_item_disambiguators, associated_item_disambiguators: &associated_item_disambiguators,
descindex,
}) })
.expect("failed serde conversion") .expect("failed serde conversion")
// All these `replace` calls are because we have to go through JS string for JSON content. // All these `replace` calls are because we have to go through JS string for JSON content.
@ -489,7 +530,45 @@ pub(crate) fn build_index<'tcx>(
.replace('\'', r"\'") .replace('\'', r"\'")
// We need to escape double quotes for the JSON. // We need to escape double quotes for the JSON.
.replace("\\\"", "\\\\\"") .replace("\\\"", "\\\\\"")
) );
SerializedSearchIndex { index, desc }
}
pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
let (sign, magnitude): (bool, u32) =
if n >= 0 { (false, n.try_into().unwrap()) } else { (true, (-n).try_into().unwrap()) };
// zig-zag encoding
let value: u32 = (magnitude << 1) | (if sign { 1 } else { 0 });
// Self-terminating hex use capital letters for everything but the
// least significant digit, which is lowercase. For example, decimal 17
// would be `` Aa `` if zig-zag encoding weren't used.
//
// Zig-zag encoding, however, stores the sign bit as the last bit.
// This means, in the last hexit, 1 is actually `c`, -1 is `b`
// (`a` is the imaginary -0), and, because all the bits are shifted
// by one, `` A` `` is actually 8 and `` Aa `` is -8.
//
// https://rust-lang.github.io/rustc-dev-guide/rustdoc-internals/search.html
// describes the encoding in more detail.
let mut shift: u32 = 28;
let mut mask: u32 = 0xF0_00_00_00;
// first skip leading zeroes
while shift < 32 {
let hexit = (value & mask) >> shift;
if hexit != 0 || shift == 0 {
break;
}
shift = shift.wrapping_sub(4);
mask = mask >> 4;
}
// now write the rest
while shift < 32 {
let hexit = (value & mask) >> shift;
let hex = char::try_from(if shift == 0 { '`' } else { '@' } as u32 + hexit).unwrap();
string.push(hex);
shift = shift.wrapping_sub(4);
mask = mask >> 4;
}
} }
pub(crate) fn get_function_type_for_search<'tcx>( pub(crate) fn get_function_type_for_search<'tcx>(

View File

@ -24,6 +24,7 @@ use crate::formats::cache::Cache;
use crate::formats::item_type::ItemType; use crate::formats::item_type::ItemType;
use crate::formats::Impl; use crate::formats::Impl;
use crate::html::format::Buffer; use crate::html::format::Buffer;
use crate::html::render::search_index::SerializedSearchIndex;
use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::render::{AssocItemLink, ImplRenderingParameters};
use crate::html::{layout, static_files}; use crate::html::{layout, static_files};
use crate::visit::DocVisitor; use crate::visit::DocVisitor;
@ -46,7 +47,7 @@ use crate::{try_err, try_none};
pub(super) fn write_shared( pub(super) fn write_shared(
cx: &mut Context<'_>, cx: &mut Context<'_>,
krate: &Crate, krate: &Crate,
search_index: String, search_index: SerializedSearchIndex,
options: &RenderOptions, options: &RenderOptions,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Write out the shared files. Note that these are shared among all rustdoc // Write out the shared files. Note that these are shared among all rustdoc
@ -312,7 +313,7 @@ pub(super) fn write_shared(
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
let (mut all_indexes, mut krates) = let (mut all_indexes, mut krates) =
try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst);
all_indexes.push(search_index); all_indexes.push(search_index.index);
krates.push(krate.name(cx.tcx()).to_string()); krates.push(krate.name(cx.tcx()).to_string());
krates.sort(); krates.sort();
@ -335,6 +336,32 @@ else if (window.initSearch) window.initSearch(searchIndex);
Ok(v.into_bytes()) Ok(v.into_bytes())
})?; })?;
let search_desc_dir = cx.dst.join(format!("search.desc/{krate}", krate = krate.name(cx.tcx())));
if Path::new(&search_desc_dir).exists() {
try_err!(std::fs::remove_dir_all(&search_desc_dir), &search_desc_dir);
}
try_err!(std::fs::create_dir_all(&search_desc_dir), &search_desc_dir);
let kratename = krate.name(cx.tcx()).to_string();
for (i, (_, data)) in search_index.desc.into_iter().enumerate() {
let output_filename = static_files::suffix_path(
&format!("{kratename}-desc-{i}-.js"),
&cx.shared.resource_suffix,
);
let path = search_desc_dir.join(output_filename);
try_err!(
std::fs::write(
&path,
&format!(
r##"searchState.loadedDescShard({kratename}, {i}, {data})"##,
kratename = serde_json::to_string(&kratename).unwrap(),
data = serde_json::to_string(&data).unwrap(),
)
.into_bytes()
),
&path
);
}
write_invocation_specific("crates.js", &|| { write_invocation_specific("crates.js", &|| {
let krates = krates.iter().map(|k| format!("\"{k}\"")).join(","); let krates = krates.iter().map(|k| format!("\"{k}\"")).join(",");
Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes()) Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes())

View File

@ -5,7 +5,7 @@ module.exports = {
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"parserOptions": { "parserOptions": {
"ecmaVersion": 2015, "ecmaVersion": 8,
"sourceType": "module" "sourceType": "module"
}, },
"rules": { "rules": {

View File

@ -329,6 +329,26 @@ function preLoadCss(cssUrl) {
search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>"; search.innerHTML = "<h3 class=\"search-loading\">" + searchState.loadingText + "</h3>";
searchState.showResults(search); searchState.showResults(search);
}, },
descShards: new Map(),
loadDesc: async function({descShard, descIndex}) {
if (descShard.promise === null) {
descShard.promise = new Promise((resolve, reject) => {
descShard.resolve = resolve;
const ds = descShard;
const fname = `${ds.crate}-desc-${ds.shard}-`;
const url = resourcePath(
`search.desc/${descShard.crate}/${fname}`,
".js",
);
loadScript(url, reject);
});
}
const list = await descShard.promise;
return list[descIndex];
},
loadedDescShard: function (crate, shard, data) {
this.descShards.get(crate)[shard].resolve(data.split("\n"));
},
}; };
const toggleAllDocsId = "toggle-all-docs"; const toggleAllDocsId = "toggle-all-docs";
@ -381,7 +401,7 @@ function preLoadCss(cssUrl) {
window.location.replace("#" + item.id); window.location.replace("#" + item.id);
}, 0); }, 0);
} }
} },
); );
} }
} }
@ -585,7 +605,7 @@ function preLoadCss(cssUrl) {
const script = document const script = document
.querySelector("script[data-ignore-extern-crates]"); .querySelector("script[data-ignore-extern-crates]");
const ignoreExternCrates = new Set( const ignoreExternCrates = new Set(
(script ? script.getAttribute("data-ignore-extern-crates") : "").split(",") (script ? script.getAttribute("data-ignore-extern-crates") : "").split(","),
); );
for (const lib of libs) { for (const lib of libs) {
if (lib === window.currentCrate || ignoreExternCrates.has(lib)) { if (lib === window.currentCrate || ignoreExternCrates.has(lib)) {
@ -1098,7 +1118,7 @@ function preLoadCss(cssUrl) {
} else { } else {
wrapper.style.setProperty( wrapper.style.setProperty(
"--popover-arrow-offset", "--popover-arrow-offset",
(wrapperPos.right - pos.right + 4) + "px" (wrapperPos.right - pos.right + 4) + "px",
); );
} }
wrapper.style.visibility = ""; wrapper.style.visibility = "";
@ -1680,7 +1700,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
pendingSidebarResizingFrame = false; pendingSidebarResizingFrame = false;
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
"--resizing-sidebar-width", "--resizing-sidebar-width",
desiredSidebarSize + "px" desiredSidebarSize + "px",
); );
}, 100); }, 100);
} }

View File

@ -206,14 +206,14 @@ const editDistanceState = {
// insertion // insertion
this.current[j - 1] + 1, this.current[j - 1] + 1,
// substitution // substitution
this.prev[j - 1] + substitutionCost this.prev[j - 1] + substitutionCost,
); );
if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) { if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {
// transposition // transposition
this.current[j] = Math.min( this.current[j] = Math.min(
this.current[j], this.current[j],
this.prevPrev[j - 2] + 1 this.prevPrev[j - 2] + 1,
); );
} }
} }
@ -856,8 +856,8 @@ function initSearch(rawSearchIndex) {
parserState, parserState,
parserState.userQuery.slice(start, end), parserState.userQuery.slice(start, end),
generics, generics,
isInGenerics isInGenerics,
) ),
); );
} }
} }
@ -1295,7 +1295,7 @@ function initSearch(rawSearchIndex) {
* *
* @return {ResultsTable} * @return {ResultsTable}
*/ */
function execQuery(parsedQuery, filterCrates, currentCrate) { async function execQuery(parsedQuery, filterCrates, currentCrate) {
const results_others = new Map(), results_in_args = new Map(), const results_others = new Map(), results_in_args = new Map(),
results_returned = new Map(); results_returned = new Map();
@ -1326,6 +1326,7 @@ function initSearch(rawSearchIndex) {
duplicates.add(obj.fullPath); duplicates.add(obj.fullPath);
obj.href = res[1]; obj.href = res[1];
obj.desc = result.desc;
out.push(obj); out.push(obj);
if (out.length >= MAX_RESULTS) { if (out.length >= MAX_RESULTS) {
break; break;
@ -1342,9 +1343,9 @@ function initSearch(rawSearchIndex) {
* @param {Results} results * @param {Results} results
* @param {boolean} isType * @param {boolean} isType
* @param {string} preferredCrate * @param {string} preferredCrate
* @returns {[ResultObject]} * @returns {Promise<[ResultObject]>}
*/ */
function sortResults(results, isType, preferredCrate) { async function sortResults(results, isType, preferredCrate) {
const userQuery = parsedQuery.userQuery; const userQuery = parsedQuery.userQuery;
const result_list = []; const result_list = [];
for (const result of results.values()) { for (const result of results.values()) {
@ -1352,6 +1353,12 @@ function initSearch(rawSearchIndex) {
result.word = searchIndex[result.id].word; result.word = searchIndex[result.id].word;
result_list.push(result); result_list.push(result);
} }
for (const result of result_list) {
result.desc = searchState.loadDesc(result.item);
}
for (const result of result_list) {
result.desc = await result.desc;
}
result_list.sort((aaa, bbb) => { result_list.sort((aaa, bbb) => {
let a, b; let a, b;
@ -1422,8 +1429,8 @@ function initSearch(rawSearchIndex) {
} }
// sort by description (no description goes later) // sort by description (no description goes later)
a = (aaa.item.desc === ""); a = (aaa.desc === "");
b = (bbb.item.desc === ""); b = (bbb.desc === "");
if (a !== b) { if (a !== b) {
return a - b; return a - b;
} }
@ -1477,7 +1484,7 @@ function initSearch(rawSearchIndex) {
whereClause, whereClause,
mgensIn, mgensIn,
solutionCb, solutionCb,
unboxingDepth unboxingDepth,
) { ) {
if (unboxingDepth >= UNBOXING_LIMIT) { if (unboxingDepth >= UNBOXING_LIMIT) {
return false; return false;
@ -1524,7 +1531,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth + 1 unboxingDepth + 1,
)) { )) {
continue; continue;
} }
@ -1541,7 +1548,7 @@ function initSearch(rawSearchIndex) {
whereClause, whereClause,
mgensScratch, mgensScratch,
solutionCb, solutionCb,
unboxingDepth + 1 unboxingDepth + 1,
)) { )) {
return true; return true;
} }
@ -1551,7 +1558,7 @@ function initSearch(rawSearchIndex) {
whereClause, whereClause,
mgens ? new Map(mgens) : null, mgens ? new Map(mgens) : null,
solutionCb, solutionCb,
unboxingDepth + 1 unboxingDepth + 1,
)) { )) {
return true; return true;
} }
@ -1625,7 +1632,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgensScratch, mgensScratch,
unboxingDepth unboxingDepth,
); );
if (!solution) { if (!solution) {
return false; return false;
@ -1638,7 +1645,7 @@ function initSearch(rawSearchIndex) {
whereClause, whereClause,
simplifiedMgens, simplifiedMgens,
solutionCb, solutionCb,
unboxingDepth unboxingDepth,
); );
if (passesUnification) { if (passesUnification) {
return true; return true;
@ -1646,7 +1653,7 @@ function initSearch(rawSearchIndex) {
} }
return false; return false;
}, },
unboxingDepth unboxingDepth,
); );
if (passesUnification) { if (passesUnification) {
return true; return true;
@ -1663,7 +1670,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth + 1 unboxingDepth + 1,
)) { )) {
continue; continue;
} }
@ -1689,7 +1696,7 @@ function initSearch(rawSearchIndex) {
whereClause, whereClause,
mgensScratch, mgensScratch,
solutionCb, solutionCb,
unboxingDepth + 1 unboxingDepth + 1,
); );
if (passesUnification) { if (passesUnification) {
return true; return true;
@ -1820,7 +1827,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgensIn, mgensIn,
unboxingDepth unboxingDepth,
) { ) {
if (fnType.bindings.size < queryElem.bindings.size) { if (fnType.bindings.size < queryElem.bindings.size) {
return false; return false;
@ -1849,7 +1856,7 @@ function initSearch(rawSearchIndex) {
// possible solutions // possible solutions
return false; return false;
}, },
unboxingDepth unboxingDepth,
); );
return newSolutions; return newSolutions;
}); });
@ -1887,7 +1894,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth unboxingDepth,
) { ) {
if (unboxingDepth >= UNBOXING_LIMIT) { if (unboxingDepth >= UNBOXING_LIMIT) {
return false; return false;
@ -1914,7 +1921,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgensTmp, mgensTmp,
unboxingDepth unboxingDepth,
); );
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) { } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
const simplifiedGenerics = [ const simplifiedGenerics = [
@ -1926,7 +1933,7 @@ function initSearch(rawSearchIndex) {
queryElem, queryElem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth unboxingDepth,
); );
} }
return false; return false;
@ -1975,7 +1982,7 @@ function initSearch(rawSearchIndex) {
elem, elem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth + 1 unboxingDepth + 1,
); );
} }
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 && if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
@ -1989,7 +1996,7 @@ function initSearch(rawSearchIndex) {
elem, elem,
whereClause, whereClause,
mgens, mgens,
unboxingDepth unboxingDepth,
); );
} }
} }
@ -2007,7 +2014,7 @@ function initSearch(rawSearchIndex) {
return 0; return 0;
} }
const maxPathEditDistance = Math.floor( const maxPathEditDistance = Math.floor(
contains.reduce((acc, next) => acc + next.length, 0) / 3 contains.reduce((acc, next) => acc + next.length, 0) / 3,
); );
let ret_dist = maxPathEditDistance + 1; let ret_dist = maxPathEditDistance + 1;
const path = ty.path.split("::"); const path = ty.path.split("::");
@ -2066,7 +2073,8 @@ function initSearch(rawSearchIndex) {
crate: item.crate, crate: item.crate,
name: item.name, name: item.name,
path: item.path, path: item.path,
desc: item.desc, descShard: item.descShard,
descIndex: item.descIndex,
ty: item.ty, ty: item.ty,
parent: item.parent, parent: item.parent,
type: item.type, type: item.type,
@ -2192,7 +2200,7 @@ function initSearch(rawSearchIndex) {
results_others, results_others,
results_in_args, results_in_args,
results_returned, results_returned,
maxEditDistance maxEditDistance,
) { ) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) { if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return; return;
@ -2204,7 +2212,7 @@ function initSearch(rawSearchIndex) {
// atoms in the function not present in the query // atoms in the function not present in the query
const tfpDist = compareTypeFingerprints( const tfpDist = compareTypeFingerprints(
fullId, fullId,
parsedQuery.typeFingerprint parsedQuery.typeFingerprint,
); );
if (tfpDist !== null) { if (tfpDist !== null) {
const in_args = row.type && row.type.inputs const in_args = row.type && row.type.inputs
@ -2276,7 +2284,7 @@ function initSearch(rawSearchIndex) {
const tfpDist = compareTypeFingerprints( const tfpDist = compareTypeFingerprints(
row.id, row.id,
parsedQuery.typeFingerprint parsedQuery.typeFingerprint,
); );
if (tfpDist === null) { if (tfpDist === null) {
return; return;
@ -2298,10 +2306,10 @@ function initSearch(rawSearchIndex) {
row.type.where_clause, row.type.where_clause,
mgens, mgens,
null, null,
0 // unboxing depth 0, // unboxing depth
); );
}, },
0 // unboxing depth 0, // unboxing depth
)) { )) {
return; return;
} }
@ -2419,7 +2427,7 @@ function initSearch(rawSearchIndex) {
} }
return [typeNameIdMap.get(name).id, constraints]; return [typeNameIdMap.get(name).id, constraints];
}) }),
); );
} }
@ -2446,7 +2454,7 @@ function initSearch(rawSearchIndex) {
results_others, results_others,
results_in_args, results_in_args,
results_returned, results_returned,
maxEditDistance maxEditDistance,
); );
} }
} }
@ -2478,9 +2486,9 @@ function initSearch(rawSearchIndex) {
} }
const ret = createQueryResults( const ret = createQueryResults(
sortResults(results_in_args, true, currentCrate), await sortResults(results_in_args, true, currentCrate),
sortResults(results_returned, true, currentCrate), await sortResults(results_returned, true, currentCrate),
sortResults(results_others, false, currentCrate), await sortResults(results_others, false, currentCrate),
parsedQuery); parsedQuery);
handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate); handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate);
if (parsedQuery.error !== null && ret.others.length !== 0) { if (parsedQuery.error !== null && ret.others.length !== 0) {
@ -2581,14 +2589,14 @@ function initSearch(rawSearchIndex) {
* @param {ParsedQuery} query * @param {ParsedQuery} query
* @param {boolean} display - True if this is the active tab * @param {boolean} display - True if this is the active tab
*/ */
function addTab(array, query, display) { async function addTab(array, query, display) {
const extraClass = display ? " active" : ""; const extraClass = display ? " active" : "";
const output = document.createElement("div"); const output = document.createElement("div");
if (array.length > 0) { if (array.length > 0) {
output.className = "search-results " + extraClass; output.className = "search-results " + extraClass;
array.forEach(item => { for (const item of array) {
const name = item.name; const name = item.name;
const type = itemTypes[item.ty]; const type = itemTypes[item.ty];
const longType = longItemTypes[item.ty]; const longType = longItemTypes[item.ty];
@ -2624,7 +2632,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
link.appendChild(description); link.appendChild(description);
output.appendChild(link); output.appendChild(link);
}); }
} else if (query.error === null) { } else if (query.error === null) {
output.className = "search-failed" + extraClass; output.className = "search-failed" + extraClass;
output.innerHTML = "No results :(<br/>" + output.innerHTML = "No results :(<br/>" +
@ -2666,7 +2674,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* @param {boolean} go_to_first * @param {boolean} go_to_first
* @param {string} filterCrates * @param {string} filterCrates
*/ */
function showResults(results, go_to_first, filterCrates) { async function showResults(results, go_to_first, filterCrates) {
const search = searchState.outputElement(); const search = searchState.outputElement();
if (go_to_first || (results.others.length === 1 if (go_to_first || (results.others.length === 1
&& getSettingValue("go-to-only-result") === "true") && getSettingValue("go-to-only-result") === "true")
@ -2699,9 +2707,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\
currentResults = results.query.userQuery; currentResults = results.query.userQuery;
const ret_others = addTab(results.others, results.query, true); const ret_others = await addTab(results.others, results.query, true);
const ret_in_args = addTab(results.in_args, results.query, false); const ret_in_args = await addTab(results.in_args, results.query, false);
const ret_returned = addTab(results.returned, results.query, false); const ret_returned = await addTab(results.returned, results.query, false);
// Navigate to the relevant tab if the current tab is empty, like in case users search // Navigate to the relevant tab if the current tab is empty, like in case users search
// for "-> String". If they had selected another tab previously, they have to click on // for "-> String". If they had selected another tab previously, they have to click on
@ -2822,7 +2830,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* and display the results. * and display the results.
* @param {boolean} [forced] * @param {boolean} [forced]
*/ */
function search(forced) { async function search(forced) {
const query = parseQuery(searchState.input.value.trim()); const query = parseQuery(searchState.input.value.trim());
let filterCrates = getFilterCrates(); let filterCrates = getFilterCrates();
@ -2850,8 +2858,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// recent search query is added to the browser history. // recent search query is added to the browser history.
updateSearchHistory(buildUrl(query.original, filterCrates)); updateSearchHistory(buildUrl(query.original, filterCrates));
showResults( await showResults(
execQuery(query, filterCrates, window.currentCrate), await execQuery(query, filterCrates, window.currentCrate),
params.go_to_first, params.go_to_first,
filterCrates); filterCrates);
} }
@ -2920,7 +2928,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
pathIndex = type[PATH_INDEX_DATA]; pathIndex = type[PATH_INDEX_DATA];
generics = buildItemSearchTypeAll( generics = buildItemSearchTypeAll(
type[GENERICS_DATA], type[GENERICS_DATA],
lowercasePaths lowercasePaths,
); );
if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) { if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) {
bindings = new Map(type[BINDINGS_DATA].map(binding => { bindings = new Map(type[BINDINGS_DATA].map(binding => {
@ -3030,101 +3038,49 @@ ${item.displayPath}<span class="${type}">${name}</span>\
* The raw function search type format is generated using serde in * The raw function search type format is generated using serde in
* librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string
* *
* @param {{
* string: string,
* offset: number,
* backrefQueue: FunctionSearchType[]
* }} itemFunctionDecoder
* @param {Array<{name: string, ty: number}>} lowercasePaths * @param {Array<{name: string, ty: number}>} lowercasePaths
* @param {Map<string, integer>}
* *
* @return {null|FunctionSearchType} * @return {null|FunctionSearchType}
*/ */
function buildFunctionSearchType(itemFunctionDecoder, lowercasePaths) { function buildFunctionSearchTypeCallback(lowercasePaths) {
const c = itemFunctionDecoder.string.charCodeAt(itemFunctionDecoder.offset); return functionSearchType => {
itemFunctionDecoder.offset += 1; if (functionSearchType === 0) {
const [zero, ua, la, ob, cb] = ["0", "@", "`", "{", "}"].map(c => c.charCodeAt(0)); return null;
// `` ` `` is used as a sentinel because it's fewer bytes than `null`, and decodes to zero
// `0` is a backref
if (c === la) {
return null;
}
// sixteen characters after "0" are backref
if (c >= zero && c < ua) {
return itemFunctionDecoder.backrefQueue[c - zero];
}
if (c !== ob) {
throw ["Unexpected ", c, " in function: expected ", "{", "; this is a bug"];
}
// call after consuming `{`
function decodeList() {
let c = itemFunctionDecoder.string.charCodeAt(itemFunctionDecoder.offset);
const ret = [];
while (c !== cb) {
ret.push(decode());
c = itemFunctionDecoder.string.charCodeAt(itemFunctionDecoder.offset);
} }
itemFunctionDecoder.offset += 1; // eat cb const INPUTS_DATA = 0;
return ret; const OUTPUT_DATA = 1;
} let inputs, output;
// consumes and returns a list or integer if (typeof functionSearchType[INPUTS_DATA] === "number") {
function decode() { inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)];
let n = 0;
let c = itemFunctionDecoder.string.charCodeAt(itemFunctionDecoder.offset);
if (c === ob) {
itemFunctionDecoder.offset += 1;
return decodeList();
}
while (c < la) {
n = (n << 4) | (c & 0xF);
itemFunctionDecoder.offset += 1;
c = itemFunctionDecoder.string.charCodeAt(itemFunctionDecoder.offset);
}
// last character >= la
n = (n << 4) | (c & 0xF);
const [sign, value] = [n & 1, n >> 1];
itemFunctionDecoder.offset += 1;
return sign ? -value : value;
}
const functionSearchType = decodeList();
const INPUTS_DATA = 0;
const OUTPUT_DATA = 1;
let inputs, output;
if (typeof functionSearchType[INPUTS_DATA] === "number") {
inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)];
} else {
inputs = buildItemSearchTypeAll(
functionSearchType[INPUTS_DATA],
lowercasePaths
);
}
if (functionSearchType.length > 1) {
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)];
} else { } else {
output = buildItemSearchTypeAll( inputs = buildItemSearchTypeAll(
functionSearchType[OUTPUT_DATA], functionSearchType[INPUTS_DATA],
lowercasePaths lowercasePaths,
); );
} }
} else { if (functionSearchType.length > 1) {
output = []; if (typeof functionSearchType[OUTPUT_DATA] === "number") {
} output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)];
const where_clause = []; } else {
const l = functionSearchType.length; output = buildItemSearchTypeAll(
for (let i = 2; i < l; ++i) { functionSearchType[OUTPUT_DATA],
where_clause.push(typeof functionSearchType[i] === "number" lowercasePaths,
? [buildItemSearchType(functionSearchType[i], lowercasePaths)] );
: buildItemSearchTypeAll(functionSearchType[i], lowercasePaths)); }
} } else {
const ret = { output = [];
inputs, output, where_clause, }
const where_clause = [];
const l = functionSearchType.length;
for (let i = 2; i < l; ++i) {
where_clause.push(typeof functionSearchType[i] === "number"
? [buildItemSearchType(functionSearchType[i], lowercasePaths)]
: buildItemSearchTypeAll(functionSearchType[i], lowercasePaths));
}
return {
inputs, output, where_clause,
};
}; };
itemFunctionDecoder.backrefQueue.unshift(ret);
if (itemFunctionDecoder.backrefQueue.length > 16) {
itemFunctionDecoder.backrefQueue.pop();
}
return ret;
} }
/** /**
@ -3245,6 +3201,68 @@ ${item.displayPath}<span class="${type}">${name}</span>\
return functionTypeFingerprint[(fullId * 4) + 3]; return functionTypeFingerprint[(fullId * 4) + 3];
} }
class VlqHexDecoder {
constructor(string, cons) {
this.string = string;
this.cons = cons;
this.offset = 0;
this.backrefQueue = [];
}
// call after consuming `{`
decodeList() {
const cb = "}".charCodeAt(0);
let c = this.string.charCodeAt(this.offset);
const ret = [];
while (c !== cb) {
ret.push(this.decode());
c = this.string.charCodeAt(this.offset);
}
this.offset += 1; // eat cb
return ret;
}
// consumes and returns a list or integer
decode() {
const [ob, la] = ["{", "`"].map(c => c.charCodeAt(0));
let n = 0;
let c = this.string.charCodeAt(this.offset);
if (c === ob) {
this.offset += 1;
return this.decodeList();
}
while (c < la) {
n = (n << 4) | (c & 0xF);
this.offset += 1;
c = this.string.charCodeAt(this.offset);
}
// last character >= la
n = (n << 4) | (c & 0xF);
const [sign, value] = [n & 1, n >> 1];
this.offset += 1;
return sign ? -value : value;
}
next() {
const c = this.string.charCodeAt(this.offset);
const [zero, ua, la] = ["0", "@", "`"].map(c => c.charCodeAt(0));
// sixteen characters after "0" are backref
if (c >= zero && c < ua) {
this.offset += 1;
return this.backrefQueue[c - zero];
}
// special exception: 0 doesn't use backref encoding
// it's already one character, and it's always nullish
if (c === la) {
this.offset += 1;
return this.cons(0);
}
const result = this.cons(this.decode());
this.backrefQueue.unshift(result);
if (this.backrefQueue.length > 16) {
this.backrefQueue.pop();
}
return result;
}
}
/** /**
* Convert raw search index into in-memory search index. * Convert raw search index into in-memory search index.
* *
@ -3271,18 +3289,32 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id = 0; id = 0;
for (const [crate, crateCorpus] of rawSearchIndex) { for (const [crate, crateCorpus] of rawSearchIndex) {
// a string representing the lengths of each description shard
// a string representing the list of function types
const itemDescShardDecoder = new VlqHexDecoder(crateCorpus.D, noop => noop);
let descShard = {
crate,
shard: 0,
start: 0,
len: itemDescShardDecoder.next(),
promise: null,
resolve: null,
};
const descShardList = [ descShard ];
// This object should have exactly the same set of fields as the "row" // This object should have exactly the same set of fields as the "row"
// object defined below. Your JavaScript runtime will thank you. // object defined below. Your JavaScript runtime will thank you.
// https://mathiasbynens.be/notes/shapes-ics // https://mathiasbynens.be/notes/shapes-ics
const crateRow = { const crateRow = {
crate: crate, crate,
ty: 3, // == ExternCrate ty: 3, // == ExternCrate
name: crate, name: crate,
path: "", path: "",
desc: crateCorpus.doc, descShard,
descIndex: 0,
parent: undefined, parent: undefined,
type: null, type: null,
id: id, id,
word: crate, word: crate,
normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
deprecated: null, deprecated: null,
@ -3302,16 +3334,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present, // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
// 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11 // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
const itemPaths = new Map(crateCorpus.q); const itemPaths = new Map(crateCorpus.q);
// an array of (String) descriptions
const itemDescs = crateCorpus.d;
// an array of (Number) the parent path index + 1 to `paths`, or 0 if none // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
const itemParentIdxs = crateCorpus.i; const itemParentIdxs = crateCorpus.i;
// a string representing the list of function types
const itemFunctionDecoder = {
string: crateCorpus.f,
offset: 0,
backrefQueue: [],
};
// an array of (Number) indices for the deprecated items // an array of (Number) indices for the deprecated items
const deprecatedItems = new Set(crateCorpus.c); const deprecatedItems = new Set(crateCorpus.c);
// an array of (Number) indices for the deprecated items // an array of (Number) indices for the deprecated items
@ -3326,6 +3350,12 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// an array of [{name: String, ty: Number}] // an array of [{name: String, ty: Number}]
const lowercasePaths = []; const lowercasePaths = [];
// a string representing the list of function types
const itemFunctionDecoder = new VlqHexDecoder(
crateCorpus.f,
buildFunctionSearchTypeCallback(lowercasePaths),
);
// convert `rawPaths` entries into object form // convert `rawPaths` entries into object form
// generate normalizedPaths for function search mode // generate normalizedPaths for function search mode
let len = paths.length; let len = paths.length;
@ -3353,13 +3383,26 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// faster analysis operations // faster analysis operations
lastPath = ""; lastPath = "";
len = itemTypes.length; len = itemTypes.length;
let descIndex = 1;
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (descIndex >= descShard.len) {
descShard = {
crate,
shard: descShard.shard + 1,
start: descShard.start + descShard.len,
len: itemDescShardDecoder.next(),
promise: null,
resolve: null,
};
descIndex = 0;
descShardList.push(descShard);
}
let word = ""; let word = "";
if (typeof itemNames[i] === "string") { if (typeof itemNames[i] === "string") {
word = itemNames[i].toLowerCase(); word = itemNames[i].toLowerCase();
} }
const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath; const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath;
const type = buildFunctionSearchType(itemFunctionDecoder, lowercasePaths); const type = itemFunctionDecoder.next();
if (type !== null) { if (type !== null) {
if (type) { if (type) {
const fp = functionTypeFingerprint.subarray(id * 4, (id + 1) * 4); const fp = functionTypeFingerprint.subarray(id * 4, (id + 1) * 4);
@ -3380,14 +3423,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
// This object should have exactly the same set of fields as the "crateRow" // This object should have exactly the same set of fields as the "crateRow"
// object defined above. // object defined above.
const row = { const row = {
crate: crate, crate,
ty: itemTypes.charCodeAt(i) - charA, ty: itemTypes.charCodeAt(i) - charA,
name: itemNames[i], name: itemNames[i],
path: path, path,
desc: itemDescs[i], descShard,
descIndex,
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
type, type,
id: id, id,
word, word,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
deprecated: deprecatedItems.has(i), deprecated: deprecatedItems.has(i),
@ -3396,6 +3440,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
id += 1; id += 1;
searchIndex.push(row); searchIndex.push(row);
lastPath = row.path; lastPath = row.path;
descIndex += 1;
} }
if (aliases) { if (aliases) {
@ -3419,6 +3464,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
} }
} }
currentIndex += itemTypes.length; currentIndex += itemTypes.length;
searchState.descShards.set(crate, descShardList);
} }
// Drop the (rather large) hash table used for reusing function items // Drop the (rather large) hash table used for reusing function items
TYPES_POOL = new Map(); TYPES_POOL = new Map();

View File

@ -211,14 +211,14 @@ function updateSidebarWidth() {
if (desktopSidebarWidth && desktopSidebarWidth !== "null") { if (desktopSidebarWidth && desktopSidebarWidth !== "null") {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
"--desktop-sidebar-width", "--desktop-sidebar-width",
desktopSidebarWidth + "px" desktopSidebarWidth + "px",
); );
} }
const srcSidebarWidth = getSettingValue("src-sidebar-width"); const srcSidebarWidth = getSettingValue("src-sidebar-width");
if (srcSidebarWidth && srcSidebarWidth !== "null") { if (srcSidebarWidth && srcSidebarWidth !== "null") {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
"--src-sidebar-width", "--src-sidebar-width",
srcSidebarWidth + "px" srcSidebarWidth + "px",
); );
} }
} }

View File

@ -6,7 +6,7 @@ module.exports = {
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"parserOptions": { "parserOptions": {
"ecmaVersion": 2015, "ecmaVersion": 8,
"sourceType": "module" "sourceType": "module"
}, },
"rules": { "rules": {

View File

@ -1,3 +1,4 @@
/* global globalThis */
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
@ -133,7 +134,7 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
expected_value, expected_value,
result.get(key), result.get(key),
error_text, error_text,
queryName queryName,
); );
} else { } else {
error_text.push(`${queryName}==> EXPECTED has extra key in map from field ` + error_text.push(`${queryName}==> EXPECTED has extra key in map from field ` +
@ -212,11 +213,11 @@ function runParser(query, expected, parseQuery, queryName) {
return error_text; return error_text;
} }
function runSearch(query, expected, doSearch, loadedFile, queryName) { async function runSearch(query, expected, doSearch, loadedFile, queryName) {
const ignore_order = loadedFile.ignore_order; const ignore_order = loadedFile.ignore_order;
const exact_check = loadedFile.exact_check; const exact_check = loadedFile.exact_check;
const results = doSearch(query, loadedFile.FILTER_CRATE); const results = await doSearch(query, loadedFile.FILTER_CRATE);
const error_text = []; const error_text = [];
for (const key in expected) { for (const key in expected) {
@ -238,7 +239,7 @@ function runSearch(query, expected, doSearch, loadedFile, queryName) {
} }
let prev_pos = -1; let prev_pos = -1;
entry.forEach((elem, index) => { for (const [index, elem] of entry.entries()) {
const entry_pos = lookForEntry(elem, results[key]); const entry_pos = lookForEntry(elem, results[key]);
if (entry_pos === -1) { if (entry_pos === -1) {
error_text.push(queryName + "==> Result not found in '" + key + "': '" + error_text.push(queryName + "==> Result not found in '" + key + "': '" +
@ -260,13 +261,13 @@ function runSearch(query, expected, doSearch, loadedFile, queryName) {
} else { } else {
prev_pos = entry_pos; prev_pos = entry_pos;
} }
}); }
} }
return error_text; return error_text;
} }
function runCorrections(query, corrections, getCorrections, loadedFile) { async function runCorrections(query, corrections, getCorrections, loadedFile) {
const qc = getCorrections(query, loadedFile.FILTER_CRATE); const qc = await getCorrections(query, loadedFile.FILTER_CRATE);
const error_text = []; const error_text = [];
if (corrections === null) { if (corrections === null) {
@ -299,18 +300,27 @@ function checkResult(error_text, loadedFile, displaySuccess) {
return 1; return 1;
} }
function runCheckInner(callback, loadedFile, entry, getCorrections, extra) { async function runCheckInner(callback, loadedFile, entry, getCorrections, extra) {
if (typeof entry.query !== "string") { if (typeof entry.query !== "string") {
console.log("FAILED"); console.log("FAILED");
console.log("==> Missing `query` field"); console.log("==> Missing `query` field");
return false; return false;
} }
let error_text = callback(entry.query, entry, extra ? "[ query `" + entry.query + "`]" : ""); let error_text = await callback(
entry.query,
entry,
extra ? "[ query `" + entry.query + "`]" : "",
);
if (checkResult(error_text, loadedFile, false) !== 0) { if (checkResult(error_text, loadedFile, false) !== 0) {
return false; return false;
} }
if (entry.correction !== undefined) { if (entry.correction !== undefined) {
error_text = runCorrections(entry.query, entry.correction, getCorrections, loadedFile); error_text = await runCorrections(
entry.query,
entry.correction,
getCorrections,
loadedFile,
);
if (checkResult(error_text, loadedFile, false) !== 0) { if (checkResult(error_text, loadedFile, false) !== 0) {
return false; return false;
} }
@ -318,16 +328,16 @@ function runCheckInner(callback, loadedFile, entry, getCorrections, extra) {
return true; return true;
} }
function runCheck(loadedFile, key, getCorrections, callback) { async function runCheck(loadedFile, key, getCorrections, callback) {
const expected = loadedFile[key]; const expected = loadedFile[key];
if (Array.isArray(expected)) { if (Array.isArray(expected)) {
for (const entry of expected) { for (const entry of expected) {
if (!runCheckInner(callback, loadedFile, entry, getCorrections, true)) { if (!await runCheckInner(callback, loadedFile, entry, getCorrections, true)) {
return 1; return 1;
} }
} }
} else if (!runCheckInner(callback, loadedFile, expected, getCorrections, false)) { } else if (!await runCheckInner(callback, loadedFile, expected, getCorrections, false)) {
return 1; return 1;
} }
console.log("OK"); console.log("OK");
@ -338,7 +348,7 @@ function hasCheck(content, checkName) {
return content.startsWith(`const ${checkName}`) || content.includes(`\nconst ${checkName}`); return content.startsWith(`const ${checkName}`) || content.includes(`\nconst ${checkName}`);
} }
function runChecks(testFile, doSearch, parseQuery, getCorrections) { async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
let checkExpected = false; let checkExpected = false;
let checkParsed = false; let checkParsed = false;
let testFileContent = readFile(testFile); let testFileContent = readFile(testFile);
@ -367,12 +377,12 @@ function runChecks(testFile, doSearch, parseQuery, getCorrections) {
let res = 0; let res = 0;
if (checkExpected) { if (checkExpected) {
res += runCheck(loadedFile, "EXPECTED", getCorrections, (query, expected, text) => { res += await runCheck(loadedFile, "EXPECTED", getCorrections, (query, expected, text) => {
return runSearch(query, expected, doSearch, loadedFile, text); return runSearch(query, expected, doSearch, loadedFile, text);
}); });
} }
if (checkParsed) { if (checkParsed) {
res += runCheck(loadedFile, "PARSED", getCorrections, (query, expected, text) => { res += await runCheck(loadedFile, "PARSED", getCorrections, (query, expected, text) => {
return runParser(query, expected, parseQuery, text); return runParser(query, expected, parseQuery, text);
}); });
} }
@ -393,6 +403,35 @@ function loadSearchJS(doc_folder, resource_suffix) {
const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js"); const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
const searchIndex = require(searchIndexJs); const searchIndex = require(searchIndexJs);
globalThis.searchState = {
descShards: new Map(),
loadDesc: async function({descShard, descIndex}) {
if (descShard.promise === null) {
descShard.promise = new Promise((resolve, reject) => {
descShard.resolve = resolve;
const ds = descShard;
const fname = `${ds.crate}-desc-${ds.shard}-${resource_suffix}.js`;
fs.readFile(
`${doc_folder}/search.desc/${descShard.crate}/${fname}`,
(err, data) => {
if (err) {
reject(err);
} else {
eval(data.toString("utf8"));
}
},
);
});
}
const list = await descShard.promise;
return list[descIndex];
},
loadedDescShard: function (crate, shard, data) {
//console.log(this.descShards);
this.descShards.get(crate)[shard].resolve(data.split("\n"));
},
};
const staticFiles = path.join(doc_folder, "static.files"); const staticFiles = path.join(doc_folder, "static.files");
const searchJs = fs.readdirSync(staticFiles).find(f => f.match(/search.*\.js$/)); const searchJs = fs.readdirSync(staticFiles).find(f => f.match(/search.*\.js$/));
const searchModule = require(path.join(staticFiles, searchJs)); const searchModule = require(path.join(staticFiles, searchJs));
@ -474,7 +513,7 @@ function parseOptions(args) {
return null; return null;
} }
function main(argv) { async function main(argv) {
const opts = parseOptions(argv.slice(2)); const opts = parseOptions(argv.slice(2));
if (opts === null) { if (opts === null) {
return 1; return 1;
@ -482,7 +521,7 @@ function main(argv) {
const parseAndSearch = loadSearchJS( const parseAndSearch = loadSearchJS(
opts["doc_folder"], opts["doc_folder"],
opts["resource_suffix"] opts["resource_suffix"],
); );
let errors = 0; let errors = 0;
@ -494,21 +533,34 @@ function main(argv) {
}; };
if (opts["test_file"].length !== 0) { if (opts["test_file"].length !== 0) {
opts["test_file"].forEach(file => { for (const file of opts["test_file"]) {
process.stdout.write(`Testing ${file} ... `); process.stdout.write(`Testing ${file} ... `);
errors += runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections); errors += await runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections);
}); }
} else if (opts["test_folder"].length !== 0) { } else if (opts["test_folder"].length !== 0) {
fs.readdirSync(opts["test_folder"]).forEach(file => { for (const file of fs.readdirSync(opts["test_folder"])) {
if (!file.endsWith(".js")) { if (!file.endsWith(".js")) {
return; continue;
} }
process.stdout.write(`Testing ${file} ... `); process.stdout.write(`Testing ${file} ... `);
errors += runChecks(path.join(opts["test_folder"], file), doSearch, errors += await runChecks(path.join(opts["test_folder"], file), doSearch,
parseAndSearch.parseQuery, getCorrections); parseAndSearch.parseQuery, getCorrections);
}); }
} }
return errors > 0 ? 1 : 0; return errors > 0 ? 1 : 0;
} }
process.exit(main(process.argv)); main(process.argv).catch(e => {
console.log(e);
process.exit(1);
}).then(x => process.exit(x));
process.on("beforeExit", () => {
console.log("process did not complete");
process.exit(1);
});
/*process.on("uncaughtException", (err) => {
console.log(`Uncaught Exception: ${err.message}`);
process.exit(1);
});*/

View File

@ -1,6 +1,6 @@
#![crate_name = "foo"] #![crate_name = "foo"]
// @hasraw 'search-index.js' 'Foo short link.' // @hasraw 'search.desc/foo/foo-desc-0-.js' 'Foo short link.'
// @!hasraw - 'www.example.com' // @!hasraw - 'www.example.com'
// @!hasraw - 'More Foo.' // @!hasraw - 'More Foo.'