Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 99d5520

Browse files
Make query parser more strict and improve display of errors
1 parent 264064d commit 99d5520

File tree

7 files changed

+242
-141
lines changed

7 files changed

+242
-141
lines changed

src/librustdoc/html/static/js/search.js

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,12 @@ window.initSearch = function(rawSearchIndex) {
132132
return "(<\"".indexOf(c) !== -1;
133133
}
134134

135-
function isStopCharacter(c) {
136-
return isWhitespace(c) || "),>-=".indexOf(c) !== -1;
135+
function isEndCharacter(c) {
136+
return "),>-".indexOf(c) !== -1;
137137
}
138138

139-
function removeEmptyStringsFromArray(arr) {
140-
for (var i = 0, len = arr.length; i < len; ++i) {
141-
if (arr[i] === "") {
142-
arr.splice(i, 1);
143-
i -= 1;
144-
}
145-
}
139+
function isStopCharacter(c) {
140+
return isWhitespace(c) || isEndCharacter(c);
146141
}
147142

148143
function itemTypeFromName(typename) {
@@ -151,7 +146,8 @@ window.initSearch = function(rawSearchIndex) {
151146
return i;
152147
}
153148
}
154-
return NO_TYPE_FILTER;
149+
150+
throw new Error("Unknown type filter `" + typename + "`");
155151
}
156152

157153
/**
@@ -189,22 +185,6 @@ window.initSearch = function(rawSearchIndex) {
189185
query.literalSearch = true;
190186
}
191187

192-
/**
193-
* Increase the parser position as long as the character is a whitespace. This check is
194-
* performed with the `isWhitespace` function.
195-
*
196-
* @param {ParserState} parserState
197-
*/
198-
function skipWhitespaces(parserState) {
199-
while (parserState.pos < parserState.length) {
200-
var c = parserState.userQuery[parserState.pos];
201-
if (!isWhitespace(c)) {
202-
break;
203-
}
204-
parserState.pos += 1;
205-
}
206-
}
207-
208188
/**
209189
* Returns `true` if the current parser position is starting with "::".
210190
*
@@ -233,15 +213,27 @@ window.initSearch = function(rawSearchIndex) {
233213
* @param {Array<QueryElement>} generics - List of generics of this query element.
234214
*/
235215
function createQueryElement(query, parserState, elems, name, generics) {
236-
removeEmptyStringsFromArray(generics);
237216
if (name === '*' || (name.length === 0 && generics.length === 0)) {
238217
return;
239218
}
240219
if (query.literalSearch && parserState.totalElems > 0) {
241220
throw new Error("You cannot have more than one element if you use quotes");
242221
}
243222
var pathSegments = name.split("::");
244-
removeEmptyStringsFromArray(pathSegments);
223+
if (pathSegments.length > 1) {
224+
for (var i = 0, len = pathSegments.length; i < len; ++i) {
225+
var pathSegment = pathSegments[i];
226+
227+
if (pathSegment.length === 0) {
228+
if (i === 0) {
229+
throw new Error("Paths cannot start with `::`");
230+
} else if (i + 1 === len) {
231+
throw new Error("Paths cannot end with `::`");
232+
}
233+
throw new Error("Unexpected `::::`");
234+
}
235+
}
236+
}
245237
// In case we only have something like `<p>`, there is no name but it remains valid.
246238
if (pathSegments.length === 0) {
247239
pathSegments = [""];
@@ -272,7 +264,6 @@ window.initSearch = function(rawSearchIndex) {
272264
start += 1;
273265
getStringElem(query, parserState, isInGenerics);
274266
end = parserState.pos - 1;
275-
skipWhitespaces(parserState);
276267
} else {
277268
while (parserState.pos < parserState.length) {
278269
var c = parserState.userQuery[parserState.pos];
@@ -289,7 +280,6 @@ window.initSearch = function(rawSearchIndex) {
289280
}
290281
parserState.pos += 1;
291282
end = parserState.pos;
292-
skipWhitespaces(parserState);
293283
}
294284
}
295285
if (parserState.pos < parserState.length &&
@@ -317,22 +307,36 @@ window.initSearch = function(rawSearchIndex) {
317307
* character.
318308
*/
319309
function getItemsBefore(query, parserState, elems, limit) {
310+
var turns = 0;
320311
while (parserState.pos < parserState.length) {
321312
var c = parserState.userQuery[parserState.pos];
322313
if (c === limit) {
323314
break;
324-
} else if (c === '(' || c === ":") {
325-
// Something weird is going on in here. Ignoring it!
315+
} else if (c === "," && limit !== "" && turns > 0) {
326316
parserState.pos += 1;
327317
continue;
318+
} else if (c === ":" && isPathStart(parserState)) {
319+
throw new Error("Unexpected `::`: paths cannot start with `::`");
320+
} else if (c === "(" || c === ":" || isEndCharacter(c)) {
321+
var extra = "";
322+
if (limit === ">") {
323+
extra = "`<`";
324+
} else if (limit === ")") {
325+
extra = "`(`";
326+
} else if (limit === "") {
327+
extra = "`->`";
328+
}
329+
throw new Error("Unexpected `" + c + "` after " + extra);
328330
}
329331
var posBefore = parserState.pos;
330332
getNextElem(query, parserState, elems, limit === ">");
333+
turns += 1;
331334
if (posBefore === parserState.pos) {
332335
parserState.pos += 1;
333336
}
334337
}
335-
// We skip the "limit".
338+
// We are either at the end of the string or on the "limit" character, let's move forward
339+
// in any case.
336340
parserState.pos += 1;
337341
}
338342

@@ -356,9 +360,13 @@ window.initSearch = function(rawSearchIndex) {
356360
break;
357361
} else if (c === ":" &&
358362
parserState.typeFilter === null &&
359-
!isPathStart(parserState) &&
360-
query.elems.length === 1)
363+
!isPathStart(parserState))
361364
{
365+
if (query.elems.length === 0) {
366+
throw new Error("Expected type filter before `:`");
367+
} else if (query.elems.length !== 1 || parserState.totalElems !== 1) {
368+
throw new Error("Unexpected `:`");
369+
}
362370
if (query.literalSearch) {
363371
throw new Error("You cannot use quotes on type filter");
364372
}
@@ -531,6 +539,10 @@ window.initSearch = function(rawSearchIndex) {
531539

532540
try {
533541
parseInput(query, parserState);
542+
if (parserState.typeFilter !== null) {
543+
var typeFilter = parserState.typeFilter.replace(/^const$/, "constant");
544+
query.typeFilter = itemTypeFromName(typeFilter);
545+
}
534546
} catch (err) {
535547
query = newParsedQuery(userQuery);
536548
query.error = err.message;
@@ -548,10 +560,6 @@ window.initSearch = function(rawSearchIndex) {
548560
createQueryElement(query, parserState, query.elems, userQuery, []);
549561
query.foundElems += 1;
550562
}
551-
if (parserState.typeFilter !== null) {
552-
var typeFilter = parserState.typeFilter.replace(/^const$/, "constant");
553-
query.typeFilter = itemTypeFromName(typeFilter);
554-
}
555563
return query;
556564
}
557565

@@ -582,9 +590,6 @@ window.initSearch = function(rawSearchIndex) {
582590
* @return {ResultsTable}
583591
*/
584592
function execQuery(parsedQuery, searchWords, filterCrates) {
585-
if (parsedQuery.error !== null) {
586-
createQueryResults([], [], [], parsedQuery);
587-
}
588593
var results_others = {}, results_in_args = {}, results_returned = {};
589594

590595
function transformResults(results) {
@@ -1267,14 +1272,21 @@ window.initSearch = function(rawSearchIndex) {
12671272
}
12681273
}
12691274
}
1270-
innerRunQuery();
1275+
1276+
if (parsedQuery.error === null) {
1277+
innerRunQuery();
1278+
}
12711279

12721280
var ret = createQueryResults(
12731281
sortResults(results_in_args, true),
12741282
sortResults(results_returned, true),
12751283
sortResults(results_others, false),
12761284
parsedQuery);
12771285
handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates);
1286+
if (parsedQuery.error !== null && ret.others.length !== 0) {
1287+
// It means some doc aliases were found so let's "remove" the error!
1288+
ret.query.error = null;
1289+
}
12781290
return ret;
12791291
}
12801292

@@ -1413,7 +1425,7 @@ window.initSearch = function(rawSearchIndex) {
14131425

14141426
var output = document.createElement("div");
14151427
var length = 0;
1416-
if (array.length > 0 && query.error === null) {
1428+
if (array.length > 0) {
14171429
output.className = "search-results " + extraClass;
14181430

14191431
array.forEach(function(item) {
@@ -1466,10 +1478,7 @@ window.initSearch = function(rawSearchIndex) {
14661478
link.appendChild(wrapper);
14671479
output.appendChild(link);
14681480
});
1469-
} else if (query.error !== null) {
1470-
output.className = "search-failed" + extraClass;
1471-
output.innerHTML = "Syntax error: " + query.error;
1472-
} else {
1481+
} else if (query.error === null) {
14731482
output.className = "search-failed" + extraClass;
14741483
output.innerHTML = "No results :(<br/>" +
14751484
"Try on <a href=\"https://duckduckgo.com/?q=" +
@@ -1552,15 +1561,19 @@ window.initSearch = function(rawSearchIndex) {
15521561
}
15531562
crates += `</select>`;
15541563
}
1564+
15551565
var typeFilter = "";
15561566
if (results.query.typeFilter !== NO_TYPE_FILTER) {
1557-
typeFilter = " (type: " + escape(results.query.typeFilter) + ")";
1567+
typeFilter = " (type: " + escape(itemTypes[results.query.typeFilter]) + ")";
15581568
}
15591569

15601570
var output = `<div id="search-settings">` +
15611571
`<h1 class="search-results-title">Results for ${escape(results.query.userQuery)}` +
1562-
`${typeFilter}</h1> in ${crates} </div>` +
1563-
`<div id="titles">` +
1572+
`${typeFilter}</h1> in ${crates} </div>`;
1573+
if (results.query.error !== null) {
1574+
output += `<h3>Query parser error: "${results.query.error}".</h3>`;
1575+
}
1576+
output += `<div id="titles">` +
15641577
makeTabHeader(0, "In Names", ret_others[1]) +
15651578
makeTabHeader(1, "In Parameters", ret_in_args[1]) +
15661579
makeTabHeader(2, "In Return Types", ret_returned[1]) +

0 commit comments

Comments
 (0)