Repository: sveltejs/language-tools
Pull request #2004 (2023-05-08 09:38:30)

(fix) allow non-literal createEventDispatcher generic template
#1388. Also deprecated `SvelteCompiledToTsx.events`. We can add support if the interface is inside the script tag. But not imported interfaces, and we no longer use this, so adding support at the cost of maintenance burden and runtime overhead doesn't make much sense. 

If anyone uses this API for documentation purposes, consider using TypeScript's TypeChecker to analyse it instead. 


Patch:
diff --git a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts
index ae9a5913..bf3c6a61 100644
--- a/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts
+++ b/packages/language-server/src/plugins/typescript/ComponentInfoProvider.ts
@@ -1,9 +1,8 @@
-import { ComponentEvents } from 'svelte2tsx';
 import ts from 'typescript';
 import { flatten, isNotNullOrUndefined } from '../../utils';
 import { findContainingNode } from './features/utils';
 
-export type ComponentPartInfo = ReturnType<ComponentEvents['getAll']>;
+export type ComponentPartInfo = Array<{ name: string; type: string; doc?: string }>;
 
 export interface ComponentInfoProvider {
     getEvents(): ComponentPartInfo;
diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts
index 879fc610..4749fce0 100644
--- a/packages/svelte2tsx/index.d.ts
+++ b/packages/svelte2tsx/index.d.ts
@@ -4,6 +4,9 @@ export interface SvelteCompiledToTsx {
     code: string;
     map: import("magic-string").SourceMap;
     exportedNames: IExportedNames;
+    /**
+     * @deprecated Use TypeScript's `TypeChecker` to get the type information instead. This only covers literal typings.
+     */
     events: ComponentEvents;
 }
 
@@ -11,6 +14,9 @@ export interface IExportedNames {
     has(name: string): boolean;
 }
 
+/**
+ * @deprecated Use TypeScript's `TypeChecker` to get the type information instead. This only covers literal typings.
+ */
 export interface ComponentEvents {
     getAll(): { name: string; type: string; doc?: string }[];
 }
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
index 48e1c810..b2bb18e8 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
@@ -224,17 +224,20 @@ class ComponentEventsFromEventsMap {
 
         const { dispatcherTyping, dispatcherName } = result;
 
-        if (dispatcherTyping && ts.isTypeLiteralNode(dispatcherTyping)) {
+        if (dispatcherTyping) {
             this.eventDispatchers.push({
                 name: dispatcherName,
                 typing: dispatcherTyping.getText()
             });
-            dispatcherTyping.members.filter(ts.isPropertySignature).forEach((member) => {
-                this.addToEvents(getName(member.name), {
-                    type: `CustomEvent<${member.type?.getText() || 'any'}>`,
-                    doc: getDoc(member)
+
+            if (ts.isTypeLiteralNode(dispatcherTyping)) {
+                dispatcherTyping.members.filter(ts.isPropertySignature).forEach((member) => {
+                    this.addToEvents(getName(member.name), {
+                        type: `CustomEvent<${member.type?.getText() || 'any'}>`,
+                        doc: getDoc(member)
+                    });
                 });
-            });
+            }
         } else {
             this.eventDispatchers.push({ name: dispatcherName });
             this.eventHandler


Test patch:
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/expectedv2.ts
new file mode 100644
index 00000000..7458f064
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/expectedv2.ts
@@ -0,0 +1,29 @@
+///<reference types="svelte" />
+;
+import { createEventDispatcher } from "svelte";
+function render() {
+
+    
+
+    type Events = {
+        hi: boolean;
+        bye: boolean;
+        btn: string;
+    };
+
+    const dispatch = createEventDispatcher<Events>();
+
+    dispatch('hi', true);
+
+    function bye() {
+        const bla = 'bye';
+        dispatch(bla, false);
+    }
+;
+async () => {
+
+ { svelteHTML.createElement("button", {  "on:click":() => dispatch('btn', ''),}); }};
+return { props: {} as Record<string, never>, slots: {}, events: {...__sveltets_2_toEventTypings<Events>()} }}
+
+export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_with_any_event(render())) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/input.svelte
new file mode 100644
index 00000000..a3a0cea0
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed-non-literal/input.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+    import { createEventDispatcher } from "svelte";
+
+    type Events = {
+        hi: boolean;
+        bye: boolean;
+        btn: string;
+    };
+
+    const dispatch = createEventDispatcher<Events>();
+
+    dispatch('hi', true);
+
+    function bye() {
+        const bla = 'bye';
+        dispatch(bla, false);
+    }
+</script>
+
+<button on:click={() => dispatch('btn', '')}></button>
\ No newline at end of file