Skip to content

Commit

Permalink
fix: allow rest parameter to follow multiple optional - or not - para…
Browse files Browse the repository at this point in the history
…meters
  • Loading branch information
Oreilles committed Jan 28, 2023
1 parent 668d2f3 commit aec8489
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-forks-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

Allows rest parameters to follow multiple optional - or not - parameters
58 changes: 22 additions & 36 deletions packages/kit/src/utils/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ export function exec(match, params, matchers) {
/** @type {Record<string, string>} */
const result = {};

/** @type {Array<string | undefined>} cause it is */
const values = match.slice(1);

let buffered = '';
let buffered = 0;

for (let i = 0; i < params.length; i += 1) {
const param = params[i];
Expand All @@ -135,48 +136,33 @@ export function exec(match, params, matchers) {
if (param.chained && param.rest && buffered) {
// in the `[[lang=lang]]/[...rest]` case, if `lang` didn't
// match, we roll it over into the rest value
value = value ? buffered + '/' + value : buffered;
const bufferedValues = values.slice(i, i + buffered + 1);
value = bufferedValues.filter((s) => s).join('/');
values.splice(i, buffered);
buffered = 0;
}

buffered = '';

if (value === undefined) {
// if `value` is undefined, it means this is
// an optional or rest parameter
// if `value` is undefined, it means this is an optional or rest parameter
if (param.rest) result[param.name] = '';
} else {
if (param.matcher && !matchers[param.matcher](value)) {
// in the `/[[a=b]]/[[c=d]]` case, if the value didn't satisfy the `b` matcher,
// try again with the next segment by shifting values rightwards
if (param.optional && param.chained) {
// @ts-expect-error TypeScript is... wrong
let j = values.indexOf(undefined, i);

if (j === -1) {
// we can't shift values any further, so hang on to this value
// so it can be rolled into a subsequent `[...rest]` param
const next = params[i + 1];
if (next?.rest && next.chained) {
buffered = value;
} else {
return;
}
}

while (j >= i) {
values[j] = values[j - 1];
j -= 1;
}

continue;
}

// otherwise, if the matcher returns `false`, the route did not match
return;
}
continue;
}

if (!param.matcher || matchers[param.matcher](value)) {
result[param.name] = value;
continue;
}

// in the `/[[a=b]]/[[c=d]]` case, if the value didn't satisfy the `b` matcher,
// try again with the next segment by shifting values rightwards
if (param.optional && param.chained) {
values.splice(i, 0, undefined);
buffered++;
continue;
}

// otherwise, if the matcher returns `false`, the route did not match
return;
}

if (buffered) return;
Expand Down
25 changes: 25 additions & 0 deletions packages/kit/src/utils/routing.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,31 @@ const exec_tests = [
path: '/foo',
expected: { rest: 'foo' }
},
{
route: '/[[slug1=doesntmatch]]/[slug2]/[...rest]',
path: '/foo/bar/baz',
expected: { slug2: 'foo', rest: 'bar/baz' }
},
{
route: '/[[slug1=doesntmatch]]/[slug2]/[...rest]/baz',
path: '/foo/bar/baz',
expected: { slug2: 'foo', rest: 'bar' }
},
{
route: '/[[a=doesntmatch]]/[[b=doesntmatch]]/[[c=doesntmatch]]/[...d]',
path: '/a/b/c/d',
expected: { d: 'a/b/c/d' }
},
{
route: '/[[a=doesntmatch]]/[b]/[...c]/[d]/e',
path: '/foo/bar/baz/qux/e',
expected: { b: 'foo', c: 'bar/baz', d: 'qux' }
},
{
route: '/[[slug1=doesntmatch]]/[[slug2=doesntmatch]]/[...rest]',
path: '/foo/bar/baz',
expected: { rest: 'foo/bar/baz' }
},
{
route: '/[[slug1=doesntmatch]]/[[slug2=matches]]/[[slug3=doesntmatch]]/[...rest].json',
path: '/foo/bar/baz.json',
Expand Down

0 comments on commit aec8489

Please sign in to comment.