Skip to content

Commit b3e8d70

Browse files
committed
feat: migrate Component to ReturnType<typeof Component> in TS
1 parent b352f08 commit b3e8d70

File tree

4 files changed

+93
-8
lines changed

4 files changed

+93
-8
lines changed

.changeset/new-parrots-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: migrate `Component` to `ReturnType<typeof Component>` in TS

packages/svelte/src/compiler/migrate/index.js

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @import { VariableDeclarator, Node, Identifier, AssignmentExpression, LabeledStatement, ExpressionStatement } from 'estree' */
2-
/** @import { Visitors } from 'zimmerframe' */
2+
/** @import { Visitors, Context } from 'zimmerframe' */
33
/** @import { ComponentAnalysis } from '../phases/types.js' */
44
/** @import { Scope, ScopeRoot } from '../phases/scope.js' */
55
/** @import { AST, Binding, SvelteNode, ValidatedCompileOptions } from '#compiler' */
@@ -323,10 +323,11 @@ export function migrate(source, { filename } = {}) {
323323
* derived_components: Map<string, string>;
324324
* derived_labeled_statements: Set<LabeledStatement>;
325325
* has_svelte_self: boolean;
326+
* migrate_prop_component_type?: boolean;
326327
* }} State
327328
*/
328329

329-
/** @type {Visitors<SvelteNode, State>} */
330+
/** @type {Visitors<SvelteNode | { type: "TSTypeReference", typeName: Identifier, start: number, end: number }, State>} */
330331
const instance_script = {
331332
_(node, { state, next }) {
332333
// @ts-expect-error
@@ -343,8 +344,37 @@ const instance_script = {
343344
}
344345
next();
345346
},
346-
Identifier(node, { state, path }) {
347+
TSTypeReference(node, { state, path }) {
348+
// we don't want to overwrite in case it's an exported prop because
349+
// we already take care of that in extract_type_and_comment
350+
if (
351+
path[1] &&
352+
path[1].type === 'ExportNamedDeclaration' &&
353+
path[1].declaration?.type === 'VariableDeclaration' &&
354+
path[1].declaration?.kind === 'let' &&
355+
!state.migrate_prop_component_type
356+
)
357+
return;
358+
if (state.analysis.runes) return;
359+
if (node.typeName.type === 'Identifier') {
360+
const binding = state.scope.get(node.typeName.name);
361+
if (
362+
binding &&
363+
binding.declaration_kind === 'import' &&
364+
binding.initial?.type === 'ImportDeclaration' &&
365+
binding.initial.source.value?.toString().endsWith('.svelte')
366+
) {
367+
state.str.overwrite(
368+
node.start,
369+
node.end,
370+
`ReturnType<typeof ${state.str.original.substring(node.start, node.end)}>`
371+
);
372+
}
373+
}
374+
},
375+
Identifier(node, { state, path, next }) {
347376
handle_identifier(node, state, path);
377+
next();
348378
},
349379
ImportDeclaration(node, { state }) {
350380
state.props_insertion_point = node.end ?? state.props_insertion_point;
@@ -370,7 +400,8 @@ const instance_script = {
370400
state.str.remove(/** @type {number} */ (node.start), /** @type {number} */ (node.end));
371401
}
372402
},
373-
VariableDeclaration(node, { state, path, visit }) {
403+
VariableDeclaration(node, context) {
404+
const { state, path, visit, next } = context;
374405
if (state.scope !== state.analysis.instance.scope) {
375406
return;
376407
}
@@ -465,7 +496,7 @@ const instance_script = {
465496
: '',
466497
optional: !!declarator.init,
467498
bindable: binding.updated,
468-
...extract_type_and_comment(declarator, state.str, path)
499+
...extract_type_and_comment(declarator, state.str, context)
469500
});
470501
}
471502

@@ -653,6 +684,7 @@ const instance_script = {
653684
while (state.str.original[end] !== '\n') end++;
654685
state.str.update(start, end, '');
655686
}
687+
next();
656688
},
657689
BreakStatement(node, { state, path }) {
658690
if (path[1].type !== 'LabeledStatement') return;
@@ -1144,9 +1176,10 @@ function migrate_slot_usage(node, path, state) {
11441176
/**
11451177
* @param {VariableDeclarator} declarator
11461178
* @param {MagicString} str
1147-
* @param {SvelteNode[]} path
1179+
* @param {Context<SvelteNode | { type: "TSTypeReference", typeName: Identifier, start: number, end: number }, State>} context
11481180
*/
1149-
function extract_type_and_comment(declarator, str, path) {
1181+
function extract_type_and_comment(declarator, str, context) {
1182+
const path = context.path;
11501183
const parent = path.at(-1);
11511184

11521185
// Try to find jsdoc above the declaration
@@ -1166,7 +1199,14 @@ function extract_type_and_comment(declarator, str, path) {
11661199
while (str.original[start] === ' ') {
11671200
start++;
11681201
}
1169-
return { type: str.original.substring(start, declarator.id.typeAnnotation.end), comment };
1202+
context.visit(declarator.id.typeAnnotation, {
1203+
...context.state,
1204+
migrate_prop_component_type: true
1205+
});
1206+
return {
1207+
type: str.snip(start, declarator.id.typeAnnotation.end).toString(),
1208+
comment
1209+
};
11701210
}
11711211

11721212
// try to find a comment with a type annotation, hinting at jsdoc
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import Component from "./Component.svelte";
3+
import type ComponentType from "./Component.svelte";
4+
5+
export let my_comp: Component;
6+
7+
export let my_component_type: ComponentType;
8+
9+
export function enhance(comp: Component, comp_type: ComponentType){
10+
11+
}
12+
13+
let comp: Component | ComponentType | undefined = undefined;
14+
15+
export const the_comp: Component | ComponentType = comp;
16+
</script>
17+
18+
<Component bind:this={comp} />
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import Component from "./Component.svelte";
3+
import type ComponentType from "./Component.svelte";
4+
5+
6+
interface Props {
7+
my_comp: ReturnType<typeof Component>;
8+
my_component_type: ReturnType<typeof ComponentType>;
9+
}
10+
11+
let { my_comp, my_component_type }: Props = $props();
12+
13+
export function enhance(comp: ReturnType<typeof Component>, comp_type: ReturnType<typeof ComponentType>){
14+
15+
}
16+
17+
let comp: ReturnType<typeof Component> | ReturnType<typeof ComponentType> | undefined = $state(undefined);
18+
19+
export const the_comp: ReturnType<typeof Component> | ReturnType<typeof ComponentType> = comp;
20+
</script>
21+
22+
<Component bind:this={comp} />

0 commit comments

Comments
 (0)