@samitouri / QOS-React-2 / commits / ea03ddd583

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,