1// ignore-tidy-file-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 decl_macro_attribute: 28,124 decl_macro_derive: 29,125});126const itemTypesName = Array.from(Object.keys(itemTypes));127128// When filtering, some types might be included as well. For example, when you filter on `constant`,129// we also include associated constant items.130//131// This map is built as follows: the first item of the array is the type to be included when the132// second type of the array is used as filter.133const itemParents = new Map([134 [itemTypes.associatedconstant, itemTypes.constant],135 [itemTypes.method, itemTypes.fn],136 [itemTypes.tymethod, itemTypes.fn],137 [itemTypes.primitive, itemTypes.type],138 [itemTypes.associatedtype, itemTypes.type],139 [itemTypes.traitalias, itemTypes.trait],140 [itemTypes.attr, itemTypes.macro],141 [itemTypes.derive, itemTypes.macro],142 [itemTypes.externcrate, itemTypes.import],143]);144145const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";146147// Hard limit on how deep to recurse into generics when doing type-driven search.148// This needs limited, partially because149// a search for `Ty` shouldn't match `WithInfcx<ParamEnvAnd<Vec<ConstTy<Interner<Ty=Ty>>>>>`,150// but mostly because this is the simplest and most principled way to limit the number151// of permutations we need to check.152const UNBOXING_LIMIT = 5;153154// used for search query verification155// because searches are often performed using substrings of identifiers,156// and not just full identiferes, we allow them to start with chars that otherwise157// can only appear in the middle of identifiers158const REGEX_IDENT = /\p{ID_Continue}+/uy;159const REGEX_INVALID_TYPE_FILTER = /[^a-z]/ui;160161const MAX_RESULTS = 200;162const NO_TYPE_FILTER = -1;163const DEPRECATED_COUNT_SELECTOR = "deprecated-count";164165/**166 * The [edit distance] is a metric for measuring the difference between two strings.167 *168 * [edit distance]: https://en.wikipedia.org/wiki/Edit_distance169 */170171/*172 * This function was translated, mostly line-for-line, from173 * https://github.com/rust-lang/rust/blob/ff4b772f805ec1e/compiler/rustc_span/src/edit_distance.rs174 *175 * The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted176 * because it does not permit modifying characters that have already been transposed. The specific177 * algorithm should not matter to the caller of the methods, which is why it is not noted in the178 * documentation.179 */180const editDistanceState = {181 /**182 * @type {number[]}183 */184 current: [],185 /**186 * @type {number[]}187 */188 prev: [],189 /**190 * @type {number[]}191 */192 prevPrev: [],193 /**194 * @param {string} a195 * @param {string} b196 * @param {number} limit197 * @returns198 */199 calculate: function calculate(a, b, limit) {200 // Ensure that `b` is the shorter string, minimizing memory use.201 if (a.length < b.length) {202 const aTmp = a;203 a = b;204 b = aTmp;205 }206207 const minDist = a.length - b.length;208 // If we know the limit will be exceeded, we can return early.209 if (minDist > limit) {210 return limit + 1;211 }212213 // Strip common prefix.214 // We know that `b` is the shorter string, so we don't need to check215 // `a.length`.216 while (b.length > 0 && b[0] === a[0]) {217 a = a.substring(1);218 b = b.substring(1);219 }220 // Strip common suffix.221 while (b.length > 0 && b[b.length - 1] === a[a.length - 1]) {222 a = a.substring(0, a.length - 1);223 b = b.substring(0, b.length - 1);224 }225226 // If either string is empty, the distance is the length of the other.227 // We know that `b` is the shorter string, so we don't need to check `a`.228 if (b.length === 0) {229 return minDist;230 }231232 const aLength = a.length;233 const bLength = b.length;234235 for (let i = 0; i <= bLength; ++i) {236 this.current[i] = 0;237 this.prev[i] = i;238 this.prevPrev[i] = Number.MAX_VALUE;239 }240241 // row by row242 for (let i = 1; i <= aLength; ++i) {243 this.current[0] = i;244 const aIdx = i - 1;245246 // column by column247 for (let j = 1; j <= bLength; ++j) {248 const bIdx = j - 1;249250 // There is no cost to substitute a character with itself.251 const substitutionCost = a[aIdx] === b[bIdx] ? 0 : 1;252253 this.current[j] = Math.min(254 // deletion255 this.prev[j] + 1,256 // insertion257 this.current[j - 1] + 1,258 // substitution259 this.prev[j - 1] + substitutionCost,260 );261262 if ((i > 1) && (j > 1) && (a[aIdx] === b[bIdx - 1]) && (a[aIdx - 1] === b[bIdx])) {263 // transposition264 this.current[j] = Math.min(265 this.current[j],266 this.prevPrev[j - 2] + 1,267 );268 }269 }270271 // Rotate the buffers, reusing the memory272 const prevPrevTmp = this.prevPrev;273 this.prevPrev = this.prev;274 this.prev = this.current;275 this.current = prevPrevTmp;276 }277278 // `prev` because we already rotated the buffers.279 const distance = this.prev[bLength];280 return distance <= limit ? distance : (limit + 1);281 },282};283284/**285 * @param {string} a286 * @param {string} b287 * @param {number} limit288 * @returns289 */290function editDistance(a, b, limit) {291 return editDistanceState.calculate(a, b, limit);292}293294/**295 * @param {string} c296 * @returns {boolean}297 */298function isEndCharacter(c) {299 return "=,>-])".indexOf(c) !== -1;300}301302/**303 * Same thing as ItemType::is_fn_like in item_type.rs304 *305 * @param {rustdoc.ItemType} ty306 * @returns307 */308function isFnLikeTy(ty) {309 return ty === itemTypes.fn || ty === itemTypes.method || ty === itemTypes.tymethod;310}311312/**313 * Returns `true` if the given `c` character is a separator.314 *315 * @param {string} c316 *317 * @return {boolean}318 */319function isSeparatorCharacter(c) {320 return c === "," || c === "=";321}322323/**324 * Returns `true` if the current parser position is starting with "->".325 *326 * @param {rustdoc.ParserState} parserState327 *328 * @return {boolean}329 */330function isReturnArrow(parserState) {331 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";332}333334/**335 * Increase current parser position until it doesn't find a whitespace anymore.336 *337 * @param {rustdoc.ParserState} parserState338 */339function skipWhitespace(parserState) {340 while (parserState.pos < parserState.userQuery.length) {341 const c = parserState.userQuery[parserState.pos];342 if (c !== " ") {343 break;344 }345 parserState.pos += 1;346 }347}348349/**350 * Returns `true` if the previous character is `lookingFor`.351 *352 * @param {rustdoc.ParserState} parserState353 * @param {String} lookingFor354 *355 * @return {boolean}356 */357function prevIs(parserState, lookingFor) {358 let pos = parserState.pos;359 while (pos > 0) {360 const c = parserState.userQuery[pos - 1];361 if (c === lookingFor) {362 return true;363 } else if (c !== " ") {364 break;365 }366 pos -= 1;367 }368 return false;369}370371/**372 * Returns `true` if the last element in the `elems` argument has generics.373 *374 * @param {Array<rustdoc.ParserQueryElement>} elems375 * @param {rustdoc.ParserState} parserState376 *377 * @return {boolean}378 */379function isLastElemGeneric(elems, parserState) {380 return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||381 prevIs(parserState, ">");382}383384/**385 *386 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query387 * @param {rustdoc.ParserState} parserState388 * @param {rustdoc.ParserQueryElement[]} elems389 * @param {boolean} isInGenerics390 */391function getFilteredNextElem(query, parserState, elems, isInGenerics) {392 const start = parserState.pos;393 if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {394 throw ["Expected type filter before ", ":"];395 }396 getNextElem(query, parserState, elems, isInGenerics);397 if (parserState.userQuery[parserState.pos] === ":" && !isPathStart(parserState)) {398 if (parserState.typeFilter !== null) {399 throw [400 "Unexpected ",401 ":",402 " (expected path after type filter ",403 parserState.typeFilter + ":",404 ")",405 ];406 }407 if (elems.length === 0) {408 throw ["Expected type filter before ", ":"];409 } else if (query.literalSearch) {410 throw ["Cannot use quotes on type filter"];411 }412 // The type filter doesn't count as an element since it's a modifier.413 const typeFilterElem = elems.pop();414 checkExtraTypeFilterCharacters(start, parserState);415 // typeFilterElem is not undefined. If it was, the elems.length check would have fired.416 // @ts-expect-error417 parserState.typeFilter = typeFilterElem.normalizedPathLast;418 parserState.pos += 1;419 parserState.totalElems -= 1;420 query.literalSearch = false;421 getNextElem(query, parserState, elems, isInGenerics);422 }423}424425/**426 * This function parses the next query element until it finds `endChar`,427 * calling `getNextElem` to collect each element.428 *429 * If there is no `endChar`, this function will implicitly stop at the end430 * without raising an error.431 *432 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query433 * @param {rustdoc.ParserState} parserState434 * @param {Array<rustdoc.ParserQueryElement>} elems435 * - This is where the new {QueryElement} will be added.436 * @param {string} endChar - This function will stop when it'll encounter this437 * character.438 * @returns {{foundSeparator: boolean}}439 */440function getItemsBefore(query, parserState, elems, endChar) {441 let foundStopChar = true;442 let foundSeparator = false;443444 // If this is a generic, keep the outer item's type filter around.445 const oldTypeFilter = parserState.typeFilter;446 parserState.typeFilter = null;447 const oldIsInBinding = parserState.isInBinding;448 parserState.isInBinding = null;449450 // ML-style Higher Order Function notation451 //452 // a way to search for any closure or fn pointer regardless of453 // which closure trait is used454 //455 // Looks like this:456 //457 // `option<t>, (t -> u) -> option<u>`458 // ^^^^^^459 //460 // The Rust-style closure notation is implemented in getNextElem461 let hofParameters = null;462463 let extra = "";464 if (endChar === ">") {465 extra = "<";466 } else if (endChar === "]") {467 extra = "[";468 } else if (endChar === ")") {469 extra = "(";470 } else if (endChar === "") {471 extra = "->";472 } else {473 extra = endChar;474 }475476 while (parserState.pos < parserState.length) {477 const c = parserState.userQuery[parserState.pos];478 if (c === endChar) {479 if (parserState.isInBinding) {480 throw ["Unexpected ", endChar, " after ", "="];481 }482 break;483 } else if (endChar !== "" && isReturnArrow(parserState)) {484 // ML-style HOF notation only works when delimited in something,485 // otherwise a function arrow starts the return type of the top486 if (parserState.isInBinding) {487 throw ["Unexpected ", "->", " after ", "="];488 }489 hofParameters = [...elems];490 elems.length = 0;491 parserState.pos += 2;492 foundStopChar = true;493 foundSeparator = false;494 continue;495 } else if (c === " ") {496 parserState.pos += 1;497 continue;498 } else if (isSeparatorCharacter(c)) {499 parserState.pos += 1;500 foundStopChar = true;501 foundSeparator = true;502 continue;503 } else if (c === ":" && isPathStart(parserState)) {504 throw ["Unexpected ", "::", ": paths cannot start with ", "::"];505 } else if (isEndCharacter(c)) {506 throw ["Unexpected ", c, " after ", extra];507 }508 if (!foundStopChar) {509 /** @type {string[]} */510 let extra = [];511 if (isLastElemGeneric(query.elems, parserState)) {512 extra = [" after ", ">"];513 } else if (prevIs(parserState, "\"")) {514 throw ["Cannot have more than one element if you use quotes"];515 }516 if (endChar !== "") {517 throw [518 "Expected ",519 ",",520 ", ",521 "=",522 ", or ",523 endChar,524 ...extra,525 ", found ",526 c,527 ];528 }529 throw [530 "Expected ",531 ",",532 " or ",533 "=",534 ...extra,535 ", found ",536 c,537 ];538 }539 const posBefore = parserState.pos;540 getFilteredNextElem(query, parserState, elems, endChar !== "");541 if (endChar !== "" && parserState.pos >= parserState.length) {542 throw ["Unclosed ", extra];543 }544 // This case can be encountered if `getNextElem` encountered a "stop character"545 // right from the start. For example if you have `,,` or `<>`. In this case,546 // we simply move up the current position to continue the parsing.547 if (posBefore === parserState.pos) {548 parserState.pos += 1;549 }550 foundStopChar = false;551 }552 if (parserState.pos >= parserState.length && endChar !== "") {553 throw ["Unclosed ", extra];554 }555 // We are either at the end of the string or on the `endChar` character, let's move556 // forward in any case.557 parserState.pos += 1;558559 if (hofParameters) {560 // Commas in a HOF don't cause wrapping parens to become a tuple.561 // If you want a one-tuple with a HOF in it, write `((a -> b),)`.562 foundSeparator = false;563 // HOFs can't have directly nested bindings.564 if ([...elems, ...hofParameters].some(x => x.bindingName)565 || parserState.isInBinding) {566 throw ["Unexpected ", "=", " within ", "->"];567 }568 // HOFs are represented the same way closures are.569 // The arguments are wrapped in a tuple, and the output570 // is a binding, even though the compiler doesn't technically571 // represent fn pointers that way.572 const hofElem = makePrimitiveElement("->", {573 generics: hofParameters,574 bindings: new Map([["output", [...elems]]]),575 typeFilter: null,576 });577 elems.length = 0;578 elems[0] = hofElem;579 }580581 parserState.typeFilter = oldTypeFilter;582 parserState.isInBinding = oldIsInBinding;583584 return { foundSeparator };585}586587/**588 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query589 * @param {rustdoc.ParserState} parserState590 * @param {Array<rustdoc.ParserQueryElement>} elems591 * - This is where the new {QueryElement} will be added.592 * @param {boolean} isInGenerics593 */594function getNextElem(query, parserState, elems, isInGenerics) {595 /** @type {rustdoc.ParserQueryElement[]} */596 const generics = [];597598 /** @type {function(string, string): void} */599 const handleRefOrPtr = (chr, name) => {600 if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {601 throw [602 "Invalid search type: primitive ",603 chr,604 " and ",605 parserState.typeFilter,606 " both specified",607 ];608 }609 parserState.typeFilter = null;610 parserState.pos += 1;611 let c = parserState.userQuery[parserState.pos];612 while (c === " " && parserState.pos < parserState.length) {613 parserState.pos += 1;614 c = parserState.userQuery[parserState.pos];615 }616 const generics = [];617 const pos = parserState.pos;618 if (parserState.userQuery.slice(pos, pos + 3) === "mut") {619 generics.push(makePrimitiveElement("mut", { typeFilter: "keyword" }));620 parserState.pos += 3;621 c = parserState.userQuery[parserState.pos];622 } else if (chr === "*" && parserState.userQuery.slice(pos, pos + 5) === "const") {623 // make *const T parse the same as *T624 parserState.pos += 5;625 c = parserState.userQuery[parserState.pos];626 }627 while (c === " " && parserState.pos < parserState.length) {628 parserState.pos += 1;629 c = parserState.userQuery[parserState.pos];630 }631 if (!isEndCharacter(c) && parserState.pos < parserState.length) {632 getFilteredNextElem(query, parserState, generics, isInGenerics);633 }634 elems.push(makePrimitiveElement(name, { generics }));635 };636637 skipWhitespace(parserState);638 let start = parserState.pos;639 let end;640 if ("[(".indexOf(parserState.userQuery[parserState.pos]) !== -1) {641 let endChar = ")";642 let name = "()";643 let friendlyName = "tuple";644645 if (parserState.userQuery[parserState.pos] === "[") {646 endChar = "]";647 name = "[]";648 friendlyName = "slice";649 }650 parserState.pos += 1;651 const { foundSeparator } = getItemsBefore(query, parserState, generics, endChar);652 const typeFilter = parserState.typeFilter;653 const bindingName = parserState.isInBinding;654 parserState.typeFilter = null;655 parserState.isInBinding = null;656 for (const gen of generics) {657 if (gen.bindingName !== null) {658 throw ["Type parameter ", "=", ` cannot be within ${friendlyName} `, name];659 }660 }661 if (name === "()" && !foundSeparator && generics.length === 1662 && typeFilter === null) {663 elems.push(generics[0]);664 } else if (name === "()" && generics.length === 1 && generics[0].name === "->") {665 // `primitive:(a -> b)` parser to `primitive:"->"<output=b, (a,)>`666 // not `primitive:"()"<"->"<output=b, (a,)>>`667 generics[0].typeFilter = typeFilter;668 elems.push(generics[0]);669 } else {670 if (typeFilter !== null && typeFilter !== "primitive") {671 throw [672 "Invalid search type: primitive ",673 name,674 " and ",675 typeFilter,676 " both specified",677 ];678 }679 parserState.totalElems += 1;680 if (isInGenerics) {681 parserState.genericsElems += 1;682 }683 elems.push(makePrimitiveElement(name, { bindingName, generics }));684 }685 } else if (parserState.userQuery[parserState.pos] === "&") {686 handleRefOrPtr("&", "reference");687 } else if (parserState.userQuery[parserState.pos] === "*") {688 handleRefOrPtr("*", "pointer");689 } else {690 const isStringElem = parserState.userQuery[start] === "\"";691 // We handle the strings on their own mostly to make code easier to follow.692 if (isStringElem) {693 start += 1;694 getStringElem(query, parserState, isInGenerics);695 end = parserState.pos - 1;696 } else {697 end = getIdentEndPosition(parserState);698 }699 if (parserState.pos < parserState.length &&700 parserState.userQuery[parserState.pos] === "<"701 ) {702 if (start >= end) {703 throw ["Found generics without a path"];704 }705 parserState.pos += 1;706 getItemsBefore(query, parserState, generics, ">");707 } else if (parserState.pos < parserState.length &&708 parserState.userQuery[parserState.pos] === "("709 ) {710 if (start >= end) {711 throw ["Found generics without a path"];712 }713 if (parserState.isInBinding) {714 throw ["Unexpected ", "(", " after ", "="];715 }716 parserState.pos += 1;717 const typeFilter = parserState.typeFilter;718 parserState.typeFilter = null;719 getItemsBefore(query, parserState, generics, ")");720 skipWhitespace(parserState);721 if (isReturnArrow(parserState)) {722 parserState.pos += 2;723 skipWhitespace(parserState);724 getFilteredNextElem(query, parserState, generics, isInGenerics);725 generics[generics.length - 1].bindingName = makePrimitiveElement("output");726 } else {727 generics.push(makePrimitiveElement(null, {728 bindingName: makePrimitiveElement("output"),729 typeFilter: null,730 }));731 }732 parserState.typeFilter = typeFilter;733 }734 if (isStringElem) {735 skipWhitespace(parserState);736 }737 if (start >= end && generics.length === 0) {738 return;739 }740 if (parserState.userQuery[parserState.pos] === "=") {741 if (parserState.isInBinding) {742 throw ["Cannot write ", "=", " twice in a binding"];743 }744 if (!isInGenerics) {745 throw ["Type parameter ", "=", " must be within generics list"];746 }747 const name = parserState.userQuery.slice(start, end).trim();748 if (name === "!") {749 throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];750 }751 if (name.includes("!")) {752 throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];753 }754 if (name.includes("::")) {755 throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];756 }757 if (name.includes(":")) {758 throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];759 }760 parserState.isInBinding = { name, generics };761 } else {762 elems.push(763 createQueryElement(764 query,765 parserState,766 parserState.userQuery.slice(start, end),767 generics,768 isInGenerics,769 ),770 );771 }772 }773}774775/**776 * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored777 * if empty).778 *779 * @param {number} start780 * @param {rustdoc.ParserState} parserState781 */782function checkExtraTypeFilterCharacters(start, parserState) {783 const query = parserState.userQuery.slice(start, parserState.pos).trim();784785 const match = query.match(REGEX_INVALID_TYPE_FILTER);786 if (match) {787 throw [788 "Unexpected ",789 match[0],790 " in type filter (before ",791 ":",792 ")",793 ];794 }795}796797/**798 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query799 * @param {rustdoc.ParserState} parserState800 * @param {string} name - Name of the query element.801 * @param {Array<rustdoc.ParserQueryElement>} generics - List of generics of this query element.802 * @param {boolean} isInGenerics803 *804 * @return {rustdoc.ParserQueryElement} - The newly created `QueryElement`.805 */806function createQueryElement(query, parserState, name, generics, isInGenerics) {807 const path = name.trim();808 if (path.length === 0 && generics.length === 0) {809 throw ["Unexpected ", parserState.userQuery[parserState.pos]];810 }811 if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {812 throw ["Cannot have more than one element if you use quotes"];813 }814 const typeFilter = parserState.typeFilter;815 parserState.typeFilter = null;816 if (name.trim() === "!") {817 if (typeFilter !== null && typeFilter !== "primitive") {818 throw [819 "Invalid search type: primitive never type ",820 "!",821 " and ",822 typeFilter,823 " both specified",824 ];825 }826 if (generics.length !== 0) {827 throw [828 "Never type ",829 "!",830 " does not accept generic parameters",831 ];832 }833 const bindingName = parserState.isInBinding;834 parserState.isInBinding = null;835 return makePrimitiveElement("never", { bindingName });836 }837 const quadcolon = /::\s*::/.exec(path);838 if (path.startsWith("::")) {839 throw ["Paths cannot start with ", "::"];840 } else if (quadcolon !== null) {841 throw ["Unexpected ", quadcolon[0]];842 }843 const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/).map(x => x.toLowerCase());844 // In case we only have something like `<p>`, there is no name.845 if (pathSegments.length === 0846 || (pathSegments.length === 1 && pathSegments[0] === "")) {847 if (generics.length > 0 || prevIs(parserState, ">")) {848 throw ["Found generics without a path"];849 } else {850 throw ["Unexpected ", parserState.userQuery[parserState.pos]];851 }852 }853 for (const [i, pathSegment] of pathSegments.entries()) {854 if (pathSegment === "!") {855 if (i !== 0) {856 throw ["Never type ", "!", " is not associated item"];857 }858 pathSegments[i] = "never";859 }860 }861 parserState.totalElems += 1;862 if (isInGenerics) {863 parserState.genericsElems += 1;864 }865 const bindingName = parserState.isInBinding;866 parserState.isInBinding = null;867 const bindings = new Map();868 const pathLast = pathSegments[pathSegments.length - 1];869 return {870 name: name.trim(),871 id: null,872 fullPath: pathSegments,873 pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),874 pathLast,875 normalizedPathLast: pathLast.replace(/_/g, ""),876 generics: generics.filter(gen => {877 // Syntactically, bindings are parsed as generics,878 // but the query engine treats them differently.879 if (gen.bindingName !== null && gen.bindingName.name !== null) {880 if (gen.name !== null) {881 gen.bindingName.generics.unshift(gen);882 }883 bindings.set(884 gen.bindingName.name.toLowerCase().replace(/_/g, ""),885 gen.bindingName.generics,886 );887 return false;888 }889 return true;890 }),891 bindings,892 typeFilter,893 bindingName,894 };895}896897/**898 *899 * @param {string|null} name900 * @param {rustdoc.ParserQueryElementFields=} extra901 * @returns {rustdoc.ParserQueryElement}902 */903function makePrimitiveElement(name, extra) {904 return Object.assign({905 name,906 id: null,907 fullPath: [name],908 pathWithoutLast: [],909 pathLast: name,910 normalizedPathLast: name,911 generics: [],912 bindings: new Map(),913 typeFilter: "primitive",914 bindingName: null,915 }, extra);916}917918/**919 * If we encounter a `"`, then we try to extract the string920 * from it until we find another `"`.921 *922 * This function will throw an error in the following cases:923 * * There is already another string element.924 * * We are parsing a generic argument.925 * * There is more than one element.926 * * There is no closing `"`.927 *928 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query929 * @param {rustdoc.ParserState} parserState930 * @param {boolean} isInGenerics931 */932function getStringElem(query, parserState, isInGenerics) {933 if (isInGenerics) {934 throw ["Unexpected ", "\"", " in generics"];935 } else if (query.literalSearch) {936 throw ["Cannot have more than one literal search element"];937 } else if (parserState.totalElems - parserState.genericsElems > 0) {938 throw ["Cannot use literal search when there is more than one element"];939 }940 parserState.pos += 1;941 const start = parserState.pos;942 const end = getIdentEndPosition(parserState);943 if (parserState.pos >= parserState.length) {944 throw ["Unclosed ", "\""];945 } else if (parserState.userQuery[end] !== "\"") {946 throw ["Unexpected ", parserState.userQuery[end], " in a string element"];947 } else if (start === end) {948 throw ["Cannot have empty string element"];949 }950 // To skip the quote at the end.951 parserState.pos += 1;952 query.literalSearch = true;953}954955/**956 * This function goes through all characters until it reaches an invalid ident957 * character or the end of the query. It returns the position of the last958 * character of the ident.959 *960 * @param {rustdoc.ParserState} parserState961 *962 * @return {number}963 */964function getIdentEndPosition(parserState) {965 let afterIdent = consumeIdent(parserState);966 let end = parserState.pos;967 let macroExclamation = -1;968 while (parserState.pos < parserState.length) {969 const c = parserState.userQuery[parserState.pos];970 if (c === "!") {971 if (macroExclamation !== -1) {972 throw ["Cannot have more than one ", "!", " in an ident"];973 } else if (parserState.pos + 1 < parserState.length) {974 const pos = parserState.pos;975 parserState.pos++;976 const beforeIdent = consumeIdent(parserState);977 parserState.pos = pos;978 if (beforeIdent) {979 throw ["Unexpected ", "!", ": it can only be at the end of an ident"];980 }981 }982 if (afterIdent) macroExclamation = parserState.pos;983 } else if (isPathSeparator(c)) {984 if (c === ":") {985 if (!isPathStart(parserState)) {986 break;987 }988 // Skip current ":".989 parserState.pos += 1;990 } else {991 while (parserState.pos + 1 < parserState.length) {992 const next_c = parserState.userQuery[parserState.pos + 1];993 if (next_c !== " ") {994 break;995 }996 parserState.pos += 1;997 }998 }999 if (macroExclamation !== -1) {1000 throw ["Cannot have associated items in macros"];1001 }1002 } else if (1003 c === "[" ||1004 c === "(" ||1005 isEndCharacter(c) ||1006 isSpecialStartCharacter(c) ||1007 isSeparatorCharacter(c)1008 ) {1009 break;1010 } else if (parserState.pos > 0) {1011 throw ["Unexpected ", c, " after ", parserState.userQuery[parserState.pos - 1],1012 " (not a valid identifier)"];1013 } else {1014 throw ["Unexpected ", c, " (not a valid identifier)"];1015 }1016 parserState.pos += 1;1017 afterIdent = consumeIdent(parserState);1018 end = parserState.pos;1019 }1020 if (macroExclamation !== -1) {1021 if (parserState.typeFilter === null) {1022 parserState.typeFilter = "macro";1023 } else if (parserState.typeFilter !== "macro") {1024 throw [1025 "Invalid search type: macro ",1026 "!",1027 " and ",1028 parserState.typeFilter,1029 " both specified",1030 ];1031 }1032 end = macroExclamation;1033 }1034 return end;1035}10361037/**1038 * @param {string} c1039 * @returns1040 */1041function isSpecialStartCharacter(c) {1042 return "<\"".indexOf(c) !== -1;1043}10441045/**1046 * Returns `true` if the current parser position is starting with "::".1047 *1048 * @param {rustdoc.ParserState} parserState1049 *1050 * @return {boolean}1051 */1052function isPathStart(parserState) {1053 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";1054}10551056/**1057 * If the current parser position is at the beginning of an identifier,1058 * move the position to the end of it and return `true`. Otherwise, return `false`.1059 *1060 * @param {rustdoc.ParserState} parserState1061 *1062 * @return {boolean}1063 */1064function consumeIdent(parserState) {1065 REGEX_IDENT.lastIndex = parserState.pos;1066 const match = parserState.userQuery.match(REGEX_IDENT);1067 if (match) {1068 parserState.pos += match[0].length;1069 return true;1070 }1071 return false;1072}10731074/**1075 * Returns `true` if the given `c` character is a path separator. For example1076 * `:` in `a::b` or a whitespace in `a b`.1077 *1078 * @param {string} c1079 *1080 * @return {boolean}1081 */1082function isPathSeparator(c) {1083 return c === ":" || c === " ";1084}10851086/**1087 * Given an array and an ascending list of indices,1088 * efficiently removes each index in the array.1089 *1090 * @template T1091 * @param {Array<T>} a1092 * @param {Array<number>} idxList1093 */1094function removeIdxListAsc(a, idxList) {1095 if (idxList.length === 0) {1096 return;1097 }1098 let removed = 0;1099 let i = idxList[0];1100 let nextToRemove = idxList[0];1101 while (i < a.length - idxList.length) {1102 while (i === nextToRemove && removed < idxList.length) {1103 removed++;1104 i++;1105 nextToRemove = idxList[removed];1106 }1107 a[i] = a[i + removed];1108 i++;1109 }1110 // truncate array1111 a.length -= idxList.length;1112}11131114/**1115 * @template T1116 */1117class VlqHexDecoder {1118 /**1119 * @param {string} string1120 * @param {function(rustdoc.VlqData): T} cons1121 */1122 constructor(string, cons) {1123 this.string = string;1124 this.cons = cons;1125 this.offset = 0;1126 this.elemCount = 0;1127 /** @type {T[]} */1128 this.backrefQueue = [];1129 }1130 /**1131 * call after consuming `{`1132 * @returns {rustdoc.VlqData[]}1133 */1134 decodeList() {1135 let c = this.string.charCodeAt(this.offset);1136 const ret = [];1137 while (c !== 125) { // 125 = "}"1138 ret.push(this.decode());1139 c = this.string.charCodeAt(this.offset);1140 }1141 this.offset += 1; // eat cb1142 return ret;1143 }1144 /**1145 * consumes and returns a list or integer1146 * @returns {rustdoc.VlqData}1147 */1148 decode() {1149 let n = 0;1150 let c = this.string.charCodeAt(this.offset);1151 if (c === 123) { // 123 = "{"1152 this.offset += 1;1153 return this.decodeList();1154 }1155 while (c < 96) { // 96 = "`"1156 n = (n << 4) | (c & 0xF);1157 this.offset += 1;1158 c = this.string.charCodeAt(this.offset);1159 }1160 // last character >= la1161 n = (n << 4) | (c & 0xF);1162 const [sign, value] = [n & 1, n >> 1];1163 this.offset += 1;1164 this.elemCount += 1;1165 return sign ? -value : value;1166 }1167 /**1168 * @returns {T}1169 */1170 next() {1171 const c = this.string.charCodeAt(this.offset);1172 // sixteen characters after "0" are backref1173 if (c >= 48 && c < 64) { // 48 = "0", 64 = "@"1174 this.offset += 1;1175 return this.backrefQueue[c - 48];1176 }1177 // special exception: 0 doesn't use backref encoding1178 // it's already one character, and it's always nullish1179 if (c === 96) { // 96 = "`"1180 this.offset += 1;1181 return this.cons(0);1182 }1183 const result = this.cons(this.decode());1184 this.backrefQueue.unshift(result);1185 if (this.backrefQueue.length > 16) {1186 this.backrefQueue.pop();1187 }1188 return result;1189 }1190}11911192/** @type {Array<string>} */1193const EMPTY_STRING_ARRAY = [];11941195/** @type {Array<rustdoc.FunctionType>} */1196const EMPTY_GENERICS_ARRAY = [];11971198/** @type {Array<[number, rustdoc.FunctionType[]]>} */1199const EMPTY_BINDINGS_ARRAY = [];12001201/** @type {Map<number, Array<any>>} */1202const EMPTY_BINDINGS_MAP = new Map();12031204/**1205 * @param {string|null} typename1206 * @returns {number}1207 */1208function itemTypeFromName(typename) {1209 if (typename === null) {1210 return NO_TYPE_FILTER;1211 }1212 // @ts-expect-error1213 const index = itemTypes[typename];1214 if (index === undefined) {1215 throw ["Unknown type filter ", typename];1216 }1217 return index;1218}12191220class DocSearch {1221 /**1222 * @param {string} rootPath1223 * @param {stringdex.Database} database1224 */1225 constructor(rootPath, database) {1226 this.rootPath = rootPath;1227 this.database = database;12281229 this.utf8decoder = new TextDecoder();12301231 /** @type {Map<number|null, rustdoc.FunctionType>} */1232 this.TYPES_POOL = new Map();1233 }12341235 /**1236 * Load type name ID set.1237 *1238 * Each of these identifiers are used specially by1239 * type-driven search. Most of them are lang items1240 * in the compiler.1241 *1242 * Use this function, which caches the result, and not1243 * getTypeNameIdsAsync, which is an internal implementation1244 * detail for this.1245 *1246 * @return {Promise<rustdoc.TypeNameIds>|rustdoc.TypeNameIds}1247 */1248 getTypeNameIds() {1249 if (this.typeNameIds) {1250 return this.typeNameIds;1251 }1252 const nn = this.database.getData("normalizedName");1253 if (!nn) {1254 return {1255 typeNameIdOfOutput: -1,1256 typeNameIdOfFnPtr: -1,1257 typeNameIdOfFn: -1,1258 typeNameIdOfFnMut: -1,1259 typeNameIdOfFnOnce: -1,1260 typeNameIdOfArray: -1,1261 typeNameIdOfSlice: -1,1262 typeNameIdOfArrayOrSlice: -1,1263 typeNameIdOfTuple: -1,1264 typeNameIdOfUnit: -1,1265 typeNameIdOfTupleOrUnit: -1,1266 typeNameIdOfReference: -1,1267 typeNameIdOfPointer: -1,1268 typeNameIdOfHof: -1,1269 typeNameIdOfNever: -1,1270 };1271 }1272 return this.getTypeNameIdsAsync(nn);1273 }1274 /**1275 * @param {stringdex.DataColumn} nn1276 * @returns {Promise<rustdoc.TypeNameIds>}1277 */1278 async getTypeNameIdsAsync(nn) {1279 // Each of these identifiers are used specially by1280 // type-driven search.1281 const [1282 // output is the special associated type that goes1283 // after the arrow: the type checker desugars1284 // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`1285 output,1286 // fn, fnmut, and fnonce all match `->`1287 fn,1288 fnMut,1289 fnOnce,1290 hof,1291 // array and slice both match `[]`1292 array,1293 slice,1294 arrayOrSlice,1295 // tuple and unit both match `()`1296 tuple,1297 unit,1298 tupleOrUnit,1299 // reference matches `&`1300 reference,1301 pointer,1302 // never matches `!`1303 never,1304 ] = await Promise.all([1305 nn.search("output"),1306 nn.search("fn"),1307 nn.search("fnmut"),1308 nn.search("fnonce"),1309 nn.search("->"),1310 nn.search("array"),1311 nn.search("slice"),1312 nn.search("[]"),1313 nn.search("tuple"),1314 nn.search("unit"),1315 nn.search("()"),1316 nn.search("reference"),1317 nn.search("pointer"),1318 nn.search("never"),1319 ]);1320 /**1321 * @param {stringdex.Trie|null|undefined} trie1322 * @param {rustdoc.ItemType} ty1323 * @param {string} modulePath1324 * @returns {Promise<number>}1325 * */1326 const first = async(trie, ty, modulePath) => {1327 if (trie) {1328 for (const id of trie.matches().entries()) {1329 const pathData = await this.getPathData(id);1330 if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {1331 return id;1332 }1333 }1334 }1335 return -1;1336 };1337 const typeNameIdOfOutput = await first(output, itemTypes.associatedtype, "");1338 const typeNameIdOfFnPtr = await first(fn, itemTypes.primitive, "");1339 const typeNameIdOfFn = await first(fn, itemTypes.trait, "core::ops");1340 const typeNameIdOfFnMut = await first(fnMut, itemTypes.trait, "core::ops");1341 const typeNameIdOfFnOnce = await first(fnOnce, itemTypes.trait, "core::ops");1342 const typeNameIdOfArray = await first(array, itemTypes.primitive, "");1343 const typeNameIdOfSlice = await first(slice, itemTypes.primitive, "");1344 const typeNameIdOfArrayOrSlice = await first(arrayOrSlice, itemTypes.primitive, "");1345 const typeNameIdOfTuple = await first(tuple, itemTypes.primitive, "");1346 const typeNameIdOfUnit = await first(unit, itemTypes.primitive, "");1347 const typeNameIdOfTupleOrUnit = await first(tupleOrUnit, itemTypes.primitive, "");1348 const typeNameIdOfReference = await first(reference, itemTypes.primitive, "");1349 const typeNameIdOfPointer = await first(pointer, itemTypes.primitive, "");1350 const typeNameIdOfHof = await first(hof, itemTypes.primitive, "");1351 const typeNameIdOfNever = await first(never, itemTypes.primitive, "");1352 this.typeNameIds = {1353 typeNameIdOfOutput,1354 typeNameIdOfFnPtr,1355 typeNameIdOfFn,1356 typeNameIdOfFnMut,1357 typeNameIdOfFnOnce,1358 typeNameIdOfArray,1359 typeNameIdOfSlice,1360 typeNameIdOfArrayOrSlice,1361 typeNameIdOfTuple,1362 typeNameIdOfUnit,1363 typeNameIdOfTupleOrUnit,1364 typeNameIdOfReference,1365 typeNameIdOfPointer,1366 typeNameIdOfHof,1367 typeNameIdOfNever,1368 };1369 return this.typeNameIds;1370 }13711372 /**1373 * Parses the query.1374 *1375 * The supported syntax by this parser is given in the rustdoc book chapter1376 * /src/doc/rustdoc/src/read-documentation/search.md1377 *1378 * When adding new things to the parser, add them there, too!1379 *1380 * @param {string} userQuery - The user query1381 *1382 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query1383 */1384 static parseQuery(userQuery) {1385 /**1386 * Takes the user search input and returns an empty `ParsedQuery`.1387 *1388 * @param {string} userQuery1389 *1390 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}1391 */1392 function newParsedQuery(userQuery) {1393 return {1394 userQuery,1395 elems: [],1396 returned: [],1397 // Total number of "top" elements (does not include generics).1398 foundElems: 0,1399 // Total number of elements (includes generics).1400 totalElems: 0,1401 literalSearch: false,1402 hasReturnArrow: false,1403 error: null,1404 correction: null,1405 proposeCorrectionFrom: null,1406 proposeCorrectionTo: null,1407 // bloom filter build from type ids1408 typeFingerprint: new Uint32Array(4),1409 };1410 }14111412 /**1413 * Parses the provided `query` input to fill `parserState`. If it encounters an error while1414 * parsing `query`, it'll throw an error.1415 *1416 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query1417 * @param {rustdoc.ParserState} parserState1418 */1419 function parseInput(query, parserState) {1420 let foundStopChar = true;14211422 while (parserState.pos < parserState.length) {1423 const c = parserState.userQuery[parserState.pos];1424 if (isEndCharacter(c)) {1425 foundStopChar = true;1426 if (isSeparatorCharacter(c)) {1427 parserState.pos += 1;1428 continue;1429 } else if (c === "-" || c === ">") {1430 if (isReturnArrow(parserState)) {1431 query.hasReturnArrow = true;1432 break;1433 }1434 throw ["Unexpected ", c, " (did you mean ", "->", "?)"];1435 } else if (parserState.pos > 0) {1436 throw ["Unexpected ", c, " after ",1437 parserState.userQuery[parserState.pos - 1]];1438 }1439 throw ["Unexpected ", c];1440 } else if (c === " ") {1441 skipWhitespace(parserState);1442 continue;1443 }1444 if (!foundStopChar) {1445 let extra = EMPTY_STRING_ARRAY;1446 if (isLastElemGeneric(query.elems, parserState)) {1447 extra = [" after ", ">"];1448 } else if (prevIs(parserState, "\"")) {1449 throw ["Cannot have more than one element if you use quotes"];1450 }1451 if (parserState.typeFilter !== null) {1452 throw [1453 "Expected ",1454 ",",1455 " or ",1456 "->",1457 ...extra,1458 ", found ",1459 c,1460 ];1461 }1462 throw [1463 "Expected ",1464 ",",1465 ", ",1466 ":",1467 " or ",1468 "->",1469 ...extra,1470 ", found ",1471 c,1472 ];1473 }1474 const before = query.elems.length;1475 getFilteredNextElem(query, parserState, query.elems, false);1476 if (query.elems.length === before) {1477 // Nothing was added, weird... Let's increase the position to not remain stuck.1478 parserState.pos += 1;1479 }1480 foundStopChar = false;1481 }1482 if (parserState.typeFilter !== null) {1483 throw [1484 "Unexpected ",1485 ":",1486 " (expected path after type filter ",1487 parserState.typeFilter + ":",1488 ")",1489 ];1490 }1491 while (parserState.pos < parserState.length) {1492 if (isReturnArrow(parserState)) {1493 parserState.pos += 2;1494 skipWhitespace(parserState);1495 // Get returned elements.1496 getItemsBefore(query, parserState, query.returned, "");1497 // Nothing can come afterward!1498 query.hasReturnArrow = true;1499 break;1500 } else {1501 parserState.pos += 1;1502 }1503 }1504 }150515061507 userQuery = userQuery.trim().replace(/\r|\n|\t/g, " ");1508 const parserState = {1509 length: userQuery.length,1510 pos: 0,1511 // Total number of elements (includes generics).1512 totalElems: 0,1513 genericsElems: 0,1514 typeFilter: null,1515 isInBinding: null,1516 userQuery,1517 };1518 let query = newParsedQuery(userQuery);15191520 try {1521 parseInput(query, parserState);15221523 // Scan for invalid type filters, so that we can report the error1524 // outside the search loop.1525 /** @param {rustdoc.ParserQueryElement} elem */1526 const checkTypeFilter = elem => {1527 const ty = itemTypeFromName(elem.typeFilter);1528 if (ty === itemTypes.generic && elem.generics.length !== 0) {1529 throw [1530 "Generic type parameter ",1531 elem.name,1532 " does not accept generic parameters",1533 ];1534 }1535 for (const generic of elem.generics) {1536 checkTypeFilter(generic);1537 }1538 for (const constraints of elem.bindings.values()) {1539 for (const constraint of constraints) {1540 checkTypeFilter(constraint);1541 }1542 }1543 };1544 for (const elem of query.elems) {1545 checkTypeFilter(elem);1546 }1547 for (const elem of query.returned) {1548 checkTypeFilter(elem);1549 }1550 } catch (err) {1551 query = newParsedQuery(userQuery);1552 if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {1553 query.error = err;1554 } else {1555 // rethrow the error if it isn't a string array1556 throw err;1557 }15581559 return query;1560 }1561 if (!query.literalSearch) {1562 // If there is more than one element in the query, we switch to literalSearch in any1563 // case.1564 query.literalSearch = parserState.totalElems > 1;1565 }1566 query.foundElems = query.elems.length + query.returned.length;1567 query.totalElems = parserState.totalElems;1568 return query;1569 }15701571 /**1572 * @param {number} id1573 * @returns {Promise<string|null>}1574 */1575 async getName(id) {1576 const ni = this.database.getData("name");1577 if (!ni) {1578 return null;1579 }1580 const name = await ni.at(id);1581 return name === undefined || name === null ? null : this.utf8decoder.decode(name);1582 }15831584 /**1585 * @param {number} id1586 * @returns {Promise<string|null>}1587 */1588 async getDesc(id) {1589 const di = this.database.getData("desc");1590 if (!di) {1591 return null;1592 }1593 const desc = await di.at(id);1594 return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);1595 }15961597 /**1598 * @param {number} id1599 * @returns {Promise<number|null>}1600 */1601 async getAliasTarget(id) {1602 const ai = this.database.getData("alias");1603 if (!ai) {1604 return null;1605 }1606 const bytes = await ai.at(id);1607 if (bytes === undefined || bytes === null || bytes.length === 0) {1608 return null;1609 } else {1610 /** @type {string} */1611 const encoded = this.utf8decoder.decode(bytes);1612 /** @type {number|null} */1613 const decoded = JSON.parse(encoded);1614 return decoded;1615 }1616 }16171618 /**1619 * @param {number} id1620 * @returns {Promise<rustdoc.EntryData|null>}1621 */1622 async getEntryData(id) {1623 const ei = this.database.getData("entry");1624 if (!ei) {1625 return null;1626 }1627 const encoded = this.utf8decoder.decode(await ei.at(id));1628 if (encoded === "" || encoded === undefined || encoded === null) {1629 return null;1630 }1631 /**1632 * krate,1633 * ty,1634 * module_path,1635 * exact_module_path,1636 * parent,1637 * trait_parent,1638 * deprecated,1639 * unstable,1640 * associated_item_disambiguator1641 * @type {rustdoc.ArrayWithOptionals<[1642 * number,1643 * rustdoc.ItemType,1644 * number,1645 * number,1646 * number,1647 * number,1648 * number,1649 * number,1650 * ], [string]>}1651 */1652 const raw = JSON.parse(encoded);1653 const item = {1654 krate: raw[0],1655 ty: raw[1],1656 modulePath: raw[2] === 0 ? null : raw[2] - 1,1657 exactModulePath: raw[3] === 0 ? null : raw[3] - 1,1658 parent: raw[4] === 0 ? null : raw[4] - 1,1659 traitParent: raw[5] === 0 ? null : raw[5] - 1,1660 deprecated: raw[6] === 1 ? true : false,1661 unstable: raw[7] === 1 ? true : false,1662 associatedItemDisambiguatorOrExternCrateUrl: raw.length === 8 ? null : raw[8],1663 forceMacroHref: false,1664 };1665 if (item.ty === itemTypes.decl_macro_attribute || item.ty === itemTypes.decl_macro_derive) {1666 // "proc attribute" is 23, "proc derive" is 24 whereas "decl macro attribute" is 28 and1667 // "decl macro derive" is 29, so 5 of difference to go from the latter to the former.1668 item.ty -= 5;1669 item.forceMacroHref = true;1670 }1671 return item;1672 }16731674 /**1675 * @param {number} id1676 * @returns {Promise<rustdoc.PathData|null>}1677 */1678 async getPathData(id) {1679 const pi = this.database.getData("path");1680 if (!pi) {1681 return null;1682 }1683 const encoded = this.utf8decoder.decode(await pi.at(id));1684 if (encoded === "" || encoded === undefined || encoded === null) {1685 return null;1686 }1687 /**1688 * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index1689 * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}1690 */1691 const raw = JSON.parse(encoded);1692 return {1693 ty: raw[0],1694 modulePath: raw[1],1695 exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],1696 };1697 }16981699 /**1700 * @param {number} id1701 * @returns {Promise<rustdoc.FunctionData|null>}1702 */1703 async getFunctionData(id) {1704 const fi = this.database.getData("function");1705 if (!fi) {1706 return null;1707 }1708 const encoded = this.utf8decoder.decode(await fi.at(id));1709 if (encoded === "" || encoded === undefined || encoded === null) {1710 return null;1711 }1712 /**1713 * function_signature, param_names1714 * @type {[string, string[]]}1715 */1716 const raw = JSON.parse(encoded);17171718 const parser = new VlqHexDecoder(raw[0], async functionSearchType => {1719 if (typeof functionSearchType === "number") {1720 return null;1721 }1722 const INPUTS_DATA = 0;1723 const OUTPUT_DATA = 1;1724 /** @type {Promise<rustdoc.FunctionType[]>} */1725 let inputs_;1726 /** @type {Promise<rustdoc.FunctionType[]>} */1727 let output_;1728 if (typeof functionSearchType[INPUTS_DATA] === "number") {1729 inputs_ = Promise.all([1730 this.buildItemSearchType(functionSearchType[INPUTS_DATA]),1731 ]);1732 } else {1733 // @ts-ignore1734 inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);1735 }1736 if (functionSearchType.length > 1) {1737 if (typeof functionSearchType[OUTPUT_DATA] === "number") {1738 output_ = Promise.all([1739 this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),1740 ]);1741 } else {1742 // @ts-expect-error1743 output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);1744 }1745 } else {1746 output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);1747 }1748 /** @type {Promise<rustdoc.FunctionType[]>[]} */1749 const where_clause_ = [];1750 const l = functionSearchType.length;1751 for (let i = 2; i < l; ++i) {1752 where_clause_.push(typeof functionSearchType[i] === "number"1753 // @ts-expect-error1754 ? Promise.all([this.buildItemSearchType(functionSearchType[i])])1755 // @ts-expect-error1756 : this.buildItemSearchTypeAll(functionSearchType[i]),1757 );1758 }1759 const [inputs, output, where_clause] = await Promise.all([1760 inputs_,1761 output_,1762 Promise.all(where_clause_),1763 ]);1764 return {1765 inputs, output, where_clause,1766 };1767 });17681769 return {1770 functionSignature: await parser.next(),1771 paramNames: raw[1],1772 elemCount: parser.elemCount,1773 };1774 }17751776 /**1777 * @param {number} id1778 * @returns {Promise<rustdoc.TypeData|null>}1779 */1780 async getTypeData(id) {1781 const ti = this.database.getData("type");1782 if (!ti) {1783 return null;1784 }1785 const encoded = this.utf8decoder.decode(await ti.at(id));1786 if (encoded === "" || encoded === undefined || encoded === null) {1787 return null;1788 }1789 /**1790 * function_signature, param_names1791 * @type {[string, string, number] | [string, string] | [] | null}1792 */1793 const raw = JSON.parse(encoded);17941795 if (!raw || raw.length === 0) {1796 return null;1797 }17981799 let searchUnbox = false;1800 const invertedFunctionInputsIndex = [];1801 const invertedFunctionOutputIndex = [];18021803 if (typeof raw[0] === "string") {1804 if (raw[2]) {1805 searchUnbox = true;1806 }1807 // the inverted function signature index is a list of bitmaps,1808 // by number of types that appear in the function1809 let i = 0;1810 let pb = makeUint8ArrayFromBase64(raw[0]);1811 let l = pb.length;1812 while (i < l) {1813 if (pb[i] === 0) {1814 invertedFunctionInputsIndex.push(RoaringBitmap.empty());1815 i += 1;1816 } else {1817 const bitmap = new RoaringBitmap(pb, i);1818 i += bitmap.consumed_len_bytes;1819 invertedFunctionInputsIndex.push(bitmap);1820 }1821 }1822 i = 0;1823 pb = makeUint8ArrayFromBase64(raw[1]);1824 l = pb.length;1825 while (i < l) {1826 if (pb[i] === 0) {1827 invertedFunctionOutputIndex.push(RoaringBitmap.empty());1828 i += 1;1829 } else {1830 const bitmap = new RoaringBitmap(pb, i);1831 i += bitmap.consumed_len_bytes;1832 invertedFunctionOutputIndex.push(bitmap);1833 }1834 }1835 } else if (raw[0]) {1836 searchUnbox = true;1837 }18381839 return { searchUnbox, invertedFunctionInputsIndex, invertedFunctionOutputIndex };1840 }18411842 /**1843 * @returns {Promise<string[]>}1844 */1845 async getCrateNameList() {1846 const crateNames = this.database.getData("crateNames");1847 if (!crateNames) {1848 return [];1849 }1850 const l = crateNames.length;1851 const names = [];1852 for (let i = 0; i < l; ++i) {1853 const name = await crateNames.at(i);1854 names.push(name === undefined ? "" : this.utf8decoder.decode(name));1855 }1856 return Promise.all(names);1857 }18581859 /**1860 * @param {number} id non-negative generic index1861 * @returns {Promise<stringdex.RoaringBitmap[]>}1862 */1863 async getGenericInvertedIndex(id) {1864 const gii = this.database.getData("generic_inverted_index");1865 if (!gii) {1866 return [];1867 }1868 const pb = await gii.at(id);1869 if (pb === undefined || pb === null || pb.length === 0) {1870 return [];1871 }18721873 const invertedFunctionSignatureIndex = [];1874 // the inverted function signature index is a list of bitmaps,1875 // by number of types that appear in the function1876 let i = 0;1877 const l = pb.length;1878 while (i < l) {1879 if (pb[i] === 0) {1880 invertedFunctionSignatureIndex.push(RoaringBitmap.empty());1881 i += 1;1882 } else {1883 const bitmap = new RoaringBitmap(pb, i);1884 i += bitmap.consumed_len_bytes;1885 invertedFunctionSignatureIndex.push(bitmap);1886 }1887 }1888 return invertedFunctionSignatureIndex;1889 }18901891 /**1892 * @param {number} id1893 * @param {boolean} loadFunctionData1894 * @returns {Promise<rustdoc.Row?>}1895 */1896 async getRow(id, loadFunctionData) {1897 const [name_, entry, path, functionData] = await Promise.all([1898 this.getName(id),1899 this.getEntryData(id),1900 this.getPathData(id),1901 loadFunctionData ? this.getFunctionData(id) : null,1902 ]);1903 if (!entry && !path) {1904 return null;1905 }1906 /** @type {function("parent" | "traitParent"): Promise<rustdoc.RowParent>} */1907 const buildParentLike = async field => {1908 const [name, path] = entry !== null && entry[field] !== null ?1909 await Promise.all([this.getName(entry[field]), this.getPathData(entry[field])]) :1910 [null, null];1911 if (name !== null && path !== null) {1912 return { name, path };1913 }1914 return null;1915 };19161917 const [1918 moduleName,1919 modulePathData,1920 exactModuleName,1921 exactModulePathData,1922 parent,1923 traitParent,1924 crateOrNull,1925 ] = await Promise.all([1926 entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,1927 entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,1928 entry && entry.exactModulePath !== null ?1929 this.getName(entry.exactModulePath) :1930 null,1931 entry && entry.exactModulePath !== null ?1932 this.getPathData(entry.exactModulePath) :1933 null,1934 buildParentLike("parent"),1935 buildParentLike("traitParent"),1936 entry ? this.getName(entry.krate) : "",1937 ]);1938 const crate = crateOrNull === null ? "" : crateOrNull;1939 const name = name_ === null ? "" : name_;1940 const normalizedName = (name.indexOf("_") === -1 ?1941 name :1942 name.replace(/_/g, "")).toLowerCase();1943 const modulePath = modulePathData === null || moduleName === null ? "" :1944 (modulePathData.modulePath === "" ?1945 moduleName :1946 `${modulePathData.modulePath}::${moduleName}`);19471948 return {1949 id,1950 crate,1951 ty: entry ? entry.ty : nonnull(path).ty,1952 name,1953 normalizedName,1954 modulePath,1955 exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :1956 (exactModulePathData.exactModulePath === "" ?1957 exactModuleName :1958 `${exactModulePathData.exactModulePath}::${exactModuleName}`),1959 entry,1960 path,1961 functionData,1962 deprecated: entry ? entry.deprecated : false,1963 unstable: entry ? entry.unstable : false,1964 parent,1965 traitParent,1966 };1967 }19681969 /**1970 * Convert a list of RawFunctionType / ID to object-based FunctionType.1971 *1972 * Crates often have lots of functions in them, and it's common to have a large number of1973 * functions that operate on a small set of data types, so the search index compresses them1974 * by encoding function parameter and return types as indexes into an array of names.1975 *1976 * Even when a general-purpose compression algorithm is used, this is still a win.1977 * I checked. https://github.com/rust-lang/rust/pull/98475#issue-12843959851978 *1979 * The format for individual function types is encoded in1980 * librustdoc/html/render/mod.rs: impl Serialize for RenderType1981 *1982 * @param {null|Array<rustdoc.RawFunctionType>} types1983 *1984 * @return {Promise<Array<rustdoc.FunctionType>>}1985 */1986 async buildItemSearchTypeAll(types) {1987 return types && types.length > 0 ?1988 await Promise.all(types.map(type => this.buildItemSearchType(type))) :1989 EMPTY_GENERICS_ARRAY;1990 }19911992 /**1993 * Converts a single type.1994 *1995 * @param {rustdoc.RawFunctionType} type1996 * @return {Promise<rustdoc.FunctionType>}1997 */1998 async buildItemSearchType(type) {1999 const PATH_INDEX_DATA = 0;2000 const GENERICS_DATA = 1;
Findings
✓ No findings reported for this file.