From 91d26594818db611145f9e0c85c04db9d27fb39e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Wed, 18 Sep 2024 12:57:43 +0200 Subject: [PATCH] fix: handle `$$Props` interface during migration fixes #13178 --- .changeset/lucky-drinks-push.md | 5 ++ packages/svelte/src/compiler/migrate/index.js | 77 ++++++++++++++++--- packages/svelte/src/compiler/phases/scope.js | 9 ++- .../samples/props-interface/input.svelte | 12 +++ .../samples/props-interface/output.svelte | 13 ++++ 5 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 .changeset/lucky-drinks-push.md create mode 100644 packages/svelte/tests/migrate/samples/props-interface/input.svelte create mode 100644 packages/svelte/tests/migrate/samples/props-interface/output.svelte diff --git a/.changeset/lucky-drinks-push.md b/.changeset/lucky-drinks-push.md new file mode 100644 index 000000000000..f272bb0ac4c5 --- /dev/null +++ b/.changeset/lucky-drinks-push.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle `$$Props` interface during migration diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index e4c759083d3e..c60d2c87190d 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -335,7 +335,8 @@ const instance_script = { // } } - const binding = /** @type {Binding} */ (state.scope.get(declarator.id.name)); + const name = declarator.id.name; + const binding = /** @type {Binding} */ (state.scope.get(name)); if (state.analysis.uses_props && (declarator.init || binding.updated)) { throw new Error( @@ -343,19 +344,33 @@ const instance_script = { ); } - state.props.push({ - local: declarator.id.name, - exported: binding.prop_alias ? binding.prop_alias : declarator.id.name, - init: declarator.init + const prop = state.props.find((prop) => prop.exported === (binding.prop_alias || name)); + if (prop) { + // $$Props type was used + prop.init = declarator.init ? state.str.original.substring( /** @type {number} */ (declarator.init.start), /** @type {number} */ (declarator.init.end) ) - : '', - optional: !!declarator.init, - bindable: binding.updated, - ...extract_type_and_comment(declarator, state.str, path) - }); + : ''; + prop.bindable = binding.updated; + prop.exported = binding.prop_alias || name; + } else { + state.props.push({ + local: name, + exported: binding.prop_alias ? binding.prop_alias : name, + init: declarator.init + ? state.str.original.substring( + /** @type {number} */ (declarator.init.start), + /** @type {number} */ (declarator.init.end) + ) + : '', + optional: !!declarator.init, + bindable: binding.updated, + ...extract_type_and_comment(declarator, state.str, path) + }); + } + state.props_insertion_point = /** @type {number} */ (declarator.end); state.str.update( /** @type {number} */ (declarator.start), @@ -944,6 +959,48 @@ function handle_identifier(node, state, path) { } } // else passed as identifier, we don't know what to do here, so let it error + } else if ( + parent?.type === 'TSInterfaceDeclaration' || + parent?.type === 'TSTypeAliasDeclaration' + ) { + const members = + parent.type === 'TSInterfaceDeclaration' ? parent.body.body : parent.typeAnnotation?.members; + if (Array.isArray(members)) { + if (node.name === '$$Props') { + for (const member of members) { + const prop = state.props.find((prop) => prop.exported === member.key.name); + + const type = state.str.original.substring( + member.typeAnnotation.typeAnnotation.start, + member.typeAnnotation.typeAnnotation.end + ); + + let comment; + const comment_node = member.leadingComments?.at(-1); + if (comment_node?.type === 'Block') { + comment = state.str.original.substring(comment_node.start, comment_node.end); + } + + if (prop) { + prop.type = type; + prop.optional = member.optional; + prop.comment = comment ?? prop.comment; + } else { + state.props.push({ + local: member.key.name, + exported: member.key.name, + init: '', + bindable: false, + optional: member.optional, + type, + comment + }); + } + } + + state.str.remove(parent.start, parent.end); + } + } } } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index c1cf1e405566..95c8bd32e065 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -343,7 +343,14 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { // references Identifier(node, { path, state }) { const parent = path.at(-1); - if (parent && is_reference(node, /** @type {Node} */ (parent))) { + if ( + parent && + is_reference(node, /** @type {Node} */ (parent)) && + // TSTypeAnnotation, TSInterfaceDeclaration etc - these are normally already filtered out, + // but for the migration they aren't, so we need to filter them out here + // -> once migration script is gone we can remove this check + !parent.type.startsWith('TS') + ) { references.push([state.scope, { node, path: path.slice() }]); } }, diff --git a/packages/svelte/tests/migrate/samples/props-interface/input.svelte b/packages/svelte/tests/migrate/samples/props-interface/input.svelte new file mode 100644 index 000000000000..0f7ee86d9913 --- /dev/null +++ b/packages/svelte/tests/migrate/samples/props-interface/input.svelte @@ -0,0 +1,12 @@ + diff --git a/packages/svelte/tests/migrate/samples/props-interface/output.svelte b/packages/svelte/tests/migrate/samples/props-interface/output.svelte new file mode 100644 index 000000000000..e9d115cd5c96 --- /dev/null +++ b/packages/svelte/tests/migrate/samples/props-interface/output.svelte @@ -0,0 +1,13 @@ + \ No newline at end of file