rustdoc: restructure type search engine to pick-and-use IDs
This change makes it so, instead of mixing string distance with type unification, function signature search works by mapping names to IDs at the start, reporting to the user any cases where it had to make corrections, and then matches with IDs when going through the items. This only changes function searches. Name searches are left alone, and corrections are only done when there's a single item in the search query.
This commit is contained in:
parent
1a7132d4f8
commit
4c11822aeb
6 changed files with 353 additions and 203 deletions
|
@ -1259,6 +1259,10 @@ a.tooltip:hover::after {
|
||||||
background-color: var(--search-error-code-background-color);
|
background-color: var(--search-error-code-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-corrections {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
#src-sidebar-toggle {
|
#src-sidebar-toggle {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -9,6 +9,7 @@ function initSearch(searchIndex){}
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* name: string,
|
* name: string,
|
||||||
|
* id: integer,
|
||||||
* fullPath: Array<string>,
|
* fullPath: Array<string>,
|
||||||
* pathWithoutLast: Array<string>,
|
* pathWithoutLast: Array<string>,
|
||||||
* pathLast: string,
|
* pathLast: string,
|
||||||
|
@ -36,6 +37,8 @@ let ParserState;
|
||||||
* args: Array<QueryElement>,
|
* args: Array<QueryElement>,
|
||||||
* returned: Array<QueryElement>,
|
* returned: Array<QueryElement>,
|
||||||
* foundElems: number,
|
* foundElems: number,
|
||||||
|
* literalSearch: boolean,
|
||||||
|
* corrections: Array<{from: string, to: integer}>,
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
let ParsedQuery;
|
let ParsedQuery;
|
||||||
|
@ -139,7 +142,7 @@ let FunctionSearchType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* name: (null|string),
|
* id: (null|number),
|
||||||
* ty: (null|number),
|
* ty: (null|number),
|
||||||
* generics: Array<FunctionType>,
|
* generics: Array<FunctionType>,
|
||||||
* }}
|
* }}
|
||||||
|
|
|
@ -58,6 +58,7 @@ function printTab(nb) {
|
||||||
}
|
}
|
||||||
iter += 1;
|
iter += 1;
|
||||||
});
|
});
|
||||||
|
const isTypeSearch = (nb > 0 || iter === 1);
|
||||||
iter = 0;
|
iter = 0;
|
||||||
onEachLazy(document.getElementById("results").childNodes, elem => {
|
onEachLazy(document.getElementById("results").childNodes, elem => {
|
||||||
if (nb === iter) {
|
if (nb === iter) {
|
||||||
|
@ -70,6 +71,13 @@ function printTab(nb) {
|
||||||
});
|
});
|
||||||
if (foundCurrentTab && foundCurrentResultSet) {
|
if (foundCurrentTab && foundCurrentResultSet) {
|
||||||
searchState.currentTab = nb;
|
searchState.currentTab = nb;
|
||||||
|
// Corrections only kick in on type-based searches.
|
||||||
|
const correctionsElem = document.getElementsByClassName("search-corrections");
|
||||||
|
if (isTypeSearch) {
|
||||||
|
removeClass(correctionsElem[0], "hidden");
|
||||||
|
} else {
|
||||||
|
addClass(correctionsElem[0], "hidden");
|
||||||
|
}
|
||||||
} else if (nb !== 0) {
|
} else if (nb !== 0) {
|
||||||
printTab(0);
|
printTab(0);
|
||||||
}
|
}
|
||||||
|
@ -191,6 +199,13 @@ function initSearch(rawSearchIndex) {
|
||||||
*/
|
*/
|
||||||
let searchIndex;
|
let searchIndex;
|
||||||
let currentResults;
|
let currentResults;
|
||||||
|
/**
|
||||||
|
* Map from normalized type names to integers. Used to make type search
|
||||||
|
* more efficient.
|
||||||
|
*
|
||||||
|
* @type {Map<string, integer>}
|
||||||
|
*/
|
||||||
|
let typeNameIdMap;
|
||||||
const ALIASES = new Map();
|
const ALIASES = new Map();
|
||||||
|
|
||||||
function isWhitespace(c) {
|
function isWhitespace(c) {
|
||||||
|
@ -358,6 +373,7 @@ function initSearch(rawSearchIndex) {
|
||||||
parserState.typeFilter = null;
|
parserState.typeFilter = null;
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
|
id: -1,
|
||||||
fullPath: pathSegments,
|
fullPath: pathSegments,
|
||||||
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
|
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
|
||||||
pathLast: pathSegments[pathSegments.length - 1],
|
pathLast: pathSegments[pathSegments.length - 1],
|
||||||
|
@ -718,6 +734,7 @@ function initSearch(rawSearchIndex) {
|
||||||
foundElems: 0,
|
foundElems: 0,
|
||||||
literalSearch: false,
|
literalSearch: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
correction: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1091,48 +1108,50 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @param {Row} row - The object to check.
|
* @param {Row} row - The object to check.
|
||||||
* @param {QueryElement} elem - The element from the parsed query.
|
* @param {QueryElement} elem - The element from the parsed query.
|
||||||
* @param {integer} defaultDistance - This is the value to return in case there are no
|
|
||||||
* generics.
|
|
||||||
*
|
*
|
||||||
* @return {integer} - Returns the best match (if any) or `maxEditDistance + 1`.
|
* @return {boolean} - Returns true if a match, false otherwise.
|
||||||
*/
|
*/
|
||||||
function checkGenerics(row, elem, defaultDistance, maxEditDistance) {
|
function checkGenerics(row, elem) {
|
||||||
if (row.generics.length === 0) {
|
if (row.generics.length === 0 || elem.generics.length === 0) {
|
||||||
return elem.generics.length === 0 ? defaultDistance : maxEditDistance + 1;
|
return false;
|
||||||
} else if (row.generics.length > 0 && row.generics[0].name === null) {
|
|
||||||
return checkGenerics(row.generics[0], elem, defaultDistance, maxEditDistance);
|
|
||||||
}
|
}
|
||||||
// The names match, but we need to be sure that all generics kinda
|
// This function is called if the names match, but we need to make
|
||||||
// match as well.
|
// sure that all generics match as well.
|
||||||
|
//
|
||||||
|
// This search engine implements order-agnostic unification. There
|
||||||
|
// should be no missing duplicates (generics have "bag semantics"),
|
||||||
|
// and the row is allowed to have extras.
|
||||||
if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
|
if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
|
||||||
const elems = new Map();
|
const elems = new Map();
|
||||||
for (const entry of row.generics) {
|
const addEntryToElems = function addEntryToElems(entry) {
|
||||||
if (entry.name === "") {
|
if (entry.id === -1) {
|
||||||
// Pure generic, needs to check into it.
|
// Pure generic, needs to check into it.
|
||||||
if (checkGenerics(entry, elem, maxEditDistance + 1, maxEditDistance)
|
for (const inner_entry of entry.generics) {
|
||||||
!== 0) {
|
addEntryToElems(inner_entry);
|
||||||
return maxEditDistance + 1;
|
|
||||||
}
|
}
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
let currentEntryElems;
|
let currentEntryElems;
|
||||||
if (elems.has(entry.name)) {
|
if (elems.has(entry.id)) {
|
||||||
currentEntryElems = elems.get(entry.name);
|
currentEntryElems = elems.get(entry.id);
|
||||||
} else {
|
} else {
|
||||||
currentEntryElems = [];
|
currentEntryElems = [];
|
||||||
elems.set(entry.name, currentEntryElems);
|
elems.set(entry.id, currentEntryElems);
|
||||||
}
|
}
|
||||||
currentEntryElems.push(entry);
|
currentEntryElems.push(entry);
|
||||||
|
};
|
||||||
|
for (const entry of row.generics) {
|
||||||
|
addEntryToElems(entry);
|
||||||
}
|
}
|
||||||
// We need to find the type that matches the most to remove it in order
|
// We need to find the type that matches the most to remove it in order
|
||||||
// to move forward.
|
// to move forward.
|
||||||
const handleGeneric = generic => {
|
const handleGeneric = generic => {
|
||||||
if (!elems.has(generic.name)) {
|
if (!elems.has(generic.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const matchElems = elems.get(generic.name);
|
const matchElems = elems.get(generic.id);
|
||||||
const matchIdx = matchElems.findIndex(tmp_elem => {
|
const matchIdx = matchElems.findIndex(tmp_elem => {
|
||||||
if (checkGenerics(tmp_elem, generic, 0, maxEditDistance) !== 0) {
|
if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return typePassesFilter(generic.typeFilter, tmp_elem.ty);
|
return typePassesFilter(generic.typeFilter, tmp_elem.ty);
|
||||||
|
@ -1142,7 +1161,7 @@ function initSearch(rawSearchIndex) {
|
||||||
}
|
}
|
||||||
matchElems.splice(matchIdx, 1);
|
matchElems.splice(matchIdx, 1);
|
||||||
if (matchElems.length === 0) {
|
if (matchElems.length === 0) {
|
||||||
elems.delete(generic.name);
|
elems.delete(generic.id);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1152,17 +1171,17 @@ function initSearch(rawSearchIndex) {
|
||||||
// own type.
|
// own type.
|
||||||
for (const generic of elem.generics) {
|
for (const generic of elem.generics) {
|
||||||
if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
|
if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
|
||||||
return maxEditDistance + 1;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const generic of elem.generics) {
|
for (const generic of elem.generics) {
|
||||||
if (generic.typeFilter === -1 && !handleGeneric(generic)) {
|
if (generic.typeFilter === -1 && !handleGeneric(generic)) {
|
||||||
return maxEditDistance + 1;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return true;
|
||||||
}
|
}
|
||||||
return maxEditDistance + 1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1172,17 +1191,15 @@ function initSearch(rawSearchIndex) {
|
||||||
* @param {Row} row
|
* @param {Row} row
|
||||||
* @param {QueryElement} elem - The element from the parsed query.
|
* @param {QueryElement} elem - The element from the parsed query.
|
||||||
*
|
*
|
||||||
* @return {integer} - Returns an edit distance to the best match.
|
* @return {boolean} - Returns true if found, false otherwise.
|
||||||
*/
|
*/
|
||||||
function checkIfInGenerics(row, elem, maxEditDistance) {
|
function checkIfInGenerics(row, elem) {
|
||||||
let dist = maxEditDistance + 1;
|
|
||||||
for (const entry of row.generics) {
|
for (const entry of row.generics) {
|
||||||
dist = Math.min(checkType(entry, elem, true, maxEditDistance), dist);
|
if (checkType(entry, elem)) {
|
||||||
if (dist === 0) {
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dist;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1191,75 +1208,30 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @param {Row} row
|
* @param {Row} row
|
||||||
* @param {QueryElement} elem - The element from the parsed query.
|
* @param {QueryElement} elem - The element from the parsed query.
|
||||||
* @param {boolean} literalSearch
|
|
||||||
*
|
*
|
||||||
* @return {integer} - Returns an edit distance to the best match. If there is
|
* @return {boolean} - Returns true if the type matches, false otherwise.
|
||||||
* no match, returns `maxEditDistance + 1`.
|
|
||||||
*/
|
*/
|
||||||
function checkType(row, elem, literalSearch, maxEditDistance) {
|
function checkType(row, elem) {
|
||||||
if (row.name === null) {
|
if (row.id === -1) {
|
||||||
// This is a pure "generic" search, no need to run other checks.
|
// This is a pure "generic" search, no need to run other checks.
|
||||||
if (row.generics.length > 0) {
|
return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false;
|
||||||
return checkIfInGenerics(row, elem, maxEditDistance);
|
|
||||||
}
|
|
||||||
return maxEditDistance + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dist;
|
if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) {
|
||||||
if (typePassesFilter(elem.typeFilter, row.ty)) {
|
if (elem.generics.length > 0) {
|
||||||
dist = editDistance(row.name, elem.name, maxEditDistance);
|
return checkGenerics(row, elem);
|
||||||
} else {
|
|
||||||
dist = maxEditDistance + 1;
|
|
||||||
}
|
|
||||||
if (literalSearch) {
|
|
||||||
if (dist !== 0) {
|
|
||||||
// The name didn't match, let's try to check if the generics do.
|
|
||||||
if (elem.generics.length === 0) {
|
|
||||||
const checkGeneric = row.generics.length > 0;
|
|
||||||
if (checkGeneric && row.generics
|
|
||||||
.findIndex(tmp_elem => tmp_elem.name === elem.name &&
|
|
||||||
typePassesFilter(elem.typeFilter, tmp_elem.ty)) !== -1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxEditDistance + 1;
|
|
||||||
} else if (elem.generics.length > 0) {
|
|
||||||
return checkGenerics(row, elem, maxEditDistance + 1, maxEditDistance);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return true;
|
||||||
} else if (row.generics.length > 0) {
|
|
||||||
if (elem.generics.length === 0) {
|
|
||||||
if (dist === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// The name didn't match so we now check if the type we're looking for is inside
|
|
||||||
// the generics!
|
|
||||||
dist = Math.min(dist, checkIfInGenerics(row, elem, maxEditDistance));
|
|
||||||
return dist;
|
|
||||||
} else if (dist > maxEditDistance) {
|
|
||||||
// So our item's name doesn't match at all and has generics.
|
|
||||||
//
|
|
||||||
// Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're
|
|
||||||
// looking for "B<C>", we'll need to go down.
|
|
||||||
return checkIfInGenerics(row, elem, maxEditDistance);
|
|
||||||
} else {
|
|
||||||
// At this point, the name kinda match and we have generics to check, so
|
|
||||||
// let's go!
|
|
||||||
const tmp_dist = checkGenerics(row, elem, dist, maxEditDistance);
|
|
||||||
if (tmp_dist > maxEditDistance) {
|
|
||||||
return maxEditDistance + 1;
|
|
||||||
}
|
|
||||||
// We compute the median value of both checks and return it.
|
|
||||||
return (tmp_dist + dist) / 2;
|
|
||||||
}
|
|
||||||
} else if (elem.generics.length > 0) {
|
|
||||||
// In this case, we were expecting generics but there isn't so we simply reject this
|
|
||||||
// one.
|
|
||||||
return maxEditDistance + 1;
|
|
||||||
}
|
}
|
||||||
// No generics on our query or on the target type so we can return without doing
|
|
||||||
// anything else.
|
// If the current item does not match, try [unboxing] the generic.
|
||||||
return dist;
|
// [unboxing]:
|
||||||
|
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
|
||||||
|
if (checkIfInGenerics(row, elem)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1267,17 +1239,11 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @param {Row} row
|
* @param {Row} row
|
||||||
* @param {QueryElement} elem - The element from the parsed query.
|
* @param {QueryElement} elem - The element from the parsed query.
|
||||||
* @param {integer} maxEditDistance
|
|
||||||
* @param {Array<integer>} skipPositions - Do not return one of these positions.
|
* @param {Array<integer>} skipPositions - Do not return one of these positions.
|
||||||
*
|
*
|
||||||
* @return {dist: integer, position: integer} - Returns an edit distance to the best match.
|
* @return {integer} - Returns the position of the match, or -1 if none.
|
||||||
* If there is no match, returns
|
|
||||||
* `maxEditDistance + 1` and position: -1.
|
|
||||||
*/
|
*/
|
||||||
function findArg(row, elem, maxEditDistance, skipPositions) {
|
function findArg(row, elem, skipPositions) {
|
||||||
let dist = maxEditDistance + 1;
|
|
||||||
let position = -1;
|
|
||||||
|
|
||||||
if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
|
if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const input of row.type.inputs) {
|
for (const input of row.type.inputs) {
|
||||||
|
@ -1285,24 +1251,13 @@ function initSearch(rawSearchIndex) {
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const typeDist = checkType(
|
if (checkType(input, elem)) {
|
||||||
input,
|
return i;
|
||||||
elem,
|
|
||||||
parsedQuery.literalSearch,
|
|
||||||
maxEditDistance
|
|
||||||
);
|
|
||||||
if (typeDist === 0) {
|
|
||||||
return {dist: 0, position: i};
|
|
||||||
}
|
|
||||||
if (typeDist < dist) {
|
|
||||||
dist = typeDist;
|
|
||||||
position = i;
|
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
|
return -1;
|
||||||
return {dist, position};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1310,43 +1265,25 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @param {Row} row
|
* @param {Row} row
|
||||||
* @param {QueryElement} elem - The element from the parsed query.
|
* @param {QueryElement} elem - The element from the parsed query.
|
||||||
* @param {integer} maxEditDistance
|
|
||||||
* @param {Array<integer>} skipPositions - Do not return one of these positions.
|
* @param {Array<integer>} skipPositions - Do not return one of these positions.
|
||||||
*
|
*
|
||||||
* @return {dist: integer, position: integer} - Returns an edit distance to the best match.
|
* @return {integer} - Returns the position of the matching item, or -1 if none.
|
||||||
* If there is no match, returns
|
|
||||||
* `maxEditDistance + 1` and position: -1.
|
|
||||||
*/
|
*/
|
||||||
function checkReturned(row, elem, maxEditDistance, skipPositions) {
|
function checkReturned(row, elem, skipPositions) {
|
||||||
let dist = maxEditDistance + 1;
|
|
||||||
let position = -1;
|
|
||||||
|
|
||||||
if (row && row.type && row.type.output.length > 0) {
|
if (row && row.type && row.type.output.length > 0) {
|
||||||
const ret = row.type.output;
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const ret_ty of ret) {
|
for (const ret_ty of row.type.output) {
|
||||||
if (skipPositions.indexOf(i) !== -1) {
|
if (skipPositions.indexOf(i) !== -1) {
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const typeDist = checkType(
|
if (checkType(ret_ty, elem)) {
|
||||||
ret_ty,
|
return i;
|
||||||
elem,
|
|
||||||
parsedQuery.literalSearch,
|
|
||||||
maxEditDistance
|
|
||||||
);
|
|
||||||
if (typeDist === 0) {
|
|
||||||
return {dist: 0, position: i};
|
|
||||||
}
|
|
||||||
if (typeDist < dist) {
|
|
||||||
dist = typeDist;
|
|
||||||
position = i;
|
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
|
return -1;
|
||||||
return {dist, position};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPath(contains, ty, maxEditDistance) {
|
function checkPath(contains, ty, maxEditDistance) {
|
||||||
|
@ -1543,17 +1480,20 @@ function initSearch(rawSearchIndex) {
|
||||||
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
|
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let dist, index = -1, path_dist = 0;
|
let index = -1, path_dist = 0;
|
||||||
const fullId = row.id;
|
const fullId = row.id;
|
||||||
const searchWord = searchWords[pos];
|
const searchWord = searchWords[pos];
|
||||||
|
|
||||||
const in_args = findArg(row, elem, maxEditDistance, []);
|
const in_args = findArg(row, elem, []);
|
||||||
const returned = checkReturned(row, elem, maxEditDistance, []);
|
if (in_args !== -1) {
|
||||||
|
// path_dist is 0 because no parent path information is currently stored
|
||||||
// path_dist is 0 because no parent path information is currently stored
|
// in the search index
|
||||||
// in the search index
|
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
|
||||||
addIntoResults(results_in_args, fullId, pos, -1, in_args.dist, 0, maxEditDistance);
|
}
|
||||||
addIntoResults(results_returned, fullId, pos, -1, returned.dist, 0, maxEditDistance);
|
const returned = checkReturned(row, elem, []);
|
||||||
|
if (returned !== -1) {
|
||||||
|
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
|
||||||
|
}
|
||||||
|
|
||||||
if (!typePassesFilter(elem.typeFilter, row.ty)) {
|
if (!typePassesFilter(elem.typeFilter, row.ty)) {
|
||||||
return;
|
return;
|
||||||
|
@ -1574,16 +1514,6 @@ function initSearch(rawSearchIndex) {
|
||||||
index = row_index;
|
index = row_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to check anything else if it's a "pure" generics search.
|
|
||||||
if (elem.name.length === 0) {
|
|
||||||
if (row.type !== null) {
|
|
||||||
dist = checkGenerics(row.type, elem, maxEditDistance + 1, maxEditDistance);
|
|
||||||
// path_dist is 0 because we know it's empty
|
|
||||||
addIntoResults(results_others, fullId, pos, index, dist, 0, maxEditDistance);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (elem.fullPath.length > 1) {
|
if (elem.fullPath.length > 1) {
|
||||||
path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance);
|
path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance);
|
||||||
if (path_dist > maxEditDistance) {
|
if (path_dist > maxEditDistance) {
|
||||||
|
@ -1598,7 +1528,7 @@ function initSearch(rawSearchIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
|
const dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
|
||||||
|
|
||||||
if (index === -1 && dist + path_dist > maxEditDistance) {
|
if (index === -1 && dist + path_dist > maxEditDistance) {
|
||||||
return;
|
return;
|
||||||
|
@ -1616,28 +1546,22 @@ function initSearch(rawSearchIndex) {
|
||||||
* @param {integer} pos - Position in the `searchIndex`.
|
* @param {integer} pos - Position in the `searchIndex`.
|
||||||
* @param {Object} results
|
* @param {Object} results
|
||||||
*/
|
*/
|
||||||
function handleArgs(row, pos, results, maxEditDistance) {
|
function handleArgs(row, pos, results) {
|
||||||
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
|
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalDist = 0;
|
|
||||||
let nbDist = 0;
|
|
||||||
|
|
||||||
// If the result is too "bad", we return false and it ends this search.
|
// If the result is too "bad", we return false and it ends this search.
|
||||||
function checkArgs(elems, callback) {
|
function checkArgs(elems, callback) {
|
||||||
const skipPositions = [];
|
const skipPositions = [];
|
||||||
for (const elem of elems) {
|
for (const elem of elems) {
|
||||||
// There is more than one parameter to the query so all checks should be "exact"
|
// There is more than one parameter to the query so all checks should be "exact"
|
||||||
const { dist, position } = callback(
|
const position = callback(
|
||||||
row,
|
row,
|
||||||
elem,
|
elem,
|
||||||
maxEditDistance,
|
|
||||||
skipPositions
|
skipPositions
|
||||||
);
|
);
|
||||||
if (dist <= 1) {
|
if (position !== -1) {
|
||||||
nbDist += 1;
|
|
||||||
totalDist += dist;
|
|
||||||
skipPositions.push(position);
|
skipPositions.push(position);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1652,11 +1576,7 @@ function initSearch(rawSearchIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbDist === 0) {
|
addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE);
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dist = Math.round(totalDist / nbDist);
|
|
||||||
addIntoResults(results, row.id, pos, 0, dist, 0, maxEditDistance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function innerRunQuery() {
|
function innerRunQuery() {
|
||||||
|
@ -1671,6 +1591,50 @@ function initSearch(rawSearchIndex) {
|
||||||
}
|
}
|
||||||
const maxEditDistance = Math.floor(queryLen / 3);
|
const maxEditDistance = Math.floor(queryLen / 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert names to ids in parsed query elements.
|
||||||
|
* This is not used for the "In Names" tab, but is used for the
|
||||||
|
* "In Params", "In Returns", and "In Function Signature" tabs.
|
||||||
|
*
|
||||||
|
* If there is no matching item, but a close-enough match, this
|
||||||
|
* function also that correction.
|
||||||
|
*
|
||||||
|
* See `buildTypeMapIndex` for more information.
|
||||||
|
*
|
||||||
|
* @param {QueryElement} elem
|
||||||
|
*/
|
||||||
|
function convertNameToId(elem) {
|
||||||
|
if (typeNameIdMap.has(elem.name)) {
|
||||||
|
elem.id = typeNameIdMap.get(elem.name);
|
||||||
|
} else if (!parsedQuery.literalSearch) {
|
||||||
|
let match = -1;
|
||||||
|
let matchDist = maxEditDistance + 1;
|
||||||
|
let matchName = "";
|
||||||
|
for (const [name, id] of typeNameIdMap) {
|
||||||
|
const dist = editDistance(name, elem.name, maxEditDistance);
|
||||||
|
if (dist <= matchDist && dist <= maxEditDistance) {
|
||||||
|
match = id;
|
||||||
|
matchDist = dist;
|
||||||
|
matchName = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match !== -1) {
|
||||||
|
parsedQuery.correction = matchName;
|
||||||
|
}
|
||||||
|
elem.id = match;
|
||||||
|
}
|
||||||
|
for (const elem2 of elem.generics) {
|
||||||
|
convertNameToId(elem2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const elem of parsedQuery.elems) {
|
||||||
|
convertNameToId(elem);
|
||||||
|
}
|
||||||
|
for (const elem of parsedQuery.returned) {
|
||||||
|
convertNameToId(elem);
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedQuery.foundElems === 1) {
|
if (parsedQuery.foundElems === 1) {
|
||||||
if (parsedQuery.elems.length === 1) {
|
if (parsedQuery.elems.length === 1) {
|
||||||
elem = parsedQuery.elems[0];
|
elem = parsedQuery.elems[0];
|
||||||
|
@ -1695,22 +1659,23 @@ function initSearch(rawSearchIndex) {
|
||||||
in_returned = checkReturned(
|
in_returned = checkReturned(
|
||||||
row,
|
row,
|
||||||
elem,
|
elem,
|
||||||
maxEditDistance,
|
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
addIntoResults(
|
if (in_returned !== -1) {
|
||||||
results_others,
|
addIntoResults(
|
||||||
row.id,
|
results_others,
|
||||||
i,
|
row.id,
|
||||||
-1,
|
i,
|
||||||
in_returned.dist,
|
-1,
|
||||||
maxEditDistance
|
0,
|
||||||
);
|
Number.MAX_VALUE
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (parsedQuery.foundElems > 0) {
|
} else if (parsedQuery.foundElems > 0) {
|
||||||
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
|
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
|
||||||
handleArgs(searchIndex[i], i, results_others, maxEditDistance);
|
handleArgs(searchIndex[i], i, results_others);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2030,6 +1995,11 @@ function initSearch(rawSearchIndex) {
|
||||||
currentTab = 0;
|
currentTab = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (results.query.correction !== null) {
|
||||||
|
output += "<h3 class=\"search-corrections\">Showing results for " +
|
||||||
|
`"${results.query.correction}".</h3>`;
|
||||||
|
}
|
||||||
|
|
||||||
const resultsElem = document.createElement("div");
|
const resultsElem = document.createElement("div");
|
||||||
resultsElem.id = "results";
|
resultsElem.id = "results";
|
||||||
resultsElem.appendChild(ret_others[0]);
|
resultsElem.appendChild(ret_others[0]);
|
||||||
|
@ -2108,6 +2078,34 @@ function initSearch(rawSearchIndex) {
|
||||||
filterCrates);
|
filterCrates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to the type Name->ID map, or, if one already exists, use it.
|
||||||
|
* Returns the number. If name is "" or null, return -1 (pure generic).
|
||||||
|
*
|
||||||
|
* This is effectively string interning, so that function matching can be
|
||||||
|
* done more quickly. Two types with the same name but different item kinds
|
||||||
|
* get the same ID.
|
||||||
|
*
|
||||||
|
* @param {Map<string, integer>} typeNameIdMap
|
||||||
|
* @param {string} name
|
||||||
|
*
|
||||||
|
* @returns {integer}
|
||||||
|
*/
|
||||||
|
function buildTypeMapIndex(typeNameIdMap, name) {
|
||||||
|
|
||||||
|
if (name === "" || name === null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeNameIdMap.has(name)) {
|
||||||
|
return typeNameIdMap.get(name);
|
||||||
|
} else {
|
||||||
|
const id = typeNameIdMap.size;
|
||||||
|
typeNameIdMap.set(name, id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a list of RawFunctionType / ID to object-based FunctionType.
|
* Convert a list of RawFunctionType / ID to object-based FunctionType.
|
||||||
*
|
*
|
||||||
|
@ -2126,7 +2124,7 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @return {Array<FunctionSearchType>}
|
* @return {Array<FunctionSearchType>}
|
||||||
*/
|
*/
|
||||||
function buildItemSearchTypeAll(types, lowercasePaths) {
|
function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) {
|
||||||
const PATH_INDEX_DATA = 0;
|
const PATH_INDEX_DATA = 0;
|
||||||
const GENERICS_DATA = 1;
|
const GENERICS_DATA = 1;
|
||||||
return types.map(type => {
|
return types.map(type => {
|
||||||
|
@ -2136,11 +2134,17 @@ function initSearch(rawSearchIndex) {
|
||||||
generics = [];
|
generics = [];
|
||||||
} else {
|
} else {
|
||||||
pathIndex = type[PATH_INDEX_DATA];
|
pathIndex = type[PATH_INDEX_DATA];
|
||||||
generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths);
|
generics = buildItemSearchTypeAll(
|
||||||
|
type[GENERICS_DATA],
|
||||||
|
lowercasePaths,
|
||||||
|
typeNameIdMap
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
// `0` is used as a sentinel because it's fewer bytes than `null`
|
// `0` is used as a sentinel because it's fewer bytes than `null`
|
||||||
name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
|
id: pathIndex === 0
|
||||||
|
? -1
|
||||||
|
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
|
||||||
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
||||||
generics: generics,
|
generics: generics,
|
||||||
};
|
};
|
||||||
|
@ -2159,10 +2163,11 @@ function initSearch(rawSearchIndex) {
|
||||||
*
|
*
|
||||||
* @param {RawFunctionSearchType} functionSearchType
|
* @param {RawFunctionSearchType} functionSearchType
|
||||||
* @param {Array<{name: string, ty: number}>} lowercasePaths
|
* @param {Array<{name: string, ty: number}>} lowercasePaths
|
||||||
|
* @param {Map<string, integer>}
|
||||||
*
|
*
|
||||||
* @return {null|FunctionSearchType}
|
* @return {null|FunctionSearchType}
|
||||||
*/
|
*/
|
||||||
function buildFunctionSearchType(functionSearchType, lowercasePaths) {
|
function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) {
|
||||||
const INPUTS_DATA = 0;
|
const INPUTS_DATA = 0;
|
||||||
const OUTPUT_DATA = 1;
|
const OUTPUT_DATA = 1;
|
||||||
// `0` is used as a sentinel because it's fewer bytes than `null`
|
// `0` is used as a sentinel because it's fewer bytes than `null`
|
||||||
|
@ -2173,23 +2178,35 @@ function initSearch(rawSearchIndex) {
|
||||||
if (typeof functionSearchType[INPUTS_DATA] === "number") {
|
if (typeof functionSearchType[INPUTS_DATA] === "number") {
|
||||||
const pathIndex = functionSearchType[INPUTS_DATA];
|
const pathIndex = functionSearchType[INPUTS_DATA];
|
||||||
inputs = [{
|
inputs = [{
|
||||||
name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
|
id: pathIndex === 0
|
||||||
|
? -1
|
||||||
|
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
|
||||||
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
||||||
generics: [],
|
generics: [],
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths);
|
inputs = buildItemSearchTypeAll(
|
||||||
|
functionSearchType[INPUTS_DATA],
|
||||||
|
lowercasePaths,
|
||||||
|
typeNameIdMap
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (functionSearchType.length > 1) {
|
if (functionSearchType.length > 1) {
|
||||||
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
|
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
|
||||||
const pathIndex = functionSearchType[OUTPUT_DATA];
|
const pathIndex = functionSearchType[OUTPUT_DATA];
|
||||||
output = [{
|
output = [{
|
||||||
name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
|
id: pathIndex === 0
|
||||||
|
? -1
|
||||||
|
: buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name),
|
||||||
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
|
||||||
generics: [],
|
generics: [],
|
||||||
}];
|
}];
|
||||||
} else {
|
} else {
|
||||||
output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths);
|
output = buildItemSearchTypeAll(
|
||||||
|
functionSearchType[OUTPUT_DATA],
|
||||||
|
lowercasePaths,
|
||||||
|
typeNameIdMap
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output = [];
|
output = [];
|
||||||
|
@ -2202,9 +2219,12 @@ function initSearch(rawSearchIndex) {
|
||||||
function buildIndex(rawSearchIndex) {
|
function buildIndex(rawSearchIndex) {
|
||||||
searchIndex = [];
|
searchIndex = [];
|
||||||
/**
|
/**
|
||||||
|
* List of normalized search words (ASCII lowercased, and undescores removed).
|
||||||
|
*
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
*/
|
*/
|
||||||
const searchWords = [];
|
const searchWords = [];
|
||||||
|
typeNameIdMap = new Map();
|
||||||
const charA = "A".charCodeAt(0);
|
const charA = "A".charCodeAt(0);
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
@ -2337,7 +2357,11 @@ function initSearch(rawSearchIndex) {
|
||||||
path: itemPaths.has(i) ? itemPaths.get(i) : lastPath,
|
path: itemPaths.has(i) ? itemPaths.get(i) : lastPath,
|
||||||
desc: itemDescs[i],
|
desc: itemDescs[i],
|
||||||
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
|
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
|
||||||
type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
|
type: buildFunctionSearchType(
|
||||||
|
itemFunctionSearchTypes[i],
|
||||||
|
lowercasePaths,
|
||||||
|
typeNameIdMap
|
||||||
|
),
|
||||||
id: id,
|
id: id,
|
||||||
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
|
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
|
||||||
deprecated: deprecatedItems.has(i),
|
deprecated: deprecatedItems.has(i),
|
||||||
|
|
|
@ -226,6 +226,24 @@ function runSearch(query, expected, doSearch, loadedFile, queryName) {
|
||||||
return error_text;
|
return error_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runCorrections(query, corrections, getCorrections, loadedFile) {
|
||||||
|
const qc = getCorrections(query, loadedFile.FILTER_CRATE);
|
||||||
|
const error_text = [];
|
||||||
|
|
||||||
|
if (corrections === null) {
|
||||||
|
if (qc !== null) {
|
||||||
|
error_text.push(`==> expected = null, found = ${qc}`);
|
||||||
|
}
|
||||||
|
return error_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qc !== corrections.toLowerCase()) {
|
||||||
|
error_text.push(`==> expected = ${corrections}, found = ${qc}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error_text;
|
||||||
|
}
|
||||||
|
|
||||||
function checkResult(error_text, loadedFile, displaySuccess) {
|
function checkResult(error_text, loadedFile, displaySuccess) {
|
||||||
if (error_text.length === 0 && loadedFile.should_fail === true) {
|
if (error_text.length === 0 && loadedFile.should_fail === true) {
|
||||||
console.log("FAILED");
|
console.log("FAILED");
|
||||||
|
@ -272,9 +290,10 @@ function runCheck(loadedFile, key, callback) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runChecks(testFile, doSearch, parseQuery) {
|
function runChecks(testFile, doSearch, parseQuery, getCorrections) {
|
||||||
let checkExpected = false;
|
let checkExpected = false;
|
||||||
let checkParsed = false;
|
let checkParsed = false;
|
||||||
|
let checkCorrections = false;
|
||||||
let testFileContent = readFile(testFile) + "exports.QUERY = QUERY;";
|
let testFileContent = readFile(testFile) + "exports.QUERY = QUERY;";
|
||||||
|
|
||||||
if (testFileContent.indexOf("FILTER_CRATE") !== -1) {
|
if (testFileContent.indexOf("FILTER_CRATE") !== -1) {
|
||||||
|
@ -291,9 +310,13 @@ function runChecks(testFile, doSearch, parseQuery) {
|
||||||
testFileContent += "exports.PARSED = PARSED;";
|
testFileContent += "exports.PARSED = PARSED;";
|
||||||
checkParsed = true;
|
checkParsed = true;
|
||||||
}
|
}
|
||||||
if (!checkParsed && !checkExpected) {
|
if (testFileContent.indexOf("\nconst CORRECTIONS") !== -1) {
|
||||||
|
testFileContent += "exports.CORRECTIONS = CORRECTIONS;";
|
||||||
|
checkCorrections = true;
|
||||||
|
}
|
||||||
|
if (!checkParsed && !checkExpected && !checkCorrections) {
|
||||||
console.log("FAILED");
|
console.log("FAILED");
|
||||||
console.log("==> At least `PARSED` or `EXPECTED` is needed!");
|
console.log("==> At least `PARSED`, `EXPECTED`, or `CORRECTIONS` is needed!");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,6 +333,11 @@ function runChecks(testFile, doSearch, parseQuery) {
|
||||||
return runParser(query, expected, parseQuery, text);
|
return runParser(query, expected, parseQuery, text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (checkCorrections) {
|
||||||
|
res += runCheck(loadedFile, "CORRECTIONS", (query, expected) => {
|
||||||
|
return runCorrections(query, expected, getCorrections, loadedFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,9 +346,10 @@ function runChecks(testFile, doSearch, parseQuery) {
|
||||||
*
|
*
|
||||||
* @param {string} doc_folder - Path to a folder generated by running rustdoc
|
* @param {string} doc_folder - Path to a folder generated by running rustdoc
|
||||||
* @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
|
* @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
|
||||||
* @returns {Object} - Object containing two keys: `doSearch`, which runs a search
|
* @returns {Object} - Object containing keys: `doSearch`, which runs a search
|
||||||
* with the loaded index and returns a table of results; and `parseQuery`, which is the
|
* with the loaded index and returns a table of results; `parseQuery`, which is the
|
||||||
* `parseQuery` function exported from the search module.
|
* `parseQuery` function exported from the search module; and `getCorrections`, which runs
|
||||||
|
* a search but returns type name corrections instead of results.
|
||||||
*/
|
*/
|
||||||
function loadSearchJS(doc_folder, resource_suffix) {
|
function loadSearchJS(doc_folder, resource_suffix) {
|
||||||
const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
|
const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
|
||||||
|
@ -336,6 +365,12 @@ function loadSearchJS(doc_folder, resource_suffix) {
|
||||||
return searchModule.execQuery(searchModule.parseQuery(queryStr), searchWords,
|
return searchModule.execQuery(searchModule.parseQuery(queryStr), searchWords,
|
||||||
filterCrate, currentCrate);
|
filterCrate, currentCrate);
|
||||||
},
|
},
|
||||||
|
getCorrections: function(queryStr, filterCrate, currentCrate) {
|
||||||
|
const parsedQuery = searchModule.parseQuery(queryStr);
|
||||||
|
searchModule.execQuery(parsedQuery, searchWords,
|
||||||
|
filterCrate, currentCrate);
|
||||||
|
return parsedQuery.correction;
|
||||||
|
},
|
||||||
parseQuery: searchModule.parseQuery,
|
parseQuery: searchModule.parseQuery,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -417,11 +452,14 @@ function main(argv) {
|
||||||
const doSearch = function(queryStr, filterCrate) {
|
const doSearch = function(queryStr, filterCrate) {
|
||||||
return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
|
return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
|
||||||
};
|
};
|
||||||
|
const getCorrections = function(queryStr, filterCrate) {
|
||||||
|
return parseAndSearch.getCorrections(queryStr, filterCrate, opts["crate_name"]);
|
||||||
|
};
|
||||||
|
|
||||||
if (opts["test_file"].length !== 0) {
|
if (opts["test_file"].length !== 0) {
|
||||||
opts["test_file"].forEach(file => {
|
opts["test_file"].forEach(file => {
|
||||||
process.stdout.write(`Testing ${file} ... `);
|
process.stdout.write(`Testing ${file} ... `);
|
||||||
errors += runChecks(file, doSearch, parseAndSearch.parseQuery);
|
errors += runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections);
|
||||||
});
|
});
|
||||||
} else if (opts["test_folder"].length !== 0) {
|
} else if (opts["test_folder"].length !== 0) {
|
||||||
fs.readdirSync(opts["test_folder"]).forEach(file => {
|
fs.readdirSync(opts["test_folder"]).forEach(file => {
|
||||||
|
@ -430,7 +468,7 @@ function main(argv) {
|
||||||
}
|
}
|
||||||
process.stdout.write(`Testing ${file} ... `);
|
process.stdout.write(`Testing ${file} ... `);
|
||||||
errors += runChecks(path.join(opts["test_folder"], file), doSearch,
|
errors += runChecks(path.join(opts["test_folder"], file), doSearch,
|
||||||
parseAndSearch.parseQuery);
|
parseAndSearch.parseQuery, getCorrections);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return errors > 0 ? 1 : 0;
|
return errors > 0 ? 1 : 0;
|
||||||
|
|
54
tests/rustdoc-gui/search-corrections.goml
Normal file
54
tests/rustdoc-gui/search-corrections.goml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Checks that the search tab result tell the user about corrections
|
||||||
|
// First, try a search-by-name
|
||||||
|
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
|
||||||
|
// Intentionally wrong spelling of "NotableStructWithLongName"
|
||||||
|
write: (".search-input", "NotableStructWithLongNamr")
|
||||||
|
// To be SURE that the search will be run.
|
||||||
|
press-key: 'Enter'
|
||||||
|
// Waiting for the search results to appear...
|
||||||
|
wait-for: "#search-tabs"
|
||||||
|
|
||||||
|
// Corrections aren't shown on the "In Names" tab.
|
||||||
|
assert: "#search-tabs button.selected:first-child"
|
||||||
|
assert-css: (".search-corrections", {
|
||||||
|
"display": "none"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Corrections do get shown on the "In Parameters" tab.
|
||||||
|
click: "#search-tabs button:nth-child(2)"
|
||||||
|
assert: "#search-tabs button.selected:nth-child(2)"
|
||||||
|
assert-css: (".search-corrections", {
|
||||||
|
"display": "block"
|
||||||
|
})
|
||||||
|
assert-text: (
|
||||||
|
".search-corrections",
|
||||||
|
"Showing results for \"notablestructwithlongname\"."
|
||||||
|
)
|
||||||
|
|
||||||
|
// Corrections do get shown on the "In Return Type" tab.
|
||||||
|
click: "#search-tabs button:nth-child(3)"
|
||||||
|
assert: "#search-tabs button.selected:nth-child(3)"
|
||||||
|
assert-css: (".search-corrections", {
|
||||||
|
"display": "block"
|
||||||
|
})
|
||||||
|
assert-text: (
|
||||||
|
".search-corrections",
|
||||||
|
"Showing results for \"notablestructwithlongname\"."
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now, explicit return values
|
||||||
|
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
|
||||||
|
// Intentionally wrong spelling of "NotableStructWithLongName"
|
||||||
|
write: (".search-input", "-> NotableStructWithLongNamr")
|
||||||
|
// To be SURE that the search will be run.
|
||||||
|
press-key: 'Enter'
|
||||||
|
// Waiting for the search results to appear...
|
||||||
|
wait-for: "#search-tabs"
|
||||||
|
|
||||||
|
assert-css: (".search-corrections", {
|
||||||
|
"display": "block"
|
||||||
|
})
|
||||||
|
assert-text: (
|
||||||
|
".search-corrections",
|
||||||
|
"Showing results for \"notablestructwithlongname\"."
|
||||||
|
)
|
|
@ -1,9 +1,21 @@
|
||||||
|
// exact-check
|
||||||
|
|
||||||
const QUERY = [
|
const QUERY = [
|
||||||
'Result<SomeTrait>',
|
'Result<SomeTrait>',
|
||||||
|
'Result<SomeTraiz>',
|
||||||
|
'OtherThingxxxxxxxx',
|
||||||
|
'OtherThingxxxxxxxy',
|
||||||
|
];
|
||||||
|
|
||||||
|
const CORRECTIONS = [
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
'OtherThingxxxxxxxx',
|
'OtherThingxxxxxxxx',
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXPECTED = [
|
const EXPECTED = [
|
||||||
|
// Result<SomeTrait>
|
||||||
{
|
{
|
||||||
'in_args': [
|
'in_args': [
|
||||||
{ 'path': 'generics_trait', 'name': 'beta' },
|
{ 'path': 'generics_trait', 'name': 'beta' },
|
||||||
|
@ -12,6 +24,21 @@ const EXPECTED = [
|
||||||
{ 'path': 'generics_trait', 'name': 'bet' },
|
{ 'path': 'generics_trait', 'name': 'bet' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// Result<SomeTraiz>
|
||||||
|
{
|
||||||
|
'in_args': [],
|
||||||
|
'returned': [],
|
||||||
|
},
|
||||||
|
// OtherThingxxxxxxxx
|
||||||
|
{
|
||||||
|
'in_args': [
|
||||||
|
{ 'path': 'generics_trait', 'name': 'alpha' },
|
||||||
|
],
|
||||||
|
'returned': [
|
||||||
|
{ 'path': 'generics_trait', 'name': 'alef' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// OtherThingxxxxxxxy
|
||||||
{
|
{
|
||||||
'in_args': [
|
'in_args': [
|
||||||
{ 'path': 'generics_trait', 'name': 'alpha' },
|
{ 'path': 'generics_trait', 'name': 'alpha' },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue