Skip to content

Commit

Permalink
fix: use typedef for JSDoc props and maintain comments (#13698)
Browse files Browse the repository at this point in the history
* fix: use typedef for JSDoc props and maintain comments

* chore: add comments

* chore: add extra spaces and delete commented line
  • Loading branch information
paoloricciuti authored Oct 19, 2024
1 parent 663a3ca commit 6ad017f
Show file tree
Hide file tree
Showing 18 changed files with 171 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/sweet-melons-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: use typedef for JSDoc props and maintain comments
38 changes: 26 additions & 12 deletions packages/svelte/src/compiler/migrate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();`;
}

Expand Down Expand Up @@ -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, '');
Expand All @@ -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 };
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script>
/** @type {{message?: import('svelte').Snippet, extra?: import('svelte').Snippet<[any]>, [key: string]: any}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [message]
* @property {import('svelte').Snippet<[any]>} [extra]
*/
/** @type {Props & { [key: string]: any }} */
let { ...props } = $props();
let showMessage = props.message;
let extraTitle = $derived(props.extra);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<script>
/** @type {{message?: import('svelte').Snippet, showMessage?: any, title?: import('svelte').Snippet<[any]>, extra?: import('svelte').Snippet<[any]>}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [message]
* @property {any} [showMessage]
* @property {import('svelte').Snippet<[any]>} [title]
* @property {import('svelte').Snippet<[any]>} [extra]
*/
/** @type {Props} */
let {
message,
showMessage = message,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<script>
/** @type {{name: any}} */
/**
* @typedef {Object} Props
* @property {any} name
*/
/** @type {Props} */
let { name } = $props();
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script>
/**
* My wonderful comment
* @type {string}
*/
export let comment;
/**
* My wonderful other comment
* @type {number}
*/
export let another_comment;
// one line comment
export let one_line;
export let no_comment;
/**
* @type {boolean}
*/
export let type_no_comment;
/**
* This is optional
*/
export let optional = {stuff: true};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script>
/**
* @typedef {Object} Props
* @property {string} comment - My wonderful comment
* @property {number} another_comment - My wonderful other comment
* @property {any} one_line - one line comment
* @property {any} no_comment
* @property {boolean} type_no_comment
* @property {any} [optional] - This is optional
*/
/** @type {Props} */
let {
comment,
another_comment,
one_line,
no_comment,
type_no_comment,
optional = {stuff: true}
} = $props();
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script>
/** @type {{readonly: any, optional?: string}} */
/**
* @typedef {Object} Props
* @property {any} readonly
* @property {string} [optional]
*/
/** @type {Props} */
let { readonly, optional = 'foo' } = $props();
let writable = $derived(!readonly);
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<script>
/** @type {{foo: any, [key: string]: any}} */
/**
* @typedef {Object} Props
* @property {any} foo
*/
/** @type {Props & { [key: string]: any }} */
let { foo, ...rest } = $props();
</script>

Expand Down
10 changes: 9 additions & 1 deletion packages/svelte/tests/migrate/samples/props/output.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
<script>
/** @type {{readonly: Record<string, { href: string; title: string; }[]>, optional?: string, binding: any, bindingOptional?: string}} */
/**
* @typedef {Object} Props
* @property {Record<string, { href: string; title: string; }[]>} readonly
* @property {string} [optional]
* @property {any} binding
* @property {string} [bindingOptional]
*/
/** @type {Props} */
let {
readonly,
optional = 'foo',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
import { blah } from './blah.js'
/** @type {{data: any}} */
/**
* @typedef {Object} Props
* @property {any} data
*/
/** @type {Props} */
let { data } = $props();
let bar = $state()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<script>
import Foo from './Foo.svelte';
/** @type {{children?: import('svelte').Snippet}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<script>
/** @type {{children?: import('svelte').Snippet}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props();
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<script>
/** @type {{children?: import('svelte').Snippet, foo?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet, [key: string]: any}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
* @property {import('svelte').Snippet<[any]>} [foo]
* @property {import('svelte').Snippet} [bar]
*/
/** @type {Props & { [key: string]: any }} */
let { ...props } = $props();
</script>

Expand Down
9 changes: 8 additions & 1 deletion packages/svelte/tests/migrate/samples/slots/output.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<script>
/** @type {{children?: import('svelte').Snippet, foo?: import('svelte').Snippet<[any]>, bar?: import('svelte').Snippet}} */
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
* @property {import('svelte').Snippet<[any]>} [foo]
* @property {import('svelte').Snippet} [bar]
*/
/** @type {Props} */
let { children, foo, bar } = $props();
</script>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
/** @type {{[key: string]: any}} */
/** @type {{ [key: string]: any }} */
let { ...rest } = $props();
let Component;
let fallback;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import Output_1 from './output.svelte';
/** @type {{[key: string]: any}} */
/** @type {{ [key: string]: any }} */
let { ...props } = $props();
let Output;
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script>
import Output from './output.svelte';
/** @type {{[key: string]: any}} */
/** @type {{ [key: string]: any }} */
let { ...props } = $props();
</script>

Expand Down

0 comments on commit 6ad017f

Please sign in to comment.