Skip to content

Commit

Permalink
(fix) handle multiple event dispatchers
Browse files Browse the repository at this point in the history
Merge definitions of multiple `createEventDispatcher`-instantations as well as bubbled events.
sveltejs#921
  • Loading branch information
Simon Holthausen committed Apr 6, 2021
1 parent 22a32ef commit f9cb4a4
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 19 deletions.
53 changes: 36 additions & 17 deletions packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -108,8 +108,7 @@ class ComponentEventsFromEventsMap {
private dispatchedEvents = new Set();
private stringVars = new Map<string, string>();
private eventDispatcherImport = '';
private eventDispatcherTyping?: string;
private dispatcherName = '';
private eventDispatchers: { name: string; typing?: string }[] = [];

constructor(private eventHandler: EventHandler) {
this.events = this.extractEvents(eventHandler);
Expand Down Expand Up @@ -155,38 +154,50 @@ 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'}>`,
doc: getDoc(member)
});
});
} 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);
}
}
}
Expand All @@ -196,18 +207,26 @@ class ComponentEventsFromEventsMap {
eventName: string,
info: { type: string; doc?: string } = { type: 'CustomEvent<any>' }
) {
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<any>' });
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(', ') +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let assert = require('assert');

module.exports = function ({ events }) {
assert.deepEqual(events.getAll(), [
{ name: 'click', type: 'Event' },
{ name: 'hi', type: 'CustomEvent<any>' },
{ name: 'bye', type: 'CustomEvent<any>' }
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
///<reference types="svelte" />
<></>;
import { createEventDispatcher, abc } from "svelte";
function render() {



const notDispatch = abc();
const dispatch1 = createEventDispatcher();
const dispatch2 = createEventDispatcher();

dispatch1('hi', true);
dispatch2('bye', true);
;
() => (<>

<button onclick={undefined}></button></>);
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))) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { createEventDispatcher, abc } from "svelte";
const notDispatch = abc();
const dispatch1 = createEventDispatcher();
const dispatch2 = createEventDispatcher();
dispatch1('hi', true);
dispatch2('bye', true);
</script>

<button on:click></button>
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function render() {
() => (<>

<button onclick={() => dispatch('btn', '')}></button></>);
return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{
return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{
/**
* A DOC
*/
Expand All @@ -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))) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let assert = require('assert');

module.exports = function ({ events }) {
assert.deepEqual(events.getAll(), [
{ name: 'click', type: 'Event' },
{ name: 'hi', type: 'CustomEvent<any>' },
{ name: 'bye', type: 'CustomEvent<any>' }
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
///<reference types="svelte" />
<></>;
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);
;
() => (<>

<button onclick={undefined}></button></>);
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))) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script>
import { createEventDispatcher, abc } from "svelte";
const notDispatch = abc();
const dispatch1 = createEventDispatcher<{
/**
* A DOC
*/
hi: boolean;
}>();
const dispatch2 = createEventDispatcher<{hi: string;}>();
const dispatch3 = createEventDispatcher();
dispatch3('bye', true);
</script>

<button on:click></button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let assert = require('assert');

module.exports = function ({ events }) {
assert.deepEqual(events.getAll(), [
{ name: 'click', type: 'Event' },
{ name: 'hi', type: 'CustomEvent<boolean>', doc: '\nA DOC\n' },
{ name: 'btn', type: 'CustomEvent<string>', doc: undefined },
{ name: 'bye', type: 'CustomEvent<any>' }
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
///<reference types="svelte" />
<></>;
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);
;
() => (<>

<button onclick={undefined}></button></>);
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))) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script>
import { createEventDispatcher, abc } from "svelte";
const notDispatch = abc();
const dispatch1 = createEventDispatcher<{
/**
* A DOC
*/
hi: boolean;
}>();
const dispatch2 = createEventDispatcher<{btn: string;}>();
const dispatch3 = createEventDispatcher();
dispatch3('bye', true);
</script>

<button on:click></button>

0 comments on commit f9cb4a4

Please sign in to comment.