diff --git a/docs/preprocessors/typescript.md b/docs/preprocessors/typescript.md
index 7345702ea..e55bf4f7d 100644
--- a/docs/preprocessors/typescript.md
+++ b/docs/preprocessors/typescript.md
@@ -44,7 +44,31 @@ Hit `ctrl-shift-p` or `cmd-shift-p` on mac, type `svelte restart`, and select `S
## Typing component events
-When you are using TypeScript, you can type which events your component has by defining a reserved `interface` (_NOT_ `type`) called `ComponentEvents`:
+When you are using TypeScript, you can type which events your component has in two ways:
+
+The first and possibly most often used way is to type the `createEventDispatcher` invocation like this:
+
+```html
+
+```
+
+This will make sure that if you use `dispatch` that you can only invoke it with the specified names and its types.
+
+Note though that this will _NOT_ make the events strict so that you get type errors when trying to listen to other events when using the component. Due to Svelte's dynamic events creation, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of another import. This is almost impossible to infer.
+
+If you want strict events, you can do so by defining a reserved `interface` (_NOT_ `type`) called `ComponentEvents`:
```html
```
-> In case you ask why the events cannot be infered: Due to Svelte's dynamic nature, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of a mixin. This is almost impossible to infer, so we need you to tell us which events are possible.
-
## Troubleshooting / FAQ
### I cannot use TS inside my script even when `lang="ts"` is present
@@ -130,13 +152,13 @@ Create a `additional-svelte-jsx.d.ts` file:
```ts
declare namespace svelte.JSX {
- interface HTMLAttributes {
- // If you want to use on:beforeinstallprompt
- onbeforeinstallprompt?: (event: any) => any;
- // If you want to use myCustomAttribute={..} (note: all lowercase)
- mycustomattribute?: any;
- // You can replace any with something more specific if you like
- }
+ interface HTMLAttributes {
+ // If you want to use on:beforeinstallprompt
+ onbeforeinstallprompt?: (event: any) => any;
+ // If you want to use myCustomAttribute={..} (note: all lowercase)
+ mycustomattribute?: any;
+ // You can replace any with something more specific if you like
+ }
}
```
diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts
index 9cda421c7..c56836532 100644
--- a/packages/svelte2tsx/src/svelte2tsx/index.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/index.ts
@@ -5,11 +5,7 @@ import path from 'path';
import { convertHtmlxToJsx } from '../htmlxtojsx';
import { parseHtmlx } from '../utils/htmlxparser';
import { ComponentDocumentation } from './nodes/ComponentDocumentation';
-import {
- ComponentEvents,
- ComponentEventsFromEventsMap,
- ComponentEventsFromInterface,
-} from './nodes/ComponentEvents';
+import { ComponentEvents } from './nodes/ComponentEvents';
import { EventHandler } from './nodes/event-handler';
import { ExportedNames } from './nodes/ExportedNames';
import { createClassGetters, createRenderFunctionGetterStr } from './nodes/exportgetters';
@@ -179,6 +175,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
case 'Identifier':
handleIdentifier(node);
stores.handleIdentifier(node, parent, prop);
+ eventHandler.handleIdentifier(node, parent, prop);
break;
case 'Slot':
slotHandler.handleSlot(node, templateScope);
@@ -266,7 +263,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult {
moduleScriptTag,
scriptTag,
slots: slotHandler.getSlotDef(),
- events: new ComponentEventsFromEventsMap(eventHandler),
+ events: new ComponentEvents(eventHandler),
uses$$props,
uses$$restProps,
uses$$slots,
@@ -472,7 +469,7 @@ export function svelte2tsx(
str,
uses$$propsOr$$restProps: uses$$props || uses$$restProps,
strictMode: !!options?.strictMode,
- strictEvents: events instanceof ComponentEventsFromInterface,
+ strictEvents: events.hasInterface(),
isTsFile: options?.isTsFile,
getters,
fileName: options?.filename,
@@ -485,6 +482,6 @@ export function svelte2tsx(
code: str.toString(),
map: str.generateMap({ hires: true, source: options?.filename }),
exportedNames,
- events,
+ events: events.createAPI(),
};
}
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
index c5b8ba134..a7e690aae 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
@@ -1,27 +1,87 @@
import ts from 'typescript';
import { EventHandler } from './event-handler';
-import { getVariableAtTopLevel } from '../utils/tsAst';
+import { getVariableAtTopLevel, getLeadingDoc } from '../utils/tsAst';
-export abstract class ComponentEvents {
- protected events = new Map();
+/**
+ * This class accumulates all events that are dispatched from the component.
+ * It also tracks bubbled/forwarded events.
+ *
+ * It can not track events which are not fired through a variable
+ * which was not instantiated within the component with `createEventDispatcher`.
+ * This means that event dispatchers which are defined outside of the component and then imported do not get picked up.
+ *
+ * The logic is as follows:
+ * - If there exists a ComponentEvents interface definition, use that and skip the rest
+ * - Else first try to find the `createEventDispatcher` import
+ * - If it exists, try to find the variable where `createEventDispatcher()` is assigned to
+ * - If that variable is found, try to find out if it's typed.
+ * - If yes, extract the event names and the event types from it
+ * - If no, track all invocations of it to get the event names
+ */
+export class ComponentEvents {
+ private componentEventsInterface?: ComponentEventsFromInterface;
+ private componentEventsFromEventsMap: ComponentEventsFromEventsMap;
- getAll(): { name: string; type?: string; doc?: string }[] {
+ private get eventsClass() {
+ return this.componentEventsInterface || this.componentEventsFromEventsMap;
+ }
+
+ constructor(eventHandler: EventHandler) {
+ this.componentEventsFromEventsMap = new ComponentEventsFromEventsMap(eventHandler);
+ }
+
+ /**
+ * Collect state and create the API which will be part
+ * of the return object of the `svelte2tsx` function.
+ */
+ createAPI() {
const entries: { name: string; type: string; doc?: string }[] = [];
- const iterableEntries = this.events.entries();
+ const iterableEntries = this.eventsClass.events.entries();
for (const entry of iterableEntries) {
entries.push({ name: entry[0], ...entry[1] });
}
- return entries;
+ return {
+ getAll(): { name: string; type?: string; doc?: string }[] {
+ return entries;
+ },
+ };
+ }
+
+ setComponentEventsInterface(node: ts.InterfaceDeclaration): void {
+ this.componentEventsInterface = new ComponentEventsFromInterface(node);
+ }
+
+ hasInterface(): boolean {
+ return !!this.componentEventsInterface;
+ }
+
+ checkIfImportIsEventDispatcher(node: ts.ImportDeclaration): void {
+ this.componentEventsFromEventsMap.checkIfImportIsEventDispatcher(node);
+ }
+
+ checkIfIsStringLiteralDeclaration(node: ts.VariableDeclaration): void {
+ this.componentEventsFromEventsMap.checkIfIsStringLiteralDeclaration(node);
+ }
+
+ checkIfDeclarationInstantiatedEventDispatcher(node: ts.VariableDeclaration): void {
+ this.componentEventsFromEventsMap.checkIfDeclarationInstantiatedEventDispatcher(node);
+ }
+
+ checkIfCallExpressionIsDispatch(node: ts.CallExpression): void {
+ this.componentEventsFromEventsMap.checkIfCallExpressionIsDispatch(node);
}
- abstract toDefString(): string;
+ toDefString(): string {
+ return this.eventsClass.toDefString();
+ }
}
-export class ComponentEventsFromInterface extends ComponentEvents {
+class ComponentEventsFromInterface {
+ events = new Map();
+
constructor(node: ts.InterfaceDeclaration) {
- super();
this.events = this.extractEvents(node);
}
@@ -33,103 +93,199 @@ export class ComponentEventsFromInterface extends ComponentEvents {
const map = new Map();
node.members.filter(ts.isPropertySignature).forEach((member) => {
- map.set(this.getName(member.name), {
+ map.set(getName(member.name), {
type: member.type?.getText() || 'Event',
- doc: this.getDoc(node, member),
+ doc: getDoc(member),
});
});
return map;
}
+}
- private getName(prop: ts.PropertyName) {
- if (ts.isIdentifier(prop) || ts.isStringLiteral(prop)) {
- return prop.text;
+class ComponentEventsFromEventsMap {
+ events = new Map();
+ private dispatchedEvents = new Set();
+ private stringVars = new Map();
+ private eventDispatcherImport = '';
+ private eventDispatcherTyping?: string;
+ private dispatcherName = '';
+
+ constructor(private eventHandler: EventHandler) {
+ this.events = this.extractEvents(eventHandler);
+ }
+
+ checkIfImportIsEventDispatcher(node: ts.ImportDeclaration) {
+ if (this.eventDispatcherImport) {
+ return;
+ }
+ if (ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text !== 'svelte') {
+ return;
}
- if (ts.isComputedPropertyName(prop)) {
- if (ts.isIdentifier(prop.expression)) {
- const identifierName = prop.expression.text;
- const identifierValue = this.getIdentifierValue(prop, identifierName);
- if (!identifierValue) {
- this.throwError(prop);
- }
- return identifierValue;
+ const namedImports = node.importClause?.namedBindings;
+ if (ts.isNamedImports(namedImports)) {
+ const eventDispatcherImport = namedImports.elements.find(
+ // If it's an aliased import, propertyName is set
+ (el) => (el.propertyName || el.name).text === 'createEventDispatcher',
+ );
+ if (eventDispatcherImport) {
+ this.eventDispatcherImport = eventDispatcherImport.name.text;
}
}
-
- this.throwError(prop);
}
- private getIdentifierValue(prop: ts.ComputedPropertyName, identifierName: string) {
- const variable = getVariableAtTopLevel(prop.getSourceFile(), identifierName);
- if (variable && ts.isStringLiteral(variable.initializer)) {
- return variable.initializer.text;
+ checkIfIsStringLiteralDeclaration(node: ts.VariableDeclaration) {
+ if (
+ ts.isIdentifier(node.name) &&
+ node.initializer &&
+ ts.isStringLiteral(node.initializer)
+ ) {
+ this.stringVars.set(node.name.text, node.initializer.text);
}
}
- private throwError(prop: ts.PropertyName) {
- const error: any = new Error(
- 'The ComponentEvents interface can only have properties of type ' +
- 'Identifier, StringLiteral or ComputedPropertyName. ' +
- 'In case of ComputedPropertyName, ' +
- 'it must be a const declared within the component and initialized with a string.',
- );
- error.start = toLineColumn(prop.getStart());
- error.end = toLineColumn(prop.getEnd());
- throw error;
-
- function toLineColumn(pos: number) {
- const lineChar = prop.getSourceFile().getLineAndCharacterOfPosition(pos);
- return {
- line: lineChar.line + 1,
- column: lineChar.character,
- };
+ checkIfDeclarationInstantiatedEventDispatcher(node: ts.VariableDeclaration) {
+ if (!ts.isIdentifier(node.name) || !node.initializer) {
+ return;
}
- }
- private getDoc(node: ts.InterfaceDeclaration, member: ts.PropertySignature) {
- let doc = undefined;
- const comment = ts.getLeadingCommentRanges(
- node.getText(),
- member.getFullStart() - node.getStart(),
- );
+ if (
+ ts.isCallExpression(node.initializer) &&
+ ts.isIdentifier(node.initializer.expression) &&
+ node.initializer.expression.text === this.eventDispatcherImport
+ ) {
+ this.dispatcherName = node.name.text;
+ const dispatcherTyping = node.initializer.typeArguments?.[0];
- if (comment) {
- doc = node
- .getText()
- .substring(comment[0].pos, comment[0].end)
- .split('\n')
- .map((line) =>
- // Remove /** */
- line
- .replace(/\s*\/\*\*/, '')
- .replace(/\s*\*\//, '')
- .replace(/\s*\*/, '')
- .trim(),
- )
- .join('\n');
+ if (dispatcherTyping && ts.isTypeLiteralNode(dispatcherTyping)) {
+ this.eventDispatcherTyping = dispatcherTyping.getText();
+ dispatcherTyping.members.filter(ts.isPropertySignature).forEach((member) => {
+ this.addToEvents(getName(member.name), {
+ type: `CustomEvent<${member.type?.getText() || 'any'}>`,
+ doc: getDoc(member),
+ });
+ });
+ } else {
+ this.eventHandler
+ .getDispatchedEventsForIdentifier(this.dispatcherName)
+ .forEach((evtName) => this.addToEvents(evtName));
+ }
}
+ }
- return doc;
+ checkIfCallExpressionIsDispatch(node: ts.CallExpression) {
+ if (
+ !this.eventDispatcherTyping &&
+ ts.isIdentifier(node.expression) &&
+ node.expression.text === this.dispatcherName
+ ) {
+ const firstArg = node.arguments[0];
+ if (ts.isStringLiteral(firstArg)) {
+ this.addToEvents(firstArg.text);
+ } else if (ts.isIdentifier(firstArg)) {
+ const str = this.stringVars.get(firstArg.text);
+ if (str) {
+ this.addToEvents(str);
+ }
+ }
+ }
}
-}
-export class ComponentEventsFromEventsMap extends ComponentEvents {
- constructor(private eventHandler: EventHandler) {
- super();
- this.events = this.extractEvents(eventHandler);
+ private addToEvents(
+ eventName: string,
+ info: { type: string; doc?: string } = { type: 'CustomEvent' },
+ ) {
+ this.events.set(eventName, info);
+ this.dispatchedEvents.add(eventName);
}
toDefString() {
- return this.eventHandler.eventMapToString();
+ if (this.eventDispatcherTyping) {
+ return `__sveltets_toEventTypings<${this.eventDispatcherTyping}>()`;
+ }
+ return (
+ '{' +
+ this.eventHandler.bubbledEventsMapToString() +
+ [...this.dispatchedEvents.keys()]
+ .map((e) => `'${e}': __sveltets_customEvent`)
+ .join(', ') +
+ '}'
+ );
}
private extractEvents(eventHandler: EventHandler) {
const map = new Map();
- for (const name of eventHandler.getEvents().keys()) {
+ for (const name of eventHandler.getBubbledEvents().keys()) {
map.set(name, { type: 'Event' });
}
return map;
}
}
+
+function getName(prop: ts.PropertyName) {
+ if (ts.isIdentifier(prop) || ts.isStringLiteral(prop)) {
+ return prop.text;
+ }
+
+ if (ts.isComputedPropertyName(prop)) {
+ if (ts.isIdentifier(prop.expression)) {
+ const identifierName = prop.expression.text;
+ const identifierValue = getIdentifierValue(prop, identifierName);
+ if (!identifierValue) {
+ throwError(prop);
+ }
+ return identifierValue;
+ }
+ }
+
+ throwError(prop);
+}
+
+function getIdentifierValue(prop: ts.ComputedPropertyName, identifierName: string) {
+ const variable = getVariableAtTopLevel(prop.getSourceFile(), identifierName);
+ if (variable && ts.isStringLiteral(variable.initializer)) {
+ return variable.initializer.text;
+ }
+}
+
+function throwError(prop: ts.PropertyName) {
+ const error: any = new Error(
+ 'The ComponentEvents interface can only have properties of type ' +
+ 'Identifier, StringLiteral or ComputedPropertyName. ' +
+ 'In case of ComputedPropertyName, ' +
+ 'it must be a const declared within the component and initialized with a string.',
+ );
+ error.start = toLineColumn(prop.getStart());
+ error.end = toLineColumn(prop.getEnd());
+ throw error;
+
+ function toLineColumn(pos: number) {
+ const lineChar = prop.getSourceFile().getLineAndCharacterOfPosition(pos);
+ return {
+ line: lineChar.line + 1,
+ column: lineChar.character,
+ };
+ }
+}
+
+function getDoc(member: ts.PropertySignature) {
+ let doc = undefined;
+ const comment = getLeadingDoc(member);
+
+ if (comment) {
+ doc = comment
+ .split('\n')
+ .map((line) =>
+ // Remove /** */
+ line
+ .replace(/\s*\/\*\*/, '')
+ .replace(/\s*\*\//, '')
+ .replace(/\s*\*/, '')
+ .trim(),
+ )
+ .join('\n');
+ }
+
+ return doc;
+}
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
index b34292ff3..334614e03 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts
@@ -1,4 +1,5 @@
import ts from 'typescript';
+import { getLeadingDoc } from '../utils/tsAst';
export interface IExportedNames {
has(name: string): boolean;
@@ -49,15 +50,7 @@ export class ExportedNames
const exportExpr = target?.parent?.parent?.parent;
if (exportExpr) {
- const fileText = exportExpr.getSourceFile().getFullText();
- const comment = ts.getLeadingCommentRanges(fileText, exportExpr.getFullStart());
-
- if (comment) {
- const [first] = comment;
- if (first?.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
- doc = fileText.substring(first.pos, first.end);
- }
- }
+ doc = getLeadingDoc(exportExpr);
}
return doc;
diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/event-handler.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/event-handler.ts
index 2ca21104a..5de08af79 100644
--- a/packages/svelte2tsx/src/svelte2tsx/nodes/event-handler.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/nodes/event-handler.ts
@@ -1,36 +1,60 @@
import { Node } from 'estree-walker';
export class EventHandler {
- events = new Map();
+ private bubbledEvents = new Map();
+ private callees: { name: string; parent: Node }[] = [];
- handleEventHandler = (node: Node, parent: Node) => {
+ handleEventHandler(node: Node, parent: Node): void {
const eventName = node.name;
- const handleEventHandlerBubble = () => {
- const componentEventDef = `__sveltets_instanceOf(${parent.name})`;
- // eslint-disable-next-line max-len
- const exp = `__sveltets_bubbleEventDef(${componentEventDef}.$$events_def, '${eventName}')`;
-
- const exist = this.events.get(eventName);
- this.events.set(eventName, exist ? [].concat(exist, exp) : exp);
- };
-
// pass-through/ bubble
if (!node.expression) {
if (parent.type === 'InlineComponent') {
- handleEventHandlerBubble();
+ this.handleEventHandlerBubble(parent, eventName);
} else {
- this.events.set(eventName, getEventDefExpressionForNonCompoent(eventName, parent));
+ this.bubbledEvents.set(
+ eventName,
+ getEventDefExpressionForNonCompoent(eventName, parent),
+ );
}
}
- };
+ }
+
+ handleIdentifier(node: Node, parent: Node, prop: string): void {
+ if (prop === 'callee') {
+ this.callees.push({ name: node.name, parent });
+ }
+ }
+
+ getBubbledEvents() {
+ return this.bubbledEvents;
+ }
+
+ getDispatchedEventsForIdentifier(name: string) {
+ const eventNames = new Set();
+
+ this.callees.forEach((callee) => {
+ if (callee.name === name) {
+ const [name] = callee.parent.arguments;
+
+ if (name.value !== undefined) {
+ eventNames.add(name.value);
+ }
+ }
+ });
+
+ return eventNames;
+ }
- getEvents() {
- return this.events;
+ bubbledEventsMapToString() {
+ return Array.from(this.bubbledEvents.entries()).map(eventMapEntryToString).join(', ');
}
- eventMapToString() {
- return '{' + Array.from(this.events.entries()).map(eventMapEntryToString).join(', ') + '}';
+ private handleEventHandlerBubble(parent: Node, eventName: string): void {
+ const componentEventDef = `__sveltets_instanceOf(${parent.name})`;
+ const exp = `__sveltets_bubbleEventDef(${componentEventDef}.$$events_def, '${eventName}')`;
+ const exist = this.bubbledEvents.get(eventName);
+ this.bubbledEvents.set(eventName, exist ? [].concat(exist, exp) : exp);
}
}
diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
index 1d9058528..94e15e55b 100644
--- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts
@@ -4,7 +4,7 @@ import * as ts from 'typescript';
import { findExportKeyword, getBinaryAssignmentExpr } from './utils/tsAst';
import { ExportedNames } from './nodes/ExportedNames';
import { ImplicitTopLevelNames } from './nodes/ImplicitTopLevelNames';
-import { ComponentEvents, ComponentEventsFromInterface } from './nodes/ComponentEvents';
+import { ComponentEvents } from './nodes/ComponentEvents';
import { Scope } from './utils/Scope';
export interface InstanceScriptProcessResult {
@@ -295,7 +295,7 @@ export function processInstanceScriptContent(
const onLeaveCallbacks: onLeaveCallback[] = [];
if (ts.isInterfaceDeclaration(node) && node.name.text === 'ComponentEvents') {
- events = new ComponentEventsFromInterface(node);
+ events.setComponentEventsInterface(node);
}
if (ts.isVariableStatement(node)) {
@@ -364,12 +364,23 @@ export function processInstanceScriptContent(
}
}
- //move imports to top of script so they appear outside our render function
if (ts.isImportDeclaration(node)) {
+ //move imports to top of script so they appear outside our render function
str.move(node.getStart() + astOffset, node.end + astOffset, script.start + 1);
//add in a \n
const originalEndChar = str.original[node.end + astOffset - 1];
str.overwrite(node.end + astOffset - 1, node.end + astOffset, originalEndChar + '\n');
+ // Check if import is the event dispatcher
+ events.checkIfImportIsEventDispatcher(node);
+ }
+
+ if (ts.isVariableDeclaration(node)) {
+ events.checkIfIsStringLiteralDeclaration(node);
+ events.checkIfDeclarationInstantiatedEventDispatcher(node);
+ }
+
+ if (ts.isCallExpression(node)) {
+ events.checkIfCallExpressionIsDispatch(node);
}
if (ts.isVariableDeclaration(parent) && parent.name == node) {
diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
index 8c7d66583..bbca23fd9 100644
--- a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
+++ b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts
@@ -120,3 +120,17 @@ export function getVariableAtTopLevel(
}
}
}
+
+/**
+ * Get the leading multiline trivia doc of the node.
+ */
+export function getLeadingDoc(node: ts.Node): string | undefined {
+ const nodeText = node.getFullText();
+ const comment = ts
+ .getLeadingCommentRanges(nodeText, 0)
+ ?.find((c) => c.kind === ts.SyntaxKind.MultiLineCommentTrivia);
+
+ if (comment) {
+ return nodeText.substring(comment.pos, comment.end);
+ }
+}
diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts
index b37329cee..ee382e4ec 100644
--- a/packages/svelte2tsx/svelte-shims.d.ts
+++ b/packages/svelte2tsx/svelte-shims.d.ts
@@ -153,6 +153,9 @@ declare function __sveltets_bubbleEventDef(
events: any, eventKey: string
): any;
+declare const __sveltets_customEvent: CustomEvent;
+declare function __sveltets_toEventTypings(): {[Key in keyof Typings]: CustomEvent};
+
declare function __sveltets_unionType(t1: T1, t2: T2): T1 | T2;
declare function __sveltets_unionType(t1: T1, t2: T2, t3: T3): T1 | T2 | T3;
declare function __sveltets_unionType(t1: T1, t2: T2, t3: T3, t4: T4): T1 | T2 | T3 | T4;
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.js
new file mode 100644
index 000000000..645047a7e
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.js
@@ -0,0 +1,12 @@
+let assert = require('assert')
+
+module.exports = function ({events}) {
+ assert.deepEqual(
+ events.getAll(),
+ [
+ {name: 'btn', type: 'CustomEvent'},
+ {name: 'hi', type: 'CustomEvent'},
+ {name: 'bye', type: 'CustomEvent'},
+ ]
+ );
+}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.tsx
new file mode 100644
index 000000000..007d13e14
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/expected.tsx
@@ -0,0 +1,24 @@
+///
+<>>;
+import { createEventDispatcher as foo, abc } from "svelte";
+function render() {
+
+
+
+ const notDispatch = abc();
+ const dispatch = foo();
+
+ dispatch('hi', true);
+
+ function bye() {
+ const bla = 'bye';
+ dispatch(bla, false);
+ }
+;
+() => (<>
+
+>);
+return { props: {}, slots: {}, getters: {}, events: {'btn': __sveltets_customEvent, 'hi': __sveltets_customEvent, 'bye': __sveltets_customEvent} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/input.svelte
new file mode 100644
index 000000000..9fc0d7a2d
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events-alias/input.svelte
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.js
new file mode 100644
index 000000000..645047a7e
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.js
@@ -0,0 +1,12 @@
+let assert = require('assert')
+
+module.exports = function ({events}) {
+ assert.deepEqual(
+ events.getAll(),
+ [
+ {name: 'btn', type: 'CustomEvent'},
+ {name: 'hi', type: 'CustomEvent'},
+ {name: 'bye', type: 'CustomEvent'},
+ ]
+ );
+}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.tsx
new file mode 100644
index 000000000..9ab7257f0
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/expected.tsx
@@ -0,0 +1,24 @@
+///
+<>>;
+import { createEventDispatcher, abc } from "svelte";
+function render() {
+
+
+
+ const notDispatch = abc();
+ const dispatch = createEventDispatcher();
+
+ dispatch('hi', true);
+
+ function bye() {
+ const bla = 'bye';
+ dispatch(bla, false);
+ }
+;
+() => (<>
+
+>);
+return { props: {}, slots: {}, getters: {}, events: {'btn': __sveltets_customEvent, 'hi': __sveltets_customEvent, 'bye': __sveltets_customEvent} }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/input.svelte
new file mode 100644
index 000000000..93facad1c
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatcher-events/input.svelte
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.js
new file mode 100644
index 000000000..a76bb956a
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.js
@@ -0,0 +1,12 @@
+let assert = require('assert')
+
+module.exports = function ({events}) {
+ assert.deepEqual(
+ events.getAll(),
+ [
+ {name: 'hi', type: 'CustomEvent', doc: '\nA DOC\n'},
+ {name: 'bye', type: 'CustomEvent', doc: '\nANOTHER DOC\n'},
+ {name: 'btn', type: 'CustomEvent', doc: undefined}
+ ]
+ );
+}
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx
new file mode 100644
index 000000000..53c489e3a
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx
@@ -0,0 +1,44 @@
+///
+<>>;
+import { createEventDispatcher, abc } from "svelte";
+function render() {
+
+
+
+ const notDispatch = abc();
+ const bla = 'bye';
+ const dispatch = createEventDispatcher<{
+ /**
+ * A DOC
+ */
+ hi: boolean;
+ /**
+ * ANOTHER DOC
+ */
+ [bla]: boolean;
+ // not this
+ btn: string;}>();
+
+ dispatch('hi', true);
+
+ function bye() {
+ dispatch(bla, false);
+ }
+;
+() => (<>
+
+>);
+return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{
+ /**
+ * A DOC
+ */
+ hi: boolean;
+ /**
+ * ANOTHER DOC
+ */
+ [bla]: boolean;
+ // not this
+ btn: string;}>() }}
+
+export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
+}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/input.svelte
new file mode 100644
index 000000000..484d8754e
--- /dev/null
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/input.svelte
@@ -0,0 +1,25 @@
+
+
+
\ No newline at end of file