@@ -261,7 +261,7 @@ function initSearch(rawSearchIndex) {
261
261
}
262
262
263
263
function isStopCharacter ( c ) {
264
- return isWhitespace ( c ) || isEndCharacter ( c ) ;
264
+ return isEndCharacter ( c ) ;
265
265
}
266
266
267
267
function isErrorCharacter ( c ) {
@@ -357,18 +357,69 @@ function initSearch(rawSearchIndex) {
357
357
* @return {boolean }
358
358
*/
359
359
function isSeparatorCharacter ( c ) {
360
- return c === "," || isWhitespaceCharacter ( c ) ;
360
+ return c === "," ;
361
361
}
362
362
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`.
365
366
*
366
367
* @param {string } c
367
368
*
368
369
* @return {boolean }
369
370
*/
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
+ }
372
423
}
373
424
374
425
/**
@@ -380,11 +431,14 @@ function initSearch(rawSearchIndex) {
380
431
* @return {QueryElement } - The newly created `QueryElement`.
381
432
*/
382
433
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 " , "*" ] ;
385
439
}
386
440
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" ] ;
388
442
}
389
443
const typeFilter = parserState . typeFilter ;
390
444
parserState . typeFilter = null ;
@@ -415,38 +469,40 @@ function initSearch(rawSearchIndex) {
415
469
typeFilter : "primitive" ,
416
470
} ;
417
471
}
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 + / ) ;
440
484
// In case we only have something like `<p>`, there is no name.
441
485
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
+ }
443
499
}
444
500
parserState . totalElems += 1 ;
445
501
if ( isInGenerics ) {
446
502
parserState . genericsElems += 1 ;
447
503
}
448
504
return {
449
- name : name ,
505
+ name : name . trim ( ) ,
450
506
id : - 1 ,
451
507
fullPath : pathSegments ,
452
508
pathWithoutLast : pathSegments . slice ( 0 , pathSegments . length - 1 ) ,
@@ -482,15 +538,21 @@ function initSearch(rawSearchIndex) {
482
538
foundExclamation = parserState . pos ;
483
539
} else if ( isErrorCharacter ( c ) ) {
484
540
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
+ }
494
556
}
495
557
if ( foundExclamation !== - 1 ) {
496
558
if ( foundExclamation !== start &&
@@ -503,8 +565,13 @@ function initSearch(rawSearchIndex) {
503
565
foundExclamation = - 1 ;
504
566
}
505
567
}
506
- // Skip current ":".
507
- parserState . pos += 1 ;
568
+ } else if (
569
+ c === "[" ||
570
+ isStopCharacter ( c ) ||
571
+ isSpecialStartCharacter ( c ) ||
572
+ isSeparatorCharacter ( c )
573
+ ) {
574
+ break ;
508
575
} else {
509
576
throw [ "Unexpected " , c ] ;
510
577
}
@@ -542,6 +609,7 @@ function initSearch(rawSearchIndex) {
542
609
function getNextElem ( query , parserState , elems , isInGenerics ) {
543
610
const generics = [ ] ;
544
611
612
+ skipWhitespace ( parserState ) ;
545
613
let start = parserState . pos ;
546
614
let end ;
547
615
if ( parserState . userQuery [ parserState . pos ] === "[" ) {
@@ -572,8 +640,9 @@ function initSearch(rawSearchIndex) {
572
640
typeFilter : "primitive" ,
573
641
} ) ;
574
642
} else {
643
+ const isStringElem = parserState . userQuery [ start ] === "\"" ;
575
644
// We handle the strings on their own mostly to make code easier to follow.
576
- if ( parserState . userQuery [ parserState . pos ] === "\"" ) {
645
+ if ( isStringElem ) {
577
646
start += 1 ;
578
647
getStringElem ( query , parserState , isInGenerics ) ;
579
648
end = parserState . pos - 1 ;
@@ -589,6 +658,9 @@ function initSearch(rawSearchIndex) {
589
658
parserState . pos += 1 ;
590
659
getItemsBefore ( query , parserState , generics , ">" ) ;
591
660
}
661
+ if ( isStringElem ) {
662
+ skipWhitespace ( parserState ) ;
663
+ }
592
664
if ( start >= end && generics . length === 0 ) {
593
665
return ;
594
666
}
@@ -653,7 +725,7 @@ function initSearch(rawSearchIndex) {
653
725
if ( elems . length === 0 ) {
654
726
throw [ "Expected type filter before " , ":" ] ;
655
727
} else if ( query . literalSearch ) {
656
- throw [ "You cannot use quotes on type filter" ] ;
728
+ throw [ "Cannot use quotes on type filter" ] ;
657
729
}
658
730
// The type filter doesn't count as an element since it's a modifier.
659
731
const typeFilterElem = elems . pop ( ) ;
@@ -668,23 +740,27 @@ function initSearch(rawSearchIndex) {
668
740
throw [ "Unexpected " , c , " after " , extra ] ;
669
741
}
670
742
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
+ }
671
749
if ( endChar !== "" ) {
672
750
throw [
673
751
"Expected " ,
674
- "," , // comma
675
- ", " ,
676
- " " , // whitespace
752
+ "," ,
677
753
" or " ,
678
754
endChar ,
755
+ ...extra ,
679
756
", found " ,
680
757
c ,
681
758
] ;
682
759
}
683
760
throw [
684
761
"Expected " ,
685
- "," , // comma
686
- " or " ,
687
- " " , // whitespace
762
+ "," ,
763
+ ...extra ,
688
764
", found " ,
689
765
c ,
690
766
] ;
@@ -720,11 +796,17 @@ function initSearch(rawSearchIndex) {
720
796
* @param {ParserState } parserState
721
797
*/
722
798
function checkExtraTypeFilterCharacters ( start , parserState ) {
723
- const query = parserState . userQuery ;
799
+ const query = parserState . userQuery . slice ( start , parserState . pos ) . trim ( ) ;
724
800
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
+ ] ;
728
810
}
729
811
}
730
812
}
@@ -757,11 +839,10 @@ function initSearch(rawSearchIndex) {
757
839
} else if ( c === ":" && ! isPathStart ( parserState ) ) {
758
840
if ( parserState . typeFilter !== null ) {
759
841
throw [ "Unexpected " , ":" ] ;
760
- }
761
- if ( query . elems . length === 0 ) {
842
+ } else if ( query . elems . length === 0 ) {
762
843
throw [ "Expected type filter before " , ":" ] ;
763
844
} else if ( query . literalSearch ) {
764
- throw [ "You cannot use quotes on type filter" ] ;
845
+ throw [ "Cannot use quotes on type filter" ] ;
765
846
}
766
847
// The type filter doesn't count as an element since it's a modifier.
767
848
const typeFilterElem = query . elems . pop ( ) ;
@@ -774,27 +855,31 @@ function initSearch(rawSearchIndex) {
774
855
continue ;
775
856
}
776
857
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
+ }
777
864
if ( parserState . typeFilter !== null ) {
778
865
throw [
779
866
"Expected " ,
780
- "," , // comma
781
- ", " ,
782
- " " , // whitespace
867
+ "," ,
783
868
" or " ,
784
- "->" , // arrow
869
+ "->" ,
870
+ ...extra ,
785
871
", found " ,
786
872
c ,
787
873
] ;
788
874
}
789
875
throw [
790
876
"Expected " ,
791
- "," , // comma
792
- ", " ,
793
- " " , // whitespace
877
+ "," ,
794
878
", " ,
795
- ":" , // colon
879
+ ":" ,
796
880
" or " ,
797
- "->" , // arrow
881
+ "->" ,
882
+ ...extra ,
798
883
", found " ,
799
884
c ,
800
885
] ;
@@ -809,11 +894,18 @@ function initSearch(rawSearchIndex) {
809
894
foundStopChar = false ;
810
895
}
811
896
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
+ ] ;
813
904
}
814
905
while ( parserState . pos < parserState . length ) {
815
906
if ( isReturnArrow ( parserState ) ) {
816
907
parserState . pos += 2 ;
908
+ skipWhitespace ( parserState ) ;
817
909
// Get returned elements.
818
910
getItemsBefore ( query , parserState , query . returned , "" ) ;
819
911
// Nothing can come afterward!
@@ -888,10 +980,10 @@ function initSearch(rawSearchIndex) {
888
980
* The supported syntax by this parser is as follow:
889
981
*
890
982
* ident = *(ALPHA / DIGIT / "_")
891
- * path = ident *(DOUBLE-COLON ident) [!]
983
+ * path = ident *(DOUBLE-COLON/{WS} ident) [!]
892
984
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
893
985
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
894
- * type-sep = COMMA/WS *(COMMA/WS )
986
+ * type-sep = COMMA *(COMMA)
895
987
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
896
988
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
897
989
* CLOSE-ANGLE-BRACKET
0 commit comments