Skip to content

Commit 309e69b

Browse files
Merge pull request #878 from pataar/fix/optional-params-array-types
Fix TypeError when optional params omitted from array (fixes #736)
2 parents 8a0b645 + ce74baa commit 309e69b

File tree

2 files changed

+27
-11
lines changed

2 files changed

+27
-11
lines changed

src/js/index.d.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,28 @@ type RouteParamsObject<N extends RouteName> = N extends KnownRouteName
114114
type GenericRouteParamsArray = unknown[];
115115
/**
116116
* An array of parameters for a specific named route.
117+
*
118+
* Required parameters come first as required tuple elements, then optional
119+
* parameters become optional tuple elements, followed by arbitrary extras.
117120
*/
118121
type KnownRouteParamsArray<I extends readonly ParameterInfo[]> = [
119-
...{ [K in keyof I]: Routable<I[K]> },
120-
...unknown[],
122+
...RequiredParamsTuple<I>,
123+
...OptionalParamsTuple<I>,
121124
];
122-
// Because `K in keyof I` for a `readonly` array is always a number, even though this
123-
// looks like `{ 0: T, 1: U, 2: V }` TypeScript generates `[T, U, V]`. The nested
124-
// array destructing lets us type the first n items in the array, which are known
125-
// route parameters, and then allow arbitrary additional items.
126-
// See https://github.com/tighten/ziggy/pull/664#discussion_r1330002370.
125+
126+
type RequiredParamsTuple<I extends readonly ParameterInfo[]> =
127+
I extends readonly [infer F extends ParameterInfo, ...infer R extends ParameterInfo[]]
128+
? F extends { required: true }
129+
? [Routable<F>, ...RequiredParamsTuple<R>]
130+
: []
131+
: [];
132+
133+
type OptionalParamsTuple<I extends readonly ParameterInfo[]> =
134+
I extends readonly [infer F extends ParameterInfo, ...infer R extends ParameterInfo[]]
135+
? F extends { required: false }
136+
? [Routable<F>?, ...OptionalParamsTuple<R>]
137+
: OptionalParamsTuple<R>
138+
: [...unknown[]];
127139

128140
// Uncomment to test:
129141
// type B = KnownRouteParamsArray<[{ name: 'post'; required: true; binding: 'uuid' }]>;

tests/js/route.test-d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ assertType(route('posts.comments.show', 'foo'));
6565
assertType(route('posts.comments.show'));
6666

6767
// Simple array examples
68-
// assertType(route('posts.comments.show', [2])); // TODO shouldn't error, only one required param
68+
assertType(route('posts.comments.show', [2]));
6969
assertType(route('posts.comments.show', [2, 3]));
70-
// assertType(route('posts.comments.show', ['foo'])); // TODO shouldn't error, only one required param
70+
assertType(route('posts.comments.show', ['foo']));
7171
assertType(route('posts.comments.show', ['foo', 'bar']));
7272
// Allows mix of plain values and parameter objects
73-
// assertType(route('posts.comments.show', [{ id: 2 }])); // TODO shouldn't error, only one required param
73+
assertType(route('posts.comments.show', [{ id: 2 }]));
7474
assertType(route('posts.comments.show', [{ id: 2 }, 3]));
7575
assertType(route('posts.comments.show', ['2', { uuid: 3 }]));
7676
assertType(route('posts.comments.show', [{ id: 2 }, { uuid: '3' }]));
@@ -105,9 +105,13 @@ assertType(route().current('missing', { foo: 1 }));
105105
assertType(route().current('posts.comments.show', { comment: 2 }));
106106
assertType(route().current('posts.comments.show', { post: 2 }));
107107
assertType(route().current('posts.comments.show', 2));
108-
// assertType(route().current('posts.comments.show', [2])); // TODO shouldn't error, only one required param
108+
assertType(route().current('posts.comments.show', [2]));
109109
assertType(route().current('posts.comments.show', 'foo'));
110110

111+
// All-optional route with array params
112+
assertType(route('optional', []));
113+
assertType(route('optional', ['foo']));
114+
111115
// Test route function return types
112116
assertType<string>(route('optional', { maybe: 'foo' }));
113117
assertType<string>(route('optional', 'foo'));

0 commit comments

Comments
 (0)