@@ -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- " " , // whitespace
752+ "," ,
677753 " or " ,
678754 endChar ,
755+ ...extra ,
679756 ", found " ,
680757 c ,
681758 ] ;
682759 }
683760 throw [
684761 "Expected " ,
685- "," , // comma
686- " or " ,
687- " " , // 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- " " , // whitespace
867+ "," ,
783868 " or " ,
784- "->" , // arrow
869+ "->" ,
870+ ...extra ,
785871 ", found " ,
786872 c ,
787873 ] ;
788874 }
789875 throw [
790876 "Expected " ,
791- "," , // comma
792- ", " ,
793- " " , // 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