Repository: sveltejs/language-tools
Pull request #1155 (2021-08-31 09:54:59)

(fix) transform interface to type in dts mode
#1126


Patch:
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index 19b53cba..da6a31d6 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -336,7 +336,13 @@ export function svelte2tsx(
         if (scriptTag.start != instanceScriptTarget) {
             str.move(scriptTag.start, scriptTag.end, instanceScriptTarget);
         }
-        const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues);
+        const res = processInstanceScriptContent(
+            str,
+            scriptTag,
+            events,
+            implicitStoreValues,
+            options.mode
+        );
         uses$$props = uses$$props || res.uses$$props;
         uses$$restProps = uses$$restProps || res.uses$$restProps;
         uses$$slots = uses$$slots || res.uses$$slots;
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index fc698843..30ade914 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -36,7 +36,8 @@ export function processInstanceScriptContent(
     str: MagicString,
     script: Node,
     events: ComponentEvents,
-    implicitStoreValues: ImplicitStoreValues
+    implicitStoreValues: ImplicitStoreValues,
+    mode: 'tsx' | 'dts'
 ): InstanceScriptProcessResult {
     const htmlx = str.original;
     const scriptContent = htmlx.substring(script.content.start, script.content.end);
@@ -375,6 +376,14 @@ export function processInstanceScriptContent(
         str.appendRight(firstImport.getStart() + astOffset, '\n');
     }
 
+    if (mode === 'dts') {
+        // Transform interface declarations to type declarations because indirectly
+        // using interfaces inside the return type of a function is forbidden.
+        // This is not a problem for intellisense/type inference but it will
+        // break dts generation (file will not be generated).
+        transformInterfacesToTypes(tsAst, str, astOffset);
+    }
+
     return {
         exportedNames,
         events,
@@ -385,3 +394,33 @@ export function processInstanceScriptContent(
         generics
     };
 }
+
+function transformInterfacesToTypes(tsAst: ts.SourceFile, str: MagicString, astOffset: any) {
+    tsAst.statements.filter(ts.isInterfaceDeclaration).forEach((node) => {
+        str.overwrite(
+            node.getStart() + astOffset,
+            node.getStart() + astOffset + 'interface'.length,
+            'type'
+        );
+
+        if (node.heritageClauses?.length) {
+            const extendsStart = node.heritageClauses[0].getStart() + astOffset;
+            str.overwrite(extendsStart, extendsStart + 'extends'.length, '=');
+
+            const extendsList = node.heritageClauses[0].types;
+            let prev = extendsList[0];
+            extendsList.slice(1).forEach((heritageClause) => {
+                str.overwrite(
+                    prev.getEnd() + astOffset,
+                    heritageClause.getStart() + astOffset,
+                    ' & '
+                );
+                prev = heritageClause;
+            });
+
+            str.appendLeft(node.heritageClauses[0].getEnd() + astOffset, ' & ');
+        } else {
+            str.prependLeft(str.original.indexOf('{', node.getStart() + astOffset), '=');
+        }
+    });
+}


Test patch:
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Interfaces.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Interfaces.svelte.d.ts
new file mode 100644
index 00000000..4fa904ec
--- /dev/null
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Interfaces.svelte.d.ts
@@ -0,0 +1,20 @@
+import { SvelteComponentTyped } from "svelte";
+declare class __sveltets_Render<T extends boolean> {
+    props(): {
+        a: T;
+    };
+    events(): {
+        b: CustomEvent<T>;
+    };
+    slots(): {
+        default: {
+            c: T;
+        };
+    };
+}
+export declare type InterfacesProps<T extends boolean> = ReturnType<__sveltets_Render<T>['props']>;
+export declare type InterfacesEvents<T extends boolean> = ReturnType<__sveltets_Render<T>['events']>;
+export declare type InterfacesSlots<T extends boolean> = ReturnType<__sveltets_Render<T>['slots']>;
+export default class Interfaces<T extends boolean> extends SvelteComponentTyped<InterfacesProps<T>, InterfacesEvents<T>, InterfacesSlots<T>> {
+}
+export {};
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test2.svelte.d.ts b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test2.svelte.d.ts
index 45e91c48..f7c09ebd 100644
--- a/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test2.svelte.d.ts
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/expected/Test2.svelte.d.ts
@@ -3,6 +3,9 @@ import type { Foo } from './foo';
 declare const __propDef: {
     props: {
         foo: Foo;
+        bar: {
+            a: true;
+        };
     };
     events: {
         [evt: string]: CustomEvent<any>;
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/src/Interfaces.svelte b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Interfaces.svelte
new file mode 100644
index 00000000..2112d179
--- /dev/null
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Interfaces.svelte
@@ -0,0 +1,12 @@
+<script lang="ts">
+    type T = $$Generic<boolean>;
+    interface $$Props {
+        a: T;
+    }
+    interface $$Events {
+        b: CustomEvent<T>;
+    }
+    interface $$Slots {
+        default: { c: T };
+    }
+</script>
diff --git a/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test2.svelte b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test2.svelte
index aef3fb6f..91da875c 100644
--- a/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test2.svelte
+++ b/packages/svelte2tsx/test/emitDts/samples/typescript/src/Test2.svelte
@@ -1,4 +1,8 @@
 <script lang="ts">
     import type { Foo } from './foo';
     export let foo: Foo;
+    interface Bar {
+        a: true;
+    }
+    export let bar: Bar;
 </script>
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/expected.tsx
new file mode 100644
index 00000000..8ba2848e
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/expected.tsx
@@ -0,0 +1,34 @@
+import { SvelteComponentTyped } from "svelte"
+
+declare function __sveltets_1_createSvelteComponentTyped<Props, Events, Slots>(
+    render: {props: Props, events: Events, slots: Slots }
+): SvelteComponentConstructor<SvelteComponentTyped<Props, Events, Slots>,Svelte2TsxComponentConstructorParameters<Props>>;
+
+
+import type { Foo } from './foo';
+function render() {
+
+    
+     let foo: Foo;
+    type Bar1 ={
+        a: true;
+    }
+    type Bar2 = Bar1 &  {
+        b: false;
+    }
+    type Bar3 = Bar1 & Bar2 &  {
+        c: false;
+    }
+    type Bar4<T extends boolean> = Bar1 & Bar2 &  {
+        c: false;
+    }
+     let bar: Bar3;
+;
+return { props: {foo: foo , bar: bar}, slots: {}, getters: {}, events: {} }}
+const __propDef = __sveltets_1_partial(__sveltets_1_with_any_event(render()));
+/** @typedef {typeof __propDef.props}  InputProps */
+/** @typedef {typeof __propDef.events}  InputEvents */
+/** @typedef {typeof __propDef.slots}  InputSlots */
+
+export default class Input extends __sveltets_1_createSvelteComponentTyped(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/input.svelte
new file mode 100644
index 00000000..9c0c4c85
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/transforms-interfaces-dts/input.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+    import type { Foo } from './foo';
+    export let foo: Foo;
+    interface Bar1 {
+        a: true;
+    }
+    interface Bar2 extends Bar1 {
+        b: false;
+    }
+    interface Bar3 extends Bar1, Bar2 {
+        c: false;
+    }
+    interface Bar4<T extends boolean> extends Bar1, Bar2 {
+        c: false;
+    }
+    export let bar: Bar3;
+</script>