1
- const DEFAULT_PREFIXES = "./" ;
2
1
const DEFAULT_DELIMITER = "/" ;
3
- const GROUPS_RE = / \( (?: \? < ( .* ?) > ) ? (? ! \? ) / g;
4
2
const NOOP_VALUE = ( value : string ) => value ;
5
- const ID_START = / ^ [ $ _ \p{ ID_Start} ] $ / u;
6
- const ID_CONTINUE = / ^ [ $ _ \u200C \u200D \p{ ID_Continue} ] $ / u;
3
+ const ID_CHAR = / ^ \p{ XID_Continue} $ / u;
7
4
8
5
/**
9
6
* Encode a string into another string.
@@ -92,6 +89,7 @@ type TokenType =
92
89
| "END"
93
90
// Reserved for use.
94
91
| "!"
92
+ | "@"
95
93
| ";" ;
96
94
97
95
/**
@@ -105,6 +103,7 @@ interface LexToken {
105
103
106
104
const SIMPLE_TOKENS : Record < string , TokenType > = {
107
105
"!" : "!" ,
106
+ "@" : "@" ,
108
107
";" : ";" ,
109
108
"*" : "*" ,
110
109
"+" : "+" ,
@@ -136,14 +135,14 @@ function lexer(str: string) {
136
135
}
137
136
138
137
if ( value === ":" ) {
139
- let name = chars [ ++ i ] ;
138
+ let name = "" ;
140
139
141
- if ( ! ID_START . test ( name ) ) {
142
- throw new TypeError ( `Missing parameter name at ${ i } ` ) ;
140
+ while ( ID_CHAR . test ( chars [ ++ i ] ) ) {
141
+ name += chars [ i ] ;
143
142
}
144
143
145
- while ( ID_CONTINUE . test ( chars [ ++ i ] ) ) {
146
- name += chars [ i ] ;
144
+ if ( ! name ) {
145
+ throw new TypeError ( `Missing parameter name at ${ i } ` ) ;
147
146
}
148
147
149
148
tokens . push ( { type : "NAME" , index : i , value : name } ) ;
@@ -248,11 +247,10 @@ export class TokenData {
248
247
*/
249
248
export function parse ( str : string , options : ParseOptions = { } ) : TokenData {
250
249
const {
251
- prefixes = DEFAULT_PREFIXES ,
250
+ prefixes = "./" ,
252
251
delimiter = DEFAULT_DELIMITER ,
253
252
encodePath = NOOP_VALUE ,
254
253
} = options ;
255
- const defaultPattern = `[^${ escape ( delimiter ) } ]+?` ;
256
254
const tokens : Token [ ] = [ ] ;
257
255
const it = lexer ( str ) ;
258
256
let key = 0 ;
@@ -265,6 +263,7 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
265
263
266
264
if ( name || pattern ) {
267
265
let prefix = char || "" ;
266
+ const modifier = it . modifier ( ) ;
268
267
269
268
if ( ! prefixes . includes ( prefix ) ) {
270
269
path += prefix ;
@@ -281,10 +280,10 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
281
280
encodePath ,
282
281
delimiter ,
283
282
name || String ( key ++ ) ,
284
- pattern || defaultPattern ,
283
+ pattern ,
285
284
prefix ,
286
285
"" ,
287
- it . modifier ( ) ,
286
+ modifier ,
288
287
) ,
289
288
) ;
290
289
continue ;
@@ -301,6 +300,22 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
301
300
path = "" ;
302
301
}
303
302
303
+ const asterisk = it . tryConsume ( "*" ) ;
304
+ if ( asterisk ) {
305
+ tokens . push (
306
+ toKey (
307
+ encodePath ,
308
+ delimiter ,
309
+ String ( key ++ ) ,
310
+ `[^${ escape ( delimiter ) } ]*` ,
311
+ "" ,
312
+ "" ,
313
+ asterisk ,
314
+ ) ,
315
+ ) ;
316
+ continue ;
317
+ }
318
+
304
319
const open = it . tryConsume ( "{" ) ;
305
320
if ( open ) {
306
321
const prefix = it . text ( ) ;
@@ -315,7 +330,7 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
315
330
encodePath ,
316
331
delimiter ,
317
332
name || ( pattern ? String ( key ++ ) : "" ) ,
318
- name && ! pattern ? defaultPattern : pattern || "" ,
333
+ pattern ,
319
334
prefix ,
320
335
suffix ,
321
336
it . modifier ( ) ,
@@ -445,14 +460,15 @@ function compileTokens<P extends ParamData>(
445
460
} = options ;
446
461
const reFlags = flags ( options ) ;
447
462
const stringify = toStringify ( loose ) ;
463
+ const keyToRegexp = toKeyRegexp ( stringify , data . delimiter ) ;
448
464
449
465
// Compile all the tokens into regexps.
450
466
const encoders : Array < ( data : ParamData ) => string > = data . tokens . map (
451
467
( token ) => {
452
468
const fn = tokenToFunction ( token , encode ) ;
453
469
if ( ! validate || typeof token === "string" ) return fn ;
454
470
455
- const pattern = keyToRegexp ( token , stringify ) ;
471
+ const pattern = keyToRegexp ( token ) ;
456
472
const validRe = new RegExp ( `^${ pattern } $` , reFlags ) ;
457
473
458
474
return ( data ) => {
@@ -516,16 +532,9 @@ function matchRegexp<P extends ParamData>(
516
532
517
533
const decoders = re . keys . map ( ( key ) => {
518
534
if ( key . separator ) {
519
- const re = new RegExp (
520
- `(${ key . pattern } )(?:${ stringify ( key . separator ) } |$)` ,
521
- "g" ,
522
- ) ;
535
+ const re = new RegExp ( stringify ( key . separator ) , "g" ) ;
523
536
524
- return ( value : string ) => {
525
- const result : string [ ] = [ ] ;
526
- for ( const m of value . matchAll ( re ) ) result . push ( decode ( m [ 1 ] ) ) ;
527
- return result ;
528
- } ;
537
+ return ( value : string ) => value . split ( re ) . map ( decode ) ;
529
538
}
530
539
531
540
return decode ;
@@ -613,14 +622,15 @@ function tokensToRegexp(
613
622
loose = DEFAULT_DELIMITER ,
614
623
} = options ;
615
624
const stringify = toStringify ( loose ) ;
625
+ const keyToRegexp = toKeyRegexp ( stringify , data . delimiter ) ;
616
626
let pattern = start ? "^" : "" ;
617
627
618
628
for ( const token of data . tokens ) {
619
629
if ( typeof token === "string" ) {
620
630
pattern += stringify ( token ) ;
621
631
} else {
622
632
if ( token . name ) keys . push ( token ) ;
623
- pattern += keyToRegexp ( token , stringify ) ;
633
+ pattern += keyToRegexp ( token ) ;
624
634
}
625
635
}
626
636
@@ -636,21 +646,26 @@ function tokensToRegexp(
636
646
/**
637
647
* Convert a token into a regexp string (re-used for path validation).
638
648
*/
639
- function keyToRegexp ( key : Key , stringify : Encode ) : string {
640
- const prefix = stringify ( key . prefix ) ;
641
- const suffix = stringify ( key . suffix ) ;
642
-
643
- if ( key . name ) {
644
- if ( key . separator ) {
645
- const mod = key . modifier === "*" ? "?" : "" ;
646
- const split = stringify ( key . separator ) ;
647
- return `(?:${ prefix } ((?:${ key . pattern } )(?:${ split } (?:${ key . pattern } ))*)${ suffix } )${ mod } ` ;
648
- } else {
649
- return `(?:${ prefix } (${ key . pattern } )${ suffix } )${ key . modifier } ` ;
649
+ function toKeyRegexp ( stringify : Encode , delimiter : string ) {
650
+ const segmentPattern = `[^${ escape ( delimiter ) } ]+?` ;
651
+
652
+ return ( key : Key ) => {
653
+ const prefix = stringify ( key . prefix ) ;
654
+ const suffix = stringify ( key . suffix ) ;
655
+
656
+ if ( key . name ) {
657
+ const pattern = key . pattern || segmentPattern ;
658
+ if ( key . separator ) {
659
+ const mod = key . modifier === "*" ? "?" : "" ;
660
+ const split = stringify ( key . separator ) ;
661
+ return `(?:${ prefix } ((?:${ pattern } )(?:${ split } (?:${ pattern } ))*)${ suffix } )${ mod } ` ;
662
+ } else {
663
+ return `(?:${ prefix } (${ pattern } )${ suffix } )${ key . modifier } ` ;
664
+ }
650
665
}
651
- } else {
666
+
652
667
return `(?:${ prefix } ${ suffix } )${ key . modifier } ` ;
653
- }
668
+ } ;
654
669
}
655
670
656
671
/**
0 commit comments