Skip to content

Commit

Permalink
Infer route parameter type from matcher's guard check if applicable (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
LorisSigrist authored Oct 12, 2023
1 parent 6dd025c commit 5a4dbe3
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-bags-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

feat: infer route parameter type from matcher's guard check if applicable
47 changes: 39 additions & 8 deletions packages/kit/src/core/sync/write_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,18 @@ function update_types(config, routes, route, to_delete = new Set()) {
// add 'Expand' helper
// Makes sure a type is "repackaged" and therefore more readable
declarations.push('type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;');

// returns the predicate of a matcher's type guard - or string if there is no type guard
declarations.push(
`type RouteParams = { ${route.params
.map((param) => `${param.name}${param.optional ? '?' : ''}: string`)
.join('; ')} }`
// TS complains on infer U, which seems weird, therefore ts-ignore it
[
'// @ts-ignore',
'type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;'
].join('\n')
);

declarations.push(
'type RouteParams = ' + generate_params_type(route.params, outdir, config) + ';'
);

if (route.params.length > 0) {
Expand Down Expand Up @@ -265,7 +273,8 @@ function update_types(config, routes, route, to_delete = new Set()) {

if (route.layout) {
let all_pages_have_load = true;
const layout_params = new Set();
/** @type {import('types').RouteParam[]} */
const layout_params = [];
const ids = ['RouteId'];

route.layout.child_pages?.forEach((page) => {
Expand All @@ -274,7 +283,9 @@ function update_types(config, routes, route, to_delete = new Set()) {
if (leaf.route.page) ids.push(`"${leaf.route.id}"`);

for (const param of leaf.route.params) {
layout_params.add(param.name);
// skip if already added
if (layout_params.some((p) => p.name === param.name)) continue;
layout_params.push({ ...param, optional: true });
}

ensureProxies(page, leaf.proxies);
Expand All @@ -301,9 +312,7 @@ function update_types(config, routes, route, to_delete = new Set()) {
declarations.push(`type LayoutRouteId = ${ids.join(' | ')}`);

declarations.push(
`type LayoutParams = RouteParams & { ${Array.from(layout_params).map(
(param) => `${param}?: string`
)} }`
'type LayoutParams = RouteParams & ' + generate_params_type(layout_params, outdir, config)
);

const {
Expand Down Expand Up @@ -567,6 +576,28 @@ function replace_ext_with_js(file_path) {
return file_path.slice(0, -ext.length) + '.js';
}

/**
* @param {import('types').RouteParam[]} params
* @param {string} outdir
* @param {import('types').ValidatedConfig} config
*/
function generate_params_type(params, outdir, config) {
/** @param {string} matcher */
const path_to_matcher = (matcher) =>
posixify(path.relative(outdir, path.join(config.kit.files.params, matcher)));

return `{ ${params
.map(
(param) =>
`${param.name}${param.optional ? '?' : ''}: ${
param.matcher
? `MatcherParam<typeof import('${path_to_matcher(param.matcher)}').match>`
: 'string'
}`
)
.join('; ')} }`;
}

/**
* @param {string} content
* @param {boolean} is_server
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/sync/write_types/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function run_test(dir) {
const initial = options({}, 'config');

initial.kit.files.assets = path.resolve(cwd, 'static');
initial.kit.files.params = path.resolve(cwd, 'params');
initial.kit.files.params = path.resolve(cwd, dir, 'params');
initial.kit.files.routes = path.resolve(cwd, dir);
initial.kit.outDir = path.resolve(cwd, path.join(dir, '.svelte-kit'));

Expand All @@ -40,6 +40,7 @@ test('Creates correct $types', async () => {
await run_test('layout-advanced');
await run_test('slugs');
await run_test('slugs-layout-not-all-pages-have-load');
await run_test('param-type-inference');
try {
execSync('pnpm testtypes', { cwd });
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */

/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/optional/[[optionalNarrowedParam=narrowed]]/$types').PageLoad} */
export function load({ params }) {
if (params.optionalNarrowedParam) {
/** @type {"a" | "b"} */
let a;
a = params.optionalNarrowedParam;
return { a };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @param {string} param
* @returns {param is "a" | "b"}
*/
export const match = (param) => ['a', 'b'].includes(param);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */

/**
* @param {string} param
* @returns {boolean}
*/
export const match = (param) => true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable */

/** @type {import('../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/$types').LayoutLoad} */
export function load({ params }) {
if (params.narrowedParam) {
/** @type {"a" | "b"} */
const a = params.narrowedParam;
}

if (params.regularParam) {
/** @type {"a" | "b"} */
let a;

//@ts-expect-error
a = params.regularParam;

/** @type {string} b*/
const b = params.regularParam;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable */

/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/[narrowedParam=narrowed]/$types').PageLoad} */
export function load({ params }) {
/** @type {"a" | "b"} */
let a;
a = params.narrowedParam;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-disable */

/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/required/[regularParam=not_narrowed]/$types').PageLoad} */
export function load({ params }) {
/** @type {string} a*/
const a = params.regularParam;

/** @type {"a" | "b"} b*/
let b;

//@ts-expect-error
b = params.regularParam;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable */

/** @type {import('../../.svelte-kit/types/src/core/sync/write_types/test/param-type-inference/spread/[...spread=narrowed]/$types').PageLoad} */
export function load({ params }) {
/** @type {"a" | "b"} */
let a;
a = params.spread;
}

0 comments on commit 5a4dbe3

Please sign in to comment.