diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 6fbb4508662..a7d5f497756 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -1259,6 +1259,10 @@ a.tooltip:hover::after {
background-color: var(--search-error-code-background-color);
}
+.search-corrections {
+ font-weight: normal;
+}
+
#src-sidebar-toggle {
position: sticky;
top: 0;
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index 4c81a0979c1..8b931f74e60 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -9,6 +9,7 @@ function initSearch(searchIndex){}
/**
* @typedef {{
* name: string,
+ * id: integer,
* fullPath: Array,
* pathWithoutLast: Array,
* pathLast: string,
@@ -36,6 +37,8 @@ let ParserState;
* args: Array,
* returned: Array,
* foundElems: number,
+ * literalSearch: boolean,
+ * corrections: Array<{from: string, to: integer}>,
* }}
*/
let ParsedQuery;
@@ -139,7 +142,7 @@ let FunctionSearchType;
/**
* @typedef {{
- * name: (null|string),
+ * id: (null|number),
* ty: (null|number),
* generics: Array,
* }}
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 9ef01b5d892..2d0a3f0192b 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -58,6 +58,7 @@ function printTab(nb) {
}
iter += 1;
});
+ const isTypeSearch = (nb > 0 || iter === 1);
iter = 0;
onEachLazy(document.getElementById("results").childNodes, elem => {
if (nb === iter) {
@@ -70,6 +71,13 @@ function printTab(nb) {
});
if (foundCurrentTab && foundCurrentResultSet) {
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) {
printTab(0);
}
@@ -191,6 +199,13 @@ function initSearch(rawSearchIndex) {
*/
let searchIndex;
let currentResults;
+ /**
+ * Map from normalized type names to integers. Used to make type search
+ * more efficient.
+ *
+ * @type {Map}
+ */
+ let typeNameIdMap;
const ALIASES = new Map();
function isWhitespace(c) {
@@ -358,6 +373,7 @@ function initSearch(rawSearchIndex) {
parserState.typeFilter = null;
return {
name: name,
+ id: -1,
fullPath: pathSegments,
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
pathLast: pathSegments[pathSegments.length - 1],
@@ -718,6 +734,7 @@ function initSearch(rawSearchIndex) {
foundElems: 0,
literalSearch: false,
error: null,
+ correction: null,
};
}
@@ -1091,48 +1108,50 @@ function initSearch(rawSearchIndex) {
*
* @param {Row} row - The object to check.
* @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) {
- if (row.generics.length === 0) {
- return elem.generics.length === 0 ? defaultDistance : maxEditDistance + 1;
- } else if (row.generics.length > 0 && row.generics[0].name === null) {
- return checkGenerics(row.generics[0], elem, defaultDistance, maxEditDistance);
+ function checkGenerics(row, elem) {
+ if (row.generics.length === 0 || elem.generics.length === 0) {
+ return false;
}
- // The names match, but we need to be sure that all generics kinda
- // match as well.
+ // This function is called if the names match, but we need to make
+ // 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) {
const elems = new Map();
- for (const entry of row.generics) {
- if (entry.name === "") {
+ const addEntryToElems = function addEntryToElems(entry) {
+ if (entry.id === -1) {
// Pure generic, needs to check into it.
- if (checkGenerics(entry, elem, maxEditDistance + 1, maxEditDistance)
- !== 0) {
- return maxEditDistance + 1;
+ for (const inner_entry of entry.generics) {
+ addEntryToElems(inner_entry);
}
- continue;
+ return;
}
let currentEntryElems;
- if (elems.has(entry.name)) {
- currentEntryElems = elems.get(entry.name);
+ if (elems.has(entry.id)) {
+ currentEntryElems = elems.get(entry.id);
} else {
currentEntryElems = [];
- elems.set(entry.name, currentEntryElems);
+ elems.set(entry.id, currentEntryElems);
}
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
// to move forward.
const handleGeneric = generic => {
- if (!elems.has(generic.name)) {
+ if (!elems.has(generic.id)) {
return false;
}
- const matchElems = elems.get(generic.name);
+ const matchElems = elems.get(generic.id);
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 typePassesFilter(generic.typeFilter, tmp_elem.ty);
@@ -1142,7 +1161,7 @@ function initSearch(rawSearchIndex) {
}
matchElems.splice(matchIdx, 1);
if (matchElems.length === 0) {
- elems.delete(generic.name);
+ elems.delete(generic.id);
}
return true;
};
@@ -1152,17 +1171,17 @@ function initSearch(rawSearchIndex) {
// own type.
for (const generic of elem.generics) {
if (generic.typeFilter !== -1 && !handleGeneric(generic)) {
- return maxEditDistance + 1;
+ return false;
}
}
for (const generic of elem.generics) {
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 {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) {
- let dist = maxEditDistance + 1;
+ function checkIfInGenerics(row, elem) {
for (const entry of row.generics) {
- dist = Math.min(checkType(entry, elem, true, maxEditDistance), dist);
- if (dist === 0) {
- break;
+ if (checkType(entry, elem)) {
+ return true;
}
}
- return dist;
+ return false;
}
/**
@@ -1191,75 +1208,30 @@ function initSearch(rawSearchIndex) {
*
* @param {Row} row
* @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
- * no match, returns `maxEditDistance + 1`.
+ * @return {boolean} - Returns true if the type matches, false otherwise.
*/
- function checkType(row, elem, literalSearch, maxEditDistance) {
- if (row.name === null) {
+ function checkType(row, elem) {
+ if (row.id === -1) {
// This is a pure "generic" search, no need to run other checks.
- if (row.generics.length > 0) {
- return checkIfInGenerics(row, elem, maxEditDistance);
- }
- return maxEditDistance + 1;
+ return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false;
}
- let dist;
- if (typePassesFilter(elem.typeFilter, row.ty)) {
- dist = editDistance(row.name, elem.name, maxEditDistance);
- } 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);
+ if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) {
+ if (elem.generics.length > 0) {
+ return checkGenerics(row, elem);
}
- return 0;
- } 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>>()", if we're
- // looking for "B", 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;
+ return true;
}
- // No generics on our query or on the target type so we can return without doing
- // anything else.
- return dist;
+
+ // If the current item does not match, try [unboxing] the generic.
+ // [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 {QueryElement} elem - The element from the parsed query.
- * @param {integer} maxEditDistance
* @param {Array} skipPositions - Do not return one of these positions.
*
- * @return {dist: integer, position: integer} - Returns an edit distance to the best match.
- * If there is no match, returns
- * `maxEditDistance + 1` and position: -1.
+ * @return {integer} - Returns the position of the match, or -1 if none.
*/
- function findArg(row, elem, maxEditDistance, skipPositions) {
- let dist = maxEditDistance + 1;
- let position = -1;
-
+ function findArg(row, elem, skipPositions) {
if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
let i = 0;
for (const input of row.type.inputs) {
@@ -1285,24 +1251,13 @@ function initSearch(rawSearchIndex) {
i += 1;
continue;
}
- const typeDist = checkType(
- input,
- elem,
- parsedQuery.literalSearch,
- maxEditDistance
- );
- if (typeDist === 0) {
- return {dist: 0, position: i};
- }
- if (typeDist < dist) {
- dist = typeDist;
- position = i;
+ if (checkType(input, elem)) {
+ return i;
}
i += 1;
}
}
- dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
- return {dist, position};
+ return -1;
}
/**
@@ -1310,43 +1265,25 @@ function initSearch(rawSearchIndex) {
*
* @param {Row} row
* @param {QueryElement} elem - The element from the parsed query.
- * @param {integer} maxEditDistance
* @param {Array} skipPositions - Do not return one of these positions.
*
- * @return {dist: integer, position: integer} - Returns an edit distance to the best match.
- * If there is no match, returns
- * `maxEditDistance + 1` and position: -1.
+ * @return {integer} - Returns the position of the matching item, or -1 if none.
*/
- function checkReturned(row, elem, maxEditDistance, skipPositions) {
- let dist = maxEditDistance + 1;
- let position = -1;
-
+ function checkReturned(row, elem, skipPositions) {
if (row && row.type && row.type.output.length > 0) {
- const ret = row.type.output;
let i = 0;
- for (const ret_ty of ret) {
+ for (const ret_ty of row.type.output) {
if (skipPositions.indexOf(i) !== -1) {
i += 1;
continue;
}
- const typeDist = checkType(
- ret_ty,
- elem,
- parsedQuery.literalSearch,
- maxEditDistance
- );
- if (typeDist === 0) {
- return {dist: 0, position: i};
- }
- if (typeDist < dist) {
- dist = typeDist;
- position = i;
+ if (checkType(ret_ty, elem)) {
+ return i;
}
i += 1;
}
}
- dist = parsedQuery.literalSearch ? maxEditDistance + 1 : dist;
- return {dist, position};
+ return -1;
}
function checkPath(contains, ty, maxEditDistance) {
@@ -1543,17 +1480,20 @@ function initSearch(rawSearchIndex) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return;
}
- let dist, index = -1, path_dist = 0;
+ let index = -1, path_dist = 0;
const fullId = row.id;
const searchWord = searchWords[pos];
- const in_args = findArg(row, elem, maxEditDistance, []);
- const returned = checkReturned(row, elem, maxEditDistance, []);
-
- // path_dist is 0 because no parent path information is currently stored
- // in the search index
- addIntoResults(results_in_args, fullId, pos, -1, in_args.dist, 0, maxEditDistance);
- addIntoResults(results_returned, fullId, pos, -1, returned.dist, 0, maxEditDistance);
+ const in_args = findArg(row, elem, []);
+ if (in_args !== -1) {
+ // path_dist is 0 because no parent path information is currently stored
+ // in the search index
+ addIntoResults(results_in_args, fullId, pos, -1, 0, 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)) {
return;
@@ -1574,16 +1514,6 @@ function initSearch(rawSearchIndex) {
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) {
path_dist = checkPath(elem.pathWithoutLast, row, maxEditDistance);
if (path_dist > maxEditDistance) {
@@ -1598,7 +1528,7 @@ function initSearch(rawSearchIndex) {
return;
}
- dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
+ const dist = editDistance(searchWord, elem.pathLast, maxEditDistance);
if (index === -1 && dist + path_dist > maxEditDistance) {
return;
@@ -1616,28 +1546,22 @@ function initSearch(rawSearchIndex) {
* @param {integer} pos - Position in the `searchIndex`.
* @param {Object} results
*/
- function handleArgs(row, pos, results, maxEditDistance) {
+ function handleArgs(row, pos, results) {
if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
return;
}
- let totalDist = 0;
- let nbDist = 0;
-
// If the result is too "bad", we return false and it ends this search.
function checkArgs(elems, callback) {
const skipPositions = [];
for (const elem of elems) {
// There is more than one parameter to the query so all checks should be "exact"
- const { dist, position } = callback(
+ const position = callback(
row,
elem,
- maxEditDistance,
skipPositions
);
- if (dist <= 1) {
- nbDist += 1;
- totalDist += dist;
+ if (position !== -1) {
skipPositions.push(position);
} else {
return false;
@@ -1652,11 +1576,7 @@ function initSearch(rawSearchIndex) {
return;
}
- if (nbDist === 0) {
- return;
- }
- const dist = Math.round(totalDist / nbDist);
- addIntoResults(results, row.id, pos, 0, dist, 0, maxEditDistance);
+ addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE);
}
function innerRunQuery() {
@@ -1671,6 +1591,50 @@ function initSearch(rawSearchIndex) {
}
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.elems.length === 1) {
elem = parsedQuery.elems[0];
@@ -1695,22 +1659,23 @@ function initSearch(rawSearchIndex) {
in_returned = checkReturned(
row,
elem,
- maxEditDistance,
[]
);
- addIntoResults(
- results_others,
- row.id,
- i,
- -1,
- in_returned.dist,
- maxEditDistance
- );
+ if (in_returned !== -1) {
+ addIntoResults(
+ results_others,
+ row.id,
+ i,
+ -1,
+ 0,
+ Number.MAX_VALUE
+ );
+ }
}
}
} else if (parsedQuery.foundElems > 0) {
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;
}
+ if (results.query.correction !== null) {
+ output += "Showing results for " +
+ `"${results.query.correction}".
`;
+ }
+
const resultsElem = document.createElement("div");
resultsElem.id = "results";
resultsElem.appendChild(ret_others[0]);
@@ -2108,6 +2078,34 @@ function initSearch(rawSearchIndex) {
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} 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.
*
@@ -2126,7 +2124,7 @@ function initSearch(rawSearchIndex) {
*
* @return {Array}
*/
- function buildItemSearchTypeAll(types, lowercasePaths) {
+ function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) {
const PATH_INDEX_DATA = 0;
const GENERICS_DATA = 1;
return types.map(type => {
@@ -2136,11 +2134,17 @@ function initSearch(rawSearchIndex) {
generics = [];
} else {
pathIndex = type[PATH_INDEX_DATA];
- generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths);
+ generics = buildItemSearchTypeAll(
+ type[GENERICS_DATA],
+ lowercasePaths,
+ typeNameIdMap
+ );
}
return {
// `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,
generics: generics,
};
@@ -2159,10 +2163,11 @@ function initSearch(rawSearchIndex) {
*
* @param {RawFunctionSearchType} functionSearchType
* @param {Array<{name: string, ty: number}>} lowercasePaths
+ * @param {Map}
*
* @return {null|FunctionSearchType}
*/
- function buildFunctionSearchType(functionSearchType, lowercasePaths) {
+ function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) {
const INPUTS_DATA = 0;
const OUTPUT_DATA = 1;
// `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") {
const pathIndex = functionSearchType[INPUTS_DATA];
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,
generics: [],
}];
} else {
- inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths);
+ inputs = buildItemSearchTypeAll(
+ functionSearchType[INPUTS_DATA],
+ lowercasePaths,
+ typeNameIdMap
+ );
}
if (functionSearchType.length > 1) {
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
const pathIndex = functionSearchType[OUTPUT_DATA];
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,
generics: [],
}];
} else {
- output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths);
+ output = buildItemSearchTypeAll(
+ functionSearchType[OUTPUT_DATA],
+ lowercasePaths,
+ typeNameIdMap
+ );
}
} else {
output = [];
@@ -2202,9 +2219,12 @@ function initSearch(rawSearchIndex) {
function buildIndex(rawSearchIndex) {
searchIndex = [];
/**
+ * List of normalized search words (ASCII lowercased, and undescores removed).
+ *
* @type {Array}
*/
const searchWords = [];
+ typeNameIdMap = new Map();
const charA = "A".charCodeAt(0);
let currentIndex = 0;
let id = 0;
@@ -2337,7 +2357,11 @@ function initSearch(rawSearchIndex) {
path: itemPaths.has(i) ? itemPaths.get(i) : lastPath,
desc: itemDescs[i],
parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
- type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
+ type: buildFunctionSearchType(
+ itemFunctionSearchTypes[i],
+ lowercasePaths,
+ typeNameIdMap
+ ),
id: id,
normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
deprecated: deprecatedItems.has(i),
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index 6b9a9b66a7d..270704ebffd 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -226,6 +226,24 @@ function runSearch(query, expected, doSearch, loadedFile, queryName) {
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) {
if (error_text.length === 0 && loadedFile.should_fail === true) {
console.log("FAILED");
@@ -272,9 +290,10 @@ function runCheck(loadedFile, key, callback) {
return 0;
}
-function runChecks(testFile, doSearch, parseQuery) {
+function runChecks(testFile, doSearch, parseQuery, getCorrections) {
let checkExpected = false;
let checkParsed = false;
+ let checkCorrections = false;
let testFileContent = readFile(testFile) + "exports.QUERY = QUERY;";
if (testFileContent.indexOf("FILTER_CRATE") !== -1) {
@@ -291,9 +310,13 @@ function runChecks(testFile, doSearch, parseQuery) {
testFileContent += "exports.PARSED = PARSED;";
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("==> At least `PARSED` or `EXPECTED` is needed!");
+ console.log("==> At least `PARSED`, `EXPECTED`, or `CORRECTIONS` is needed!");
return 1;
}
@@ -310,6 +333,11 @@ function runChecks(testFile, doSearch, parseQuery) {
return runParser(query, expected, parseQuery, text);
});
}
+ if (checkCorrections) {
+ res += runCheck(loadedFile, "CORRECTIONS", (query, expected) => {
+ return runCorrections(query, expected, getCorrections, loadedFile);
+ });
+ }
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} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
- * @returns {Object} - Object containing two keys: `doSearch`, which runs a search
- * with the loaded index and returns a table of results; and `parseQuery`, which is the
- * `parseQuery` function exported from the search module.
+ * @returns {Object} - Object containing keys: `doSearch`, which runs a search
+ * with the loaded index and returns a table of results; `parseQuery`, which is the
+ * `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) {
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,
filterCrate, currentCrate);
},
+ getCorrections: function(queryStr, filterCrate, currentCrate) {
+ const parsedQuery = searchModule.parseQuery(queryStr);
+ searchModule.execQuery(parsedQuery, searchWords,
+ filterCrate, currentCrate);
+ return parsedQuery.correction;
+ },
parseQuery: searchModule.parseQuery,
};
}
@@ -417,11 +452,14 @@ function main(argv) {
const doSearch = function(queryStr, filterCrate) {
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) {
opts["test_file"].forEach(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) {
fs.readdirSync(opts["test_folder"]).forEach(file => {
@@ -430,7 +468,7 @@ function main(argv) {
}
process.stdout.write(`Testing ${file} ... `);
errors += runChecks(path.join(opts["test_folder"], file), doSearch,
- parseAndSearch.parseQuery);
+ parseAndSearch.parseQuery, getCorrections);
});
}
return errors > 0 ? 1 : 0;
diff --git a/tests/rustdoc-gui/search-corrections.goml b/tests/rustdoc-gui/search-corrections.goml
new file mode 100644
index 00000000000..832aa153054
--- /dev/null
+++ b/tests/rustdoc-gui/search-corrections.goml
@@ -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\"."
+)
diff --git a/tests/rustdoc-js/generics-trait.js b/tests/rustdoc-js/generics-trait.js
index 7876622435b..0e84751603e 100644
--- a/tests/rustdoc-js/generics-trait.js
+++ b/tests/rustdoc-js/generics-trait.js
@@ -1,9 +1,21 @@
+// exact-check
+
const QUERY = [
'Result',
+ 'Result',
+ 'OtherThingxxxxxxxx',
+ 'OtherThingxxxxxxxy',
+];
+
+const CORRECTIONS = [
+ null,
+ null,
+ null,
'OtherThingxxxxxxxx',
];
const EXPECTED = [
+ // Result
{
'in_args': [
{ 'path': 'generics_trait', 'name': 'beta' },
@@ -12,6 +24,21 @@ const EXPECTED = [
{ 'path': 'generics_trait', 'name': 'bet' },
],
},
+ // Result
+ {
+ 'in_args': [],
+ 'returned': [],
+ },
+ // OtherThingxxxxxxxx
+ {
+ 'in_args': [
+ { 'path': 'generics_trait', 'name': 'alpha' },
+ ],
+ 'returned': [
+ { 'path': 'generics_trait', 'name': 'alef' },
+ ],
+ },
+ // OtherThingxxxxxxxy
{
'in_args': [
{ 'path': 'generics_trait', 'name': 'alpha' },