Skip to content

Commit 41d61c7

Browse files
fix: properly migrate ts with inferred type comments (#13761)
Closes #13747
1 parent 9832c63 commit 41d61c7

File tree

4 files changed

+26
-14
lines changed

4 files changed

+26
-14
lines changed

.changeset/chatty-feet-unite.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: properly migrate ts with inferred type comments

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ export function migrate(source, { filename } = {}) {
114114
script_insertions: new Set(),
115115
derived_components: new Map(),
116116
derived_labeled_statements: new Set(),
117-
has_svelte_self: false
117+
has_svelte_self: false,
118+
uses_ts: !!parsed.instance?.attributes.some(
119+
(attr) => attr.name === 'lang' && /** @type {any} */ (attr).value[0].data === 'ts'
120+
)
118121
};
119122

120123
if (parsed.module) {
@@ -207,15 +210,12 @@ export function migrate(source, { filename } = {}) {
207210
// some render tags or forwarded event attributes to add
208211
str.appendRight(insertion_point, ` ${props},`);
209212
} else {
210-
const uses_ts = parsed.instance?.attributes.some(
211-
(attr) => attr.name === 'lang' && /** @type {any} */ (attr).value[0].data === 'ts'
212-
);
213213
const type_name = state.scope.root.unique('Props').name;
214214
let type = '';
215215

216216
// Try to infer when we don't want to add types (e.g. user doesn't use types, or this is a zero-types +page.svelte)
217217
if (state.has_type_or_fallback || state.props.every((prop) => prop.slot_name)) {
218-
if (uses_ts) {
218+
if (state.uses_ts) {
219219
type = `interface ${type_name} {${newline_separator}${state.props
220220
.map((prop) => {
221221
const comment = prop.comment ? `${prop.comment}${newline_separator}` : '';
@@ -236,7 +236,7 @@ export function migrate(source, { filename } = {}) {
236236
}
237237

238238
let props_declaration = `let {${props_separator}${props}${has_many_props ? `\n${indent}` : ' '}}`;
239-
if (uses_ts) {
239+
if (state.uses_ts) {
240240
if (type) {
241241
props_declaration = `${type}\n\n${indent}${props_declaration}`;
242242
}
@@ -355,6 +355,7 @@ export function migrate(source, { filename } = {}) {
355355
* derived_components: Map<string, string>;
356356
* derived_labeled_statements: Set<LabeledStatement>;
357357
* has_svelte_self: boolean;
358+
* uses_ts: boolean;
358359
* }} State
359360
*/
360361

@@ -1319,7 +1320,7 @@ function extract_type_and_comment(declarator, state, path) {
13191320
return { type: str.original.substring(start, declarator.id.typeAnnotation.end), comment };
13201321
}
13211322

1322-
let cleaned_comment = comment
1323+
let cleaned_comment_arr = comment
13231324
?.split('\n')
13241325
.map((line) =>
13251326
line
@@ -1334,17 +1335,17 @@ function extract_type_and_comment(declarator, state, path) {
13341335
.replace(/^\*\s*/g, '')
13351336
)
13361337
.filter(Boolean);
1337-
const first_at_comment = cleaned_comment?.findIndex((line) => line.startsWith('@'));
1338-
comment = cleaned_comment
1339-
?.slice(0, first_at_comment !== -1 ? first_at_comment : cleaned_comment.length)
1338+
const first_at_comment = cleaned_comment_arr?.findIndex((line) => line.startsWith('@'));
1339+
let cleaned_comment = cleaned_comment_arr
1340+
?.slice(0, first_at_comment !== -1 ? first_at_comment : cleaned_comment_arr.length)
13401341
.join('\n');
13411342

13421343
// try to find a comment with a type annotation, hinting at jsdoc
13431344
if (parent?.type === 'ExportNamedDeclaration' && comment_node) {
13441345
state.has_type_or_fallback = true;
13451346
const match = /@type {(.+)}/.exec(comment_node.value);
13461347
if (match) {
1347-
return { type: match[1], comment };
1348+
return { type: match[1], comment: cleaned_comment };
13481349
}
13491350
}
13501351

@@ -1353,11 +1354,11 @@ function extract_type_and_comment(declarator, state, path) {
13531354
state.has_type_or_fallback = true; // only assume type if it's trivial to infer - else someone would've added a type annotation
13541355
const type = typeof declarator.init.value;
13551356
if (type === 'string' || type === 'number' || type === 'boolean') {
1356-
return { type, comment };
1357+
return { type, comment: state.uses_ts ? comment : cleaned_comment };
13571358
}
13581359
}
13591360

1360-
return { type: 'any', comment };
1361+
return { type: 'any', comment: state.uses_ts ? comment : cleaned_comment };
13611362
}
13621363

13631364
// Ensure modifiers are applied in the same order as Svelte 4

packages/svelte/tests/migrate/samples/props-ts/input.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
export let optional = 'foo';
55
export let binding: string;
66
export let bindingOptional: string | undefined = 'bar';
7+
/** this should stay a comment */
8+
export let no_type_but_comment = 0;
79
</script>
810

911
{readonly}

packages/svelte/tests/migrate/samples/props-ts/output.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
<script lang="ts">
22
3+
34
interface Props {
45
/** some comment */
56
readonly: number;
67
optional?: string;
78
binding: string;
89
bindingOptional?: string | undefined;
10+
/** this should stay a comment */
11+
no_type_but_comment?: number;
912
}
1013
1114
let {
1215
readonly,
1316
optional = 'foo',
1417
binding = $bindable(),
15-
bindingOptional = $bindable('bar')
18+
bindingOptional = $bindable('bar'),
19+
no_type_but_comment = 0
1620
}: Props = $props();
1721
</script>
1822

0 commit comments

Comments
 (0)