Skip to content

Commit c694ec4

Browse files
committed
fix(unplugin): skip unnamed params in generated route types
Segments like `[[]]+` produce a param without a name. The param parsers version of the type generation kept it, generating invalid types such as `{ : string[] }` without reporting anything. Move the filtering (and the VUE_ROUTER_B0017 report) into `normalizeParamsForTypes()` so both versions drop unnamed params.
1 parent a25fa87 commit c694ec4

3 files changed

Lines changed: 74 additions & 22 deletions

File tree

packages/router/src/unplugin/codegen/generateRouteMap.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
22
import { generateRouteNamedMap } from './generateRouteMap'
33
import { PrefixTree } from '../core/tree'
44
import { resolveOptions } from '../options'
5+
import { mockWarn } from '../../tests/vitest-mock-warn'
56

67
const DEFAULT_OPTIONS = resolveOptions({})
78

@@ -1135,6 +1136,56 @@ describe('generateRouteNamedMap', () => {
11351136
`)
11361137
})
11371138
})
1139+
1140+
describe('unnamed params', () => {
1141+
mockWarn()
1142+
const OPTIONS_WITH_PARSERS = resolveOptions({
1143+
experimental: { paramParsers: true },
1144+
})
1145+
1146+
it('skips unnamed params with param parsers enabled', () => {
1147+
const tree = new PrefixTree(OPTIONS_WITH_PARSERS)
1148+
// `[[]]+` produces a param without a name
1149+
tree.insert('[[]]+', '[[]]+.vue')
1150+
const routeMap = formatExports(
1151+
generateRouteNamedMap(tree, OPTIONS_WITH_PARSERS, new Map())
1152+
)
1153+
expect('VUE_ROUTER_B0017').toHaveBeenWarnedTimes(1)
1154+
1155+
expect(routeMap).toMatchInlineSnapshot(`
1156+
"export interface RouteNamedMap {
1157+
'/[[]]+': RouteRecordInfo<
1158+
'/[[]]+',
1159+
'/:*',
1160+
Record<never, never>,
1161+
Record<never, never>,
1162+
| never
1163+
>,
1164+
}"
1165+
`)
1166+
})
1167+
1168+
it('skips unnamed params without param parsers', () => {
1169+
const tree = new PrefixTree(DEFAULT_OPTIONS)
1170+
tree.insert('[[]]+', '[[]]+.vue')
1171+
const routeMap = formatExports(
1172+
generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map())
1173+
)
1174+
expect('VUE_ROUTER_B0017').toHaveBeenWarned()
1175+
1176+
expect(routeMap).toMatchInlineSnapshot(`
1177+
"export interface RouteNamedMap {
1178+
'/[[]]+': RouteRecordInfo<
1179+
'/[[]]+',
1180+
'/:*',
1181+
Record<never, never>,
1182+
Record<never, never>,
1183+
| never
1184+
>,
1185+
}"
1186+
`)
1187+
})
1188+
})
11381189
})
11391190

11401191
/**

packages/router/src/unplugin/codegen/generateRouteMap.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ export function generateRouteRecordInfo(
4545
options: ResolvedOptions,
4646
paramParsersMap: ParamParsersMap
4747
): string {
48-
let paramParsers: Array<string | null> = []
49-
const params = normalizeParamsForTypes(node.params)
50-
if (options.experimental.paramParsers) {
51-
paramParsers = generateParamsTypes(params, paramParsersMap)
52-
}
48+
// only the experimental version handles query params and param parsers, the
49+
// other one extracts the params from the node itself
50+
const params = options.experimental.paramParsers
51+
? normalizeParamsForTypes(node, node.params)
52+
: []
53+
const paramParsers: Array<string | null> = options.experimental.paramParsers
54+
? generateParamsTypes(params, paramParsersMap)
55+
: []
5356

5457
const typeParams = [
5558
toStringLiteral(node.name),

packages/router/src/unplugin/codegen/generateRouteParams.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,27 @@ import type { ParamParsersMap } from './generateParamParsers'
1010
import { diagnostics } from '../diagnostics'
1111

1212
/**
13-
* Prepares params to be rendered as a type: a param can be declared more than
14-
* once across the chain of records, and duplicated keys are invalid in a type
15-
* literal. The deepest declaration wins like at runtime, then params are sorted
16-
* by name to keep the generated types stable.
13+
* Prepares params to be rendered as a type: params without a name are dropped
14+
* and reported as they would generate invalid types. A param can also be
15+
* declared more than once across the chain of records, and duplicated keys are
16+
* invalid in a type literal, so the deepest declaration wins like at runtime.
17+
* Params are then sorted by name to keep the generated types stable.
1718
*
1819
* @internal
1920
*/
2021
export function normalizeParamsForTypes<
2122
T extends TreePathParam | TreeQueryParam,
22-
>(params: T[]): T[] {
23+
>(node: TreeNode, params: T[]): T[] {
2324
const byName = new Map<string, T>()
2425
for (const param of params) {
26+
// invalid segments like `[[]]+` produce params without a name
27+
if (!param.paramName) {
28+
diagnostics.VUE_ROUTER_B0017({
29+
fullPath: node.fullPath,
30+
path: node.path,
31+
})
32+
continue
33+
}
2534
byName.set(param.paramName, param)
2635
}
2736

@@ -34,18 +43,7 @@ export function normalizeParamsForTypes<
3443
export function generateRouteParams(node: TreeNode, isRaw: boolean): string {
3544
// node.pathParams is a getter so we compute it once
3645
// this version does not support query params
37-
const nodeParams = normalizeParamsForTypes(
38-
node.pathParams.filter(param => {
39-
if (!param.paramName) {
40-
diagnostics.VUE_ROUTER_B0017({
41-
fullPath: node.fullPath,
42-
path: node.path,
43-
})
44-
return false
45-
}
46-
return true
47-
})
48-
)
46+
const nodeParams = normalizeParamsForTypes(node, node.pathParams)
4947
return nodeParams.length > 0
5048
? `{ ${nodeParams
5149
.map(

0 commit comments

Comments
 (0)