From f9cb4a49902f3b915f9ed7c22e3af5aea31e4347 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 6 Apr 2021 15:30:43 +0200 Subject: [PATCH] (fix) handle multiple event dispatchers Merge definitions of multiple `createEventDispatcher`-instantations as well as bubbled events. #921 --- .../src/svelte2tsx/nodes/ComponentEvents.ts | 53 +++++++++++++------ .../samples/event-dispatchers/expected.js | 9 ++++ .../samples/event-dispatchers/expected.tsx | 21 ++++++++ .../samples/event-dispatchers/input.svelte | 12 +++++ .../ts-event-dispatcher-typed/expected.tsx | 4 +- .../expected.js | 9 ++++ .../expected.tsx | 31 +++++++++++ .../input.svelte | 17 ++++++ .../samples/ts-event-dispatchers/expected.js | 10 ++++ .../samples/ts-event-dispatchers/expected.tsx | 31 +++++++++++ .../samples/ts-event-dispatchers/input.svelte | 17 ++++++ 11 files changed, 195 insertions(+), 19 deletions(-) create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.js create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.js create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.js create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/input.svelte diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts index 011e43024..9f7a9fce8 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts @@ -13,8 +13,8 @@ import { getVariableAtTopLevel, getLastLeadingDoc } from '../utils/tsAst'; * 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 it exists, try to find the variables where `createEventDispatcher()` is assigned to + * - For each variable 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 */ @@ -108,8 +108,7 @@ class ComponentEventsFromEventsMap { private dispatchedEvents = new Set(); private stringVars = new Map(); private eventDispatcherImport = ''; - private eventDispatcherTyping?: string; - private dispatcherName = ''; + private eventDispatchers: { name: string; typing?: string }[] = []; constructor(private eventHandler: EventHandler) { this.events = this.extractEvents(eventHandler); @@ -155,11 +154,14 @@ class ComponentEventsFromEventsMap { ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === this.eventDispatcherImport ) { - this.dispatcherName = node.name.text; + const dispatcherName = node.name.text; const dispatcherTyping = node.initializer.typeArguments?.[0]; if (dispatcherTyping && ts.isTypeLiteralNode(dispatcherTyping)) { - this.eventDispatcherTyping = dispatcherTyping.getText(); + 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'}>`, @@ -167,26 +169,35 @@ class ComponentEventsFromEventsMap { }); }); } else { + this.eventDispatchers.push({ name: dispatcherName }); this.eventHandler - .getDispatchedEventsForIdentifier(this.dispatcherName) - .forEach((evtName) => this.addToEvents(evtName)); + .getDispatchedEventsForIdentifier(dispatcherName) + .forEach((evtName) => { + this.addToEvents(evtName); + this.dispatchedEvents.add(evtName); + }); } } } checkIfCallExpressionIsDispatch(node: ts.CallExpression) { if ( - !this.eventDispatcherTyping && - ts.isIdentifier(node.expression) && - node.expression.text === this.dispatcherName + this.eventDispatchers.some( + (dispatcher) => + !dispatcher.typing && + ts.isIdentifier(node.expression) && + node.expression.text === dispatcher.name + ) ) { const firstArg = node.arguments[0]; if (ts.isStringLiteral(firstArg)) { this.addToEvents(firstArg.text); + this.dispatchedEvents.add(firstArg.text); } else if (ts.isIdentifier(firstArg)) { const str = this.stringVars.get(firstArg.text); if (str) { this.addToEvents(str); + this.dispatchedEvents.add(str); } } } @@ -196,18 +207,26 @@ class ComponentEventsFromEventsMap { eventName: string, info: { type: string; doc?: string } = { type: 'CustomEvent' } ) { - this.events.set(eventName, info); - this.dispatchedEvents.add(eventName); + if (this.events.has(eventName)) { + // If there are multiple definitions, merge them by falling back to any-typing + this.events.set(eventName, { type: 'CustomEvent' }); + this.dispatchedEvents.add(eventName); + } else { + this.events.set(eventName, info); + } } toDefString() { - if (this.eventDispatcherTyping) { - return `__sveltets_toEventTypings<${this.eventDispatcherTyping}>()`; - } - return ( '{' + [ + ...this.eventDispatchers + .map( + (dispatcher) => + dispatcher.typing && + `...__sveltets_toEventTypings<${dispatcher.typing}>()` + ) + .filter((str) => !!str), ...this.eventHandler.bubbledEventsAsStrings(), ...[...this.dispatchedEvents.keys()].map((e) => `'${e}': __sveltets_customEvent`) ].join(', ') + diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.js new file mode 100644 index 000000000..ef47078ef --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.js @@ -0,0 +1,9 @@ +let assert = require('assert'); + +module.exports = function ({ events }) { + assert.deepEqual(events.getAll(), [ + { name: 'click', type: 'Event' }, + { name: 'hi', type: 'CustomEvent' }, + { name: 'bye', type: 'CustomEvent' } + ]); +}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.tsx new file mode 100644 index 000000000..8313f1340 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/expected.tsx @@ -0,0 +1,21 @@ +/// +<>; +import { createEventDispatcher, abc } from "svelte"; +function render() { + + + + const notDispatch = abc(); + const dispatch1 = createEventDispatcher(); + const dispatch2 = createEventDispatcher(); + + dispatch1('hi', true); + dispatch2('bye', true); +; +() => (<> + +); +return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_mapElementEvent('click'), '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-dispatchers/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/input.svelte new file mode 100644 index 000000000..c8be16c96 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-dispatchers/input.svelte @@ -0,0 +1,12 @@ + + + \ No newline at end of file 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 index a1a907310..be079bd09 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx @@ -31,7 +31,7 @@ function render() { () => (<> ); -return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{ +return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{ /** * A DOC */ @@ -44,7 +44,7 @@ return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{ */ [bla]: boolean; // not this - btn: string;}>() }} + btn: string;}>()} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) { } \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.js new file mode 100644 index 000000000..ef47078ef --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.js @@ -0,0 +1,9 @@ +let assert = require('assert'); + +module.exports = function ({ events }) { + assert.deepEqual(events.getAll(), [ + { name: 'click', type: 'Event' }, + { name: 'hi', type: 'CustomEvent' }, + { name: 'bye', type: 'CustomEvent' } + ]); +}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.tsx new file mode 100644 index 000000000..8de728584 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/expected.tsx @@ -0,0 +1,31 @@ +/// +<>; +import { createEventDispatcher, abc } from "svelte"; +function render() { + + + + const notDispatch = abc(); + const dispatch1 = createEventDispatcher<{ + /** + * A DOC + */ + hi: boolean; + }>(); + const dispatch2 = createEventDispatcher<{hi: string;}>(); + const dispatch3 = createEventDispatcher(); + + dispatch3('bye', true); +; +() => (<> + +); +return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{ + /** + * A DOC + */ + hi: boolean; + }>(), ...__sveltets_toEventTypings<{hi: string;}>(), 'click':__sveltets_mapElementEvent('click'), 'hi': __sveltets_customEvent, 'bye': __sveltets_customEvent} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/input.svelte new file mode 100644 index 000000000..e933ed4fb --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers-same-event/input.svelte @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.js new file mode 100644 index 000000000..d8d4451bb --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.js @@ -0,0 +1,10 @@ +let assert = require('assert'); + +module.exports = function ({ events }) { + assert.deepEqual(events.getAll(), [ + { name: 'click', type: 'Event' }, + { name: 'hi', type: 'CustomEvent', doc: '\nA DOC\n' }, + { name: 'btn', type: 'CustomEvent', doc: undefined }, + { name: 'bye', type: 'CustomEvent' } + ]); +}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.tsx new file mode 100644 index 000000000..e6c13a989 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/expected.tsx @@ -0,0 +1,31 @@ +/// +<>; +import { createEventDispatcher, abc } from "svelte"; +function render() { + + + + const notDispatch = abc(); + const dispatch1 = createEventDispatcher<{ + /** + * A DOC + */ + hi: boolean; + }>(); + const dispatch2 = createEventDispatcher<{btn: string;}>(); + const dispatch3 = createEventDispatcher(); + + dispatch3('bye', true); +; +() => (<> + +); +return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{ + /** + * A DOC + */ + hi: boolean; + }>(), ...__sveltets_toEventTypings<{btn: string;}>(), 'click':__sveltets_mapElementEvent('click'), 'bye': __sveltets_customEvent} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/input.svelte new file mode 100644 index 000000000..3032507ae --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatchers/input.svelte @@ -0,0 +1,17 @@ + + + \ No newline at end of file