Skip to content

Commit 6ad017f

Browse files
fix: use typedef for JSDoc props and maintain comments (#13698)
* fix: use typedef for JSDoc props and maintain comments * chore: add comments * chore: add extra spaces and delete commented line
1 parent 663a3ca commit 6ad017f

File tree

18 files changed

+171
-26
lines changed

18 files changed

+171
-26
lines changed

.changeset/sweet-melons-itch.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: use typedef for JSDoc props and maintain comments

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

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,23 +213,18 @@ export function migrate(source, { filename } = {}) {
213213
}
214214
type += `\n${indent}}`;
215215
} else {
216-
type = `{${state.props
216+
type = `/**\n${indent} * @typedef {Object} ${type_name}${state.props
217217
.map((prop) => {
218-
return `${prop.exported}${prop.optional ? '?' : ''}: ${prop.type}`;
218+
return `\n${indent} * @property {${prop.type}} ${prop.optional ? `[${prop.exported}]` : prop.exported}${prop.comment ? ` - ${prop.comment}` : ''}`;
219219
})
220-
.join(`, `)}`;
221-
if (analysis.uses_props || analysis.uses_rest_props) {
222-
type += `${state.props.length > 0 ? ', ' : ''}[key: string]: any`;
223-
}
224-
type += '}';
220+
.join(``)}\n${indent} */`;
225221
}
226-
227222
let props_declaration = `let {${props_separator}${props}${has_many_props ? `\n${indent}` : ' '}}`;
228223
if (uses_ts) {
229224
props_declaration = `${type}\n\n${indent}${props_declaration}`;
230225
props_declaration = `${props_declaration}${type ? `: ${type_name}` : ''} = $props();`;
231226
} else {
232-
props_declaration = `/** @type {${type}} */\n${indent}${props_declaration}`;
227+
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}`;
233228
props_declaration = `${props_declaration} = $props();`;
234229
}
235230

@@ -1265,11 +1260,10 @@ function extract_type_and_comment(declarator, str, path) {
12651260

12661261
// Try to find jsdoc above the declaration
12671262
let comment_node = /** @type {Node} */ (parent)?.leadingComments?.at(-1);
1268-
if (comment_node?.type !== 'Block') comment_node = undefined;
12691263

12701264
const comment_start = /** @type {any} */ (comment_node)?.start;
12711265
const comment_end = /** @type {any} */ (comment_node)?.end;
1272-
const comment = comment_node && str.original.substring(comment_start, comment_end);
1266+
let comment = comment_node && str.original.substring(comment_start, comment_end);
12731267

12741268
if (comment_node) {
12751269
str.update(comment_start, comment_end, '');
@@ -1283,11 +1277,31 @@ function extract_type_and_comment(declarator, str, path) {
12831277
return { type: str.original.substring(start, declarator.id.typeAnnotation.end), comment };
12841278
}
12851279

1280+
let cleaned_comment = comment
1281+
?.split('\n')
1282+
.map((line) =>
1283+
line
1284+
.trim()
1285+
// replace `// ` for one liners
1286+
.replace(/^\/\/\s*/g, '')
1287+
// replace `\**` for the initial JSDoc
1288+
.replace(/^\/\*\*?\s*/g, '')
1289+
// migrate `*/` for the end of JSDoc
1290+
.replace(/\s*\*\/$/g, '')
1291+
// remove any initial `* ` to clean the comment
1292+
.replace(/^\*\s*/g, '')
1293+
)
1294+
.filter(Boolean);
1295+
const first_at_comment = cleaned_comment?.findIndex((line) => line.startsWith('@'));
1296+
comment = cleaned_comment
1297+
?.slice(0, first_at_comment !== -1 ? first_at_comment : cleaned_comment.length)
1298+
.join('\n');
1299+
12861300
// try to find a comment with a type annotation, hinting at jsdoc
12871301
if (parent?.type === 'ExportNamedDeclaration' && comment_node) {
12881302
const match = /@type {(.+)}/.exec(comment_node.value);
12891303
if (match) {
1290-
return { type: match[1] };
1304+
return { type: match[1], comment };
12911305
}
12921306
}
12931307

packages/svelte/tests/migrate/samples/$$slots-used-as-variable-$$props/output.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<script>
2-
/** @type {{message?: import('svelte').Snippet, extra?: import('svelte').Snippet<[any]>, [key: string]: any}} */
2+
/**
3+
* @typedef {Object} Props
4+
* @property {import('svelte').Snippet} [message]
5+
* @property {import('svelte').Snippet<[any]>} [extra]
6+
*/
7+
8+
/** @type {Props & { [key: string]: any }} */
39
let { ...props } = $props();
410
let showMessage = props.message;
511
let extraTitle = $derived(props.extra);

packages/svelte/tests/migrate/samples/$$slots-used-as-variable/output.svelte

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
<script>
2-
/** @type {{message?: import('svelte').Snippet, showMessage?: any, title?: import('svelte').Snippet<[any]>, extra?: import('svelte').Snippet<[any]>}} */
2+
/**
3+
* @typedef {Object} Props
4+
* @property {import('svelte').Snippet} [message]
5+
* @property {any} [showMessage]
6+
* @property {import('svelte').Snippet<[any]>} [title]
7+
* @property {import('svelte').Snippet<[any]>} [extra]
8+
*/
9+
10+
/** @type {Props} */
311
let {
412
message,
513
showMessage = message,

packages/svelte/tests/migrate/samples/css-ignore/output.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<script>
2-
/** @type {{name: any}} */
2+
/**
3+
* @typedef {Object} Props
4+
* @property {any} name
5+
*/
6+
7+
/** @type {Props} */
38
let { name } = $props();
49
</script>
510

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script>
2+
/**
3+
* My wonderful comment
4+
* @type {string}
5+
*/
6+
export let comment;
7+
8+
/**
9+
* My wonderful other comment
10+
* @type {number}
11+
*/
12+
export let another_comment;
13+
14+
// one line comment
15+
export let one_line;
16+
17+
export let no_comment;
18+
19+
/**
20+
* @type {boolean}
21+
*/
22+
export let type_no_comment;
23+
24+
/**
25+
* This is optional
26+
*/
27+
export let optional = {stuff: true};
28+
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script>
2+
3+
4+
5+
6+
7+
8+
9+
10+
11+
12+
/**
13+
* @typedef {Object} Props
14+
* @property {string} comment - My wonderful comment
15+
* @property {number} another_comment - My wonderful other comment
16+
* @property {any} one_line - one line comment
17+
* @property {any} no_comment
18+
* @property {boolean} type_no_comment
19+
* @property {any} [optional] - This is optional
20+
*/
21+
22+
/** @type {Props} */
23+
let {
24+
comment,
25+
another_comment,
26+
one_line,
27+
no_comment,
28+
type_no_comment,
29+
optional = {stuff: true}
30+
} = $props();
31+
</script>

packages/svelte/tests/migrate/samples/props-and-labeled/output.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<script>
2-
/** @type {{readonly: any, optional?: string}} */
2+
/**
3+
* @typedef {Object} Props
4+
* @property {any} readonly
5+
* @property {string} [optional]
6+
*/
7+
8+
/** @type {Props} */
39
let { readonly, optional = 'foo' } = $props();
410
let writable = $derived(!readonly);
511
</script>

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<script>
2-
/** @type {{foo: any, [key: string]: any}} */
2+
/**
3+
* @typedef {Object} Props
4+
* @property {any} foo
5+
*/
6+
7+
/** @type {Props & { [key: string]: any }} */
38
let { foo, ...rest } = $props();
49
</script>
510

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
<script>
22
3-
/** @type {{readonly: Record<string, { href: string; title: string; }[]>, optional?: string, binding: any, bindingOptional?: string}} */
3+
/**
4+
* @typedef {Object} Props
5+
* @property {Record<string, { href: string; title: string; }[]>} readonly
6+
* @property {string} [optional]
7+
* @property {any} binding
8+
* @property {string} [bindingOptional]
9+
*/
10+
11+
/** @type {Props} */
412
let {
513
readonly,
614
optional = 'foo',

0 commit comments

Comments
 (0)