11import { identityFn } from '../../../utils'
2- import { decode , encodeParam } from '../../../encoding'
2+ import { decode , encodeParam , encodePath } from '../../../encoding'
33import { warn } from '../../../warning'
44import { miss } from './errors'
55import { ParamParser } from './param-parsers/types'
@@ -92,7 +92,11 @@ export interface MatcherPatternPathDynamic_ParamOptions<
9292 TIn extends string | string [ ] | null = string | string [ ] | null ,
9393 TOut = string | string [ ] | null ,
9494> extends ParamParser < TOut , TIn > {
95+ /**
96+ * Is tha param a repeatable param and should be converted to an array
97+ */
9598 repeat ?: boolean
99+
96100 // NOTE: not needed because in the regexp, the value is undefined if
97101 // the group is optional and not given
98102 // optional?: boolean
@@ -133,9 +137,7 @@ export class MatcherPatternPathDynamic<
133137 // otherwise, we need to use a factory function: https://github.com/microsoft/TypeScript/issues/40451
134138 readonly params : TParamsOptions &
135139 Record < string , MatcherPatternPathDynamic_ParamOptions < any , any > > ,
136- // A better version could be using all the parts to join them
137- // .e.g ['users', 0, 'profile', 1] -> /users/123/profile/456
138- // numbers are indexes of the params in the params object keys
140+ // 0 means a regular param, 1 means a splat, the order comes from the keys in params
139141 readonly pathParts : Array < string | number | Array < string | number > >
140142 ) {
141143 this . paramsKeys = Object . keys ( this . params ) as Array < keyof TParamsOptions >
@@ -174,34 +176,40 @@ export class MatcherPatternPathDynamic<
174176
175177 build ( params : ExtractParamTypeFromOptions < TParamsOptions > ) : string {
176178 let paramIndex = 0
177- return (
179+ let paramName : keyof TParamsOptions
180+ let paramOptions : ( TParamsOptions &
181+ Record <
182+ string ,
183+ MatcherPatternPathDynamic_ParamOptions < any , any >
184+ > ) [ keyof TParamsOptions ]
185+ let lastParamPart : number | undefined
186+ let value : ReturnType < NonNullable < ParamParser [ 'set' ] > > | undefined
187+ const path =
178188 '/' +
179189 this . pathParts
180190 . map ( part => {
181191 if ( typeof part === 'string' ) {
182192 return part
183193 } else if ( typeof part === 'number' ) {
184- const paramName = this . paramsKeys [ paramIndex ++ ]
185- const paramOptions = this . params [ paramName ]
186- const value : ReturnType < NonNullable < ParamParser [ 'set' ] > > = (
187- paramOptions . set || identityFn
188- ) ( params [ paramName ] )
194+ paramName = this . paramsKeys [ paramIndex ++ ]
195+ paramOptions = this . params [ paramName ]
196+ lastParamPart = part
197+ value = ( paramOptions . set || identityFn ) ( params [ paramName ] )
189198
190199 return Array . isArray ( value )
191200 ? value . map ( encodeParam ) . join ( '/' )
192- : encodeParam ( value )
201+ : // part == 0 means a regular param, 1 means a splat
202+ ( part /* part !== 0 */ ? encodePath : encodeParam ) ( value )
193203 } else {
194204 return part
195205 . map ( subPart => {
196206 if ( typeof subPart === 'string' ) {
197207 return subPart
198208 }
199209
200- const paramName = this . paramsKeys [ paramIndex ++ ]
201- const paramOptions = this . params [ paramName ]
202- const value : ReturnType < NonNullable < ParamParser [ 'set' ] > > = (
203- paramOptions . set || identityFn
204- ) ( params [ paramName ] )
210+ paramName = this . paramsKeys [ paramIndex ++ ]
211+ paramOptions = this . params [ paramName ]
212+ value = ( paramOptions . set || identityFn ) ( params [ paramName ] )
205213
206214 return Array . isArray ( value )
207215 ? value . map ( encodeParam ) . join ( '/' )
@@ -212,7 +220,14 @@ export class MatcherPatternPathDynamic<
212220 } )
213221 . filter ( identityFn ) // filter out empty values
214222 . join ( '/' )
215- )
223+
224+ /**
225+ * If the last part of the path is a splat param and its value is empty, it gets
226+ * filteretd out, resulting in a path that doesn't end with a `/` and doesn't even match
227+ * with the original splat path: e.g. /teams/[...pathMatch] does not match /teams, so it makes
228+ * no sense to build a path it cannot match.
229+ */
230+ return lastParamPart && ! value ? path + '/' : path
216231 }
217232}
218233
0 commit comments