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 @@
+