Added Flow type coverage for all EventEmitter subclasses
Brian Vaughn committed
Jun 9, 2019 at 15:50 UTC
ea03ddd583baad7790f36959fb4abe308e85ac24
13 files changed
+151
-69
flow.js
+11
-5
@@ -1,11 +1,17 @@
1
// @flow
2
3
declare module 'events' {
4
- declare class EventEmitter {
5
- addListener: (type: string, fn: Function) => void;
6
- emit: (type: string, data: any) => void;
7
- removeListener: (type: string, fn: Function) => void;
8
- removeAllListeners: (type?: string) => void;
4
+ declare class EventEmitter<Events: Object> {
5
+ addListener<Event: $Keys<Events>>(
6
+ event: Event,
7
+ listener: (...$ElementType<Events, Event>) => any
8
+ ): void;
9
+ emit: <Event: $Keys<Events>>(
10
+ event: Event,
11
+ ...$ElementType<Events, Event>
12
+ ) => void;
13
+ removeListener(event: $Keys<Events>, listener: Function): void;
14
+ removeAllListeners(event?: $Keys<Events>): void;
15
}
16
17
declare export default typeof EventEmitter;
src/backend/agent.js
+7
-2
@@ -3,6 +3,7 @@
3
import EventEmitter from 'events';
4
import memoize from 'memoize-one';
5
import throttle from 'lodash.throttle';
6
+import Bridge from 'src/bridge';
7
import {
8
SESSION_STORAGE_LAST_SELECTION_KEY,
9
SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
@@ -23,7 +24,7 @@ import type {
24
RendererID,
25
RendererInterface,
26
} from './types';
26
-import type { Bridge, ComponentFilter } from '../types';
27
+import type { ComponentFilter } from '../types';
28
29
const debug = (methodName, ...args) => {
30
if (__DEBUG__) {
@@ -67,7 +68,9 @@ type PersistedSelection = {|
68
path: Array<PathFrame>,
69
|};
70
70
-export default class Agent extends EventEmitter {
71
+export default class Agent extends EventEmitter<{|
72
+ shutdown: [],
73
+|}> {
74
_bridge: Bridge;
75
_isProfiling: boolean = false;
76
_recordChangeDescriptions: boolean = false;
@@ -273,9 +276,11 @@ export default class Agent extends EventEmitter {
276
screenshotCaptured = ({
277
commitIndex,
278
dataURL,
279
+ rootID,
280
}: {|
281
commitIndex: number,
282
dataURL: string,
283
+ rootID: number,
284
|}) => {
285
this._bridge.send('screenshotCaptured', { commitIndex, dataURL });
286
};
src/backend/types.js
+2
-2
@@ -13,10 +13,10 @@ export type ExpirationTime = number;
13
export type RefObject = {|
14
current: any,
15
|};
16
-export type Source = {
16
+export type Source = {|
17
fileName: string,
18
lineNumber: number,
19
-};
19
+|};
20
export type HookType =
21
| 'useState'
22
| 'useReducer'
src/bridge.js
+74
-3
@@ -2,16 +2,85 @@
2
3
import EventEmitter from 'events';
4
5
-import type { Wall } from './types';
5
+import type { ComponentFilter, Wall } from './types';
6
+import type {
7
+ InspectedElement,
8
+ OwnersList,
9
+ ProfilingDataBackend,
10
+ RendererID,
11
+} from 'src/backend/types';
12
13
const BATCH_DURATION = 100;
14
15
+type ElementAndRendererID = {| id: number, rendererID: RendererID |};
16
+
17
type Message = {|
18
event: string,
19
payload: any,
20
|};
21
14
-export default class Bridge extends EventEmitter {
22
+type HighlightElementInDOM = {|
23
+ ...ElementAndRendererID,
24
+ displayName: string,
25
+ hideAfterTimeout: boolean,
26
+ openNativeElementsPanel: boolean,
27
+ scrollIntoView: boolean,
28
+|};
29
+
30
+type OverrideValue = {|
31
+ ...ElementAndRendererID,
32
+ path: Array<string | number>,
33
+ value: any,
34
+|};
35
+
36
+type OverrideHookState = {|
37
+ ...OverrideValue,
38
+ hookID: number,
39
+|};
40
+
41
+type OverrideSuspense = {|
42
+ ...ElementAndRendererID,
43
+ forceFallback: boolean,
44
+|};
45
+
46
+export default class Bridge extends EventEmitter<{|
47
+ captureScreenshot: [{| commitIndex: number, rootID: number |}],
48
+ clearHighlightedElementInDOM: [],
49
+ getOwnersList: [ElementAndRendererID],
50
+ getProfilingData: [{| rendererID: RendererID |}],
51
+ getProfilingStatus: [],
52
+ highlightElementInDOM: [HighlightElementInDOM],
53
+ init: [],
54
+ inspectElement: [ElementAndRendererID],
55
+ inspectedElement: [InspectedElement | number | null],
56
+ isBackendStorageAPISupported: [boolean],
57
+ logElementToConsole: [ElementAndRendererID],
58
+ operations: [Uint32Array],
59
+ ownersList: [OwnersList],
60
+ overrideContext: [OverrideValue],
61
+ overrideHookState: [OverrideHookState],
62
+ overrideProps: [OverrideValue],
63
+ overrideState: [OverrideValue],
64
+ overrideSuspense: [OverrideSuspense],
65
+ profilingData: [ProfilingDataBackend],
66
+ profilingStatus: [boolean],
67
+ reloadAndProfile: [boolean],
68
+ reloadAppForProfiling: [],
69
+ screenshotCaptured: [
70
+ {| commitIndex: number, dataURL: string, rootID: number |},
71
+ ],
72
+ selectElement: [ElementAndRendererID],
73
+ selectFiber: [number],
74
+ shutdown: [],
75
+ startInspectingDOM: [],
76
+ startProfiling: [boolean],
77
+ stopInspectingDOM: [boolean],
78
+ stopProfiling: [],
79
+ syncSelectionFromNativeElementsPanel: [],
80
+ syncSelectionToNativeElementsPanel: [],
81
+ updateComponentFilters: [Array<ComponentFilter>],
82
+ viewElementSource: [ElementAndRendererID],
83
+|}> {
84
_isShutdown: boolean = false;
85
_messageQueue: Array<any> = [];
86
_timeoutID: TimeoutID | null = null;
@@ -25,7 +94,7 @@ export default class Bridge extends EventEmitter {
94
95
this._wallUnlisten =
96
wall.listen((message: Message) => {
28
- this.emit(message.event, message.payload);
97
+ (this: any).emit(message.event, message.payload);
98
}) || null;
99
}
100
@@ -64,7 +133,9 @@ export default class Bridge extends EventEmitter {
133
this._isShutdown = true;
134
135
// Disable the API inherited from EventEmitter that can add more listeners and send more messages.
136
+ // $FlowFixMe This property is not writable.
137
this.addListener = function() {};
138
+ // $FlowFixMe This property is not writable.
139
this.emit = function() {};
140
// NOTE: There's also EventEmitter API like `on` and `prependListener` that we didn't add to our Flow type of EventEmitter.
141
src/devtools/ProfilerStore.js
+6
-2
@@ -3,6 +3,7 @@
3
import EventEmitter from 'events';
4
import memoize from 'memoize-one';
5
import throttle from 'lodash.throttle';
6
+import Bridge from 'src/bridge';
7
import { prepareProfilingDataFrontendFromBackendAndStore } from './views/Profiler/utils';
8
import ProfilingCache from './ProfilingCache';
9
import Store from './store';
@@ -14,11 +15,14 @@ import type {
15
ProfilingDataFrontend,
16
SnapshotNode,
17
} from './views/Profiler/types';
17
-import type { Bridge } from '../types';
18
19
const THROTTLE_CAPTURE_SCREENSHOT_DURATION = 500;
20
21
-export default class ProfilerStore extends EventEmitter {
21
+export default class ProfilerStore extends EventEmitter<{|
22
+ isProcessingData: [],
23
+ isProfiling: [],
24
+ profilingData: [],
25
+|}> {
26
_bridge: Bridge;
27
28
// Suspense cache for lazily calculating derived profiling data.
src/devtools/index.js
+1
-1
@@ -1,6 +1,6 @@
1
// @flow
2
3
-import type { Bridge } from '../types';
3
+import Bridge from 'src/bridge';
4
5
type Shell = {|
6
connect: (callback: Function) => void,
src/devtools/store.js
+12
-2
@@ -2,6 +2,7 @@
2
3
import EventEmitter from 'events';
4
import { inspect } from 'util';
5
+import Bridge from 'src/bridge';
6
import {
7
TREE_OPERATION_ADD,
8
TREE_OPERATION_REMOVE,
@@ -21,7 +22,7 @@ import { printStore } from 'src/__tests__/storeSerializer';
22
import ProfilerStore from './ProfilerStore';
23
24
import type { Element } from './views/Components/types';
24
-import type { Bridge, ComponentFilter, ElementType } from '../types';
25
+import type { ComponentFilter, ElementType } from '../types';
26
27
const debug = (methodName, ...args) => {
28
if (__DEBUG__) {
@@ -57,7 +58,16 @@ export type Capabilities = {|
58
* The store is the single source of truth for updates from the backend.
59
* ContextProviders can subscribe to the Store for specific things they want to provide.
60
*/
60
-export default class Store extends EventEmitter {
61
+export default class Store extends EventEmitter<{|
62
+ captureScreenshots: [],
63
+ collapseNodesByDefault: [],
64
+ componentFilters: [],
65
+ mutated: [[Array<number>, Map<number, number>]],
66
+ recordChangeDescriptions: [],
67
+ roots: [],
68
+ supportsProfiling: [],
69
+ supportsReloadAndProfile: [],
70
+|}> {
71
_bridge: Bridge;
72
73
_captureScreenshots: boolean = false;
src/devtools/views/Components/InspectedElementContext.js
+34
-35
@@ -14,28 +14,33 @@ import { hydrate } from 'src/hydration';
14
import { TreeStateContext } from './TreeContext';
15
import { separateDisplayNameAndHOCs } from 'src/utils';
16
17
+import type { InspectedElement as InspectedElementBackend } from 'src/backend/types';
18
import type {
19
DehydratedData,
20
Element,
20
- InspectedElement,
21
+ InspectedElement as InspectedElementFrontend,
22
} from 'src/devtools/views/Components/types';
23
import type { Resource, Thenable } from '../../cache';
24
25
type Context = {|
25
- read(id: number): InspectedElement | null,
26
+ read(id: number): InspectedElementFrontend | null,
27
|};
28
29
const InspectedElementContext = createContext<Context>(((null: any): Context));
30
InspectedElementContext.displayName = 'InspectedElementContext';
31
31
-type ResolveFn = (inspectedElement: InspectedElement) => void;
32
+type ResolveFn = (inspectedElement: InspectedElementFrontend) => void;
33
type InProgressRequest = {|
33
- promise: Thenable<InspectedElement>,
34
+ promise: Thenable<InspectedElementFrontend>,
35
resolveFn: ResolveFn,
36
|};
37
38
const inProgressRequests: WeakMap<Element, InProgressRequest> = new WeakMap();
38
-const resource: Resource<Element, Element, InspectedElement> = createResource(
39
+const resource: Resource<
40
+ Element,
41
+ Element,
42
+ InspectedElementFrontend
43
+> = createResource(
44
(element: Element) => {
45
let request = inProgressRequests.get(element);
46
if (request != null) {
@@ -85,7 +90,7 @@ function InspectedElementContextController({ children }: Props) {
90
// This effect handler invalidates the suspense cache and schedules rendering updates with React.
91
useEffect(() => {
92
const onInspectedElement = (
88
- inspectedElement: InspectedElement | number | null
93
+ data: InspectedElementBackend | number | null
94
) => {
95
// A null value means that the element no longer exists in the backend.
96
// If it's the same element that's currently selected, that selection will be removed once the Store updates.
@@ -93,23 +98,21 @@ function InspectedElementContextController({ children }: Props) {
98
// Either way there is nothing we need to do in this case.
99
// A numeric value indicates that the element hasn't changed since we last requested its data,
100
// in which case we don't need to invalidate the cache and re-render anything in the DevTools.
96
- if (inspectedElement !== null && typeof inspectedElement === 'object') {
97
- const id = inspectedElement.id;
98
-
99
- const { displayName, type } = inspectedElement;
100
-
101
- const [
102
- displayNameWithoutHOCs,
103
- hocDisplayNames,
104
- ] = separateDisplayNameAndHOCs(displayName, type);
105
-
106
- inspectedElement = (({
107
- ...inspectedElement,
108
- displayName: displayNameWithoutHOCs,
101
+ if (data !== null && typeof data === 'object') {
102
+ const id = data.id;
103
+
104
+ const inspectedElement: InspectedElementFrontend = {
105
+ canEditFunctionProps: data.canEditFunctionProps,
106
+ canEditHooks: data.canEditHooks,
107
+ canToggleSuspense: data.canToggleSuspense,
108
+ canViewSource: data.canViewSource,
109
+ id: data.id,
110
+ source: data.source,
111
+ type: data.type,
112
owners:
110
- inspectedElement.owners === null
113
+ data.owners === null
114
? null
112
- : inspectedElement.owners.map(owner => {
115
+ : data.owners.map(owner => {
116
const [
117
displayName,
118
hocDisplayNames,
@@ -120,13 +123,12 @@ function InspectedElementContextController({ children }: Props) {
123
hocDisplayNames,
124
};
125
}),
123
- hocDisplayNames,
124
- context: hydrateHelper(inspectedElement.context),
125
- events: hydrateHelper(inspectedElement.events),
126
- hooks: hydrateHelper(inspectedElement.hooks),
127
- props: hydrateHelper(inspectedElement.props),
128
- state: hydrateHelper(inspectedElement.state),
129
- }: any): InspectedElement);
126
+ context: hydrateHelper(data.context),
127
+ events: hydrateHelper(data.events),
128
+ hooks: hydrateHelper(data.hooks),
129
+ props: hydrateHelper(data.props),
130
+ state: hydrateHelper(data.state),
131
+ };
132
133
const element = store.getElementByID(id);
134
if (element !== null) {
@@ -174,16 +176,13 @@ function InspectedElementContextController({ children }: Props) {
176
bridge.send('selectElement', { id: selectedElementID, rendererID });
177
178
const onInspectedElement = (
177
- inspectedElement: InspectedElement | number | null
179
+ data: InspectedElementBackend | number | null
180
) => {
179
- if (inspectedElement !== null) {
181
+ if (data !== null) {
182
// If this is the element we requested, wait a little bit and then ask for an update.
181
- if (inspectedElement === selectedElementID) {
183
+ if (data === selectedElementID) {
184
timeoutID = setTimeout(sendRequest, 1000);
183
- } else if (
184
- typeof inspectedElement === 'object' &&
185
- inspectedElement.id === selectedElementID
186
- ) {
185
+ } else if (typeof data === 'object' && data.id === selectedElementID) {
186
timeoutID = setTimeout(sendRequest, 1000);
187
}
188
}
src/devtools/views/Components/OwnersListContext.js
+2
-5
@@ -11,11 +11,8 @@ import { BridgeContext, StoreContext } from '../context';
11
import { TreeStateContext } from './TreeContext';
12
import { separateDisplayNameAndHOCs } from 'src/utils';
13
14
-import type {
15
- Element,
16
- Owner,
17
- OwnersList,
18
-} from 'src/devtools/views/Components/types';
14
+import type { OwnersList } from 'src/backend/types';
15
+import type { Element, Owner } from 'src/devtools/views/Components/types';
16
import type { Resource, Thenable } from '../../cache';
17
18
type Context = (id: number) => Array<Owner> | null;
src/devtools/views/Components/types.js
-2
@@ -52,8 +52,6 @@ export type Source = {|
52
export type InspectedElement = {|
53
id: number,
54
55
- displayName: string | null,
56
-
55
// Does the current renderer support editable hooks?
56
canEditHooks: boolean,
57
src/devtools/views/DevTools.js
+1
-2
@@ -6,6 +6,7 @@ import '@reach/menu-button/styles.css';
6
import '@reach/tooltip/styles.css';
7
8
import React, { useState } from 'react';
9
+import Bridge from 'src/bridge';
10
import Store from '../store';
11
import { BridgeContext, StoreContext } from './context';
12
import Components from './Components/Components';
@@ -23,8 +24,6 @@ import styles from './DevTools.css';
24
25
import './root.css';
26
26
-import type { Bridge } from '../../types';
27
-
27
export type BrowserName = 'Chrome' | 'Firefox';
28
export type BrowserTheme = 'dark' | 'light';
29
export type TabID = 'components' | 'profiler' | 'settings';
src/devtools/views/context.js
+1
-2
@@ -1,8 +1,7 @@
1
// @flow
2
3
import { createContext } from 'react';
4
-
5
-import type { Bridge } from '../../types';
4
+import Bridge from 'src/bridge';
5
6
import Store from '../store';
7
src/types.js
-6
@@ -1,11 +1,5 @@
1
// @flow
2
3
-export type Bridge = {
4
- addListener(type: string, callback: Function): void,
5
- removeListener(type: string, callback: Function): void,
6
- send(event: string, payload: any, transferable?: Array<any>): void,
7
-};
8
-
3
export type Wall = {|
4
// `listen` returns the "unlisten" function.
5
listen: (fn: Function) => Function,