diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index 5987f7928..3f7190de0 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -19,7 +19,13 @@ interface ExportedName { export class ExportedNames { private uses$$Props = false; private exports = new Map(); - private possibleExports = new Map(); + private possibleExports = new Map< + string, + ExportedName & { + declaration: ts.VariableDeclarationList; + } + >(); + private doneDeclarationTransformation = new Set(); private getters = new Set(); constructor(private str: MagicString, private astOffset: number) {} @@ -30,9 +36,8 @@ export class ExportedNames { const isLet = node.declarationList.flags === ts.NodeFlags.Let; const isConst = node.declarationList.flags === ts.NodeFlags.Const; - this.handleExportedVariableDeclarationList( - node.declarationList, - this.addExport.bind(this) + this.handleExportedVariableDeclarationList(node.declarationList, (_, ...args) => + this.addExport(...args) ); if (isLet) { this.propTypeAssertToUserDefined(node.declarationList); @@ -88,7 +93,16 @@ export class ExportedNames { this.str.remove(exportStart, exportEnd); } + /** + * Appends `prop = __sveltets_any(prop)` to given declaration in order to + * trick TS into widening the type. Else for example `let foo: string | undefined = undefined` + * is narrowed to `undefined` by TS. + */ private propTypeAssertToUserDefined(node: ts.VariableDeclarationList) { + if (this.doneDeclarationTransformation.has(node)) { + return; + } + const hasInitializers = node.declarations.filter((declaration) => declaration.initializer); const handleTypeAssertion = (declaration: ts.VariableDeclaration) => { const identifier = declaration.name; @@ -133,24 +147,25 @@ export class ExportedNames { for (const declaration of hasInitializers) { handleTypeAssertion(declaration); } + this.doneDeclarationTransformation.add(node); } private handleExportedVariableDeclarationList( list: ts.VariableDeclarationList, - add: ExportedNames['addExport'] + add: ExportedNames['addPossibleExport'] ) { const isLet = list.flags === ts.NodeFlags.Let; ts.forEachChild(list, (node) => { if (ts.isVariableDeclaration(node)) { if (ts.isIdentifier(node.name)) { - add(node.name, isLet, node.name, node.type, !node.initializer); + add(list, node.name, isLet, node.name, node.type, !node.initializer); } else if ( ts.isObjectBindingPattern(node.name) || ts.isArrayBindingPattern(node.name) ) { ts.forEachChild(node.name, (element) => { if (ts.isBindingElement(element)) { - add(element.name, isLet); + add(list, element.name, isLet); } }); } @@ -158,7 +173,7 @@ export class ExportedNames { }); } - addGetter(node: ts.Identifier): void { + private addGetter(node: ts.Identifier): void { if (!node) { return; } @@ -203,7 +218,8 @@ export class ExportedNames { * Marks a top level declaration as a possible export * which could be exported through `export { .. }` later. */ - addPossibleExport( + private addPossibleExport( + declaration: ts.VariableDeclarationList, name: ts.BindingName, isLet: boolean, target: ts.BindingName = null, @@ -216,6 +232,7 @@ export class ExportedNames { if (target && ts.isIdentifier(target)) { this.possibleExports.set(name.text, { + declaration, isLet, type: type?.getText(), identifierText: (target as ts.Identifier).text, @@ -224,6 +241,7 @@ export class ExportedNames { }); } else { this.possibleExports.set(name.text, { + declaration, isLet }); } @@ -232,7 +250,7 @@ export class ExportedNames { /** * Adds export to map */ - addExport( + private addExport( name: ts.BindingName, isLet: boolean, target: ts.BindingName = null, @@ -247,6 +265,7 @@ export class ExportedNames { } const existingDeclaration = this.possibleExports.get(name.text); + if (target) { this.exports.set(name.text, { isLet: isLet || existingDeclaration?.isLet, @@ -263,6 +282,10 @@ export class ExportedNames { doc: existingDeclaration?.doc }); } + + if (existingDeclaration?.isLet) { + this.propTypeAssertToUserDefined(existingDeclaration.declaration); + } } private getDoc(target: ts.BindingName) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx index e4b7bb756..542f10ee3 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-interface/expected.tsx @@ -14,10 +14,10 @@ let exported1: string; let exported2: string = '';exported2 = __sveltets_any(exported2);; - let name1: string = "world" + let name1: string = "world";name1 = __sveltets_any(name1); let name2: string; - let rename1: string = ''; + let rename1: string = '';rename1 = __sveltets_any(rename1);; let rename2: string; class Foo {} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx index 993a4e94b..2fe135194 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-type/expected.tsx @@ -14,10 +14,10 @@ let exported1: string; let exported2: string = '';exported2 = __sveltets_any(exported2);; - let name1: string = "world" + let name1: string = "world";name1 = __sveltets_any(name1); let name2: string; - let rename1: string = ''; + let rename1: string = '';rename1 = __sveltets_any(rename1);; let rename2: string; class Foo {} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx index 7a5d2b2d4..b4628b154 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/expected.tsx @@ -1,10 +1,11 @@ /// <>;function render() { - let name1: string = "world" + let name1: string = "world";name1 = __sveltets_any(name1); let name2: string; + let name3: string = '';name3 = __sveltets_any(name3);;let name4: string; - let rename1: string = ''; + let rename1: string = '';rename1 = __sveltets_any(rename1);; let rename2: string; class Foo {} @@ -18,7 +19,7 @@ ; () => (<>); -return { props: {name1: name1 , name2: name2 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz} as {name1?: string, name2: string, renamed1?: string, renamed2: string, Foo?: typeof Foo, bar?: typeof bar, baz?: string, RenamedFoo?: typeof RenameFoo, renamedbar?: typeof renamebar, renamedbaz?: string}, slots: {}, getters: {}, events: {} }} +return { props: {name1: name1 , name2: name2 , name3: name3 , name4: name4 , renamed1: rename1 , renamed2: rename2 , Foo: Foo , bar: bar , baz: baz , RenamedFoo: RenameFoo , renamedbar: renamebar , renamedbaz: renamebaz} 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}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_with_any_event(render())) { } \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte index 97aaf2197..6aaa4f0d2 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-list/input.svelte @@ -1,6 +1,7 @@