Skip to content

Commit 562c944

Browse files
authored
(fix) handle multiple event dispatchers (#938)
Merge definitions of multiple `createEventDispatcher`-instantations as well as bubbled events. #921
1 parent d73447d commit 562c944

File tree

11 files changed

+195
-19
lines changed

11 files changed

+195
-19
lines changed

packages/svelte2tsx/src/svelte2tsx/nodes/ComponentEvents.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { getVariableAtTopLevel, getLastLeadingDoc } from '../utils/tsAst';
1313
* The logic is as follows:
1414
* - If there exists a ComponentEvents interface definition, use that and skip the rest
1515
* - Else first try to find the `createEventDispatcher` import
16-
* - If it exists, try to find the variable where `createEventDispatcher()` is assigned to
17-
* - If that variable is found, try to find out if it's typed.
16+
* - If it exists, try to find the variables where `createEventDispatcher()` is assigned to
17+
* - For each variable found, try to find out if it's typed.
1818
* - If yes, extract the event names and the event types from it
1919
* - If no, track all invocations of it to get the event names
2020
*/
@@ -108,8 +108,7 @@ class ComponentEventsFromEventsMap {
108108
private dispatchedEvents = new Set();
109109
private stringVars = new Map<string, string>();
110110
private eventDispatcherImport = '';
111-
private eventDispatcherTyping?: string;
112-
private dispatcherName = '';
111+
private eventDispatchers: Array<{ name: string; typing?: string }> = [];
113112

114113
constructor(private eventHandler: EventHandler) {
115114
this.events = this.extractEvents(eventHandler);
@@ -155,38 +154,50 @@ class ComponentEventsFromEventsMap {
155154
ts.isIdentifier(node.initializer.expression) &&
156155
node.initializer.expression.text === this.eventDispatcherImport
157156
) {
158-
this.dispatcherName = node.name.text;
157+
const dispatcherName = node.name.text;
159158
const dispatcherTyping = node.initializer.typeArguments?.[0];
160159

161160
if (dispatcherTyping && ts.isTypeLiteralNode(dispatcherTyping)) {
162-
this.eventDispatcherTyping = dispatcherTyping.getText();
161+
this.eventDispatchers.push({
162+
name: dispatcherName,
163+
typing: dispatcherTyping.getText()
164+
});
163165
dispatcherTyping.members.filter(ts.isPropertySignature).forEach((member) => {
164166
this.addToEvents(getName(member.name), {
165167
type: `CustomEvent<${member.type?.getText() || 'any'}>`,
166168
doc: getDoc(member)
167169
});
168170
});
169171
} else {
172+
this.eventDispatchers.push({ name: dispatcherName });
170173
this.eventHandler
171-
.getDispatchedEventsForIdentifier(this.dispatcherName)
172-
.forEach((evtName) => this.addToEvents(evtName));
174+
.getDispatchedEventsForIdentifier(dispatcherName)
175+
.forEach((evtName) => {
176+
this.addToEvents(evtName);
177+
this.dispatchedEvents.add(evtName);
178+
});
173179
}
174180
}
175181
}
176182

177183
checkIfCallExpressionIsDispatch(node: ts.CallExpression) {
178184
if (
179-
!this.eventDispatcherTyping &&
180-
ts.isIdentifier(node.expression) &&
181-
node.expression.text === this.dispatcherName
185+
this.eventDispatchers.some(
186+
(dispatcher) =>
187+
!dispatcher.typing &&
188+
ts.isIdentifier(node.expression) &&
189+
node.expression.text === dispatcher.name
190+
)
182191
) {
183192
const firstArg = node.arguments[0];
184193
if (ts.isStringLiteral(firstArg)) {
185194
this.addToEvents(firstArg.text);
195+
this.dispatchedEvents.add(firstArg.text);
186196
} else if (ts.isIdentifier(firstArg)) {
187197
const str = this.stringVars.get(firstArg.text);
188198
if (str) {
189199
this.addToEvents(str);
200+
this.dispatchedEvents.add(str);
190201
}
191202
}
192203
}
@@ -196,18 +207,26 @@ class ComponentEventsFromEventsMap {
196207
eventName: string,
197208
info: { type: string; doc?: string } = { type: 'CustomEvent<any>' }
198209
) {
199-
this.events.set(eventName, info);
200-
this.dispatchedEvents.add(eventName);
210+
if (this.events.has(eventName)) {
211+
// If there are multiple definitions, merge them by falling back to any-typing
212+
this.events.set(eventName, { type: 'CustomEvent<any>' });
213+
this.dispatchedEvents.add(eventName);
214+
} else {
215+
this.events.set(eventName, info);
216+
}
201217
}
202218

203219
toDefString() {
204-
if (this.eventDispatcherTyping) {
205-
return `__sveltets_toEventTypings<${this.eventDispatcherTyping}>()`;
206-
}
207-
208220
return (
209221
'{' +
210222
[
223+
...this.eventDispatchers
224+
.map(
225+
(dispatcher) =>
226+
dispatcher.typing &&
227+
`...__sveltets_toEventTypings<${dispatcher.typing}>()`
228+
)
229+
.filter((str) => !!str),
211230
...this.eventHandler.bubbledEventsAsStrings(),
212231
...[...this.dispatchedEvents.keys()].map((e) => `'${e}': __sveltets_customEvent`)
213232
].join(', ') +
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let assert = require('assert');
2+
3+
module.exports = function ({ events }) {
4+
assert.deepEqual(events.getAll(), [
5+
{ name: 'click', type: 'Event' },
6+
{ name: 'hi', type: 'CustomEvent<any>' },
7+
{ name: 'bye', type: 'CustomEvent<any>' }
8+
]);
9+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
///<reference types="svelte" />
2+
<></>;
3+
import { createEventDispatcher, abc } from "svelte";
4+
function render() {
5+
6+
7+
8+
const notDispatch = abc();
9+
const dispatch1 = createEventDispatcher();
10+
const dispatch2 = createEventDispatcher();
11+
12+
dispatch1('hi', true);
13+
dispatch2('bye', true);
14+
;
15+
() => (<>
16+
17+
<button onclick={undefined}></button></>);
18+
return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_mapElementEvent('click'), 'hi': __sveltets_customEvent, 'bye': __sveltets_customEvent} }}
19+
20+
export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) {
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
import { createEventDispatcher, abc } from "svelte";
3+
4+
const notDispatch = abc();
5+
const dispatch1 = createEventDispatcher();
6+
const dispatch2 = createEventDispatcher();
7+
8+
dispatch1('hi', true);
9+
dispatch2('bye', true);
10+
</script>
11+
12+
<button on:click></button>

packages/svelte2tsx/test/svelte2tsx/samples/ts-event-dispatcher-typed/expected.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function render() {
3131
() => (<>
3232

3333
<button onclick={() => dispatch('btn', '')}></button></>);
34-
return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{
34+
return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{
3535
/**
3636
* A DOC
3737
*/
@@ -44,7 +44,7 @@ return { props: {}, slots: {}, getters: {}, events: __sveltets_toEventTypings<{
4444
*/
4545
[bla]: boolean;
4646
// not this
47-
btn: string;}>() }}
47+
btn: string;}>()} }}
4848

4949
export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) {
5050
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let assert = require('assert');
2+
3+
module.exports = function ({ events }) {
4+
assert.deepEqual(events.getAll(), [
5+
{ name: 'click', type: 'Event' },
6+
{ name: 'hi', type: 'CustomEvent<any>' },
7+
{ name: 'bye', type: 'CustomEvent<any>' }
8+
]);
9+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
///<reference types="svelte" />
2+
<></>;
3+
import { createEventDispatcher, abc } from "svelte";
4+
function render() {
5+
6+
7+
8+
const notDispatch = abc();
9+
const dispatch1 = createEventDispatcher<{
10+
/**
11+
* A DOC
12+
*/
13+
hi: boolean;
14+
}>();
15+
const dispatch2 = createEventDispatcher<{hi: string;}>();
16+
const dispatch3 = createEventDispatcher();
17+
18+
dispatch3('bye', true);
19+
;
20+
() => (<>
21+
22+
<button onclick={undefined}></button></>);
23+
return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{
24+
/**
25+
* A DOC
26+
*/
27+
hi: boolean;
28+
}>(), ...__sveltets_toEventTypings<{hi: string;}>(), 'click':__sveltets_mapElementEvent('click'), 'hi': __sveltets_customEvent, 'bye': __sveltets_customEvent} }}
29+
30+
export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) {
31+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
import { createEventDispatcher, abc } from "svelte";
3+
4+
const notDispatch = abc();
5+
const dispatch1 = createEventDispatcher<{
6+
/**
7+
* A DOC
8+
*/
9+
hi: boolean;
10+
}>();
11+
const dispatch2 = createEventDispatcher<{hi: string;}>();
12+
const dispatch3 = createEventDispatcher();
13+
14+
dispatch3('bye', true);
15+
</script>
16+
17+
<button on:click></button>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
let assert = require('assert');
2+
3+
module.exports = function ({ events }) {
4+
assert.deepEqual(events.getAll(), [
5+
{ name: 'click', type: 'Event' },
6+
{ name: 'hi', type: 'CustomEvent<boolean>', doc: '\nA DOC\n' },
7+
{ name: 'btn', type: 'CustomEvent<string>', doc: undefined },
8+
{ name: 'bye', type: 'CustomEvent<any>' }
9+
]);
10+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
///<reference types="svelte" />
2+
<></>;
3+
import { createEventDispatcher, abc } from "svelte";
4+
function render() {
5+
6+
7+
8+
const notDispatch = abc();
9+
const dispatch1 = createEventDispatcher<{
10+
/**
11+
* A DOC
12+
*/
13+
hi: boolean;
14+
}>();
15+
const dispatch2 = createEventDispatcher<{btn: string;}>();
16+
const dispatch3 = createEventDispatcher();
17+
18+
dispatch3('bye', true);
19+
;
20+
() => (<>
21+
22+
<button onclick={undefined}></button></>);
23+
return { props: {}, slots: {}, getters: {}, events: {...__sveltets_toEventTypings<{
24+
/**
25+
* A DOC
26+
*/
27+
hi: boolean;
28+
}>(), ...__sveltets_toEventTypings<{btn: string;}>(), 'click':__sveltets_mapElementEvent('click'), 'bye': __sveltets_customEvent} }}
29+
30+
export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial_ts(__sveltets_with_any_event(render))) {
31+
}

0 commit comments

Comments
 (0)