Skip to content

Commit b12afd1

Browse files
fix: include named exports in svelte 5 type (#2528)
fixes sveltejs/svelte#13508
1 parent 4dfb988 commit b12afd1

File tree

10 files changed

+157
-20
lines changed

10 files changed

+157
-20
lines changed

packages/svelte2tsx/repl/index.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
<svelte:options runes />
2+
13
<script>
2-
export let value;
4+
let name = "world"
5+
let name2 = "world"
6+
export { name as name3, name2 as name4 };
37
</script>
4-
5-
{#if value}
6-
<input bind:value on:change />
7-
{/if}

packages/svelte2tsx/src/svelte2tsx/index.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type TemplateProcessResult = {
4444
events: ComponentEvents;
4545
resolvedStores: string[];
4646
usesAccessors: boolean;
47+
isRunes: boolean;
4748
};
4849

4950
function processSvelteTemplate(
@@ -64,6 +65,7 @@ function processSvelteTemplate(
6465
let uses$$restProps = false;
6566
let uses$$slots = false;
6667
let usesAccessors = !!options.accessors;
68+
let isRunes = false;
6769

6870
const componentDocumentation = new ComponentDocumentation();
6971

@@ -92,6 +94,9 @@ function processSvelteTemplate(
9294
usesAccessors = true;
9395
}
9496
break;
97+
case 'runes':
98+
isRunes = true;
99+
break;
95100
}
96101
}
97102
};
@@ -303,7 +308,8 @@ function processSvelteTemplate(
303308
uses$$slots,
304309
componentDocumentation,
305310
resolvedStores,
306-
usesAccessors
311+
usesAccessors,
312+
isRunes
307313
};
308314
}
309315

@@ -342,7 +348,8 @@ export function svelte2tsx(
342348
events,
343349
componentDocumentation,
344350
resolvedStores,
345-
usesAccessors
351+
usesAccessors,
352+
isRunes
346353
} = processSvelteTemplate(str, options.parse || parse, {
347354
...options,
348355
svelte5Plus
@@ -370,7 +377,14 @@ export function svelte2tsx(
370377
: instanceScriptTarget;
371378
const implicitStoreValues = new ImplicitStoreValues(resolvedStores, renderFunctionStart);
372379
//move the instance script and process the content
373-
let exportedNames = new ExportedNames(str, 0, basename, options?.isTsFile, svelte5Plus);
380+
let exportedNames = new ExportedNames(
381+
str,
382+
0,
383+
basename,
384+
options?.isTsFile,
385+
svelte5Plus,
386+
isRunes
387+
);
374388
let generics = new Generics(str, 0, { attributes: [] } as any);
375389
let uses$$SlotsInterface = false;
376390
if (scriptTag) {
@@ -387,7 +401,8 @@ export function svelte2tsx(
387401
/**hasModuleScripts */ !!moduleScriptTag,
388402
options?.isTsFile,
389403
basename,
390-
svelte5Plus
404+
svelte5Plus,
405+
isRunes
391406
);
392407
uses$$props = uses$$props || res.uses$$props;
393408
uses$$restProps = uses$$restProps || res.uses$$restProps;

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

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface ExportedName {
1717
identifierText?: string;
1818
required?: boolean;
1919
doc?: string;
20+
isNamedExport?: boolean;
2021
}
2122

2223
export class ExportedNames {
@@ -54,7 +55,8 @@ export class ExportedNames {
5455
private astOffset: number,
5556
private basename: string,
5657
private isTsFile: boolean,
57-
private isSvelte5Plus: boolean
58+
private isSvelte5Plus: boolean,
59+
private isRunes: boolean
5860
) {}
5961

6062
handleVariableStatement(node: ts.VariableStatement, parent: ts.Node): void {
@@ -127,9 +129,9 @@ export class ExportedNames {
127129
if (ts.isNamedExports(exportClause)) {
128130
for (const ne of exportClause.elements) {
129131
if (ne.propertyName) {
130-
this.addExport(ne.propertyName, false, ne.name);
132+
this.addExport(ne.propertyName, false, ne.name, undefined, undefined, true);
131133
} else {
132-
this.addExport(ne.name, false);
134+
this.addExport(ne.name, false, undefined, undefined, undefined, true);
133135
}
134136
}
135137
//we can remove entire statement
@@ -552,24 +554,26 @@ export class ExportedNames {
552554
isLet: boolean,
553555
target: ts.Identifier = null,
554556
type: ts.TypeNode = null,
555-
required = false
557+
required = false,
558+
isNamedExport = false
556559
): void {
557560
const existingDeclaration = this.possibleExports.get(name.text);
558-
559561
if (target) {
560562
this.exports.set(name.text, {
561563
isLet: isLet || existingDeclaration?.isLet,
562564
type: type?.getText() || existingDeclaration?.type,
563565
identifierText: target.text,
564566
required: required || existingDeclaration?.required,
565-
doc: this.getDoc(target) || existingDeclaration?.doc
567+
doc: this.getDoc(target) || existingDeclaration?.doc,
568+
isNamedExport
566569
});
567570
} else {
568571
this.exports.set(name.text, {
569572
isLet: isLet || existingDeclaration?.isLet,
570573
type: existingDeclaration?.type,
571574
required: existingDeclaration?.required,
572-
doc: existingDeclaration?.doc
575+
doc: existingDeclaration?.doc,
576+
isNamedExport
573577
});
574578
}
575579

@@ -706,7 +710,9 @@ export class ExportedNames {
706710
*/
707711
createExportsStr(): string {
708712
const names = Array.from(this.exports.entries());
709-
const others = names.filter(([, { isLet }]) => !isLet);
713+
const others = names.filter(
714+
([, { isLet, isNamedExport }]) => !isLet || (this.usesRunes() && isNamedExport)
715+
);
710716
const needsAccessors = this.usesAccessors && names.length > 0 && !this.usesRunes(); // runes mode doesn't support accessors
711717

712718
if (this.isSvelte5Plus) {
@@ -803,6 +809,6 @@ export class ExportedNames {
803809
}
804810

805811
usesRunes() {
806-
return this.hasRunesGlobals || this.hasPropsRune();
812+
return this.hasRunesGlobals || this.hasPropsRune() || this.isRunes;
807813
}
808814
}

packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export function processInstanceScriptContent(
4242
hasModuleScript: boolean,
4343
isTSFile: boolean,
4444
basename: string,
45-
isSvelte5Plus: boolean
45+
isSvelte5Plus: boolean,
46+
isRunes: boolean
4647
): InstanceScriptProcessResult {
4748
const htmlx = str.original;
4849
const scriptContent = htmlx.substring(script.content.start, script.content.end);
@@ -54,7 +55,14 @@ export function processInstanceScriptContent(
5455
ts.ScriptKind.TS
5556
);
5657
const astOffset = script.content.start;
57-
const exportedNames = new ExportedNames(str, astOffset, basename, isTSFile, isSvelte5Plus);
58+
const exportedNames = new ExportedNames(
59+
str,
60+
astOffset,
61+
basename,
62+
isTSFile,
63+
isSvelte5Plus,
64+
isRunes
65+
);
5866
const generics = new Generics(str, astOffset, script);
5967
const interfacesAndTypes = new InterfacesAndTypes();
6068

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
let name1 = "world"
5+
let name2/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/
6+
7+
let rename1 = '';
8+
let rename2/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/;
9+
10+
class Foo {}
11+
function bar() {}
12+
const baz = '';
13+
14+
class RenameFoo {}
15+
function renamebar() {}
16+
const renamebaz = '';
17+
18+
19+
;
20+
async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});}
21+
22+
};
23+
return { props: /** @type {Record<string, never>} */ ({}), exports: /** @type {{name1: typeof name1,name2: typeof name2,renamed1: typeof rename1,renamed2: typeof rename2,Foo: typeof Foo,bar: typeof bar,baz: typeof baz,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: typeof renamebaz}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
24+
const Input__SvelteComponent_ = __sveltets_2_fn_component(render());
25+
export default Input__SvelteComponent_;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<svelte:options runes />
2+
3+
<script>
4+
let name1 = "world"
5+
let name2
6+
7+
let rename1 = '';
8+
let rename2;
9+
10+
class Foo {}
11+
function bar() {}
12+
const baz = '';
13+
14+
class RenameFoo {}
15+
function renamebar() {}
16+
const renamebaz = '';
17+
18+
export { name1, name2, rename1 as renamed1, rename2 as renamed2, Foo, bar, baz, RenameFoo as RenamedFoo, renamebar as renamedbar, renamebaz as renamedbaz };
19+
</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+
let name = "world"
5+
let name2 = "world"
6+
7+
;
8+
async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});}
9+
10+
};
11+
return { props: /** @type {Record<string, never>} */ ({}), exports: /** @type {{name3: typeof name,name4: typeof name2}} */ ({}), bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
12+
const Input__SvelteComponent_ = __sveltets_2_fn_component(render());
13+
export default Input__SvelteComponent_;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<svelte:options runes />
2+
3+
<script>
4+
let name = "world"
5+
let name2 = "world"
6+
export { name as name3, name2 as name4 };
7+
</script>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
///<reference types="svelte" />
2+
;function render() {
3+
4+
let name1: string = "world"/*Ωignore_startΩ*/;name1 = __sveltets_2_any(name1);/*Ωignore_endΩ*/
5+
let name2: string/*Ωignore_startΩ*/;name2 = __sveltets_2_any(name2);/*Ωignore_endΩ*/;
6+
let name3: string = ''/*Ωignore_startΩ*/;name3 = __sveltets_2_any(name3);/*Ωignore_endΩ*/;let name4: string/*Ωignore_startΩ*/;name4 = __sveltets_2_any(name4);/*Ωignore_endΩ*/;
7+
8+
let rename1: string = ''/*Ωignore_startΩ*/;rename1 = __sveltets_2_any(rename1);/*Ωignore_endΩ*/;
9+
let rename2: string/*Ωignore_startΩ*/;rename2 = __sveltets_2_any(rename2);/*Ωignore_endΩ*/;
10+
11+
class Foo {}
12+
function bar() {}
13+
const baz: string = '';
14+
15+
class RenameFoo {}
16+
function renamebar() {}
17+
const renamebaz: string = '';
18+
19+
20+
;
21+
async () => { { svelteHTML.createElement("svelte:options", {"runes":true,});}
22+
};
23+
return { props: {} as Record<string, never>, exports: {} as any as { name1: string,name2: string,name3: string,name4: string,renamed1: string,renamed2: string,Foo: typeof Foo,bar: typeof bar,baz: string,RenamedFoo: typeof RenameFoo,renamedbar: typeof renamebar,renamedbaz: string }, bindings: __sveltets_$$bindings(''), slots: {}, events: {} }}
24+
const Input__SvelteComponent_ = __sveltets_2_fn_component(render());
25+
export default Input__SvelteComponent_;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<svelte:options runes />
2+
<script>
3+
let name1: string = "world"
4+
let name2: string;
5+
let name3: string = '', name4: string;
6+
7+
let rename1: string = '';
8+
let rename2: string;
9+
10+
class Foo {}
11+
function bar() {}
12+
const baz: string = '';
13+
14+
class RenameFoo {}
15+
function renamebar() {}
16+
const renamebaz: string = '';
17+
18+
export { name1, name2, name3, name4, rename1 as renamed1, rename2 as renamed2, Foo, bar, baz, RenameFoo as RenamedFoo, renamebar as renamedbar, renamebaz as renamedbaz };
19+
</script>

0 commit comments

Comments
 (0)