diff --git a/.changeset/sweet-melons-itch.md b/.changeset/sweet-melons-itch.md
new file mode 100644
index 0000000000..6d2f9d3e63
--- /dev/null
+++ b/.changeset/sweet-melons-itch.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: use typedef for JSDoc props and maintain comments
diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js
index 43d4720a75..b0867766e3 100644
--- a/packages/svelte/src/compiler/migrate/index.js
+++ b/packages/svelte/src/compiler/migrate/index.js
@@ -213,23 +213,18 @@ export function migrate(source, { filename } = {}) {
}
type += `\n${indent}}`;
} else {
- type = `{${state.props
+ type = `/**\n${indent} * @typedef {Object} ${type_name}${state.props
.map((prop) => {
- return `${prop.exported}${prop.optional ? '?' : ''}: ${prop.type}`;
+ return `\n${indent} * @property {${prop.type}} ${prop.optional ? `[${prop.exported}]` : prop.exported}${prop.comment ? ` - ${prop.comment}` : ''}`;
})
- .join(`, `)}`;
- if (analysis.uses_props || analysis.uses_rest_props) {
- type += `${state.props.length > 0 ? ', ' : ''}[key: string]: any`;
- }
- type += '}';
+ .join(``)}\n${indent} */`;
}
-
let props_declaration = `let {${props_separator}${props}${has_many_props ? `\n${indent}` : ' '}}`;
if (uses_ts) {
props_declaration = `${type}\n\n${indent}${props_declaration}`;
props_declaration = `${props_declaration}${type ? `: ${type_name}` : ''} = $props();`;
} else {
- props_declaration = `/** @type {${type}} */\n${indent}${props_declaration}`;
+ props_declaration = `${type && state.props.length > 0 ? `${type}\n\n${indent}` : ''}/** @type {${state.props.length > 0 ? type_name : ''}${analysis.uses_props || analysis.uses_rest_props ? `${state.props.length > 0 ? ' & ' : ''}{ [key: string]: any }` : ''}} */\n${indent}${props_declaration}`;
props_declaration = `${props_declaration} = $props();`;
}
@@ -1265,11 +1260,10 @@ function extract_type_and_comment(declarator, str, path) {
// Try to find jsdoc above the declaration
let comment_node = /** @type {Node} */ (parent)?.leadingComments?.at(-1);
- if (comment_node?.type !== 'Block') comment_node = undefined;
const comment_start = /** @type {any} */ (comment_node)?.start;
const comment_end = /** @type {any} */ (comment_node)?.end;
- const comment = comment_node && str.original.substring(comment_start, comment_end);
+ let comment = comment_node && str.original.substring(comment_start, comment_end);
if (comment_node) {
str.update(comment_start, comment_end, '');
@@ -1283,11 +1277,31 @@ function extract_type_and_comment(declarator, str, path) {
return { type: str.original.substring(start, declarator.id.typeAnnotation.end), comment };
}
+ let cleaned_comment = comment
+ ?.split('\n')
+ .map((line) =>
+ line
+ .trim()
+ // replace `// ` for one liners
+ .replace(/^\/\/\s*/g, '')
+ // replace `\**` for the initial JSDoc
+ .replace(/^\/\*\*?\s*/g, '')
+ // migrate `*/` for the end of JSDoc
+ .replace(/\s*\*\/$/g, '')
+ // remove any initial `* ` to clean the comment
+ .replace(/^\*\s*/g, '')
+ )
+ .filter(Boolean);
+ const first_at_comment = cleaned_comment?.findIndex((line) => line.startsWith('@'));
+ comment = cleaned_comment
+ ?.slice(0, first_at_comment !== -1 ? first_at_comment : cleaned_comment.length)
+ .join('\n');
+
// try to find a comment with a type annotation, hinting at jsdoc
if (parent?.type === 'ExportNamedDeclaration' && comment_node) {
const match = /@type {(.+)}/.exec(comment_node.value);
if (match) {
- return { type: match[1] };
+ return { type: match[1], comment };
}
}
diff --git a/packages/svelte/tests/migrate/samples/$$slots-used-as-variable-$$props/output.svelte b/packages/svelte/tests/migrate/samples/$$slots-used-as-variable-$$props/output.svelte
index 72960b7e34..c1a3e8d636 100644
--- a/packages/svelte/tests/migrate/samples/$$slots-used-as-variable-$$props/output.svelte
+++ b/packages/svelte/tests/migrate/samples/$$slots-used-as-variable-$$props/output.svelte
@@ -1,5 +1,11 @@
diff --git a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte
new file mode 100644
index 0000000000..333980513a
--- /dev/null
+++ b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/input.svelte
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte
new file mode 100644
index 0000000000..271dd23ffb
--- /dev/null
+++ b/packages/svelte/tests/migrate/samples/jsdoc-with-comments/output.svelte
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/migrate/samples/props-and-labeled/output.svelte b/packages/svelte/tests/migrate/samples/props-and-labeled/output.svelte
index 57893d11ac..8b0d6ea35b 100644
--- a/packages/svelte/tests/migrate/samples/props-and-labeled/output.svelte
+++ b/packages/svelte/tests/migrate/samples/props-and-labeled/output.svelte
@@ -1,5 +1,11 @@
diff --git a/packages/svelte/tests/migrate/samples/props-rest-props/output.svelte b/packages/svelte/tests/migrate/samples/props-rest-props/output.svelte
index 7afdc7aac7..d2e4f81619 100644
--- a/packages/svelte/tests/migrate/samples/props-rest-props/output.svelte
+++ b/packages/svelte/tests/migrate/samples/props-rest-props/output.svelte
@@ -1,5 +1,10 @@
diff --git a/packages/svelte/tests/migrate/samples/props/output.svelte b/packages/svelte/tests/migrate/samples/props/output.svelte
index 346489e456..80b63c008e 100644
--- a/packages/svelte/tests/migrate/samples/props/output.svelte
+++ b/packages/svelte/tests/migrate/samples/props/output.svelte
@@ -1,6 +1,14 @@
diff --git a/packages/svelte/tests/migrate/samples/slots-multiple/output.svelte b/packages/svelte/tests/migrate/samples/slots-multiple/output.svelte
index c663f2bcdd..521a4b47d8 100644
--- a/packages/svelte/tests/migrate/samples/slots-multiple/output.svelte
+++ b/packages/svelte/tests/migrate/samples/slots-multiple/output.svelte
@@ -1,5 +1,10 @@
diff --git a/packages/svelte/tests/migrate/samples/slots-with-$$props/output.svelte b/packages/svelte/tests/migrate/samples/slots-with-$$props/output.svelte
index c76c7d8283..2d6d5b8a36 100644
--- a/packages/svelte/tests/migrate/samples/slots-with-$$props/output.svelte
+++ b/packages/svelte/tests/migrate/samples/slots-with-$$props/output.svelte
@@ -1,5 +1,12 @@
diff --git a/packages/svelte/tests/migrate/samples/slots/output.svelte b/packages/svelte/tests/migrate/samples/slots/output.svelte
index 30fdf8dfbe..550661e31e 100644
--- a/packages/svelte/tests/migrate/samples/slots/output.svelte
+++ b/packages/svelte/tests/migrate/samples/slots/output.svelte
@@ -1,5 +1,12 @@
diff --git a/packages/svelte/tests/migrate/samples/svelte-component/output.svelte b/packages/svelte/tests/migrate/samples/svelte-component/output.svelte
index 1e61d23ba5..e8e29edd19 100644
--- a/packages/svelte/tests/migrate/samples/svelte-component/output.svelte
+++ b/packages/svelte/tests/migrate/samples/svelte-component/output.svelte
@@ -1,5 +1,5 @@
diff --git a/packages/svelte/tests/migrate/samples/svelte-self/output.svelte b/packages/svelte/tests/migrate/samples/svelte-self/output.svelte
index 23b9af5c00..e23b185b90 100644
--- a/packages/svelte/tests/migrate/samples/svelte-self/output.svelte
+++ b/packages/svelte/tests/migrate/samples/svelte-self/output.svelte
@@ -1,6 +1,6 @@