Repository: sveltejs/language-tools
Pull request #1268 (2021-12-08 10:42:20)

(fix) don't widen props type when using $$props while using $$Props


Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
index a601a89c..723a447d 100644
--- a/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/addComponentExport.ts
@@ -7,7 +7,7 @@ import { Generics } from './nodes/Generics';
 
 export interface AddComponentExportPara {
     str: MagicString;
-    uses$$propsOr$$restProps: boolean;
+    canHaveAnyProp: boolean;
     /**
      * If true, not fallback to `any`
      * -> all unknown events will throw a type error
@@ -38,7 +38,7 @@ export function addComponentExport(params: AddComponentExportPara) {
 
 function addGenericsComponentExport({
     strictEvents,
-    uses$$propsOr$$restProps,
+    canHaveAnyProp,
     exportedNames,
     componentDocumentation,
     fileName,
@@ -60,12 +60,7 @@ function addGenericsComponentExport({
     let statement = `
 class __sveltets_Render${genericsDef} {
     props() {
-        return ${props(
-            true,
-            uses$$propsOr$$restProps,
-            exportedNames,
-            `render${genericsRef}()`
-        )}.props;
+        return ${props(true, canHaveAnyProp, exportedNames, `render${genericsRef}()`)}.props;
     }
     events() {
         return ${events(strictEvents, `render${genericsRef}()`)}.events;
@@ -105,7 +100,7 @@ class __sveltets_Render${genericsDef} {
 function addSimpleComponentExport({
     strictEvents,
     isTsFile,
-    uses$$propsOr$$restProps,
+    canHaveAnyProp,
     exportedNames,
     componentDocumentation,
     fileName,
@@ -115,7 +110,7 @@ function addSimpleComponentExport({
 }: AddComponentExportPara) {
     const propDef = props(
         isTsFile,
-        uses$$propsOr$$restProps,
+        canHaveAnyProp,
         exportedNames,
         events(strictEvents, 'render()')
     );
@@ -167,15 +162,15 @@ function events(strictEvents: boolean, renderStr: string) {
 
 function props(
     isTsFile: boolean,
-    uses$$propsOr$$restProps: boolean,
+    canHaveAnyProp: boolean,
     exportedNames: ExportedNames,
     renderStr: string
 ) {
     if (isTsFile) {
-        return uses$$propsOr$$restProps ? `__sveltets_1_with_any(${renderStr})` : renderStr;
+        return canHaveAnyProp ? `__sveltets_1_with_any(${renderStr})` : renderStr;
     } else {
         const optionalProps = exportedNames.createOptionalPropsArray();
-        const partial = `__sveltets_1_partial${uses$$propsOr$$restProps ? '_with_any' : ''}`;
+        const partial = `__sveltets_1_partial${canHaveAnyProp ? '_with_any' : ''}`;
         return optionalProps.length > 0
             ? `${partial}([${optionalProps.join(',')}], ${renderStr})`
             : `${partial}(${renderStr})`;
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index d74b5fce..33e5ba1f 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -382,7 +382,7 @@ export function svelte2tsx(
 
     addComponentExport({
         str,
-        uses$$propsOr$$restProps: uses$$props || uses$$restProps,
+        canHaveAnyProp: !exportedNames.uses$$Props && (uses$$props || uses$$restProps),
         strictEvents: events.hasStrictEvents(),
         isTsFile: options?.isTsFile,
         exportedNames,
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
index f509a7fc..68b69e1a 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
@@ -17,7 +17,7 @@ interface ExportedName {
 }
 
 export class ExportedNames {
-    private uses$$Props = false;
+    public uses$$Props = false;
     private exports = new Map<string, ExportedName>();
     private possibleExports = new Map<
         string,
@@ -210,10 +210,6 @@ export class ExportedNames {
             .join('');
     }
 
-    setUses$$Props(): void {
-        this.uses$$Props = true;
-    }
-
     /**
      * Marks a top level declaration as a possible export
      * which could be exported through `export { .. }` later.
@@ -332,6 +328,9 @@ export class ExportedNames {
                 '...__sveltets_1_ensureRightProps<Partial<$$Props>>({' +
                 this.createReturnElements(lets, false).join(',') +
                 '}), ...{} as unknown as $$Props, ...{' +
+                // We add other exports of classes and functions here because
+                // they need to appear in the props object in order to properly
+                // type bind:xx but they are not needed to be part of $$Props
                 this.createReturnElements(others, false).join(', ') +
                 '} as {' +
                 this.createReturnElementsType(others).join(',') +
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 46447145..7fd86f76 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -250,7 +250,7 @@ export function processInstanceScriptContent(
             uses$$SlotsInterface = true;
         }
         if (is$$PropsDeclaration(node)) {
-            exportedNames.setUses$$Props();
+            exportedNames.uses$$Props = true;
         }
 
         if (ts.isVariableStatement(node)) {


Test patch:
diff --git a/packages/svelte2tsx/test/emitDts/index.ts b/packages/svelte2tsx/test/emitDts/index.ts
index 5469c88c..0d189301 100644
--- a/packages/svelte2tsx/test/emitDts/index.ts
+++ b/packages/svelte2tsx/test/emitDts/index.ts
@@ -37,8 +37,12 @@ async function testEmitDts(sample: string) {
                 true,
                 `Did not expect file or folder ${file}`
             );
-            const expectedContent = fs.readFileSync(join(cwd, 'expected', file), 'utf-8');
-            const actualContent = fs.readFileSync(join(cwd, 'package', file), 'utf-8');
+            const expectedContent = fs
+                .readFileSync(join(cwd, 'expected', file), 'utf-8')
+                .replace(/\r\n/g, '\n');
+            const actualContent = fs
+                .readFileSync(join(cwd, 'package', file), 'utf-8')
+                .replace(/\r\n/g, '\n');
             assert.strictEqual(
                 actualContent,
                 expectedContent,
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test3$$Props.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test3$$Props.svelte.d.ts
new file mode 100644
index 00000000..7609c004
--- /dev/null
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test3$$Props.svelte.d.ts
@@ -0,0 +1,21 @@
+import { SvelteComponentTyped } from "svelte";
+declare const __propDef: {
+    props: {
+        b?: () => void;
+        /**
+         * comment is preserved
+         */
+        a: string;
+    };
+    events: {
+        [evt: string]: CustomEvent<any>;
+    };
+    slots: {};
+};
+export declare type Test3PropsProps = typeof __propDef.props;
+export declare type Test3PropsEvents = typeof __propDef.events;
+export declare type Test3PropsSlots = typeof __propDef.slots;
+export default class Test3Props extends SvelteComponentTyped<Test3PropsProps, Test3PropsEvents, Test3PropsSlots> {
+    get b(): () => void;
+}
+export {};
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test3$$Props.svelte b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test3$$Props.svelte
new file mode 100644
index 00000000..dae98582
--- /dev/null
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test3$$Props.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+    interface $$Props {
+        /**
+         * comment is preserved
+         */
+        a: string;
+    }
+    export function b() {}
+</script>
+
+{$$props}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/expected.tsx
new file mode 100644
index 00000000..0813a5a9
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/expected.tsx
@@ -0,0 +1,20 @@
+///<reference types="svelte" />
+<></>;function render() { let $$props = __sveltets_1_allPropsType();
+
+    interface $$Props {
+        /**
+         * comment
+         */
+         a: boolean;
+         b?: string;
+    }
+     function c() {}
+;
+() => (<>
+
+{$$props}</>);
+return { props: {...__sveltets_1_ensureRightProps<{}>(__sveltets_1_any("") as $$Props), ...__sveltets_1_ensureRightProps<Partial<$$Props>>({}), ...{} as unknown as $$Props, ...{c: c} as {c?: typeof c}}, slots: {}, getters: {c: c}, events: {} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_with_any_event(render())) {
+    get c() { return render().getters.c }
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/input.svelte
new file mode 100644
index 00000000..05291c12
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$Props-with-$$props/input.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+    interface $$Props {
+        /**
+         * comment
+         */
+         a: boolean;
+         b?: string;
+    }
+    export function c() {}
+</script>
+<!-- using $$props while using $$Props shouldn't widen the props type allow any prop -->
+{$$props}