1// ignore-tidy-filelength2/* global addClass, getNakedUrl, getVar, getSettingValue, hasClass, nonnull */3/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi */45"use strict";67/**8 * @param {stringdex.Stringdex} Stringdex9 * @param {typeof stringdex.RoaringBitmap} RoaringBitmap10 * @param {stringdex.Hooks} hooks11 */12const initSearch = async function(Stringdex, RoaringBitmap, hooks) {1314// polyfill15// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced16if (!Array.prototype.toSpliced) {17 // Can't use arrow functions, because we want `this`18 Array.prototype.toSpliced = function() {19 const me = this.slice();20 // @ts-expect-error21 Array.prototype.splice.apply(me, arguments);22 return me;23 };24}2526/**27 *28 * @template T29 * @param {Iterable<T>} arr30 * @param {function(T): Promise<any>} func31 * @param {function(T): void} funcBtwn32 */33async function onEachBtwnAsync(arr, func, funcBtwn) {34 let skipped = true;35 for (const value of arr) {36 if (!skipped) {37 funcBtwn(value);38 }39 skipped = await func(value);40 }41}4243/**44 * Allow the browser to redraw.45 * @returns {Promise<void>}46 */47const yieldToBrowser = typeof window !== "undefined" && window.requestIdleCallback ?48 function() {49 return new Promise((resolve, _reject) => {50 window.requestIdleCallback(resolve);51 });52 } :53 function() {54 return new Promise((resolve, _reject) => {55 setTimeout(resolve, 0);56 });57 };5859/**60 * Promise-based timer wrapper.61 * @param {number} ms62 * @returns {Promise<void>}63 */64const timeout = function(ms) {65 return new Promise((resolve, _reject) => {66 setTimeout(resolve, ms);67 });68};6970if (!Promise.withResolvers) {71 /**72 * Polyfill73 * @template T74 * @returns {{75 "promise": Promise<T>,76 "resolve": (function(T): void),77 "reject": (function(any): void)78 }}79 */80 Promise.withResolvers = () => {81 let resolve, reject;82 const promise = new Promise((res, rej) => {83 resolve = res;84 reject = rej;85 });86 // @ts-expect-error87 return {promise, resolve, reject};88 };89}9091// ==================== Core search logic begin ====================92// This mapping table should match the discriminants of93// `rustdoc::formats::item_type::ItemType` type in Rust.94const itemTypes = Object.freeze({95 keyword: 0,96 primitive: 1,97 mod: 2,98 externcrate: 3,99 import: 4,100 struct: 5,101 enum: 6,102 fn: 7,103 type: 8,104 static: 9,105 trait: 10,106 impl: 11,107 tymethod: 12,108 method: 13,109 structfield: 14,110 variant: 15,111 macro: 16,112 associatedtype: 17,113 constant: 18,114 associatedconstant: 19,115 union: 20,116 foreigntype: 21,117 existential: 22,118 attr: 23,119 derive: 24,120 traitalias: 25,121 generic: 26,122 attribute: 27,123});124const itemTypesName = Array.from(Object.keys(itemTypes));125126// When filtering, some types might be included as well. For example, when you filter on `constant`,127// we also include associated constant items.128//129// This map is built as follows: the first item of the array is the type to be included when the130// second type of the array is used as filter.131const itemParents = new Map([132 [itemTypes.associatedconstant, itemTypes.constant],133 [itemTypes.method, itemTypes.fn],134 [itemTypes.tymethod, itemTypes.fn],135 [itemTypes.primitive, itemTypes.type],136 [itemTypes.associatedtype, itemTypes.type],137 [itemTypes.traitalias, itemTypes.trait],138 [itemTypes.attr, itemTypes.macro],139 [itemTypes.derive, itemTypes.macro],140 [itemTypes.externcrate, itemTypes.import],141]);142143const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";144145// Hard limit on how deep to recurse into generics when doing type-driven search.146// This needs limited, partially because147// a search for `Ty` shouldn't match `WithInfcx<ParamEnvAnd<Vec<ConstTy<Interner<Ty=Ty>>>>>`,148// but mostly because this is the simplest and most principled way to limit the number149// of permutations we need to check.150const UNBOXING_LIMIT = 5;151152// used for search query verification153// because searches are often performed using substrings of identifiers,154// and not just full identiferes, we allow them to start with chars that otherwise155// can only appear in the middle of identifiers156const REGEX_IDENT = /\p{ID_Continue}+/uy;157const REGEX_INVALID_TYPE_FILTER = /[^a-z]/ui;158159const MAX_RESULTS = 200;160const NO_TYPE_FILTER = -1;161const DEPRECATED_COUNT_SELECTOR = "deprecated-count";162163/**164 * The [edit distance] is a metric for measuring the difference between two strings.165 *166 * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance167 */168169/*170 * This function was translated, mostly line-for-line, from171 * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs172 *173 * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted174 * because it does not permit modifying characters that have already been transposed. The specific175 * algorithm should not matter to the caller of the methods, which is why it is not noted in the176 * documentation.177 */178const editDistanceState = {179 /**180 * @type {number[]}181 */182 current: [],183 /**184 * @type {number[]}185 */186 prev: [],187 /**188 * @type {number[]}189 */190 prevPrev: [],191 /**192 * @param {string} a193 * @param {string} b194 * @param {number} limit195 * @returns196 */197 calculate: function calculate(a, b, limit) {198 // Ensure that `b` is the shorter string, minimizing memory use.199 if (a.length < b.length) {200 const aTmp = a;201 a = b;202 b = aTmp;203 }204205 const minDist = a.length - b.length;206 // If we know the limit will be exceeded, we can return early.207 if (minDist > limit) {208 return limit + 1;209 }210211 // Strip common prefix.212 // We know that `b` is the shorter string, so we don't need to check213 // `a.length`.214 while (b.length > 0 && b[0] === a[0]) {215 a = a.substring(1);216 b = b.substring(1);217 }218 // Strip common suffix.219 while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {220 a = a.substring(0, a.length - 1);221 b = b.substring(0, b.length - 1);222 }223224 // If either string is empty, the distance is the length of the other.225 // We know that `b` is the shorter string, so we don't need to check `a`.226 if (b.length === 0) {227 return minDist;228 }229230 const aLength = a.length;231 const bLength = b.length;232233 for (let i = 0; i <= bLength; ++i) {234 this.current[i] = 0;235 this.prev[i] = i;236 this.prevPrev[i] = Number.MAX_VALUE;237 }238239 // row by row240 for (let i = 1; i <= aLength; ++i) {241 this.current[0] = i;242 const aIdx = i - 1;243244 // column by column245 for (let j = 1; j <= bLength; ++j) {246 const bIdx = j - 1;247248 // There is no cost to substitute a character with itself.249 const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;250251 this.current[j] = Math.min(252 // deletion253 this.prev[j] + 1,254 // insertion255 this.current[j - 1] + 1,256 // substitution257 this.prev[j - 1] + substitutionCost,258 );259260 if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {261 // transposition262 this.current[j] = Math.min(263 this.current[j],264 this.prevPrev[j - 2] + 1,265 );266 }267 }268269 // Rotate the buffers, reusing the memory270 const prevPrevTmp = this.prevPrev;271 this.prevPrev = this.prev;272 this.prev = this.current;273 this.current = prevPrevTmp;274 }275276 // `prev` because we already rotated the buffers.277 const distance = this.prev[bLength];278 return distance <= limit ? distance : (limit + 1);279 },280};281282/**283 * @param {string} a284 * @param {string} b285 * @param {number} limit286 * @returns287 */288function editDistance(a, b, limit) {289 return editDistanceState.calculate(a, b, limit);290}291292/**293 * @param {string} c294 * @returns {boolean}295 */296function isEndCharacter(c) {297 return "=,>-])".indexOf(c) !== -1;298}299300/**301 * Same thing as ItemType::is_fn_like in item_type.rs302 *303 * @param {rustdoc.ItemType} ty304 * @returns305 */306function isFnLikeTy(ty) {307 return ty === itemTypes.fn || ty === itemTypes.method || ty === itemTypes.tymethod;308}309310/**311 * Returns `true` if the given `c` character is a separator.312 *313 * @param {string} c314 *315 * @return {boolean}316 */317function isSeparatorCharacter(c) {318 return c === "," || c === "=";319}320321/**322 * Returns `true` if the current parser position is starting with "->".323 *324 * @param {rustdoc.ParserState} parserState325 *326 * @return {boolean}327 */328function isReturnArrow(parserState) {329 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";330}331332/**333 * Increase current parser position until it doesn't find a whitespace anymore.334 *335 * @param {rustdoc.ParserState} parserState336 */337function skipWhitespace(parserState) {338 while (parserState.pos < parserState.userQuery.length) {339 const c = parserState.userQuery[parserState.pos];340 if (c !== " ") {341 break;342 }343 parserState.pos += 1;344 }345}346347/**348 * Returns `true` if the previous character is `lookingFor`.349 *350 * @param {rustdoc.ParserState} parserState351 * @param {String} lookingFor352 *353 * @return {boolean}354 */355function prevIs(parserState, lookingFor) {356 let pos = parserState.pos;357 while (pos > 0) {358 const c = parserState.userQuery[pos - 1];359 if (c === lookingFor) {360 return true;361 } else if (c !== " ") {362 break;363 }364 pos -= 1;365 }366 return false;367}368369/**370 * Returns `true` if the last element in the `elems` argument has generics.371 *372 * @param {Array<rustdoc.ParserQueryElement>} elems373 * @param {rustdoc.ParserState} parserState374 *375 * @return {boolean}376 */377function isLastElemGeneric(elems, parserState) {378 return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||379 prevIs(parserState, ">");380}381382/**383 *384 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query385 * @param {rustdoc.ParserState} parserState386 * @param {rustdoc.ParserQueryElement[]} elems387 * @param {boolean} isInGenerics388 */389function getFilteredNextElem(query, parserState, elems, isInGenerics) {390 const start = parserState.pos;391 if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {392 throw ["Expected type filter before ", ":"];393 }394 getNextElem(query, parserState, elems, isInGenerics);395 if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {396 if (parserState.typeFilter !== null) {397 throw [398 "Unexpected ",399 ":",400 " (expected path after type filter ",401 parserState.typeFilter + ":",402 ")",403 ];404 }405 if (elems.length === 0) {406 throw ["Expected type filter before ", ":"];407 } else if (query.literalSearch) {408 throw ["Cannot use quotes on type filter"];409 }410 // The type filter doesn't count as an element since it's a modifier.411 const typeFilterElem = elems.pop();412 checkExtraTypeFilterCharacters(start, parserState);413 // typeFilterElem is not undefined. If it was, the elems.length check would have fired.414 // @ts-expect-error415 parserState.typeFilter = typeFilterElem.normalizedPathLast;416 parserState.pos += 1;417 parserState.totalElems -= 1;418 query.literalSearch = false;419 getNextElem(query, parserState, elems, isInGenerics);420 }421}422423/**424 * This function parses the next query element until it finds `endChar`,425 * calling `getNextElem` to collect each element.426 *427 * If there is no `endChar`, this function will implicitly stop at the end428 * without raising an error.429 *430 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query431 * @param {rustdoc.ParserState} parserState432 * @param {Array<rustdoc.ParserQueryElement>} elems433 * - This is where the new {QueryElement} will be added.434 * @param {string} endChar - This function will stop when it'll encounter this435 * character.436 * @returns {{foundSeparator: boolean}}437 */438function getItemsBefore(query, parserState, elems, endChar) {439 let foundStopChar = true;440 let foundSeparator = false;441442 // If this is a generic, keep the outer item's type filter around.443 const oldTypeFilter = parserState.typeFilter;444 parserState.typeFilter = null;445 const oldIsInBinding = parserState.isInBinding;446 parserState.isInBinding = null;447448 // ML-style Higher Order Function notation449 //450 // a way to search for any closure or fn pointer regardless of451 // which closure trait is used452 //453 // Looks like this:454 //455 // `option<t>, (t -> u) -> option<u>`456 // ^^^^^^457 //458 // The Rust-style closure notation is implemented in getNextElem459 let hofParameters = null;460461 let extra = "";462 if (endChar === ">") {463 extra = "<";464 } else if (endChar === "]") {465 extra = "[";466 } else if (endChar === ")") {467 extra = "(";468 } else if (endChar === "") {469 extra = "->";470 } else {471 extra = endChar;472 }473474 while (parserState.pos < parserState.length) {475 const c = parserState.userQuery[parserState.pos];476 if (c === endChar) {477 if (parserState.isInBinding) {478 throw ["Unexpected ", endChar, " after ", "="];479 }480 break;481 } else if (endChar !== "" && isReturnArrow(parserState)) {482 // ML-style HOF notation only works when delimited in something,483 // otherwise a function arrow starts the return type of the top484 if (parserState.isInBinding) {485 throw ["Unexpected ", "->", " after ", "="];486 }487 hofParameters = [...elems];488 elems.length = 0;489 parserState.pos += 2;490 foundStopChar = true;491 foundSeparator = false;492 continue;493 } else if (c === " ") {494 parserState.pos += 1;495 continue;496 } else if (isSeparatorCharacter(c)) {497 parserState.pos += 1;498 foundStopChar = true;499 foundSeparator = true;500 continue;501 } else if (c === ":" && isPathStart(parserState)) {502 throw ["Unexpected ", "::", ": paths cannot start with ", "::"];503 } else if (isEndCharacter(c)) {504 throw ["Unexpected ", c, " after ", extra];505 }506 if (!foundStopChar) {507 /** @type {string[]} */508 let extra = [];509 if (isLastElemGeneric(query.elems, parserState)) {510 extra = [" after ", ">"];511 } else if (prevIs(parserState, "\"")) {512 throw ["Cannot have more than one element if you use quotes"];513 }514 if (endChar !== "") {515 throw [516 "Expected ",517 ",",518 ", ",519 "=",520 ", or ",521 endChar,522 ...extra,523 ", found ",524 c,525 ];526 }527 throw [528 "Expected ",529 ",",530 " or ",531 "=",532 ...extra,533 ", found ",534 c,535 ];536 }537 const posBefore = parserState.pos;538 getFilteredNextElem(query, parserState, elems, endChar !== "");539 if (endChar !== "" && parserState.pos >= parserState.length) {540 throw ["Unclosed ", extra];541 }542 // This case can be encountered if `getNextElem` encountered a "stop character"543 // right from the start. For example if you have `,,` or `<>`. In this case,544 // we simply move up the current position to continue the parsing.545 if (posBefore === parserState.pos) {546 parserState.pos += 1;547 }548 foundStopChar = false;549 }550 if (parserState.pos >= parserState.length && endChar !== "") {551 throw ["Unclosed ", extra];552 }553 // We are either at the end of the string or on the `endChar` character, let's move554 // forward in any case.555 parserState.pos += 1;556557 if (hofParameters) {558 // Commas in a HOF don't cause wrapping parens to become a tuple.559 // If you want a one-tuple with a HOF in it, write `((a -> b),)`.560 foundSeparator = false;561 // HOFs can't have directly nested bindings.562 if ([...elems, ...hofParameters].some(x => x.bindingName)563 || parserState.isInBinding) {564 throw ["Unexpected ", "=", " within ", "->"];565 }566 // HOFs are represented the same way closures are.567 // The arguments are wrapped in a tuple, and the output568 // is a binding, even though the compiler doesn't technically569 // represent fn pointers that way.570 const hofElem = makePrimitiveElement("->", {571 generics: hofParameters,572 bindings: new Map([["output", [...elems]]]),573 typeFilter: null,574 });575 elems.length = 0;576 elems[0] = hofElem;577 }578579 parserState.typeFilter = oldTypeFilter;580 parserState.isInBinding = oldIsInBinding;581582 return { foundSeparator };583}584585/**586 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query587 * @param {rustdoc.ParserState} parserState588 * @param {Array<rustdoc.ParserQueryElement>} elems589 * - This is where the new {QueryElement} will be added.590 * @param {boolean} isInGenerics591 */592function getNextElem(query, parserState, elems, isInGenerics) {593 /** @type {rustdoc.ParserQueryElement[]} */594 const generics = [];595596 /** @type {function(string, string): void} */597 const handleRefOrPtr = (chr, name) => {598 if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {599 throw [600 "Invalid search type: primitive ",601 chr,602 " and ",603 parserState.typeFilter,604 " both specified",605 ];606 }607 parserState.typeFilter = null;608 parserState.pos += 1;609 let c = parserState.userQuery[parserState.pos];610 while (c === " " && parserState.pos < parserState.length) {611 parserState.pos += 1;612 c = parserState.userQuery[parserState.pos];613 }614 const generics = [];615 const pos = parserState.pos;616 if (parserState.userQuery.slice(pos, pos + 3) === "mut") {617 generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));618 parserState.pos += 3;619 c = parserState.userQuery[parserState.pos];620 } else if (chr === "*" && parserState.userQuery.slice(pos, pos + 5) === "const") {621 // make *const T parse the same as *T622 parserState.pos += 5;623 c = parserState.userQuery[parserState.pos];624 }625 while (c === " " && parserState.pos < parserState.length) {626 parserState.pos += 1;627 c = parserState.userQuery[parserState.pos];628 }629 if (!isEndCharacter(c) && parserState.pos < parserState.length) {630 getFilteredNextElem(query, parserState, generics, isInGenerics);631 }632 elems.push(makePrimitiveElement(name, { generics }));633 };634635 skipWhitespace(parserState);636 let start = parserState.pos;637 let end;638 if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {639 let endChar = ")";640 let name = "()";641 let friendlyName = "tuple";642643 if (parserState.userQuery[parserState.pos] === "[") {644 endChar = "]";645 name = "[]";646 friendlyName = "slice";647 }648 parserState.pos += 1;649 const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);650 const typeFilter = parserState.typeFilter;651 const bindingName = parserState.isInBinding;652 parserState.typeFilter = null;653 parserState.isInBinding = null;654 for (const gen of generics) {655 if (gen.bindingName !== null) {656 throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];657 }658 }659 if (name === "()" && !foundSeparator && generics.length === 1660 && typeFilter === null) {661 elems.push(generics[0]);662 } else if (name === "()" && generics.length === 1 && generics[0].name === "->") {663 // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`664 // not `primitive:"()"<"->"<output=b, (a,)>>`665 generics[0].typeFilter = typeFilter;666 elems.push(generics[0]);667 } else {668 if (typeFilter !== null && typeFilter !== "primitive") {669 throw [670 "Invalid search type: primitive ",671 name,672 " and ",673 typeFilter,674 " both specified",675 ];676 }677 parserState.totalElems += 1;678 if (isInGenerics) {679 parserState.genericsElems += 1;680 }681 elems.push(makePrimitiveElement(name, { bindingName, generics }));682 }683 } else if (parserState.userQuery[parserState.pos] === "&") {684 handleRefOrPtr("&", "reference");685 } else if (parserState.userQuery[parserState.pos] === "*") {686 handleRefOrPtr("*", "pointer");687 } else {688 const isStringElem = parserState.userQuery[start] === "\"";689 // We handle the strings on their own mostly to make code easier to follow.690 if (isStringElem) {691 start += 1;692 getStringElem(query, parserState, isInGenerics);693 end = parserState.pos - 1;694 } else {695 end = getIdentEndPosition(parserState);696 }697 if (parserState.pos < parserState.length &&698 parserState.userQuery[parserState.pos] === "<"699 ) {700 if (start >= end) {701 throw ["Found generics without a path"];702 }703 parserState.pos += 1;704 getItemsBefore(query, parserState, generics, ">");705 } else if (parserState.pos < parserState.length &&706 parserState.userQuery[parserState.pos] === "("707 ) {708 if (start >= end) {709 throw ["Found generics without a path"];710 }711 if (parserState.isInBinding) {712 throw ["Unexpected ", "(", " after ", "="];713 }714 parserState.pos += 1;715 const typeFilter = parserState.typeFilter;716 parserState.typeFilter = null;717 getItemsBefore(query, parserState, generics, ")");718 skipWhitespace(parserState);719 if (isReturnArrow(parserState)) {720 parserState.pos += 2;721 skipWhitespace(parserState);722 getFilteredNextElem(query, parserState, generics, isInGenerics);723 generics[generics.length - 1].bindingName = makePrimitiveElement("output");724 } else {725 generics.push(makePrimitiveElement(null, {726 bindingName: makePrimitiveElement("output"),727 typeFilter: null,728 }));729 }730 parserState.typeFilter = typeFilter;731 }732 if (isStringElem) {733 skipWhitespace(parserState);734 }735 if (start >= end && generics.length === 0) {736 return;737 }738 if (parserState.userQuery[parserState.pos] === "=") {739 if (parserState.isInBinding) {740 throw ["Cannot write ", "=", " twice in a binding"];741 }742 if (!isInGenerics) {743 throw ["Type parameter ", "=", " must be within generics list"];744 }745 const name = parserState.userQuery.slice(start, end).trim();746 if (name === "!") {747 throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];748 }749 if (name.includes("!")) {750 throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];751 }752 if (name.includes("::")) {753 throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];754 }755 if (name.includes(":")) {756 throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];757 }758 parserState.isInBinding = { name, generics };759 } else {760 elems.push(761 createQueryElement(762 query,763 parserState,764 parserState.userQuery.slice(start, end),765 generics,766 isInGenerics,767 ),768 );769 }770 }771}772773/**774 * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored775 * if empty).776 *777 * @param {number} start778 * @param {rustdoc.ParserState} parserState779 */780function checkExtraTypeFilterCharacters(start, parserState) {781 const query = parserState.userQuery.slice(start, parserState.pos).trim();782783 const match = query.match(REGEX_INVALID_TYPE_FILTER);784 if (match) {785 throw [786 "Unexpected ",787 match[0],788 " in type filter (before ",789 ":",790 ")",791 ];792 }793}794795/**796 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query797 * @param {rustdoc.ParserState} parserState798 * @param {string} name - Name of the query element.799 * @param {Array<rustdoc.ParserQueryElement>} generics - List of generics of this query element.800 * @param {boolean} isInGenerics801 *802 * @return {rustdoc.ParserQueryElement} - The newly created `QueryElement`.803 */804function createQueryElement(query, parserState, name, generics, isInGenerics) {805 const path = name.trim();806 if (path.length === 0 && generics.length === 0) {807 throw ["Unexpected ", parserState.userQuery[parserState.pos]];808 }809 if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {810 throw ["Cannot have more than one element if you use quotes"];811 }812 const typeFilter = parserState.typeFilter;813 parserState.typeFilter = null;814 if (name.trim() === "!") {815 if (typeFilter !== null && typeFilter !== "primitive") {816 throw [817 "Invalid search type: primitive never type ",818 "!",819 " and ",820 typeFilter,821 " both specified",822 ];823 }824 if (generics.length !== 0) {825 throw [826 "Never type ",827 "!",828 " does not accept generic parameters",829 ];830 }831 const bindingName = parserState.isInBinding;832 parserState.isInBinding = null;833 return makePrimitiveElement("never", { bindingName });834 }835 const quadcolon = /::\s*::/.exec(path);836 if (path.startsWith("::")) {837 throw ["Paths cannot start with ", "::"];838 } else if (quadcolon !== null) {839 throw ["Unexpected ", quadcolon[0]];840 }841 const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x => x.toLowerCase());842 // In case we only have something like `<p>`, there is no name.843 if (pathSegments.length === 0844 || (pathSegments.length === 1 && pathSegments[0] === "")) {845 if (generics.length > 0 || prevIs(parserState, ">")) {846 throw ["Found generics without a path"];847 } else {848 throw ["Unexpected ", parserState.userQuery[parserState.pos]];849 }850 }851 for (const [i, pathSegment] of pathSegments.entries()) {852 if (pathSegment === "!") {853 if (i !== 0) {854 throw ["Never type ", "!", " is not associated item"];855 }856 pathSegments[i] = "never";857 }858 }859 parserState.totalElems += 1;860 if (isInGenerics) {861 parserState.genericsElems += 1;862 }863 const bindingName = parserState.isInBinding;864 parserState.isInBinding = null;865 const bindings = new Map();866 const pathLast = pathSegments[pathSegments.length - 1];867 return {868 name: name.trim(),869 id: null,870 fullPath: pathSegments,871 pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),872 pathLast,873 normalizedPathLast: pathLast.replace(/_/g, ""),874 generics: generics.filter(gen => {875 // Syntactically, bindings are parsed as generics,876 // but the query engine treats them differently.877 if (gen.bindingName !== null && gen.bindingName.name !== null) {878 if (gen.name !== null) {879 gen.bindingName.generics.unshift(gen);880 }881 bindings.set(882 gen.bindingName.name.toLowerCase().replace(/_/g, ""),883 gen.bindingName.generics,884 );885 return false;886 }887 return true;888 }),889 bindings,890 typeFilter,891 bindingName,892 };893}894895/**896 *897 * @param {string|null} name898 * @param {rustdoc.ParserQueryElementFields=} extra899 * @returns {rustdoc.ParserQueryElement}900 */901function makePrimitiveElement(name, extra) {902 return Object.assign({903 name,904 id: null,905 fullPath: [name],906 pathWithoutLast: [],907 pathLast: name,908 normalizedPathLast: name,909 generics: [],910 bindings: new Map(),911 typeFilter: "primitive",912 bindingName: null,913 }, extra);914}915916/**917 * If we encounter a `"`, then we try to extract the string918 * from it until we find another `"`.919 *920 * This function will throw an error in the following cases:921 * * There is already another string element.922 * * We are parsing a generic argument.923 * * There is more than one element.924 * * There is no closing `"`.925 *926 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query927 * @param {rustdoc.ParserState} parserState928 * @param {boolean} isInGenerics929 */930function getStringElem(query, parserState, isInGenerics) {931 if (isInGenerics) {932 throw ["Unexpected ", "\"", " in generics"];933 } else if (query.literalSearch) {934 throw ["Cannot have more than one literal search element"];935 } else if (parserState.totalElems - parserState.genericsElems > 0) {936 throw ["Cannot use literal search when there is more than one element"];937 }938 parserState.pos += 1;939 const start = parserState.pos;940 const end = getIdentEndPosition(parserState);941 if (parserState.pos >= parserState.length) {942 throw ["Unclosed ", "\""];943 } else if (parserState.userQuery[end] !== "\"") {944 throw ["Unexpected ", parserState.userQuery[end], " in a string element"];945 } else if (start === end) {946 throw ["Cannot have empty string element"];947 }948 // To skip the quote at the end.949 parserState.pos += 1;950 query.literalSearch = true;951}952953/**954 * This function goes through all characters until it reaches an invalid ident955 * character or the end of the query. It returns the position of the last956 * character of the ident.957 *958 * @param {rustdoc.ParserState} parserState959 *960 * @return {number}961 */962function getIdentEndPosition(parserState) {963 let afterIdent = consumeIdent(parserState);964 let end = parserState.pos;965 let macroExclamation = -1;966 while (parserState.pos < parserState.length) {967 const c = parserState.userQuery[parserState.pos];968 if (c === "!") {969 if (macroExclamation !== -1) {970 throw ["Cannot have more than one ", "!", " in an ident"];971 } else if (parserState.pos + 1 < parserState.length) {972 const pos = parserState.pos;973 parserState.pos++;974 const beforeIdent = consumeIdent(parserState);975 parserState.pos = pos;976 if (beforeIdent) {977 throw ["Unexpected ", "!", ": it can only be at the end of an ident"];978 }979 }980 if (afterIdent) macroExclamation = parserState.pos;981 } else if (isPathSeparator(c)) {982 if (c === ":") {983 if (!isPathStart(parserState)) {984 break;985 }986 // Skip current ":".987 parserState.pos += 1;988 } else {989 while (parserState.pos + 1 < parserState.length) {990 const next_c = parserState.userQuery[parserState.pos + 1];991 if (next_c !== " ") {992 break;993 }994 parserState.pos += 1;995 }996 }997 if (macroExclamation !== -1) {998 throw ["Cannot have associated items in macros"];999 }1000 } else if (1001 c === "[" ||1002 c === "(" ||1003 isEndCharacter(c) ||1004 isSpecialStartCharacter(c) ||1005 isSeparatorCharacter(c)1006 ) {1007 break;1008 } else if (parserState.pos > 0) {1009 throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1],1010 " (not a valid identifier)"];1011 } else {1012 throw ["Unexpected ", c, " (not a valid identifier)"];1013 }1014 parserState.pos += 1;1015 afterIdent = consumeIdent(parserState);1016 end = parserState.pos;1017 }1018 if (macroExclamation !== -1) {1019 if (parserState.typeFilter === null) {1020 parserState.typeFilter = "macro";1021 } else if (parserState.typeFilter !== "macro") {1022 throw [1023 "Invalid search type: macro ",1024 "!",1025 " and ",1026 parserState.typeFilter,1027 " both specified",1028 ];1029 }1030 end = macroExclamation;1031 }1032 return end;1033}10341035/**1036 * @param {string} c1037 * @returns1038 */1039function isSpecialStartCharacter(c) {1040 return "<\"".indexOf(c) !== -1;1041}10421043/**1044 * Returns `true` if the current parser position is starting with "::".1045 *1046 * @param {rustdoc.ParserState} parserState1047 *1048 * @return {boolean}1049 */1050function isPathStart(parserState) {1051 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";1052}10531054/**1055 * If the current parser position is at the beginning of an identifier,1056 * move the position to the end of it and return `true`. Otherwise, return `false`.1057 *1058 * @param {rustdoc.ParserState} parserState1059 *1060 * @return {boolean}1061 */1062function consumeIdent(parserState) {1063 REGEX_IDENT.lastIndex = parserState.pos;1064 const match = parserState.userQuery.match(REGEX_IDENT);1065 if (match) {1066 parserState.pos += match[0].length;1067 return true;1068 }1069 return false;1070}10711072/**1073 * Returns `true` if the given `c` character is a path separator. For example1074 * `:` in `a::b` or a whitespace in `a b`.1075 *1076 * @param {string} c1077 *1078 * @return {boolean}1079 */1080function isPathSeparator(c) {1081 return c === ":" || c === " ";1082}10831084/**1085 * Given an array and an ascending list of indices,1086 * efficiently removes each index in the array.1087 *1088 * @template T1089 * @param {Array<T>} a1090 * @param {Array<number>} idxList1091 */1092function removeIdxListAsc(a, idxList) {1093 if (idxList.length === 0) {1094 return;1095 }1096 let removed = 0;1097 let i = idxList[0];1098 let nextToRemove = idxList[0];1099 while (i < a.length - idxList.length) {1100 while (i === nextToRemove && removed < idxList.length) {1101 removed++;1102 i++;1103 nextToRemove = idxList[removed];1104 }1105 a[i] = a[i + removed];1106 i++;1107 }1108 // truncate array1109 a.length -= idxList.length;1110}11111112/**1113 * @template T1114 */1115class VlqHexDecoder {1116 /**1117 * @param {string} string1118 * @param {function(rustdoc.VlqData): T} cons1119 */1120 constructor(string, cons) {1121 this.string = string;1122 this.cons = cons;1123 this.offset = 0;1124 this.elemCount = 0;1125 /** @type {T[]} */1126 this.backrefQueue = [];1127 }1128 /**1129 * call after consuming `{`1130 * @returns {rustdoc.VlqData[]}1131 */1132 decodeList() {1133 let c = this.string.charCodeAt(this.offset);1134 const ret = [];1135 while (c !== 125) { // 125 = "}"1136 ret.push(this.decode());1137 c = this.string.charCodeAt(this.offset);1138 }1139 this.offset += 1; // eat cb1140 return ret;1141 }1142 /**1143 * consumes and returns a list or integer1144 * @returns {rustdoc.VlqData}1145 */1146 decode() {1147 let n = 0;1148 let c = this.string.charCodeAt(this.offset);1149 if (c === 123) { // 123 = "{"1150 this.offset += 1;1151 return this.decodeList();1152 }1153 while (c < 96) { // 96 = "`"1154 n = (n << 4) | (c & 0xF);1155 this.offset += 1;1156 c = this.string.charCodeAt(this.offset);1157 }1158 // last character >= la1159 n = (n << 4) | (c & 0xF);1160 const [sign, value] = [n & 1, n >> 1];1161 this.offset += 1;1162 this.elemCount += 1;1163 return sign ? -value : value;1164 }1165 /**1166 * @returns {T}1167 */1168 next() {1169 const c = this.string.charCodeAt(this.offset);1170 // sixteen characters after "0" are backref1171 if (c >= 48 && c < 64) { // 48 = "0", 64 = "@"1172 this.offset += 1;1173 return this.backrefQueue[c - 48];1174 }1175 // special exception: 0 doesn't use backref encoding1176 // it's already one character, and it's always nullish1177 if (c === 96) { // 96 = "`"1178 this.offset += 1;1179 return this.cons(0);1180 }1181 const result = this.cons(this.decode());1182 this.backrefQueue.unshift(result);1183 if (this.backrefQueue.length > 16) {1184 this.backrefQueue.pop();1185 }1186 return result;1187 }1188}11891190/** @type {Array<string>} */1191const EMPTY_STRING_ARRAY = [];11921193/** @type {Array<rustdoc.FunctionType>} */1194const EMPTY_GENERICS_ARRAY = [];11951196/** @type {Array<[number, rustdoc.FunctionType[]]>} */1197const EMPTY_BINDINGS_ARRAY = [];11981199/** @type {Map<number, Array<any>>} */1200const EMPTY_BINDINGS_MAP = new Map();12011202/**1203 * @param {string|null} typename1204 * @returns {number}1205 */1206function itemTypeFromName(typename) {1207 if (typename === null) {1208 return NO_TYPE_FILTER;1209 }1210 // @ts-expect-error1211 const index = itemTypes[typename];1212 if (index === undefined) {1213 throw ["Unknown type filter ", typename];1214 }1215 return index;1216}12171218class DocSearch {1219 /**1220 * @param {string} rootPath1221 * @param {stringdex.Database} database1222 */1223 constructor(rootPath, database) {1224 this.rootPath = rootPath;1225 this.database = database;12261227 this.utf8decoder = new TextDecoder();12281229 /** @type {Map<number|null, rustdoc.FunctionType>} */1230 this.TYPES_POOL = new Map();1231 }12321233 /**1234 * Load type name ID set.1235 *1236 * Each of these identifiers are used specially by1237 * type-driven search. Most of them are lang items1238 * in the compiler.1239 *1240 * Use this function, which caches the result, and not1241 * getTypeNameIdsAsync, which is an internal implementation1242 * detail for this.1243 *1244 * @return {Promise<rustdoc.TypeNameIds>|rustdoc.TypeNameIds}1245 */1246 getTypeNameIds() {1247 if (this.typeNameIds) {1248 return this.typeNameIds;1249 }1250 const nn = this.database.getData("normalizedName");1251 if (!nn) {1252 return {1253 typeNameIdOfOutput: -1,1254 typeNameIdOfFnPtr: -1,1255 typeNameIdOfFn: -1,1256 typeNameIdOfFnMut: -1,1257 typeNameIdOfFnOnce: -1,1258 typeNameIdOfArray: -1,1259 typeNameIdOfSlice: -1,1260 typeNameIdOfArrayOrSlice: -1,1261 typeNameIdOfTuple: -1,1262 typeNameIdOfUnit: -1,1263 typeNameIdOfTupleOrUnit: -1,1264 typeNameIdOfReference: -1,1265 typeNameIdOfPointer: -1,1266 typeNameIdOfHof: -1,1267 typeNameIdOfNever: -1,1268 };1269 }1270 return this.getTypeNameIdsAsync(nn);1271 }1272 /**1273 * @param {stringdex.DataColumn} nn1274 * @returns {Promise<rustdoc.TypeNameIds>}1275 */1276 async getTypeNameIdsAsync(nn) {1277 // Each of these identifiers are used specially by1278 // type-driven search.1279 const [1280 // output is the special associated type that goes1281 // after the arrow: the type checker desugars1282 // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`1283 output,1284 // fn, fnmut, and fnonce all match `->`1285 fn,1286 fnMut,1287 fnOnce,1288 hof,1289 // array and slice both match `[]`1290 array,1291 slice,1292 arrayOrSlice,1293 // tuple and unit both match `()`1294 tuple,1295 unit,1296 tupleOrUnit,1297 // reference matches `&`1298 reference,1299 pointer,1300 // never matches `!`1301 never,1302 ] = await Promise.all([1303 nn.search("output"),1304 nn.search("fn"),1305 nn.search("fnmut"),1306 nn.search("fnonce"),1307 nn.search("->"),1308 nn.search("array"),1309 nn.search("slice"),1310 nn.search("[]"),1311 nn.search("tuple"),1312 nn.search("unit"),1313 nn.search("()"),1314 nn.search("reference"),1315 nn.search("pointer"),1316 nn.search("never"),1317 ]);1318 /**1319 * @param {stringdex.Trie|null|undefined} trie1320 * @param {rustdoc.ItemType} ty1321 * @param {string} modulePath1322 * @returns {Promise<number>}1323 * */1324 const first = async(trie, ty, modulePath) => {1325 if (trie) {1326 for (const id of trie.matches().entries()) {1327 const pathData = await this.getPathData(id);1328 if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {1329 return id;1330 }1331 }1332 }1333 return -1;1334 };1335 const typeNameIdOfOutput = await first(output, itemTypes.associatedtype, "");1336 const typeNameIdOfFnPtr = await first(fn, itemTypes.primitive, "");1337 const typeNameIdOfFn = await first(fn, itemTypes.trait, "core::ops");1338 const typeNameIdOfFnMut = await first(fnMut, itemTypes.trait, "core::ops");1339 const typeNameIdOfFnOnce = await first(fnOnce, itemTypes.trait, "core::ops");1340 const typeNameIdOfArray = await first(array, itemTypes.primitive, "");1341 const typeNameIdOfSlice = await first(slice, itemTypes.primitive, "");1342 const typeNameIdOfArrayOrSlice = await first(arrayOrSlice, itemTypes.primitive, "");1343 const typeNameIdOfTuple = await first(tuple, itemTypes.primitive, "");1344 const typeNameIdOfUnit = await first(unit, itemTypes.primitive, "");1345 const typeNameIdOfTupleOrUnit = await first(tupleOrUnit, itemTypes.primitive, "");1346 const typeNameIdOfReference = await first(reference, itemTypes.primitive, "");1347 const typeNameIdOfPointer = await first(pointer, itemTypes.primitive, "");1348 const typeNameIdOfHof = await first(hof, itemTypes.primitive, "");1349 const typeNameIdOfNever = await first(never, itemTypes.primitive, "");1350 this.typeNameIds = {1351 typeNameIdOfOutput,1352 typeNameIdOfFnPtr,1353 typeNameIdOfFn,1354 typeNameIdOfFnMut,1355 typeNameIdOfFnOnce,1356 typeNameIdOfArray,1357 typeNameIdOfSlice,1358 typeNameIdOfArrayOrSlice,1359 typeNameIdOfTuple,1360 typeNameIdOfUnit,1361 typeNameIdOfTupleOrUnit,1362 typeNameIdOfReference,1363 typeNameIdOfPointer,1364 typeNameIdOfHof,1365 typeNameIdOfNever,1366 };1367 return this.typeNameIds;1368 }13691370 /**1371 * Parses the query.1372 *1373 * The supported syntax by this parser is given in the rustdoc book chapter1374 * /src/doc/rustdoc/src/read-documentation/search.md1375 *1376 * When adding new things to the parser, add them there, too!1377 *1378 * @param {string} userQuery - The user query1379 *1380 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query1381 */1382 static parseQuery(userQuery) {1383 /**1384 * Takes the user search input and returns an empty `ParsedQuery`.1385 *1386 * @param {string} userQuery1387 *1388 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}1389 */1390 function newParsedQuery(userQuery) {1391 return {1392 userQuery,1393 elems: [],1394 returned: [],1395 // Total number of "top" elements (does not include generics).1396 foundElems: 0,1397 // Total number of elements (includes generics).1398 totalElems: 0,1399 literalSearch: false,1400 hasReturnArrow: false,1401 error: null,1402 correction: null,1403 proposeCorrectionFrom: null,1404 proposeCorrectionTo: null,1405 // bloom filter build from type ids1406 typeFingerprint: new Uint32Array(4),1407 };1408 }14091410 /**1411 * Parses the provided `query` input to fill `parserState`. If it encounters an error while1412 * parsing `query`, it'll throw an error.1413 *1414 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query1415 * @param {rustdoc.ParserState} parserState1416 */1417 function parseInput(query, parserState) {1418 let foundStopChar = true;14191420 while (parserState.pos < parserState.length) {1421 const c = parserState.userQuery[parserState.pos];1422 if (isEndCharacter(c)) {1423 foundStopChar = true;1424 if (isSeparatorCharacter(c)) {1425 parserState.pos += 1;1426 continue;1427 } else if (c === "-" || c === ">") {1428 if (isReturnArrow(parserState)) {1429 query.hasReturnArrow = true;1430 break;1431 }1432 throw ["Unexpected ", c, " (did you mean ", "->", "?)"];1433 } else if (parserState.pos > 0) {1434 throw ["Unexpected ", c, " after ",1435 parserState.userQuery[parserState.pos - 1]];1436 }1437 throw ["Unexpected ", c];1438 } else if (c === " ") {1439 skipWhitespace(parserState);1440 continue;1441 }1442 if (!foundStopChar) {1443 let extra = EMPTY_STRING_ARRAY;1444 if (isLastElemGeneric(query.elems, parserState)) {1445 extra = [" after ", ">"];1446 } else if (prevIs(parserState, "\"")) {1447 throw ["Cannot have more than one element if you use quotes"];1448 }1449 if (parserState.typeFilter !== null) {1450 throw [1451 "Expected ",1452 ",",1453 " or ",1454 "->",1455 ...extra,1456 ", found ",1457 c,1458 ];1459 }1460 throw [1461 "Expected ",1462 ",",1463 ", ",1464 ":",1465 " or ",1466 "->",1467 ...extra,1468 ", found ",1469 c,1470 ];1471 }1472 const before = query.elems.length;1473 getFilteredNextElem(query, parserState, query.elems, false);1474 if (query.elems.length === before) {1475 // Nothing was added, weird... Let's increase the position to not remain stuck.1476 parserState.pos += 1;1477 }1478 foundStopChar = false;1479 }1480 if (parserState.typeFilter !== null) {1481 throw [1482 "Unexpected ",1483 ":",1484 " (expected path after type filter ",1485 parserState.typeFilter + ":",1486 ")",1487 ];1488 }1489 while (parserState.pos < parserState.length) {1490 if (isReturnArrow(parserState)) {1491 parserState.pos += 2;1492 skipWhitespace(parserState);1493 // Get returned elements.1494 getItemsBefore(query, parserState, query.returned, "");1495 // Nothing can come afterward!1496 query.hasReturnArrow = true;1497 break;1498 } else {1499 parserState.pos += 1;1500 }1501 }1502 }150315041505 userQuery = userQuery.trim().replace(/\r|\n|\t/g, " ");1506 const parserState = {1507 length: userQuery.length,1508 pos: 0,1509 // Total number of elements (includes generics).1510 totalElems: 0,1511 genericsElems: 0,1512 typeFilter: null,1513 isInBinding: null,1514 userQuery,1515 };1516 let query = newParsedQuery(userQuery);15171518 try {1519 parseInput(query, parserState);15201521 // Scan for invalid type filters, so that we can report the error1522 // outside the search loop.1523 /** @param {rustdoc.ParserQueryElement} elem */1524 const checkTypeFilter = elem => {1525 const ty = itemTypeFromName(elem.typeFilter);1526 if (ty === itemTypes.generic && elem.generics.length !== 0) {1527 throw [1528 "Generic type parameter ",1529 elem.name,1530 " does not accept generic parameters",1531 ];1532 }1533 for (const generic of elem.generics) {1534 checkTypeFilter(generic);1535 }1536 for (const constraints of elem.bindings.values()) {1537 for (const constraint of constraints) {1538 checkTypeFilter(constraint);1539 }1540 }1541 };1542 for (const elem of query.elems) {1543 checkTypeFilter(elem);1544 }1545 for (const elem of query.returned) {1546 checkTypeFilter(elem);1547 }1548 } catch (err) {1549 query = newParsedQuery(userQuery);1550 if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {1551 query.error = err;1552 } else {1553 // rethrow the error if it isn't a string array1554 throw err;1555 }15561557 return query;1558 }1559 if (!query.literalSearch) {1560 // If there is more than one element in the query, we switch to literalSearch in any1561 // case.1562 query.literalSearch = parserState.totalElems > 1;1563 }1564 query.foundElems = query.elems.length + query.returned.length;1565 query.totalElems = parserState.totalElems;1566 return query;1567 }15681569 /**1570 * @param {number} id1571 * @returns {Promise<string|null>}1572 */1573 async getName(id) {1574 const ni = this.database.getData("name");1575 if (!ni) {1576 return null;1577 }1578 const name = await ni.at(id);1579 return name === undefined || name === null ? null : this.utf8decoder.decode(name);1580 }15811582 /**1583 * @param {number} id1584 * @returns {Promise<string|null>}1585 */1586 async getDesc(id) {1587 const di = this.database.getData("desc");1588 if (!di) {1589 return null;1590 }1591 const desc = await di.at(id);1592 return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);1593 }15941595 /**1596 * @param {number} id1597 * @returns {Promise<number|null>}1598 */1599 async getAliasTarget(id) {1600 const ai = this.database.getData("alias");1601 if (!ai) {1602 return null;1603 }1604 const bytes = await ai.at(id);1605 if (bytes === undefined || bytes === null || bytes.length === 0) {1606 return null;1607 } else {1608 /** @type {string} */1609 const encoded = this.utf8decoder.decode(bytes);1610 /** @type {number|null} */1611 const decoded = JSON.parse(encoded);1612 return decoded;1613 }1614 }16151616 /**1617 * @param {number} id1618 * @returns {Promise<rustdoc.EntryData|null>}1619 */1620 async getEntryData(id) {1621 const ei = this.database.getData("entry");1622 if (!ei) {1623 return null;1624 }1625 const encoded = this.utf8decoder.decode(await ei.at(id));1626 if (encoded === "" || encoded === undefined || encoded === null) {1627 return null;1628 }1629 /**1630 * krate,1631 * ty,1632 * module_path,1633 * exact_module_path,1634 * parent,1635 * trait_parent,1636 * deprecated,1637 * unstable,1638 * associated_item_disambiguator1639 * @type {rustdoc.ArrayWithOptionals<[1640 * number,1641 * rustdoc.ItemType,1642 * number,1643 * number,1644 * number,1645 * number,1646 * number,1647 * number,1648 * ], [string]>}1649 */1650 const raw = JSON.parse(encoded);1651 return {1652 krate: raw[0],1653 ty: raw[1],1654 modulePath: raw[2] === 0 ? null : raw[2] - 1,1655 exactModulePath: raw[3] === 0 ? null : raw[3] - 1,1656 parent: raw[4] === 0 ? null : raw[4] - 1,1657 traitParent: raw[5] === 0 ? null : raw[5] - 1,1658 deprecated: raw[6] === 1 ? true : false,1659 unstable: raw[7] === 1 ? true : false,1660 associatedItemDisambiguatorOrExternCrateUrl: raw.length === 8 ? null : raw[8],1661 };1662 }16631664 /**1665 * @param {number} id1666 * @returns {Promise<rustdoc.PathData|null>}1667 */1668 async getPathData(id) {1669 const pi = this.database.getData("path");1670 if (!pi) {1671 return null;1672 }1673 const encoded = this.utf8decoder.decode(await pi.at(id));1674 if (encoded === "" || encoded === undefined || encoded === null) {1675 return null;1676 }1677 /**1678 * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index1679 * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}1680 */1681 const raw = JSON.parse(encoded);1682 return {1683 ty: raw[0],1684 modulePath: raw[1],1685 exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],1686 };1687 }16881689 /**1690 * @param {number} id1691 * @returns {Promise<rustdoc.FunctionData|null>}1692 */1693 async getFunctionData(id) {1694 const fi = this.database.getData("function");1695 if (!fi) {1696 return null;1697 }1698 const encoded = this.utf8decoder.decode(await fi.at(id));1699 if (encoded === "" || encoded === undefined || encoded === null) {1700 return null;1701 }1702 /**1703 * function_signature, param_names1704 * @type {[string, string[]]}1705 */1706 const raw = JSON.parse(encoded);17071708 const parser = new VlqHexDecoder(raw[0], async functionSearchType => {1709 if (typeof functionSearchType === "number") {1710 return null;1711 }1712 const INPUTS_DATA = 0;1713 const OUTPUT_DATA = 1;1714 /** @type {Promise<rustdoc.FunctionType[]>} */1715 let inputs_;1716 /** @type {Promise<rustdoc.FunctionType[]>} */1717 let output_;1718 if (typeof functionSearchType[INPUTS_DATA] === "number") {1719 inputs_ = Promise.all([1720 this.buildItemSearchType(functionSearchType[INPUTS_DATA]),1721 ]);1722 } else {1723 // @ts-ignore1724 inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);1725 }1726 if (functionSearchType.length > 1) {1727 if (typeof functionSearchType[OUTPUT_DATA] === "number") {1728 output_ = Promise.all([1729 this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),1730 ]);1731 } else {1732 // @ts-expect-error1733 output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);1734 }1735 } else {1736 output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);1737 }1738 /** @type {Promise<rustdoc.FunctionType[]>[]} */1739 const where_clause_ = [];1740 const l = functionSearchType.length;1741 for (let i = 2; i < l; ++i) {1742 where_clause_.push(typeof functionSearchType[i] === "number"1743 // @ts-expect-error1744 ? Promise.all([this.buildItemSearchType(functionSearchType[i])])1745 // @ts-expect-error1746 : this.buildItemSearchTypeAll(functionSearchType[i]),1747 );1748 }1749 const [inputs, output, where_clause] = await Promise.all([1750 inputs_,1751 output_,1752 Promise.all(where_clause_),1753 ]);1754 return {1755 inputs, output, where_clause,1756 };1757 });17581759 return {1760 functionSignature: await parser.next(),1761 paramNames: raw[1],1762 elemCount: parser.elemCount,1763 };1764 }17651766 /**1767 * @param {number} id1768 * @returns {Promise<rustdoc.TypeData|null>}1769 */1770 async getTypeData(id) {1771 const ti = this.database.getData("type");1772 if (!ti) {1773 return null;1774 }1775 const encoded = this.utf8decoder.decode(await ti.at(id));1776 if (encoded === "" || encoded === undefined || encoded === null) {1777 return null;1778 }1779 /**1780 * function_signature, param_names1781 * @type {[string, string, number] | [string, string] | [] | null}1782 */1783 const raw = JSON.parse(encoded);17841785 if (!raw || raw.length === 0) {1786 return null;1787 }17881789 let searchUnbox = false;1790 const invertedFunctionInputsIndex = [];1791 const invertedFunctionOutputIndex = [];17921793 if (typeof raw[0] === "string") {1794 if (raw[2]) {1795 searchUnbox = true;1796 }1797 // the inverted function signature index is a list of bitmaps,1798 // by number of types that appear in the function1799 let i = 0;1800 let pb = makeUint8ArrayFromBase64(raw[0]);1801 let l = pb.length;1802 while (i < l) {1803 if (pb[i] === 0) {1804 invertedFunctionInputsIndex.push(RoaringBitmap.empty());1805 i += 1;1806 } else {1807 const bitmap = new RoaringBitmap(pb, i);1808 i += bitmap.consumed_len_bytes;1809 invertedFunctionInputsIndex.push(bitmap);1810 }1811 }1812 i = 0;1813 pb = makeUint8ArrayFromBase64(raw[1]);1814 l = pb.length;1815 while (i < l) {1816 if (pb[i] === 0) {1817 invertedFunctionOutputIndex.push(RoaringBitmap.empty());1818 i += 1;1819 } else {1820 const bitmap = new RoaringBitmap(pb, i);1821 i += bitmap.consumed_len_bytes;1822 invertedFunctionOutputIndex.push(bitmap);1823 }1824 }1825 } else if (raw[0]) {1826 searchUnbox = true;1827 }18281829 return { searchUnbox, invertedFunctionInputsIndex, invertedFunctionOutputIndex };1830 }18311832 /**1833 * @returns {Promise<string[]>}1834 */1835 async getCrateNameList() {1836 const crateNames = this.database.getData("crateNames");1837 if (!crateNames) {1838 return [];1839 }1840 const l = crateNames.length;1841 const names = [];1842 for (let i = 0; i < l; ++i) {1843 const name = await crateNames.at(i);1844 names.push(name === undefined ? "" : this.utf8decoder.decode(name));1845 }1846 return Promise.all(names);1847 }18481849 /**1850 * @param {number} id non-negative generic index1851 * @returns {Promise<stringdex.RoaringBitmap[]>}1852 */1853 async getGenericInvertedIndex(id) {1854 const gii = this.database.getData("generic_inverted_index");1855 if (!gii) {1856 return [];1857 }1858 const pb = await gii.at(id);1859 if (pb === undefined || pb === null || pb.length === 0) {1860 return [];1861 }18621863 const invertedFunctionSignatureIndex = [];1864 // the inverted function signature index is a list of bitmaps,1865 // by number of types that appear in the function1866 let i = 0;1867 const l = pb.length;1868 while (i < l) {1869 if (pb[i] === 0) {1870 invertedFunctionSignatureIndex.push(RoaringBitmap.empty());1871 i += 1;1872 } else {1873 const bitmap = new RoaringBitmap(pb, i);1874 i += bitmap.consumed_len_bytes;1875 invertedFunctionSignatureIndex.push(bitmap);1876 }1877 }1878 return invertedFunctionSignatureIndex;1879 }18801881 /**1882 * @param {number} id1883 * @param {boolean} loadFunctionData1884 * @returns {Promise<rustdoc.Row?>}1885 */1886 async getRow(id, loadFunctionData) {1887 const [name_, entry, path, functionData] = await Promise.all([1888 this.getName(id),1889 this.getEntryData(id),1890 this.getPathData(id),1891 loadFunctionData ? this.getFunctionData(id) : null,1892 ]);1893 if (!entry && !path) {1894 return null;1895 }1896 /** @type {function("parent" | "traitParent"): Promise<rustdoc.RowParent>} */1897 const buildParentLike = async field => {1898 const [name, path] = entry !== null && entry[field] !== null ?1899 await Promise.all([this.getName(entry[field]), this.getPathData(entry[field])]) :1900 [null, null];1901 if (name !== null && path !== null) {1902 return { name, path };1903 }1904 return null;1905 };19061907 const [1908 moduleName,1909 modulePathData,1910 exactModuleName,1911 exactModulePathData,1912 parent,1913 traitParent,1914 crateOrNull,1915 ] = await Promise.all([1916 entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,1917 entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,1918 entry && entry.exactModulePath !== null ?1919 this.getName(entry.exactModulePath) :1920 null,1921 entry && entry.exactModulePath !== null ?1922 this.getPathData(entry.exactModulePath) :1923 null,1924 buildParentLike("parent"),1925 buildParentLike("traitParent"),1926 entry ? this.getName(entry.krate) : "",1927 ]);1928 const crate = crateOrNull === null ? "" : crateOrNull;1929 const name = name_ === null ? "" : name_;1930 const normalizedName = (name.indexOf("_") === -1 ?1931 name :1932 name.replace(/_/g, "")).toLowerCase();1933 const modulePath = modulePathData === null || moduleName === null ? "" :1934 (modulePathData.modulePath === "" ?1935 moduleName :1936 `${modulePathData.modulePath}::${moduleName}`);19371938 return {1939 id,1940 crate,1941 ty: entry ? entry.ty : nonnull(path).ty,1942 name,1943 normalizedName,1944 modulePath,1945 exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :1946 (exactModulePathData.exactModulePath === "" ?1947 exactModuleName :1948 `${exactModulePathData.exactModulePath}::${exactModuleName}`),1949 entry,1950 path,1951 functionData,1952 deprecated: entry ? entry.deprecated : false,1953 unstable: entry ? entry.unstable : false,1954 parent,1955 traitParent,1956 };1957 }19581959 /**1960 * Convert a list of RawFunctionType / ID to object-based FunctionType.1961 *1962 * Crates often have lots of functions in them, and it's common to have a large number of1963 * functions that operate on a small set of data types, so the search index compresses them1964 * by encoding function parameter and return types as indexes into an array of names.1965 *1966 * Even when a general-purpose compression algorithm is used, this is still a win.1967 * I checked. https://github.com/rust-lang/rust/pull/98475#issue-12843959851968 *1969 * The format for individual function types is encoded in1970 * librustdoc/html/render/mod.rs: impl Serialize for RenderType1971 *1972 * @param {null|Array<rustdoc.RawFunctionType>} types1973 *1974 * @return {Promise<Array<rustdoc.FunctionType>>}1975 */1976 async buildItemSearchTypeAll(types) {1977 return types && types.length > 0 ?1978 await Promise.all(types.map(type => this.buildItemSearchType(type))) :1979 EMPTY_GENERICS_ARRAY;1980 }19811982 /**1983 * Converts a single type.1984 *1985 * @param {rustdoc.RawFunctionType} type1986 * @return {Promise<rustdoc.FunctionType>}1987 */1988 async buildItemSearchType(type) {1989 const PATH_INDEX_DATA = 0;1990 const GENERICS_DATA = 1;1991 const BINDINGS_DATA = 2;1992 let id, generics;1993 /**1994 * @type {Map<number, rustdoc.FunctionType[]>}1995 */1996 let bindings;1997 if (typeof type === "number") {1998 id = type;1999 generics = EMPTY_GENERICS_ARRAY;2000 bindings = EMPTY_BINDINGS_MAP;
Findings
✓ No findings reported for this file.