Skip to content

Commit b99bf46

Browse files
Use whitespace as path separator like double colon
1 parent 57c215b commit b99bf46

File tree

1 file changed

+163
-71
lines changed

1 file changed

+163
-71
lines changed

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

+163-71
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ function initSearch(rawSearchIndex) {
261261
}
262262

263263
function isStopCharacter(c) {
264-
return isWhitespace(c) || isEndCharacter(c);
264+
return isEndCharacter(c);
265265
}
266266

267267
function isErrorCharacter(c) {
@@ -357,18 +357,69 @@ function initSearch(rawSearchIndex) {
357357
* @return {boolean}
358358
*/
359359
function isSeparatorCharacter(c) {
360-
return c === "," || isWhitespaceCharacter(c);
360+
return c === ",";
361361
}
362362

363-
/**
364-
* Returns `true` if the given `c` character is a whitespace.
363+
/**
364+
* Returns `true` if the given `c` character is a path separator. For example
365+
* `:` in `a::b` or a whitespace in `a b`.
365366
*
366367
* @param {string} c
367368
*
368369
* @return {boolean}
369370
*/
370-
function isWhitespaceCharacter(c) {
371-
return c === " " || c === "\t";
371+
function isPathSeparator(c) {
372+
return c === ":" || isWhitespace(c);
373+
}
374+
375+
/**
376+
* Returns `true` if the previous character is `lookingFor`.
377+
*
378+
* @param {ParserState} parserState
379+
* @param {String} lookingFor
380+
*
381+
* @return {boolean}
382+
*/
383+
function prevIs(parserState, lookingFor) {
384+
let pos = parserState.pos;
385+
while (pos > 0) {
386+
const c = parserState.userQuery[pos - 1];
387+
if (c === lookingFor) {
388+
return true;
389+
} else if (!isWhitespace(c)) {
390+
break;
391+
}
392+
pos -= 1;
393+
}
394+
return false;
395+
}
396+
397+
/**
398+
* Returns `true` if the last element in the `elems` argument has generics.
399+
*
400+
* @param {Array<QueryElement>} elems
401+
* @param {ParserState} parserState
402+
*
403+
* @return {boolean}
404+
*/
405+
function isLastElemGeneric(elems, parserState) {
406+
return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) ||
407+
prevIs(parserState, ">");
408+
}
409+
410+
/**
411+
* Increase current parser position until it doesn't find a whitespace anymore.
412+
*
413+
* @param {ParserState} parserState
414+
*/
415+
function skipWhitespace(parserState) {
416+
while (parserState.pos < parserState.userQuery.length) {
417+
const c = parserState.userQuery[parserState.pos];
418+
if (!isWhitespace(c)) {
419+
break;
420+
}
421+
parserState.pos += 1;
422+
}
372423
}
373424

374425
/**
@@ -380,11 +431,14 @@ function initSearch(rawSearchIndex) {
380431
* @return {QueryElement} - The newly created `QueryElement`.
381432
*/
382433
function createQueryElement(query, parserState, name, generics, isInGenerics) {
383-
if (name === "*" || (name.length === 0 && generics.length === 0)) {
384-
return;
434+
const path = name.trim();
435+
if (path.length === 0 && generics.length === 0) {
436+
throw ["Unexpected ", parserState.userQuery[parserState.pos]];
437+
} else if (path === "*") {
438+
throw ["Unexpected ", "*"];
385439
}
386440
if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
387-
throw ["You cannot have more than one element if you use quotes"];
441+
throw ["Cannot have more than one element if you use quotes"];
388442
}
389443
const typeFilter = parserState.typeFilter;
390444
parserState.typeFilter = null;
@@ -415,38 +469,40 @@ function initSearch(rawSearchIndex) {
415469
typeFilter: "primitive",
416470
};
417471
}
418-
const pathSegments = name.split("::");
419-
if (pathSegments.length > 1) {
420-
for (let i = 0, len = pathSegments.length; i < len; ++i) {
421-
const pathSegment = pathSegments[i];
422-
423-
if (pathSegment.length === 0) {
424-
if (i === 0) {
425-
throw ["Paths cannot start with ", "::"];
426-
} else if (i + 1 === len) {
427-
throw ["Paths cannot end with ", "::"];
428-
}
429-
throw ["Unexpected ", "::::"];
430-
}
431-
432-
if (pathSegment === "!") {
433-
pathSegments[i] = "never";
434-
if (i !== 0) {
435-
throw ["Never type ", "!", " is not associated item"];
436-
}
437-
}
438-
}
439-
}
472+
if (path.startsWith("::")) {
473+
throw ["Paths cannot start with ", "::"];
474+
} else if (path.endsWith("::")) {
475+
throw ["Paths cannot end with ", "::"];
476+
} else if (path.includes("::::")) {
477+
throw ["Unexpected ", "::::"];
478+
} else if (path.includes(" ::")) {
479+
throw ["Unexpected ", " ::"];
480+
} else if (path.includes(":: ")) {
481+
throw ["Unexpected ", ":: "];
482+
}
483+
const pathSegments = path.split(/::|\s+/);
440484
// In case we only have something like `<p>`, there is no name.
441485
if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
442-
throw ["Found generics without a path"];
486+
if (generics.length > 0 || prevIs(parserState, ">")) {
487+
throw ["Found generics without a path"];
488+
} else {
489+
throw ["Unexpected ", parserState.userQuery[parserState.pos]];
490+
}
491+
}
492+
for (const [i, pathSegment] of pathSegments.entries()) {
493+
if (pathSegment === "!") {
494+
if (i !== 0) {
495+
throw ["Never type ", "!", " is not associated item"];
496+
}
497+
pathSegments[i] = "never";
498+
}
443499
}
444500
parserState.totalElems += 1;
445501
if (isInGenerics) {
446502
parserState.genericsElems += 1;
447503
}
448504
return {
449-
name: name,
505+
name: name.trim(),
450506
id: -1,
451507
fullPath: pathSegments,
452508
pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
@@ -482,15 +538,21 @@ function initSearch(rawSearchIndex) {
482538
foundExclamation = parserState.pos;
483539
} else if (isErrorCharacter(c)) {
484540
throw ["Unexpected ", c];
485-
} else if (
486-
isStopCharacter(c) ||
487-
isSpecialStartCharacter(c) ||
488-
isSeparatorCharacter(c)
489-
) {
490-
break;
491-
} else if (c === ":") { // If we allow paths ("str::string" for example).
492-
if (!isPathStart(parserState)) {
493-
break;
541+
} else if (isPathSeparator(c)) {
542+
if (c === ":") {
543+
if (!isPathStart(parserState)) {
544+
break;
545+
}
546+
// Skip current ":".
547+
parserState.pos += 1;
548+
} else {
549+
while (parserState.pos + 1 < parserState.length) {
550+
const next_c = parserState.userQuery[parserState.pos + 1];
551+
if (!isWhitespace(next_c)) {
552+
break;
553+
}
554+
parserState.pos += 1;
555+
}
494556
}
495557
if (foundExclamation !== -1) {
496558
if (foundExclamation !== start &&
@@ -503,8 +565,13 @@ function initSearch(rawSearchIndex) {
503565
foundExclamation = -1;
504566
}
505567
}
506-
// Skip current ":".
507-
parserState.pos += 1;
568+
} else if (
569+
c === "[" ||
570+
isStopCharacter(c) ||
571+
isSpecialStartCharacter(c) ||
572+
isSeparatorCharacter(c)
573+
) {
574+
break;
508575
} else {
509576
throw ["Unexpected ", c];
510577
}
@@ -542,6 +609,7 @@ function initSearch(rawSearchIndex) {
542609
function getNextElem(query, parserState, elems, isInGenerics) {
543610
const generics = [];
544611

612+
skipWhitespace(parserState);
545613
let start = parserState.pos;
546614
let end;
547615
if (parserState.userQuery[parserState.pos] === "[") {
@@ -572,8 +640,9 @@ function initSearch(rawSearchIndex) {
572640
typeFilter: "primitive",
573641
});
574642
} else {
643+
const isStringElem = parserState.userQuery[start] === "\"";
575644
// We handle the strings on their own mostly to make code easier to follow.
576-
if (parserState.userQuery[parserState.pos] === "\"") {
645+
if (isStringElem) {
577646
start += 1;
578647
getStringElem(query, parserState, isInGenerics);
579648
end = parserState.pos - 1;
@@ -589,6 +658,9 @@ function initSearch(rawSearchIndex) {
589658
parserState.pos += 1;
590659
getItemsBefore(query, parserState, generics, ">");
591660
}
661+
if (isStringElem) {
662+
skipWhitespace(parserState);
663+
}
592664
if (start >= end && generics.length === 0) {
593665
return;
594666
}
@@ -653,7 +725,7 @@ function initSearch(rawSearchIndex) {
653725
if (elems.length === 0) {
654726
throw ["Expected type filter before ", ":"];
655727
} else if (query.literalSearch) {
656-
throw ["You cannot use quotes on type filter"];
728+
throw ["Cannot use quotes on type filter"];
657729
}
658730
// The type filter doesn't count as an element since it's a modifier.
659731
const typeFilterElem = elems.pop();
@@ -668,23 +740,27 @@ function initSearch(rawSearchIndex) {
668740
throw ["Unexpected ", c, " after ", extra];
669741
}
670742
if (!foundStopChar) {
743+
let extra = [];
744+
if (isLastElemGeneric(query.elems, parserState)) {
745+
extra = [" after ", ">"];
746+
} else if (prevIs(parserState, "\"")) {
747+
throw ["Cannot have more than one element if you use quotes"];
748+
}
671749
if (endChar !== "") {
672750
throw [
673751
"Expected ",
674-
",", // comma
675-
", ",
676-
"&nbsp;", // whitespace
752+
",",
677753
" or ",
678754
endChar,
755+
...extra,
679756
", found ",
680757
c,
681758
];
682759
}
683760
throw [
684761
"Expected ",
685-
",", // comma
686-
" or ",
687-
"&nbsp;", // whitespace
762+
",",
763+
...extra,
688764
", found ",
689765
c,
690766
];
@@ -720,11 +796,17 @@ function initSearch(rawSearchIndex) {
720796
* @param {ParserState} parserState
721797
*/
722798
function checkExtraTypeFilterCharacters(start, parserState) {
723-
const query = parserState.userQuery;
799+
const query = parserState.userQuery.slice(start, parserState.pos).trim();
724800

725-
for (let pos = start; pos < parserState.pos; ++pos) {
726-
if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
727-
throw ["Unexpected ", query[pos], " in type filter"];
801+
for (const c in query) {
802+
if (!isIdentCharacter(query[c])) {
803+
throw [
804+
"Unexpected ",
805+
query[c],
806+
" in type filter (before ",
807+
":",
808+
")",
809+
];
728810
}
729811
}
730812
}
@@ -757,11 +839,10 @@ function initSearch(rawSearchIndex) {
757839
} else if (c === ":" && !isPathStart(parserState)) {
758840
if (parserState.typeFilter !== null) {
759841
throw ["Unexpected ", ":"];
760-
}
761-
if (query.elems.length === 0) {
842+
} else if (query.elems.length === 0) {
762843
throw ["Expected type filter before ", ":"];
763844
} else if (query.literalSearch) {
764-
throw ["You cannot use quotes on type filter"];
845+
throw ["Cannot use quotes on type filter"];
765846
}
766847
// The type filter doesn't count as an element since it's a modifier.
767848
const typeFilterElem = query.elems.pop();
@@ -774,27 +855,31 @@ function initSearch(rawSearchIndex) {
774855
continue;
775856
}
776857
if (!foundStopChar) {
858+
let extra = "";
859+
if (isLastElemGeneric(query.elems, parserState)) {
860+
extra = [" after ", ">"];
861+
} else if (prevIs(parserState, "\"")) {
862+
throw ["Cannot have more than one element if you use quotes"];
863+
}
777864
if (parserState.typeFilter !== null) {
778865
throw [
779866
"Expected ",
780-
",", // comma
781-
", ",
782-
"&nbsp;", // whitespace
867+
",",
783868
" or ",
784-
"->", // arrow
869+
"->",
870+
...extra,
785871
", found ",
786872
c,
787873
];
788874
}
789875
throw [
790876
"Expected ",
791-
",", // comma
792-
", ",
793-
"&nbsp;", // whitespace
877+
",",
794878
", ",
795-
":", // colon
879+
":",
796880
" or ",
797-
"->", // arrow
881+
"->",
882+
...extra,
798883
", found ",
799884
c,
800885
];
@@ -809,11 +894,18 @@ function initSearch(rawSearchIndex) {
809894
foundStopChar = false;
810895
}
811896
if (parserState.typeFilter !== null) {
812-
throw ["Unexpected ", ":", " (expected path after type filter)"];
897+
throw [
898+
"Unexpected ",
899+
":",
900+
" (expected path after type filter ",
901+
parserState.typeFilter + ":",
902+
")",
903+
];
813904
}
814905
while (parserState.pos < parserState.length) {
815906
if (isReturnArrow(parserState)) {
816907
parserState.pos += 2;
908+
skipWhitespace(parserState);
817909
// Get returned elements.
818910
getItemsBefore(query, parserState, query.returned, "");
819911
// Nothing can come afterward!
@@ -888,10 +980,10 @@ function initSearch(rawSearchIndex) {
888980
* The supported syntax by this parser is as follow:
889981
*
890982
* ident = *(ALPHA / DIGIT / "_")
891-
* path = ident *(DOUBLE-COLON ident) [!]
983+
* path = ident *(DOUBLE-COLON/{WS} ident) [!]
892984
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
893985
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
894-
* type-sep = COMMA/WS *(COMMA/WS)
986+
* type-sep = COMMA *(COMMA)
895987
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
896988
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
897989
* CLOSE-ANGLE-BRACKET

0 commit comments

Comments
 (0)