From bb733a2b10cf61fc98e1f429dc644146c439aeea Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Sat, 23 Oct 2021 17:21:21 +0200 Subject: [PATCH] (feat) auto-add types to exports when $$Props used So people don't have to do the type work twice TODO: results in buggy behavior for rename --- .../src/svelte2tsx/nodes/ExportedNames.ts | 34 +++++++++++++++++-- .../processInstanceScriptContent.ts | 11 +++--- .../ts-$$Props-auto-add-type/expected.tsx | 24 +++++++++++++ .../ts-$$Props-auto-add-type/input.svelte | 17 ++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/input.svelte diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index f509a7fc6..a39d9b693 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -10,6 +10,7 @@ export function is$$PropsDeclaration( interface ExportedName { isLet: boolean; + identifierEnd: number; type?: string; identifierText?: string; required?: boolean; @@ -36,9 +37,17 @@ export class ExportedNames { const isLet = node.declarationList.flags === ts.NodeFlags.Let; const isConst = node.declarationList.flags === ts.NodeFlags.Const; - this.handleExportedVariableDeclarationList(node.declarationList, (_, ...args) => - this.addExport(...args) - ); + this.handleExportedVariableDeclarationList(node.declarationList, (_, ...args) => { + this.addExport(...args); + ts.forEachChild(_, (node) => { + if ( + ts.isVariableDeclaration(node) && + ts.isIdentifier(node.name) && + !node.type + ) { + } + }); + }); if (isLet) { this.propTypeAssertToUserDefined(node.declarationList); } else if (isConst) { @@ -233,6 +242,7 @@ export class ExportedNames { if (target && ts.isIdentifier(target)) { this.possibleExports.set(name.text, { declaration, + identifierEnd: name.getEnd() + this.astOffset, isLet, type: type?.getText(), identifierText: (target as ts.Identifier).text, @@ -241,6 +251,7 @@ export class ExportedNames { }); } else { this.possibleExports.set(name.text, { + identifierEnd: name.getEnd() + this.astOffset, declaration, isLet }); @@ -269,6 +280,7 @@ export class ExportedNames { if (target) { this.exports.set(name.text, { isLet: isLet || existingDeclaration?.isLet, + identifierEnd: name.getEnd() + this.astOffset, type: type?.getText() || existingDeclaration?.type, identifierText: (target as ts.Identifier).text, required: required || existingDeclaration?.required, @@ -277,6 +289,7 @@ export class ExportedNames { } else { this.exports.set(name.text, { isLet: isLet || existingDeclaration?.isLet, + identifierEnd: name.getEnd() + this.astOffset, type: existingDeclaration?.type, required: existingDeclaration?.required, doc: existingDeclaration?.doc @@ -307,6 +320,21 @@ export class ExportedNames { return doc; } + addTypesToExportsIf$$PropsUsed(): void { + if (!this.uses$$Props) { + return; + } + + for (const [name, { identifierEnd, type, isLet }] of this.exports.entries()) { + if (isLet && !type) { + // TODO: this makes rename behave buggy + // - you can't enter rename when the cursor is at the last char: export let foo| + // - renaming itself produces buggy results for export let: rename foo to fo1o --> fofo11o. + this.str.prependRight(identifierEnd, `: $$Props['${name}']`); + } + } + } + /** * Creates a string from the collected props * diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index 464471454..84629df83 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -357,22 +357,25 @@ export function processInstanceScriptContent( handleTypeAssertion(str, node, astOffset); } - //to save a bunch of condition checks on each node, we recurse into processChild which skips all the checks for top level items + // to save a bunch of condition checks on each node, we recurse into processChild which skips all the checks for top level items ts.forEachChild(node, (n) => walk(n, node)); - //fire off the on leave callbacks + // fire off the on leave callbacks onLeaveCallbacks.map((c) => c()); }; - //walk the ast and convert to tsx as we go + // walk the ast and convert to tsx as we go tsAst.forEachChild((n) => walk(n, tsAst)); - //resolve stores + // resolve stores pendingStoreResolutions.map(resolveStore); // declare implicit reactive variables we found in the script implicitTopLevelNames.modifyCode(rootScope.declared); implicitStoreValues.modifyCode(astOffset, str); + // add types from $$Props to props that don't have a type defined + exportedNames.addTypesToExportsIf$$PropsUsed(); + const firstImport = tsAst.statements .filter(ts.isImportDeclaration) .sort((a, b) => a.end - b.end)[0]; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/expected.tsx new file mode 100644 index 000000000..0eefdfee0 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/expected.tsx @@ -0,0 +1,24 @@ +/// +<>;function render() { + + + interface $$Props { + exported1: string; + exported2?: boolean; + exported3: number; + exported4?: 'foo'; + exported5?: '1'; + exported6?: '2'; + } + + let exported1: $$Props['exported1']; + let exported2: $$Props['exported2'] = true;exported2 = __sveltets_1_any(exported2);; + let exported3: number; + let exported4: 'foo' = 'foo';exported4 = __sveltets_1_any(exported4);; + let exported5: '1' = '1';exported5 = __sveltets_1_any(exported5);;let exported6: $$Props['exported6'] = '2'; +; +() => (<>); +return { props: {...__sveltets_1_ensureRightProps<{exported1: typeof exported1,exported2?: typeof exported2,exported3: number,exported4?: 'foo',exported5?: '1',exported6?: typeof exported6}>(__sveltets_1_any("") as $$Props), ...__sveltets_1_ensureRightProps>({exported1: exported1,exported2: exported2,exported3: exported3,exported4: exported4,exported5: exported5,exported6: exported6}), ...{} as unknown as $$Props, ...{} as {}}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_with_any_event(render())) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/input.svelte new file mode 100644 index 000000000..a261eca14 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-auto-add-type/input.svelte @@ -0,0 +1,17 @@ +