Skip to content

Commit a5eb6c6

Browse files
committed
recursion preserves order
1 parent e5501fb commit a5eb6c6

File tree

1 file changed

+59
-65
lines changed

1 file changed

+59
-65
lines changed

packages/router-core/tests/built.test.ts

Lines changed: 59 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -161,57 +161,43 @@ describe('work in progress', () => {
161161
`)
162162
})
163163

164-
const parsedRoutes = result.flatRoutes.map((route) =>
165-
parsePathname(route.fullPath),
166-
)
167-
168-
const logParsed = (parsed: ReturnType<typeof parsePathname>) =>
169-
'/' +
170-
parsed
171-
.slice(1)
172-
.map((s) => s.value)
173-
.join('/')
174-
175-
const rebuildPath = (leaf: ReturnType<typeof parsePathname>) =>
176-
`/${leaf
177-
.slice(1)
178-
.map((s) =>
179-
s.value === '/'
180-
? ''
181-
: `${s.prefixSegment ?? ''}${s.prefixSegment || s.suffixSegment || s.type === SEGMENT_TYPE_OPTIONAL_PARAM ? '{' : ''}${s.type === SEGMENT_TYPE_OPTIONAL_PARAM ? '-' : ''}${s.value}${s.prefixSegment || s.suffixSegment || s.type === SEGMENT_TYPE_OPTIONAL_PARAM ? '}' : ''}${s.suffixSegment ?? ''}`,
182-
)
183-
.join('/')}`
164+
const parsedRoutes = result.flatRoutes.map((route) => ({
165+
path: route.fullPath,
166+
segments: parsePathname(route.fullPath),
167+
}))
184168

185169
const initialDepth = 0
186170
let fn = 'const baseSegments = parsePathname(from);'
187171
fn += '\nconst l = baseSegments.length;'
188172

173+
type ParsedRoute = { path: string, segments: ReturnType<typeof parsePathname> }
174+
189175
function recursiveStaticMatch(
190-
parsedRoutes: Array<ReturnType<typeof parsePathname>>,
176+
parsedRoutes: Array<ParsedRoute>,
191177
depth = initialDepth,
192178
indent = '',
193179
) {
194-
const resolved = new Set<ReturnType<typeof parsePathname>>()
195-
for (const routeSegments of parsedRoutes) {
196-
if (resolved.has(routeSegments)) continue // already resolved
180+
const resolved = new Set<ParsedRoute>()
181+
for (const route of parsedRoutes) {
182+
if (resolved.has(route)) continue // already resolved
197183
console.log('\n')
198184
console.log(
199185
'resolving: depth=',
200186
depth,
201187
'parsed=',
202-
logParsed(routeSegments),
188+
route.path,
203189
)
204190
console.log('\u001b[34m' + fn + '\u001b[0m')
205-
const currentSegment = routeSegments[depth]
191+
const currentSegment = route.segments[depth]
206192
if (!currentSegment) {
207193
throw new Error(
208194
'Implementation error: this should not happen, depth=' +
209195
depth +
210-
`, route=${rebuildPath(routeSegments)}`,
196+
`, route=${route.path}`,
211197
)
212198
}
213199
const candidates = parsedRoutes.filter((r) => {
214-
const rParsed = r[depth]
200+
const rParsed = r.segments[depth]
215201
if (!rParsed) return false
216202

217203
// For SEGMENT_TYPE_PARAM (type 1), match only on type and prefix/suffix constraints
@@ -241,14 +227,14 @@ describe('work in progress', () => {
241227
rParsed.suffixSegment === currentSegment.suffixSegment
242228
)
243229
})
244-
console.log('candidates:', candidates.map(logParsed))
230+
console.log('candidates:', candidates.map(r => r.path))
245231
if (candidates.length === 0) {
246232
throw new Error('Implementation error: this should not happen')
247233
}
248234
if (candidates.length > 1) {
249-
let skipDepth = routeSegments.slice(depth + 1).findIndex((s, i) =>
235+
let skipDepth = route.segments.slice(depth + 1).findIndex((s, i) =>
250236
candidates.some((c) => {
251-
const segment = c[depth + 1 + i]
237+
const segment = c.segments[depth + 1 + i]
252238
return (
253239
!segment ||
254240
segment.type !== s.type ||
@@ -259,12 +245,12 @@ describe('work in progress', () => {
259245
)
260246
}),
261247
)
262-
if (skipDepth === -1) skipDepth = routeSegments.length - depth - 1
248+
if (skipDepth === -1) skipDepth = route.segments.length - depth - 1
263249
const lCondition =
264250
skipDepth || depth > initialDepth ? `l > ${depth + skipDepth}` : ''
265251
const skipConditions =
266252
Array.from({ length: skipDepth + 1 }, (_, i) => {
267-
const segment = candidates[0]![depth + i]!
253+
const segment = candidates[0]!.segments[depth + i]!
268254
if (segment.type === SEGMENT_TYPE_PARAM) {
269255
const conditions = []
270256
if (segment.prefixSegment) {
@@ -291,48 +277,56 @@ describe('work in progress', () => {
291277
if (hasCondition) {
292278
fn += `\n${indent}if (${lCondition}${lCondition && skipConditions ? ' && ' : ''}${skipConditions}) {`
293279
}
294-
const deeper = candidates.filter(
295-
(c) => c.length > depth + 1 + skipDepth,
296-
)
297-
const leaves = candidates.filter(
298-
(c) => c.length <= depth + 1 + skipDepth,
299-
)
300-
if (deeper.length + leaves.length !== candidates.length) {
301-
throw new Error('Implementation error: this should not happen')
280+
const deeperBefore: Array<ParsedRoute> = []
281+
const deeperAfter: Array<ParsedRoute> = []
282+
let leaf: ParsedRoute | undefined
283+
for (const c of candidates) {
284+
const isLeaf = c.segments.length <= depth + 1 + skipDepth
285+
if (isLeaf && !leaf) {
286+
leaf = c
287+
continue
288+
}
289+
if (isLeaf) {
290+
continue // ignore subsequent leaves, they can never be matched
291+
}
292+
if (!leaf) {
293+
deeperBefore.push(c)
294+
} else {
295+
deeperAfter.push(c)
296+
}
302297
}
303-
if (deeper.length > 0) {
298+
if (deeperBefore.length > 0) {
304299
recursiveStaticMatch(
305-
deeper,
300+
deeperBefore,
306301
depth + 1 + skipDepth,
307302
hasCondition ? indent + ' ' : indent,
308303
)
309304
}
310-
if (leaves.length > 1) {
311-
// WARN: we should probably support "multiple leaves"
312-
// 1. user error: it's possible that a user created both `/a/$id` and `/a/$foo`, they'd be both matched, just use the 1st one
313-
// 2. wildcards: if a user created both `/a/$` and `/a/b`, we could have 2 leaves. the order in `leaves` will be `[/a/b, /a/$]` which is correct, try to match `/a/b` first, then `/a/$`
314-
throw new Error(
315-
`Multiple candidates found for depth ${depth} with type ${routeSegments[depth]!.type} and value ${routeSegments[depth]!.value}: ${leaves.map(logParsed).join(', ')}`,
316-
)
317-
} else if (leaves.length === 1) {
318-
// WARN: is it ok that the leaf is matched last?
319-
fn += `\n${indent} if (l === ${leaves[0]!.length}) {`
320-
fn += `\n${indent} return '${rebuildPath(leaves[0]!)}';`
305+
if (leaf) {
306+
fn += `\n${indent} if (l === ${leaf.segments.length}) {`
307+
fn += `\n${indent} return '${leaf.path}';`
321308
fn += `\n${indent} }`
322309
}
310+
if (deeperAfter.length > 0) {
311+
recursiveStaticMatch(
312+
deeperAfter,
313+
depth + 1 + skipDepth,
314+
hasCondition ? indent + ' ' : indent,
315+
)
316+
}
323317
if (hasCondition) {
324318
fn += `\n${indent}}`
325319
}
326320
} else {
327321
const leaf = candidates[0]!
328322

329323
// Check if this route contains a wildcard segment
330-
const wildcardIndex = leaf.findIndex((s) => s && s.type === SEGMENT_TYPE_WILDCARD)
324+
const wildcardIndex = leaf.segments.findIndex((s) => s && s.type === SEGMENT_TYPE_WILDCARD)
331325

332326
if (wildcardIndex !== -1 && wildcardIndex >= depth) {
333327
// This route has a wildcard at or after the current depth
334-
const wildcardSegment = leaf[wildcardIndex]!
335-
const done = `return '${rebuildPath(leaf)}';`
328+
const wildcardSegment = leaf.segments[wildcardIndex]!
329+
const done = `return '${leaf.path}';`
336330

337331
// For wildcards, we need to check:
338332
// 1. All static/param segments before the wildcard match
@@ -343,7 +337,7 @@ describe('work in progress', () => {
343337

344338
// Add conditions for all segments before the wildcard
345339
for (let i = depth; i < wildcardIndex; i++) {
346-
const segment = leaf[i]!
340+
const segment = leaf.segments[i]!
347341
const value = `baseSegments[${i}].value`
348342

349343
if (segment.type === SEGMENT_TYPE_PARAM) {
@@ -383,10 +377,10 @@ describe('work in progress', () => {
383377
fn += `\n${indent}}`
384378
} else {
385379
// No wildcard in this route, use the original logic
386-
const done = `return '${rebuildPath(leaf)}';`
387-
fn += `\n${indent}if (l === ${leaf.length}`
388-
for (let i = depth; i < leaf.length; i++) {
389-
const segment = leaf[i]!
380+
const done = `return '${leaf.path}';`
381+
fn += `\n${indent}if (l === ${leaf.segments.length}`
382+
for (let i = depth; i < leaf.segments.length; i++) {
383+
const segment = leaf.segments[i]!
390384
const value = `baseSegments[${i}].value`
391385

392386
// For SEGMENT_TYPE_PARAM (type 1), check if base has static segment (type 0) that satisfies constraints
@@ -602,6 +596,9 @@ describe('work in progress', () => {
602596
) {
603597
return '/about';
604598
}
599+
if (l === 1) {
600+
return '/';
601+
}
605602
if (l > 1) {
606603
if (l === 4
607604
&& baseSegments[2].value === 'bar'
@@ -616,9 +613,6 @@ describe('work in progress', () => {
616613
return '/$id/foo/bar';
617614
}
618615
}
619-
if (l === 1) {
620-
return '/';
621-
}
622616
}"
623617
`)
624618
})

0 commit comments

Comments
 (0)