Skip to content

Commit 02573d3

Browse files
committed
feat: support $props.bindable()
sveltejs/svelte#10768
1 parent ceb4f70 commit 02573d3

File tree

25 files changed

+259
-29
lines changed

25 files changed

+259
-29
lines changed

packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,14 @@ export class ExportedNames {
9191
for (const declaration of node.declarationList.declarations) {
9292
if (
9393
declaration.initializer !== undefined &&
94-
ts.isCallExpression(declaration.initializer) &&
95-
declaration.initializer.expression.getText() === '$props'
94+
ts.isCallExpression(declaration.initializer)
9695
) {
97-
// @ts-expect-error TS is too stupid to narrow this properly
98-
this.handle$propsRune(declaration);
99-
break;
96+
const text = declaration.initializer.expression.getText();
97+
if (text === '$props' || text === '$props.bindable') {
98+
// @ts-expect-error TS is too stupid to narrow this properly
99+
this.handle$propsRune(declaration, text === '$props.bindable');
100+
break;
101+
}
100102
}
101103
}
102104
}
@@ -135,7 +137,8 @@ export class ExportedNames {
135137
private handle$propsRune(
136138
node: ts.VariableDeclaration & {
137139
initializer: ts.CallExpression & { expression: ts.Identifier };
138-
}
140+
},
141+
bindable: boolean
139142
): void {
140143
// Check if the $props() rune has a children prop
141144
if (ts.isObjectBindingPattern(node.name)) {
@@ -160,27 +163,50 @@ export class ExportedNames {
160163
this.$props.mayHaveChildrenProp = true;
161164
}
162165

166+
const generic_name = bindable ? '$$ComponentBindableProps' : '$$ComponentProps';
167+
const construct_generic = (generic: string) => {
168+
if (this.isTsFile) {
169+
this.$props.generic =
170+
this.$props.generic === generic || !this.$props.generic
171+
? generic
172+
: this.$props.generic + ' & ' + generic;
173+
} else {
174+
if (this.$props.comment) {
175+
if (generic !== this.$props.comment) {
176+
const pos =
177+
this.$props.comment.indexOf('{', this.$props.comment.indexOf('@type')) +
178+
1;
179+
const start = generic.indexOf('{', generic.indexOf('@type')) + 1;
180+
const end = generic.lastIndexOf('}');
181+
this.$props.comment =
182+
this.$props.comment.slice(0, pos) +
183+
generic.slice(start, end) +
184+
' & ' +
185+
this.$props.comment.slice(pos);
186+
}
187+
} else {
188+
this.$props.comment = generic;
189+
}
190+
}
191+
};
192+
163193
if (node.initializer.typeArguments?.length > 0 || node.type) {
164194
const generic_arg = node.initializer.typeArguments?.[0] || node.type;
165195
const generic = generic_arg.getText();
166196
if (!generic.includes('{')) {
167-
this.$props.generic = generic;
197+
construct_generic(generic);
168198
} else {
169199
// Create a virtual type alias for the unnamed generic and reuse it for the props return type
170200
// so that rename, find references etc works seamlessly across components
171-
this.$props.generic = '$$ComponentProps';
172-
preprendStr(
173-
this.str,
174-
generic_arg.pos + this.astOffset,
175-
`;type ${this.$props.generic} = `
176-
);
201+
construct_generic(generic_name);
202+
preprendStr(this.str, generic_arg.pos + this.astOffset, `;type ${generic_name} = `);
177203
this.str.appendLeft(generic_arg.end + this.astOffset, ';');
178204
this.str.move(
179205
generic_arg.pos + this.astOffset,
180206
generic_arg.end + this.astOffset,
181207
node.parent.pos + this.astOffset
182208
);
183-
this.str.appendRight(generic_arg.end + this.astOffset, this.$props.generic);
209+
this.str.appendRight(generic_arg.end + this.astOffset, generic_name);
184210
}
185211
} else {
186212
if (!this.isTsFile) {
@@ -209,23 +235,26 @@ export class ExportedNames {
209235
}
210236
}
211237

238+
let jsdoc = '';
239+
212240
if (comment && /\/\*\*[^@]*?@type\s*{\s*{.*}\s*}\s*\*\//.test(comment)) {
213241
// Create a virtual type alias for the unnamed generic and reuse it for the props return type
214242
// so that rename, find references etc works seamlessly across components
215-
this.$props.comment = '/** @type {$$ComponentProps} */';
243+
jsdoc = `/** @type {${generic_name}} */`;
216244
const type_start = this.str.original.indexOf('@type', start);
217245
this.str.overwrite(type_start, type_start + 5, '@typedef');
218246
const end = this.str.original.indexOf('*/', start);
219-
this.str.overwrite(end, end + 2, ' $$ComponentProps */' + this.$props.comment);
247+
this.str.overwrite(end, end + 2, ` ${generic_name} */` + jsdoc);
220248
} else {
221249
// Complex comment or simple `@type {AType}` comment which we just use as-is.
222250
// For the former this means things like rename won't work properly across components.
223-
this.$props.comment = comment || '';
251+
jsdoc = comment || '';
224252
}
225-
}
226253

227-
if (this.$props.comment) {
228-
return;
254+
if (jsdoc) {
255+
construct_generic(jsdoc);
256+
return;
257+
}
229258
}
230259

231260
// Do a best-effort to extract the props from the object literal
@@ -302,26 +331,23 @@ export class ExportedNames {
302331
// Create a virtual type alias for the unnamed generic and reuse it for the props return type
303332
// so that rename, find references etc works seamlessly across components
304333
if (this.isTsFile) {
305-
this.$props.generic = '$$ComponentProps';
334+
construct_generic(generic_name);
306335
if (props.length > 0 || withUnknown) {
307336
preprendStr(
308337
this.str,
309338
node.parent.pos + this.astOffset,
310-
surroundWithIgnoreComments(`;type $$ComponentProps = ${propsStr};`)
311-
);
312-
preprendStr(
313-
this.str,
314-
node.name.end + this.astOffset,
315-
`: ${this.$props.generic}`
339+
surroundWithIgnoreComments(`;type ${generic_name} = ${propsStr};`)
316340
);
341+
preprendStr(this.str, node.name.end + this.astOffset, `: ${generic_name}`);
317342
}
318343
} else {
319-
this.$props.comment = '/** @type {$$ComponentProps} */';
344+
const jsdoc = `/** @type {${generic_name}} */`;
345+
construct_generic(jsdoc);
320346
if (props.length > 0 || withUnknown) {
321347
preprendStr(
322348
this.str,
323349
node.pos + this.astOffset,
324-
`/** @typedef {${propsStr}} $$ComponentProps */${this.$props.comment}`
350+
`/** @typedef {${propsStr}} ${generic_name} */${jsdoc}`
325351
);
326352
}
327353
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
let/** @typedef {{ a: unknown, b?: number }} $$ComponentBindableProps *//** @type {$$ComponentBindableProps} */ { a, b = 1 } = $props.bindable();
5+
;
6+
async () => {};
7+
return { props: /** @type {$$ComponentBindableProps} */({}), slots: {}, events: {} }}
8+
9+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script>
2+
let { a, b = 1 } = $props.bindable();
3+
</script>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
/** @typedef {{ a: string; b?: number }} $$ComponentBindableProps *//** @type {$$ComponentBindableProps} */
5+
let { a, b = 1 } = $props.bindable();
6+
;
7+
async () => {};
8+
return { props: /** @type {$$ComponentBindableProps} */({}), slots: {}, events: {} }}
9+
10+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
/** @type {{ a: string; b?: number }} */
3+
let { a, b = 1 } = $props.bindable();
4+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
/** @typedef {{ a: string; b?: number }} Foo */
5+
6+
/** @type {Foo} */
7+
let { a, b = 1 } = $props.bindable();
8+
;
9+
async () => {};
10+
return { props: /** @type {Foo} */({}), slots: {}, events: {} }}
11+
12+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
/** @typedef {{ a: string; b?: number }} Foo */
3+
4+
/** @type {Foo} */
5+
let { a, b = 1 } = $props.bindable();
6+
</script>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
/** @typedef {{ a: string; b?: number }} Foo */
5+
6+
/** @type {Foo} */
7+
let { b = 1 } = $props();
8+
9+
/** @type {Foo} */
10+
let { a } = $props.bindable();
11+
;
12+
async () => {};
13+
return { props: /** @type {Foo} */({}), slots: {}, events: {} }}
14+
15+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
/** @typedef {{ a: string; b?: number }} Foo */
3+
4+
/** @type {Foo} */
5+
let { b = 1 } = $props();
6+
7+
/** @type {Foo} */
8+
let { a } = $props.bindable();
9+
</script>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
/** @typedef {{b?: number}} $$ComponentProps *//** @type {$$ComponentProps} */
5+
let { b = 1 } = $props();
6+
7+
/** @typedef {{a: string}} $$ComponentBindableProps *//** @type {$$ComponentBindableProps} */
8+
let { a } = $props.bindable();
9+
;
10+
async () => {};
11+
return { props: /** @type {$$ComponentBindableProps & $$ComponentProps} */({}), slots: {}, events: {} }}
12+
13+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
/** @type {{b?: number}} */
3+
let { b = 1 } = $props();
4+
5+
/** @type {{a: string}} */
6+
let { a } = $props.bindable();
7+
</script>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
let/** @typedef {{ b?: number }} $$ComponentProps *//** @type {$$ComponentProps} */ { b = 1 } = $props();
5+
let/** @typedef {{ a: unknown }} $$ComponentBindableProps *//** @type {$$ComponentBindableProps} */ { a } = $props.bindable();
6+
;
7+
async () => {};
8+
return { props: /** @type {$$ComponentBindableProps & $$ComponentProps} */({}), slots: {}, events: {} }}
9+
10+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script>
2+
let { b = 1 } = $props();
3+
let { a } = $props.bindable();
4+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
/*Ωignore_startΩ*/;type $$ComponentBindableProps = { a: unknown, b?: number };/*Ωignore_endΩ*/
4+
let { a, b = 1 }: $$ComponentBindableProps = $props.bindable();
5+
;
6+
async () => {};
7+
return { props: {} as any as $$ComponentBindableProps, slots: {}, events: {} }}
8+
9+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script lang="ts">
2+
let { a, b = 1 } = $props.bindable();
3+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
;type $$ComponentBindableProps = { a: string; b?: number };
4+
let { a, b = 1 }:$$ComponentBindableProps = $props.bindable();
5+
;
6+
async () => {};
7+
return { props: {} as any as $$ComponentBindableProps, slots: {}, events: {} }}
8+
9+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script lang="ts">
2+
let { a, b = 1 }: { a: string; b?: number } = $props.bindable();
3+
</script>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
type Foo = { a: string; b?: number }
5+
6+
let { a, b = 1 }: Foo = $props.bindable();
7+
;
8+
async () => {};
9+
return { props: {} as any as Foo, slots: {}, events: {} }}
10+
11+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
type Foo = { a: string; b?: number }
3+
4+
let { a, b = 1 }: Foo = $props.bindable();
5+
</script>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
type Foo = { a: string; b?: number }
5+
6+
let { b = 1 }: Foo = $props();
7+
8+
let { a }: Foo = $props.bindable();
9+
;
10+
async () => {};
11+
return { props: {} as any as Foo, slots: {}, events: {} }}
12+
13+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script lang="ts">
2+
type Foo = { a: string; b?: number }
3+
4+
let { b = 1 }: Foo = $props();
5+
6+
let { a }: Foo = $props.bindable();
7+
</script>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
;type $$ComponentProps = {b?: number};
4+
let { b = 1 }:$$ComponentProps = $props();;type $$ComponentBindableProps = {a: string};
5+
6+
let { a }:$$ComponentBindableProps = $props.bindable();
7+
;
8+
async () => {};
9+
return { props: {} as any as $$ComponentProps & $$ComponentBindableProps, slots: {}, events: {} }}
10+
11+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts">
2+
let { b = 1 }: {b?: number} = $props();
3+
4+
let { a }: {a: string} = $props.bindable();
5+
</script>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
/*Ωignore_startΩ*/;type $$ComponentProps = { b?: number };/*Ωignore_endΩ*/
4+
let { b = 1 }: $$ComponentProps = $props();/*Ωignore_startΩ*/;type $$ComponentBindableProps = { a: unknown };/*Ωignore_endΩ*/
5+
let { a }: $$ComponentBindableProps = $props.bindable();
6+
;
7+
async () => {};
8+
return { props: {} as any as $$ComponentProps & $$ComponentBindableProps, slots: {}, events: {} }}
9+
10+
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<script lang="ts">
2+
let { b = 1 } = $props();
3+
let { a } = $props.bindable();
4+
</script>

0 commit comments

Comments
 (0)