diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index fa0d5d51b466..443b7e366d4d 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -173,7 +173,14 @@ window.initSearch = function(rawSearchIndex) {
function isStopCharacter(c) {
return isWhitespace(c) || "),>-=".indexOf(c) !== -1;
}
- function getStringElem(query) {
+ function getStringElem(query, isInGenerics) {
+ if (isInGenerics) {
+ throw new Error("`\"` cannot be used in generics");
+ } else if (query.literalSearch) {
+ throw new Error("Cannot have more than one literal search element");
+ } else if (query.totalElems !== 0) {
+ throw new Error("Cannot use literal search when there is more than one element");
+ }
query.pos += 1;
while (query.pos < query.length && query.val[query.pos] !== "\"") {
if (query.val[query.pos] === "\\") {
@@ -182,24 +189,25 @@ window.initSearch = function(rawSearchIndex) {
}
query.pos += 1;
}
+ if (query.pos >= query.length) {
+ throw new Error("Unclosed `\"`");
+ }
// To skip the quote at the end.
query.pos += 1;
+ query.literalSearch = true;
}
function skipWhitespaces(query) {
- var c;
while (query.pos < query.length) {
- c = query.val[query.pos];
+ var c = query.val[query.pos];
if (!isWhitespace(c)) {
break;
}
query.pos += 1;
}
-
}
function skipStopCharacters(query) {
- var c;
while (query.pos < query.length) {
- c = query.val[query.pos];
+ var c = query.val[query.pos];
if (!isStopCharacter(c)) {
break;
}
@@ -222,7 +230,7 @@ window.initSearch = function(rawSearchIndex) {
}
}
}
- function createQueryElement(elems, val, generics, isExact) {
+ function createQueryElement(query, elems, val, generics) {
removeEmptyStringsFromArray(generics);
if (val === '*' || (val.length === 0 && generics.length === 0)) {
return;
@@ -234,16 +242,15 @@ window.initSearch = function(rawSearchIndex) {
paths = [""];
}
elems.push({
- isExact: isExact,
name: val,
fullPath: paths,
pathWithoutLast: paths.slice(0, paths.length - 1),
pathLast: paths[paths.length - 1],
generics: generics,
});
+ query.totalElems += 1;
}
- function getNextElem(query, elems) {
- var isExact = false;
+ function getNextElem(query, elems, isInGenerics) {
var generics = [];
skipStopCharacters(query);
@@ -251,9 +258,8 @@ window.initSearch = function(rawSearchIndex) {
var end = start;
// We handle the strings on their own mostly to make code easier to follow.
if (query.val[query.pos] === "\"") {
- isExact = true;
start += 1;
- getStringElem(query);
+ getStringElem(query, isInGenerics);
end = query.pos - 1;
skipWhitespaces(query);
} else {
@@ -281,20 +287,18 @@ window.initSearch = function(rawSearchIndex) {
if (start >= end && generics.length === 0) {
return;
}
- createQueryElement(elems, query.val.slice(start, end), generics, isExact);
+ createQueryElement(query, elems, query.val.slice(start, end), generics);
}
function getItemsBefore(query, elems, limit) {
- var c;
-
while (query.pos < query.length) {
- c = query.val[query.pos];
+ var c = query.val[query.pos];
if (c === limit) {
break;
} else if (isSpecialStartCharacter(c) || c === ":") {
// Something weird is going on in here. Ignoring it!
query.pos += 1;
}
- getNextElem(query, elems);
+ getNextElem(query, elems, limit === ">");
}
// We skip the "limit".
query.pos += 1;
@@ -319,10 +323,12 @@ window.initSearch = function(rawSearchIndex) {
// The type filter doesn't count as an element since it's a modifier.
query.typeFilter = query.elems.pop().name;
query.pos += 1;
+ query.totalElems = 0;
+ query.literalSearch = false;
continue;
}
before = query.elems.length;
- getNextElem(query, query.elems);
+ getNextElem(query, query.elems, false);
if (query.elems.length === before) {
// Nothing was added, let's check it's not because of a solo ":"!
if (query.pos >= query.length || query.val[query.pos] !== ":") {
@@ -369,20 +375,39 @@ window.initSearch = function(rawSearchIndex) {
elemName: null,
args: [],
returned: [],
+ // Total number of elements (includes generics).
+ totalElems: 0,
+ // Total number of "top" elements (does not include generics).
foundElems: 0,
// This field is used to check if it's needed to re-run a search or not.
id: "",
// This field is used in `sortResults`.
nameSplit: null,
+ literalSearch: false,
+ error: null,
};
- parseInput(query);
+ query.id = val;
+ try {
+ parseInput(query);
+ } catch (err) {
+ query.error = err.message;
+ query.elems = [];
+ query.returned = [];
+ query.args = [];
+ return query;
+ }
query.foundElems = query.elems.length + query.args.length + query.returned.length;
+ if (!query.literalSearch) {
+ // If there is more than one element in the query, we switch to literalSearch in any
+ // case.
+ query.literalSearch = query.foundElems > 1;
+ }
if (query.elemName !== null) {
query.foundElems += 1;
}
if (query.foundElems === 0 && val.length !== 0) {
// In this case, we'll simply keep whatever was entered by the user...
- createQueryElement(query.elems, val, [], false);
+ createQueryElement(query, query.elems, val, []);
query.foundElems += 1;
}
if (query.typeFilter !== null) {
@@ -391,7 +416,6 @@ window.initSearch = function(rawSearchIndex) {
} else {
query.typeFilter = NO_TYPE_FILTER;
}
- query.id = val;
// In case we only have one argument, we move it back to `elems` to keep things simple.
if (query.foundElems === 1 && query.elemName !== null) {
query.elems.push(query.elemName);
@@ -404,15 +428,36 @@ window.initSearch = function(rawSearchIndex) {
return query;
}
+ /**
+ * Creates the query results.
+ *
+ * @param {Array