Skip to content

Commit eba8fb0

Browse files
fix: allow rest parameters to follow multiple optional - or not - parameters (#8761)
* fix: allow rest parameter to follow multiple optional - or not - parameters * refactor: made it even simpler and patched comments * refactor: consistent comments position * chore: correct changeset * minor stylistic tweak - avoid intermediate values unless needed for comprehension etc --------- Co-authored-by: Aurele Nitoref <aurele.nitoref@icloud.com> Co-authored-by: Rich Harris <hello@rich-harris.dev>
1 parent c14f3ae commit eba8fb0

File tree

3 files changed

+56
-39
lines changed

3 files changed

+56
-39
lines changed

.changeset/fast-jokes-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: allow rest parameters to follow multiple optional - or not - parameters

packages/kit/src/utils/routing.js

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -126,57 +126,44 @@ export function exec(match, params, matchers) {
126126

127127
const values = match.slice(1);
128128

129-
let buffered = '';
129+
let buffered = 0;
130130

131131
for (let i = 0; i < params.length; i += 1) {
132132
const param = params[i];
133-
let value = values[i];
133+
const value = values[i - buffered];
134134

135+
// in the `[[a=b]]/.../[...rest]` case, if one or more optional parameters
136+
// weren't matched, roll the skipped values into the rest
135137
if (param.chained && param.rest && buffered) {
136-
// in the `[[lang=lang]]/[...rest]` case, if `lang` didn't
137-
// match, we roll it over into the rest value
138-
value = value ? buffered + '/' + value : buffered;
139-
}
138+
result[param.name] = values
139+
.slice(i - buffered, i + 1)
140+
.filter((s) => s)
141+
.join('/');
140142

141-
buffered = '';
143+
buffered = 0;
144+
continue;
145+
}
142146

147+
// if `value` is undefined, it means this is an optional or rest parameter
143148
if (value === undefined) {
144-
// if `value` is undefined, it means this is
145-
// an optional or rest parameter
146149
if (param.rest) result[param.name] = '';
147-
} else {
148-
if (param.matcher && !matchers[param.matcher](value)) {
149-
// in the `/[[a=b]]/[[c=d]]` case, if the value didn't satisfy the `b` matcher,
150-
// try again with the next segment by shifting values rightwards
151-
if (param.optional && param.chained) {
152-
// @ts-expect-error TypeScript is... wrong
153-
let j = values.indexOf(undefined, i);
154-
155-
if (j === -1) {
156-
// we can't shift values any further, so hang on to this value
157-
// so it can be rolled into a subsequent `[...rest]` param
158-
const next = params[i + 1];
159-
if (next?.rest && next.chained) {
160-
buffered = value;
161-
} else {
162-
return;
163-
}
164-
}
165-
166-
while (j >= i) {
167-
values[j] = values[j - 1];
168-
j -= 1;
169-
}
170-
171-
continue;
172-
}
173-
174-
// otherwise, if the matcher returns `false`, the route did not match
175-
return;
176-
}
150+
continue;
151+
}
177152

153+
if (!param.matcher || matchers[param.matcher](value)) {
178154
result[param.name] = value;
155+
continue;
179156
}
157+
158+
// in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
159+
// keep track of the number of skipped optional parameters and continue
160+
if (param.optional && param.chained) {
161+
buffered++;
162+
continue;
163+
}
164+
165+
// otherwise, if the matcher returns `false`, the route did not match
166+
return;
180167
}
181168

182169
if (buffered) return;

packages/kit/src/utils/routing.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,31 @@ const exec_tests = [
153153
path: '/foo',
154154
expected: { rest: 'foo' }
155155
},
156+
{
157+
route: '/[[slug1=doesntmatch]]/[slug2]/[...rest]',
158+
path: '/foo/bar/baz',
159+
expected: { slug2: 'foo', rest: 'bar/baz' }
160+
},
161+
{
162+
route: '/[[slug1=doesntmatch]]/[slug2]/[...rest]/baz',
163+
path: '/foo/bar/baz',
164+
expected: { slug2: 'foo', rest: 'bar' }
165+
},
166+
{
167+
route: '/[[a=doesntmatch]]/[[b=doesntmatch]]/[[c=doesntmatch]]/[...d]',
168+
path: '/a/b/c/d',
169+
expected: { d: 'a/b/c/d' }
170+
},
171+
{
172+
route: '/[[a=doesntmatch]]/[b]/[...c]/[d]/e',
173+
path: '/foo/bar/baz/qux/e',
174+
expected: { b: 'foo', c: 'bar/baz', d: 'qux' }
175+
},
176+
{
177+
route: '/[[slug1=doesntmatch]]/[[slug2=doesntmatch]]/[...rest]',
178+
path: '/foo/bar/baz',
179+
expected: { rest: 'foo/bar/baz' }
180+
},
156181
{
157182
route: '/[[slug1=doesntmatch]]/[[slug2=matches]]/[[slug3=doesntmatch]]/[...rest].json',
158183
path: '/foo/bar/baz.json',

0 commit comments

Comments
 (0)