Skip to content

Commit b235161

Browse files
authored
fix: handle $$Props interface during migration (#13305)
fixes #13178
1 parent d338e80 commit b235161

File tree

5 files changed

+105
-11
lines changed

5 files changed

+105
-11
lines changed

.changeset/lucky-drinks-push.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+
fix: handle `$$Props` interface during migration

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

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -335,27 +335,42 @@ const instance_script = {
335335
// }
336336
}
337337

338-
const binding = /** @type {Binding} */ (state.scope.get(declarator.id.name));
338+
const name = declarator.id.name;
339+
const binding = /** @type {Binding} */ (state.scope.get(name));
339340

340341
if (state.analysis.uses_props && (declarator.init || binding.updated)) {
341342
throw new Error(
342343
'$$props is used together with named props in a way that cannot be automatically migrated.'
343344
);
344345
}
345346

346-
state.props.push({
347-
local: declarator.id.name,
348-
exported: binding.prop_alias ? binding.prop_alias : declarator.id.name,
349-
init: declarator.init
347+
const prop = state.props.find((prop) => prop.exported === (binding.prop_alias || name));
348+
if (prop) {
349+
// $$Props type was used
350+
prop.init = declarator.init
350351
? state.str.original.substring(
351352
/** @type {number} */ (declarator.init.start),
352353
/** @type {number} */ (declarator.init.end)
353354
)
354-
: '',
355-
optional: !!declarator.init,
356-
bindable: binding.updated,
357-
...extract_type_and_comment(declarator, state.str, path)
358-
});
355+
: '';
356+
prop.bindable = binding.updated;
357+
prop.exported = binding.prop_alias || name;
358+
} else {
359+
state.props.push({
360+
local: name,
361+
exported: binding.prop_alias ? binding.prop_alias : name,
362+
init: declarator.init
363+
? state.str.original.substring(
364+
/** @type {number} */ (declarator.init.start),
365+
/** @type {number} */ (declarator.init.end)
366+
)
367+
: '',
368+
optional: !!declarator.init,
369+
bindable: binding.updated,
370+
...extract_type_and_comment(declarator, state.str, path)
371+
});
372+
}
373+
359374
state.props_insertion_point = /** @type {number} */ (declarator.end);
360375
state.str.update(
361376
/** @type {number} */ (declarator.start),
@@ -944,6 +959,48 @@ function handle_identifier(node, state, path) {
944959
}
945960
}
946961
// else passed as identifier, we don't know what to do here, so let it error
962+
} else if (
963+
parent?.type === 'TSInterfaceDeclaration' ||
964+
parent?.type === 'TSTypeAliasDeclaration'
965+
) {
966+
const members =
967+
parent.type === 'TSInterfaceDeclaration' ? parent.body.body : parent.typeAnnotation?.members;
968+
if (Array.isArray(members)) {
969+
if (node.name === '$$Props') {
970+
for (const member of members) {
971+
const prop = state.props.find((prop) => prop.exported === member.key.name);
972+
973+
const type = state.str.original.substring(
974+
member.typeAnnotation.typeAnnotation.start,
975+
member.typeAnnotation.typeAnnotation.end
976+
);
977+
978+
let comment;
979+
const comment_node = member.leadingComments?.at(-1);
980+
if (comment_node?.type === 'Block') {
981+
comment = state.str.original.substring(comment_node.start, comment_node.end);
982+
}
983+
984+
if (prop) {
985+
prop.type = type;
986+
prop.optional = member.optional;
987+
prop.comment = comment ?? prop.comment;
988+
} else {
989+
state.props.push({
990+
local: member.key.name,
991+
exported: member.key.name,
992+
init: '',
993+
bindable: false,
994+
optional: member.optional,
995+
type,
996+
comment
997+
});
998+
}
999+
}
1000+
1001+
state.str.remove(parent.start, parent.end);
1002+
}
1003+
}
9471004
}
9481005
}
9491006

packages/svelte/src/compiler/phases/scope.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
343343
// references
344344
Identifier(node, { path, state }) {
345345
const parent = path.at(-1);
346-
if (parent && is_reference(node, /** @type {Node} */ (parent))) {
346+
if (
347+
parent &&
348+
is_reference(node, /** @type {Node} */ (parent)) &&
349+
// TSTypeAnnotation, TSInterfaceDeclaration etc - these are normally already filtered out,
350+
// but for the migration they aren't, so we need to filter them out here
351+
// -> once migration script is gone we can remove this check
352+
!parent.type.startsWith('TS')
353+
) {
347354
references.push([state.scope, { node, path: path.slice() }]);
348355
}
349356
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script lang="ts">
2+
interface $$Props {
3+
/** foo */
4+
foo: string;
5+
bar: boolean;
6+
}
7+
8+
export let foo: $$Props['foo'];
9+
export let bar = true;
10+
11+
foo = '';
12+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script lang="ts">
2+
3+
4+
interface Props {
5+
/** foo */
6+
foo: string;
7+
bar: boolean;
8+
}
9+
10+
let { foo = $bindable(), bar = true }: Props = $props();
11+
12+
foo = '';
13+
</script>

0 commit comments

Comments
 (0)