@@ -619,40 +619,65 @@ function negate(delimiter: string, backtrack: string) {
619619}
620620
621621/**
622- * Stringify token data into a path string.
622+ * Stringify an array of tokens into a path string.
623623 */
624- export function stringify ( data : TokenData ) {
625- return data . tokens
626- . map ( function stringifyToken ( token , index , tokens ) : string {
627- if ( token . type === "text" ) return escapeText ( token . value ) ;
628- if ( token . type === "group" ) {
629- return `{${ token . tokens . map ( stringifyToken ) . join ( "" ) } }` ;
630- }
624+ function stringifyTokens ( tokens : Token [ ] ) : string {
625+ let value = "" ;
626+ let i = 0 ;
627+
628+ function name ( value : string ) {
629+ const isSafe = isNameSafe ( value ) && isNextNameSafe ( tokens [ i ] ) ;
630+ return isSafe ? value : JSON . stringify ( value ) ;
631+ }
632+
633+ while ( i < tokens . length ) {
634+ const token = tokens [ i ++ ] ;
631635
632- const isSafe =
633- isNameSafe ( token . name ) && isNextNameSafe ( tokens [ index + 1 ] ) ;
634- const key = isSafe ? token . name : JSON . stringify ( token . name ) ;
636+ if ( token . type === "text" ) {
637+ value += escapeText ( token . value ) ;
638+ continue ;
639+ }
635640
636- if ( token . type === "param" ) return `:${ key } ` ;
637- if ( token . type === "wildcard" ) return `*${ key } ` ;
638- throw new TypeError ( `Unexpected token: ${ token } ` ) ;
639- } )
640- . join ( "" ) ;
641+ if ( token . type === "group" ) {
642+ value += `{${ stringifyTokens ( token . tokens ) } }` ;
643+ continue ;
644+ }
645+
646+ if ( token . type === "param" ) {
647+ value += `:${ name ( token . name ) } ` ;
648+ continue ;
649+ }
650+
651+ if ( token . type === "wildcard" ) {
652+ value += `*${ name ( token . name ) } ` ;
653+ continue ;
654+ }
655+
656+ throw new TypeError ( `Unknown token type: ${ ( token as any ) . type } ` ) ;
657+ }
658+
659+ return value ;
660+ }
661+
662+ /**
663+ * Stringify token data into a path string.
664+ */
665+ export function stringify ( data : TokenData ) {
666+ return stringifyTokens ( data . tokens ) ;
641667}
642668
643669/**
644670 * Validate the parameter name contains valid ID characters.
645671 */
646672function isNameSafe ( name : string ) {
647673 const [ first , ...rest ] = name ;
648- if ( ! ID_START . test ( first ) ) return false ;
649- return rest . every ( ( char ) => ID_CONTINUE . test ( char ) ) ;
674+ return ID_START . test ( first ) && rest . every ( ( char ) => ID_CONTINUE . test ( char ) ) ;
650675}
651676
652677/**
653678 * Validate the next token does not interfere with the current param name.
654679 */
655680function isNextNameSafe ( token : Token | undefined ) {
656- if ( ! token || token . type !== "text" ) return true ;
657- return ! ID_CONTINUE . test ( token . value [ 0 ] ) ;
681+ if ( token && token . type === "text" ) return ! ID_CONTINUE . test ( token . value [ 0 ] ) ;
682+ return true ;
658683}
0 commit comments