-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(fromEvent): infer from Node.js EventEmitter with types #6669
Closed
Closed
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f4eddaa
fix(fromEvent): add unit test to show the failure of infer string lit…
huan 0ac11dd
fix(fromEvent): add code to fix the failed unit test
huan bde7c40
fix(fromEvent): make dtslint happy
huan b069b10
fix(fromEvent): fix code
huan 29910b9
fix(fromEvent): add AnyToUnknown converter
huan 62bced1
fix(fromEvent): fix dtslint spec
huan f80414f
fix(fromEvent): update api_guard
huan f25a174
fix(fromEvent): add two event name for multi type testing
huan 62c7582
fix(fromEvent): add helper file with TypeScript overload function wor…
huan c145cac
fix(fromEvent): add helper functio unit test
huan a3fa4a7
fix(fromEvent): add util helper for workaround TypeScript limitation
huan b17c418
fix(fromEvent): clean util function code
huan ff95a6c
fix(fromEvent): clean fromEvent.ts code to make sure its clean
huan 740abf3
fix(fromEvent): integrate code to fromEvent
huan 42aee0d
fix(fromEvent): code clean & add unit test for AnyToUnknown type caster
huan f2638fa
fix(fromEvent): code clean
huan fa79faf
fix(fromEvent): update api guard
huan d6da834
fix(fromEvent): CI green! code clean
huan 1554c9f
fix(fromEvent): increase overload max number to 9
huan 7b7739e
fix(fromEvent): increase overload max number to 9
huan 88acd54
chore(fromEvent): move types test to dtslint & remove comments
huan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { | ||
NodeEventEmitterNameDataPair, | ||
NodeEventEmitterDataType, | ||
NodeEventEmitterDataTypeUnknown, | ||
AnyToUnknown, | ||
} from '../../src/internal/util/NodeEventEmitterDataType'; | ||
|
||
it('NodeEventEmitterDataType smoke testing', () => { | ||
const fooEvent = 'fooEvent'; | ||
const barEvent = 'barEvent'; | ||
|
||
type FOO_DATA = typeof fooEvent; | ||
type BAR_DATA = typeof barEvent; | ||
|
||
class NodeEventEmitterFixture { | ||
addListener(eventName: 'foo', listener: (foo: FOO_DATA) => void): this | ||
addListener(eventName: 'bar', listener: (bar: BAR_DATA) => void): this | ||
addListener(eventName: 'foo' | 'bar', listener: ((foo: FOO_DATA) => void) | ((bar: BAR_DATA) => void)): this { return this; } | ||
|
||
removeListener(eventName: 'foo', listener: (foo: FOO_DATA) => void ): this | ||
removeListener(eventName: 'bar', listener: (bar: BAR_DATA) => void ): this | ||
removeListener(eventName: 'foo' | 'bar', listener: ((foo: FOO_DATA) => void) | ((bar: BAR_DATA) => void)): this { return this; } | ||
|
||
/** | ||
* TODO: JQueryStyle compatible in the future | ||
*/ | ||
// on(eventName: 'foo', listener: (foo: number) => void): void | ||
// on(eventName: 'bar', listener: (bar: string) => void): void | ||
// on(eventName: 'foo' | 'bar', listener: ((foo: number) => void) | ((bar: string) => void)): void {} | ||
|
||
// off(eventName: 'foo', listener: (foo: number) => void ): void | ||
// off(eventName: 'bar', listener: (bar: string) => void ): void | ||
// off(eventName: 'foo' | 'bar', listener: ((foo: number) => void) | ((bar: string) => void)): void {} | ||
} | ||
|
||
it('should get emitter name & data types correctly', () => { | ||
// $ExpectType "fooEvent" | ||
type Foo = NodeEventEmitterDataType<NodeEventEmitterFixture, 'foo'>; | ||
// $ExpectType "barEvent" | ||
type Bar = NodeEventEmitterDataType<NodeEventEmitterFixture, 'bar'>; | ||
}); | ||
|
||
it('should get name & data from NodeEventEmitterNameDataPair', () => { | ||
// type EVENT_PAIR = TypeEventPair<NodeEventEmitterTest['addListener']> | ||
type EVENT_PAIR = NodeEventEmitterNameDataPair<NodeEventEmitterFixture>; | ||
|
||
// $ExpectType "foo" | "bar" | ||
type EVENT_NAME = EVENT_PAIR[0]; | ||
// $ExpecTType FOO_DATA | BAR_DATA | ||
type EVENT_DATA = EVENT_PAIR[1]; | ||
}); | ||
|
||
it('should get `unknown` for `process` events by NodeEventEmitterDataTypeUnknown', () => { | ||
// $ExpectType unknown | ||
type Exit = NodeEventEmitterDataTypeUnknown< | ||
Pick< | ||
typeof process, | ||
'addListener' | 'removeListener' | ||
>, | ||
'exit' | ||
>; | ||
}); | ||
|
||
it('should get `never` for `process` events by NodeEventEmitterDataType', () => { | ||
// $ExpectType never | ||
type Exit = NodeEventEmitterDataType< | ||
Pick< | ||
typeof process, | ||
'addListener' | 'removeListener' | ||
>, | ||
'exit' | ||
> | ||
}); | ||
}); | ||
|
||
it('AnyToUnknown smoke testing', () => { | ||
it('should only convert any to unknown', () => { | ||
type T_ANY = AnyToUnknown<any> | ||
type T_UNKNOWN = AnyToUnknown<unknown> | ||
|
||
type T_BOOLEAN = AnyToUnknown<boolean> | ||
type T_NULL = AnyToUnknown<null> | ||
type T_OBJ = AnyToUnknown<object> | ||
type T_STRING = AnyToUnknown<string> | ||
type T_UNDEFINED = AnyToUnknown<undefined> | ||
type T_VOID = AnyToUnknown<void> | ||
type T_NEVER = AnyToUnknown<never> | ||
|
||
// $ExpectType unknown | ||
type UNKNOWN_TYPE = T_ANY & T_UNKNOWN | ||
|
||
type KNOWN_TYPE = T_VOID | T_BOOLEAN | T_STRING | T_UNDEFINED | T_NULL | T_OBJ | T_NEVER | ||
// $ExpectType true | ||
type T = unknown extends KNOWN_TYPE ? never : true | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
/* eslint-disable no-use-before-define */ | ||
/* eslint-disable max-len */ | ||
|
||
/** | ||
* Node.js EventEmitter Add/Remove Listener interface | ||
*/ | ||
interface L<N, D> { | ||
(name: N, listener: (data: D, ..._: any[]) => any): any; | ||
} | ||
|
||
/** | ||
* | ||
* Overload function inferencer | ||
* | ||
* - L: Listener interface with Add/Remove methods | ||
* - N: Name of the event | ||
* - D: Data of the event | ||
* | ||
*/ | ||
interface L1<N1, D1> extends L<N1, D1> {} | ||
interface L2<N1, N2, D1, D2> extends L<N1, D1>, L<N2, D2> {} | ||
interface L3<N1, N2, N3, D1, D2, D3> extends L<N1, D1>, L<N2, D2>, L<N3, D3> {} | ||
interface L4<N1, N2, N3, N4, D1, D2, D3, D4> extends L<N1, D1>, L<N2, D2>, L<N3, D3>, L<N4, D4> {} | ||
interface L5<N1, N2, N3, N4, N5, D1, D2, D3, D4, D5> extends L<N1, D1>, L<N2, D2>, L<N3, D3>, L<N4, D4>, L<N5, D5> {} | ||
interface L6<N1, N2, N3, N4, N5, N6, D1, D2, D3, D4, D5, D6> extends L<N1, D1>, L<N2, D2>, L<N3, D3>, L<N4, D4>, L<N5, D5>, L<N6, D6> {} | ||
interface L7<N1, N2, N3, N4, N5, N6, N7, D1, D2, D3, D4, D5, D6, D7> | ||
extends L<N1, D1>, | ||
L<N2, D2>, | ||
L<N3, D3>, | ||
L<N4, D4>, | ||
L<N5, D5>, | ||
L<N6, D6>, | ||
L<N7, D7> {} | ||
interface L8<N1, N2, N3, N4, N5, N6, N7, N8, D1, D2, D3, D4, D5, D6, D7, D8> | ||
extends L<N1, D1>, | ||
L<N2, D2>, | ||
L<N3, D3>, | ||
L<N4, D4>, | ||
L<N5, D5>, | ||
L<N6, D6>, | ||
L<N7, D7>, | ||
L<N8, D8> {} | ||
interface L9<N1, N2, N3, N4, N5, N6, N7, N8, N9, D1, D2, D3, D4, D5, D6, D7, D8, D9> | ||
extends L<N1, D1>, | ||
L<N2, D2>, | ||
L<N3, D3>, | ||
L<N4, D4>, | ||
L<N5, D5>, | ||
L<N6, D6>, | ||
L<N7, D7>, | ||
L<N8, D8>, | ||
L<N9, D9> {} | ||
|
||
type EventNameDataPair1<AddRemoveListener> = AddRemoveListener extends L1<infer N1, infer D1> ? [N1, D1] : never; | ||
type EventNameDataPair2<AddRemoveListener> = AddRemoveListener extends L2<infer N1, infer N2, infer D1, infer D2> | ||
? [N1, D1] | [N2, D2] | ||
: never; | ||
type EventNameDataPair3<AddRemoveListener> = AddRemoveListener extends L3<infer N1, infer N2, infer N3, infer D1, infer D2, infer D3> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | ||
: never; | ||
type EventNameDataPair4<AddRemoveListener> = AddRemoveListener extends L4< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | ||
: never; | ||
type EventNameDataPair5<AddRemoveListener> = AddRemoveListener extends L5< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer N5, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4, | ||
infer D5 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | [N5, D5] | ||
: never; | ||
type EventNameDataPair6<AddRemoveListener> = AddRemoveListener extends L6< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer N5, | ||
infer N6, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4, | ||
infer D5, | ||
infer D6 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | [N5, D5] | [N6, D6] | ||
: never; | ||
type EventNameDataPair7<AddRemoveListener> = AddRemoveListener extends L7< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer N5, | ||
infer N6, | ||
infer N7, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4, | ||
infer D5, | ||
infer D6, | ||
infer D7 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | [N5, D5] | [N6, D6] | [N7, D7] | ||
: never; | ||
type EventNameDataPair8<AddRemoveListener> = AddRemoveListener extends L8< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer N5, | ||
infer N6, | ||
infer N7, | ||
infer N8, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4, | ||
infer D5, | ||
infer D6, | ||
infer D7, | ||
infer D8 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | [N5, D5] | [N6, D6] | [N7, D7] | [N8, D8] | ||
: never; | ||
type EventNameDataPair9<AddRemoveListener> = AddRemoveListener extends L9< | ||
infer N1, | ||
infer N2, | ||
infer N3, | ||
infer N4, | ||
infer N5, | ||
infer N6, | ||
infer N7, | ||
infer N8, | ||
infer N9, | ||
infer D1, | ||
infer D2, | ||
infer D3, | ||
infer D4, | ||
infer D5, | ||
infer D6, | ||
infer D7, | ||
infer D8, | ||
infer D9 | ||
> | ||
? [N1, D1] | [N2, D2] | [N3, D3] | [N4, D4] | [N5, D5] | [N6, D6] | [N7, D7] | [N8, D8] | [N9, D9] | ||
: never; | ||
|
||
interface HasNodeEventEmitterAddRemove<N, D> { | ||
addListener(name: N, listener: (data: D, ...args: any[]) => void): this; | ||
removeListener(name: N, listener: (data: D, ...args: any[]) => void): this; | ||
} | ||
|
||
/** | ||
* Get the event name/data pair types from an event emitter | ||
* | ||
* @return `['foo', number] | ['bar', string]` | ||
*/ | ||
type EventNameDataPair<AddRemoveListener extends HasNodeEventEmitterAddRemove<any, any>['addListener']> = | ||
| EventNameDataPair9<AddRemoveListener> | ||
| EventNameDataPair8<AddRemoveListener> | ||
| EventNameDataPair7<AddRemoveListener> | ||
| EventNameDataPair6<AddRemoveListener> | ||
| EventNameDataPair5<AddRemoveListener> | ||
| EventNameDataPair4<AddRemoveListener> | ||
| EventNameDataPair3<AddRemoveListener> | ||
| EventNameDataPair2<AddRemoveListener> | ||
| EventNameDataPair1<AddRemoveListener>; | ||
|
||
/** | ||
* Convert the `any` type to `unknown for a better safety | ||
* | ||
* @return `AnyToUnknown<any> -> unknown` | ||
* | ||
* TODO: huan(202111) need to be tested more and confirm it has no bug in edge cases | ||
*/ | ||
type AnyToUnknown<T> = unknown extends T ? unknown : T; | ||
|
||
// the [eventName, eventData] types array | ||
type NodeEventEmitterNameDataPair<E extends HasNodeEventEmitterAddRemove<any, any>> = EventNameDataPair<E['addListener']>; | ||
|
||
/** | ||
* | ||
* Tada! Get event emitter data type by event name 8-D | ||
* | ||
*/ | ||
type NodeEventEmitterDataType<E extends HasNodeEventEmitterAddRemove<T, any>, T> = Extract<NodeEventEmitterNameDataPair<E>, [T, any]>[1]; | ||
|
||
// Convert `never` to `unknown` | ||
type NodeEventEmitterDataTypeUnknown<E extends HasNodeEventEmitterAddRemove<T, any>, T> = NodeEventEmitterDataType<E, T> extends never | ||
? unknown | ||
: NodeEventEmitterDataType<E, T>; | ||
|
||
interface NamedNodeEventEmitter<N> { | ||
addListener(name: N, handler: (data: NodeEventEmitterDataType<HasNodeEventEmitterAddRemove<N, any>, N>, ...args: any[]) => any): this; | ||
removeListener(name: N, handler: (data: NodeEventEmitterDataType<HasNodeEventEmitterAddRemove<N, any>, N>, ...args: any[]) => any): this; | ||
} | ||
|
||
export type { | ||
AnyToUnknown, | ||
NamedNodeEventEmitter, | ||
NodeEventEmitterDataType, | ||
NodeEventEmitterDataTypeUnknown, | ||
NodeEventEmitterNameDataPair, | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we used more descriptive names here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the descriptive name will be more readable, and at first, I was using the descriptive name.
However, the reason that I finally use the short name (
L
for Listener,N
for Name, andD
for Data) is that the below code will have lots of repeated those type names:For example:
The above code will be very long and I feel the short version of the name is more readable, so I'd like to suggest that we can keep this short name because they are very to be understood:
L
- ListenerN
- NameD
- DataOn the other hand, I'm OK with using a more descriptive(long) name if you think the long version is better.
Please let me know your final decision and I'll follow it.