mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Misc Flow and import fixes
1. Fixed all reported Flow errors 2. Added a few missing package declarations 3. Deleted ReactDebugHooks fork in favor of react-debug-tools
This commit is contained in:
+2
-2
@@ -253,14 +253,14 @@ const Dispatcher: DispatcherType = {
|
||||
|
||||
// Inspect
|
||||
|
||||
type HooksNode = {
|
||||
export type HooksNode = {
|
||||
id: number | null,
|
||||
isStateEditable: boolean,
|
||||
name: string,
|
||||
value: mixed,
|
||||
subHooks: Array<HooksNode>,
|
||||
};
|
||||
type HooksTree = Array<HooksNode>;
|
||||
export type HooksTree = Array<HooksNode>;
|
||||
|
||||
// Don't assume
|
||||
//
|
||||
|
||||
+3
-1
@@ -300,7 +300,9 @@ function startServer(port?: number = 8097) {
|
||||
close: function() {
|
||||
connected = null;
|
||||
onDisconnected();
|
||||
clearTimeout(startServerTimeoutID);
|
||||
if (startServerTimeoutID !== null) {
|
||||
clearTimeout(startServerTimeoutID);
|
||||
}
|
||||
server.close();
|
||||
httpServer.close();
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
declare module 'events' {
|
||||
declare module 'node-events' {
|
||||
declare class EventEmitter<Events: Object> {
|
||||
addListener<Event: $Keys<Events>>(
|
||||
event: Event,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"clipboard-js": "^0.3.6",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"memoize-one": "^3.1.1",
|
||||
"node-events": "npm:events@^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
const { printOwnersList } = require('./storeSerializer');
|
||||
const { printOwnersList } = require('../devtools/utils');
|
||||
|
||||
describe('Store owners list', () => {
|
||||
let React;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { printStore } from 'react-devtools-shared/src/devtools/utils'
|
||||
|
||||
// test() is part of Jest's serializer API
|
||||
export function test(maybeStore) {
|
||||
// It's important to lazy-require the Store rather than imported at the head of the module.
|
||||
@@ -11,74 +13,6 @@ export function print(store, serialize, indent) {
|
||||
return printStore(store);
|
||||
}
|
||||
|
||||
export function printElement(element, includeWeight = false) {
|
||||
let prefix = ' ';
|
||||
if (element.children.length > 0) {
|
||||
prefix = element.isCollapsed ? '▸' : '▾';
|
||||
}
|
||||
|
||||
let key = '';
|
||||
if (element.key !== null) {
|
||||
key = ` key="${element.key}"`;
|
||||
}
|
||||
|
||||
let hocs = '';
|
||||
if (element.hocDisplayNames !== null) {
|
||||
hocs = ` [${element.hocDisplayNames.join('][')}]`;
|
||||
}
|
||||
|
||||
let suffix = '';
|
||||
if (includeWeight) {
|
||||
suffix = ` (${element.isCollapsed ? 1 : element.weight})`;
|
||||
}
|
||||
|
||||
return `${' '.repeat(element.depth + 1)}${prefix} <${element.displayName ||
|
||||
'null'}${key}>${hocs}${suffix}`;
|
||||
}
|
||||
|
||||
export function printOwnersList(elements, includeWeight = false) {
|
||||
return elements
|
||||
.map(element => printElement(element, includeWeight))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Used for Jest snapshot testing.
|
||||
// May also be useful for visually debugging the tree, so it lives on the Store.
|
||||
export function printStore(store, includeWeight = false) {
|
||||
const snapshotLines = [];
|
||||
|
||||
let rootWeight = 0;
|
||||
|
||||
store.roots.forEach(rootID => {
|
||||
const { weight } = store.getElementByID(rootID);
|
||||
|
||||
snapshotLines.push('[root]' + (includeWeight ? ` (${weight})` : ''));
|
||||
|
||||
for (let i = rootWeight; i < rootWeight + weight; i++) {
|
||||
const element = store.getElementAtIndex(i);
|
||||
|
||||
if (element == null) {
|
||||
throw Error(`Could not find element at index ${i}`);
|
||||
}
|
||||
|
||||
snapshotLines.push(printElement(element, includeWeight));
|
||||
}
|
||||
|
||||
rootWeight += weight;
|
||||
});
|
||||
|
||||
// Make sure the pretty-printed test align with the Store's reported number of total rows.
|
||||
if (rootWeight !== store.numElements) {
|
||||
throw Error(
|
||||
`Inconsistent Store state. Individual root weights (${rootWeight}) do not match total weight (${
|
||||
store.numElements
|
||||
})`
|
||||
);
|
||||
}
|
||||
|
||||
// If roots have been unmounted, verify that they've been removed from maps.
|
||||
// This helps ensure the Store doesn't leak memory.
|
||||
store.assertExpectedRootMapSizes();
|
||||
|
||||
return snapshotLines.join('\n');
|
||||
}
|
||||
export { printStore };
|
||||
|
||||
+3
-4
@@ -7,7 +7,7 @@ import type { BackendBridge } from 'react-devtools-shared/src/bridge';
|
||||
import type { RendererID } from '../types';
|
||||
import type { StyleAndLayout } from './types';
|
||||
|
||||
export type ResolveNativeStyle = (stylesheetID: number) => ?Object;
|
||||
export type ResolveNativeStyle = (stylesheetID: any) => ?Object;
|
||||
|
||||
export default function setupNativeStyleEditor(
|
||||
bridge: BackendBridge,
|
||||
@@ -136,9 +136,8 @@ function measureStyle(
|
||||
);
|
||||
return;
|
||||
}
|
||||
const margin = resolveBoxStyle('margin', resolvedStyle) || EMPTY_BOX_STYLE;
|
||||
const padding =
|
||||
resolveBoxStyle('padding', resolvedStyle) || EMPTY_BOX_STYLE;
|
||||
const margin = resolvedStyle != null && resolveBoxStyle('margin', resolvedStyle) || EMPTY_BOX_STYLE;
|
||||
const padding = resolvedStyle != null && resolveBoxStyle('padding', resolvedStyle) || EMPTY_BOX_STYLE;
|
||||
bridge.send(
|
||||
'NativeStyleEditor_styleAndLayout',
|
||||
({
|
||||
|
||||
@@ -1,636 +0,0 @@
|
||||
// @flow
|
||||
|
||||
// This file was forked from the React GitHub repo:
|
||||
// https://github.com/facebook/react/blob/master/packages/react-debug-tools/src/ReactDebugHooks.js
|
||||
// It has been modified slightly though to account for "shared" imports and different lint configs.
|
||||
// I've also removed some of the Flow types that don't exist in DevTools.
|
||||
// TODO Remove this fork and use the NPM version of this package once it's released.
|
||||
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
|
||||
import type {
|
||||
ReactContext,
|
||||
ReactEventResponder,
|
||||
ReactEventResponderListener,
|
||||
} from './types';
|
||||
|
||||
type Fiber = any;
|
||||
type Hook = any;
|
||||
|
||||
// HACK: These values are copied from attachRendererFiber
|
||||
// In the future, the react-debug-hooks package will be published to NPM,
|
||||
// and be locked to a specific range of react versions
|
||||
// For now we are just hard-coding the current/latest versions.
|
||||
const ContextProvider = 10;
|
||||
const ForwardRef = 11;
|
||||
const FunctionComponent = 0;
|
||||
const SimpleMemoComponent = 15;
|
||||
|
||||
// Used to track hooks called during a render
|
||||
|
||||
type HookLogEntry = {
|
||||
primitive: string,
|
||||
stackError: Error,
|
||||
value: mixed,
|
||||
};
|
||||
|
||||
let hookLog: Array<HookLogEntry> = [];
|
||||
|
||||
// Primitives
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
|
||||
type Dispatch<A> = A => void;
|
||||
|
||||
let primitiveStackCache: null | Map<string, Array<any>> = null;
|
||||
|
||||
function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
// This initializes a cache of all primitive hooks so that the top
|
||||
// most stack frames added by calling the primitive hook can be removed.
|
||||
if (primitiveStackCache === null) {
|
||||
const cache = new Map();
|
||||
let readHookLog;
|
||||
try {
|
||||
// Use all hooks here to add them to the hook log.
|
||||
Dispatcher.useContext(({ _currentValue: null }: any));
|
||||
Dispatcher.useState(null);
|
||||
Dispatcher.useReducer((s, a) => s, null);
|
||||
Dispatcher.useRef(null);
|
||||
Dispatcher.useLayoutEffect(() => {});
|
||||
Dispatcher.useEffect(() => {});
|
||||
Dispatcher.useImperativeHandle(undefined, () => null);
|
||||
Dispatcher.useDebugValue(null);
|
||||
Dispatcher.useCallback(() => {});
|
||||
Dispatcher.useMemo(() => null);
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
}
|
||||
for (let i = 0; i < readHookLog.length; i++) {
|
||||
const hook = readHookLog[i];
|
||||
cache.set(hook.primitive, ErrorStackParser.parse(hook.stackError));
|
||||
}
|
||||
primitiveStackCache = cache;
|
||||
}
|
||||
return primitiveStackCache;
|
||||
}
|
||||
|
||||
let currentHook: null | Hook = null;
|
||||
function nextHook(): null | Hook {
|
||||
const hook = currentHook;
|
||||
if (hook !== null) {
|
||||
currentHook = hook.next;
|
||||
}
|
||||
return hook;
|
||||
}
|
||||
|
||||
function readContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean
|
||||
): T {
|
||||
// For now we don't expose readContext usage in the hooks debugging info.
|
||||
return context._currentValue;
|
||||
}
|
||||
|
||||
function useContext<T>(
|
||||
context: ReactContext<T>,
|
||||
observedBits: void | number | boolean
|
||||
): T {
|
||||
hookLog.push({
|
||||
primitive: 'Context',
|
||||
stackError: new Error(),
|
||||
value: context._currentValue,
|
||||
});
|
||||
return context._currentValue;
|
||||
}
|
||||
|
||||
function useState<S>(
|
||||
initialState: (() => S) | S
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
const hook = nextHook();
|
||||
const state: S =
|
||||
hook !== null
|
||||
? hook.memoizedState
|
||||
: typeof initialState === 'function'
|
||||
? (initialState: any)()
|
||||
: initialState;
|
||||
hookLog.push({ primitive: 'State', stackError: new Error(), value: state });
|
||||
return [state, (action: BasicStateAction<S>) => {}];
|
||||
}
|
||||
|
||||
function useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S
|
||||
): [S, Dispatch<A>] {
|
||||
const hook = nextHook();
|
||||
let state;
|
||||
if (hook !== null) {
|
||||
state = hook.memoizedState;
|
||||
} else {
|
||||
state = init !== undefined ? init(initialArg) : ((initialArg: any): S);
|
||||
}
|
||||
hookLog.push({
|
||||
primitive: 'Reducer',
|
||||
stackError: new Error(),
|
||||
value: state,
|
||||
});
|
||||
return [state, (action: A) => {}];
|
||||
}
|
||||
|
||||
function useRef<T>(initialValue: T): { current: T } {
|
||||
const hook = nextHook();
|
||||
const ref = hook !== null ? hook.memoizedState : { current: initialValue };
|
||||
hookLog.push({
|
||||
primitive: 'Ref',
|
||||
stackError: new Error(),
|
||||
value: ref.current,
|
||||
});
|
||||
return ref;
|
||||
}
|
||||
|
||||
function useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
primitive: 'LayoutEffect',
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
});
|
||||
}
|
||||
|
||||
function useEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null
|
||||
): void {
|
||||
nextHook();
|
||||
hookLog.push({ primitive: 'Effect', stackError: new Error(), value: create });
|
||||
}
|
||||
|
||||
function useImperativeHandle<T>(
|
||||
ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
inputs: Array<mixed> | void | null
|
||||
): void {
|
||||
nextHook();
|
||||
// We don't actually store the instance anywhere if there is no ref callback
|
||||
// and if there is a ref callback it might not store it but if it does we
|
||||
// have no way of knowing where. So let's only enable introspection of the
|
||||
// ref itself if it is using the object form.
|
||||
let instance = undefined;
|
||||
if (ref !== null && typeof ref === 'object') {
|
||||
instance = ref.current;
|
||||
}
|
||||
hookLog.push({
|
||||
primitive: 'ImperativeHandle',
|
||||
stackError: new Error(),
|
||||
value: instance,
|
||||
});
|
||||
}
|
||||
|
||||
function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
|
||||
hookLog.push({
|
||||
primitive: 'DebugValue',
|
||||
stackError: new Error(),
|
||||
value: typeof formatterFn === 'function' ? formatterFn(value) : value,
|
||||
});
|
||||
}
|
||||
|
||||
function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
|
||||
const hook = nextHook();
|
||||
hookLog.push({
|
||||
primitive: 'Callback',
|
||||
stackError: new Error(),
|
||||
value: hook !== null ? hook.memoizedState[0] : callback,
|
||||
});
|
||||
return callback;
|
||||
}
|
||||
|
||||
function useMemo<T>(
|
||||
nextCreate: () => T,
|
||||
inputs: Array<mixed> | void | null
|
||||
): T {
|
||||
const hook = nextHook();
|
||||
const value = hook !== null ? hook.memoizedState[0] : nextCreate();
|
||||
hookLog.push({ primitive: 'Memo', stackError: new Error(), value });
|
||||
return value;
|
||||
}
|
||||
|
||||
function useResponder(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
listenerProps: Object
|
||||
): ReactEventResponderListener<any, any> {
|
||||
// Don't put the actual event responder object in, just its displayName
|
||||
const value = {
|
||||
responder: responder.displayName || 'EventResponder',
|
||||
props: listenerProps,
|
||||
};
|
||||
hookLog.push({ primitive: 'Responder', stackError: new Error(), value });
|
||||
return {
|
||||
responder,
|
||||
props: listenerProps,
|
||||
};
|
||||
}
|
||||
|
||||
const Dispatcher = {
|
||||
readContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useResponder,
|
||||
};
|
||||
|
||||
// Inspect
|
||||
|
||||
type ReactCurrentDispatcher = {
|
||||
current: null | typeof Dispatcher,
|
||||
};
|
||||
|
||||
type HooksNode = {
|
||||
id: number | null,
|
||||
isStateEditable: boolean,
|
||||
name: string,
|
||||
value: mixed,
|
||||
subHooks: Array<HooksNode>,
|
||||
};
|
||||
type HooksTree = Array<HooksNode>;
|
||||
|
||||
// Don't assume
|
||||
//
|
||||
// We can't assume that stack frames are nth steps away from anything.
|
||||
// E.g. we can't assume that the root call shares all frames with the stack
|
||||
// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
|
||||
// in a wrapper constructor like a polyfill. That'll add an extra frame.
|
||||
// Similar things can happen with the call to the dispatcher. The top frame
|
||||
// may not be the primitive. Likewise the primitive can have fewer stack frames
|
||||
// such as when a call to useState got inlined to use dispatcher.useState.
|
||||
//
|
||||
// We also can't assume that the last frame of the root call is the same
|
||||
// frame as the last frame of the hook call because long stack traces can be
|
||||
// truncated to a stack trace limit.
|
||||
|
||||
let mostLikelyAncestorIndex = 0;
|
||||
|
||||
function findSharedIndex(hookStack, rootStack, rootIndex) {
|
||||
const source = rootStack[rootIndex].source;
|
||||
hookSearch: for (let i = 0; i < hookStack.length; i++) {
|
||||
if (hookStack[i].source === source) {
|
||||
// This looks like a match. Validate that the rest of both stack match up.
|
||||
for (
|
||||
let a = rootIndex + 1, b = i + 1;
|
||||
a < rootStack.length && b < hookStack.length;
|
||||
a++, b++
|
||||
) {
|
||||
if (hookStack[b].source !== rootStack[a].source) {
|
||||
// If not, give up and try a different match.
|
||||
continue hookSearch;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findCommonAncestorIndex(rootStack, hookStack) {
|
||||
let rootIndex = findSharedIndex(
|
||||
hookStack,
|
||||
rootStack,
|
||||
mostLikelyAncestorIndex
|
||||
);
|
||||
if (rootIndex !== -1) {
|
||||
return rootIndex;
|
||||
}
|
||||
// If the most likely one wasn't a hit, try any other frame to see if it is shared.
|
||||
// If that takes more than 5 frames, something probably went wrong.
|
||||
for (let i = 0; i < rootStack.length && i < 5; i++) {
|
||||
rootIndex = findSharedIndex(hookStack, rootStack, i);
|
||||
if (rootIndex !== -1) {
|
||||
mostLikelyAncestorIndex = i;
|
||||
return rootIndex;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isReactWrapper(functionName, primitiveName) {
|
||||
if (!functionName) {
|
||||
return false;
|
||||
}
|
||||
const expectedPrimitiveName = 'use' + primitiveName;
|
||||
if (functionName.length < expectedPrimitiveName.length) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
functionName.lastIndexOf(expectedPrimitiveName) ===
|
||||
functionName.length - expectedPrimitiveName.length
|
||||
);
|
||||
}
|
||||
|
||||
function findPrimitiveIndex(hookStack, hook) {
|
||||
const stackCache = getPrimitiveStackCache();
|
||||
const primitiveStack = stackCache.get(hook.primitive);
|
||||
if (primitiveStack === undefined) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < primitiveStack.length && i < hookStack.length; i++) {
|
||||
if (primitiveStack[i].source !== hookStack[i].source) {
|
||||
// If the next two frames are functions called `useX` then we assume that they're part of the
|
||||
// wrappers that the React packager or other packages adds around the dispatcher.
|
||||
if (
|
||||
i < hookStack.length - 1 &&
|
||||
isReactWrapper(hookStack[i].functionName, hook.primitive)
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
if (
|
||||
i < hookStack.length - 1 &&
|
||||
isReactWrapper(hookStack[i].functionName, hook.primitive)
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function parseTrimmedStack(rootStack, hook) {
|
||||
// Get the stack trace between the primitive hook function and
|
||||
// the root function call. I.e. the stack frames of custom hooks.
|
||||
const hookStack = ErrorStackParser.parse(hook.stackError);
|
||||
const rootIndex = findCommonAncestorIndex(rootStack, hookStack);
|
||||
const primitiveIndex = findPrimitiveIndex(hookStack, hook);
|
||||
if (
|
||||
rootIndex === -1 ||
|
||||
primitiveIndex === -1 ||
|
||||
rootIndex - primitiveIndex < 2
|
||||
) {
|
||||
// Something went wrong. Give up.
|
||||
return null;
|
||||
}
|
||||
return hookStack.slice(primitiveIndex, rootIndex - 1);
|
||||
}
|
||||
|
||||
function parseCustomHookName(functionName: void | string): string {
|
||||
if (!functionName) {
|
||||
return '';
|
||||
}
|
||||
let startIndex = functionName.lastIndexOf('.');
|
||||
if (startIndex === -1) {
|
||||
startIndex = 0;
|
||||
}
|
||||
if (functionName.substr(startIndex, 3) === 'use') {
|
||||
startIndex += 3;
|
||||
}
|
||||
return functionName.substr(startIndex);
|
||||
}
|
||||
|
||||
function buildTree(rootStack, readHookLog): HooksTree {
|
||||
const rootChildren = [];
|
||||
let prevStack = null;
|
||||
let levelChildren = rootChildren;
|
||||
let nativeHookID = 0;
|
||||
const stackOfChildren = [];
|
||||
for (let i = 0; i < readHookLog.length; i++) {
|
||||
const hook = readHookLog[i];
|
||||
const stack = parseTrimmedStack(rootStack, hook);
|
||||
if (stack !== null) {
|
||||
// Note: The indices 0 <= n < length-1 will contain the names.
|
||||
// The indices 1 <= n < length will contain the source locations.
|
||||
// That's why we get the name from n - 1 and don't check the source
|
||||
// of index 0.
|
||||
let commonSteps = 0;
|
||||
if (prevStack !== null) {
|
||||
// Compare the current level's stack to the new stack.
|
||||
while (commonSteps < stack.length && commonSteps < prevStack.length) {
|
||||
const stackSource = stack[stack.length - commonSteps - 1].source;
|
||||
const prevSource =
|
||||
prevStack[prevStack.length - commonSteps - 1].source;
|
||||
if (stackSource !== prevSource) {
|
||||
break;
|
||||
}
|
||||
commonSteps++;
|
||||
}
|
||||
// Pop back the stack as many steps as were not common.
|
||||
for (let j = prevStack.length - 1; j > commonSteps; j--) {
|
||||
levelChildren = stackOfChildren.pop();
|
||||
}
|
||||
}
|
||||
// The remaining part of the new stack are custom hooks. Push them
|
||||
// to the tree.
|
||||
for (let j = stack.length - commonSteps - 1; j >= 1; j--) {
|
||||
const children = [];
|
||||
levelChildren.push({
|
||||
id: null,
|
||||
isStateEditable: false,
|
||||
name: parseCustomHookName(stack[j - 1].functionName),
|
||||
value: undefined,
|
||||
subHooks: children,
|
||||
});
|
||||
stackOfChildren.push(levelChildren);
|
||||
levelChildren = children;
|
||||
}
|
||||
prevStack = stack;
|
||||
}
|
||||
const { primitive } = hook;
|
||||
|
||||
// For now, the "id" of stateful hooks is just the stateful hook index.
|
||||
// Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
|
||||
const id =
|
||||
primitive === 'Context' || primitive === 'DebugValue'
|
||||
? null
|
||||
: nativeHookID++;
|
||||
|
||||
// For the time being, only State and Reducer hooks support runtime overrides.
|
||||
const isStateEditable = primitive === 'Reducer' || primitive === 'State';
|
||||
|
||||
levelChildren.push({
|
||||
id,
|
||||
isStateEditable,
|
||||
name: primitive,
|
||||
value: hook.value,
|
||||
subHooks: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Associate custom hook values (useDebugValue() hook entries) with the correct hooks.
|
||||
processDebugValues(rootChildren, null);
|
||||
|
||||
return rootChildren;
|
||||
}
|
||||
|
||||
// Custom hooks support user-configurable labels (via the special useDebugValue() hook).
|
||||
// That hook adds user-provided values to the hooks tree,
|
||||
// but these values aren't intended to appear alongside of the other hooks.
|
||||
// Instead they should be attributed to their parent custom hook.
|
||||
// This method walks the tree and assigns debug values to their custom hook owners.
|
||||
function processDebugValues(
|
||||
hooksTree: HooksTree,
|
||||
parentHooksNode: HooksNode | null
|
||||
): void {
|
||||
const debugValueHooksNodes: Array<HooksNode> = [];
|
||||
|
||||
for (let i = 0; i < hooksTree.length; i++) {
|
||||
const hooksNode = hooksTree[i];
|
||||
if (hooksNode.name === 'DebugValue' && hooksNode.subHooks.length === 0) {
|
||||
hooksTree.splice(i, 1);
|
||||
i--;
|
||||
debugValueHooksNodes.push(hooksNode);
|
||||
} else {
|
||||
processDebugValues(hooksNode.subHooks, hooksNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble debug value labels to their custom hook owner.
|
||||
// If there is no parent hook, just ignore them for now.
|
||||
// (We may warn about this in the future.)
|
||||
if (parentHooksNode !== null) {
|
||||
if (debugValueHooksNodes.length === 1) {
|
||||
parentHooksNode.value = debugValueHooksNodes[0].value;
|
||||
} else if (debugValueHooksNodes.length > 1) {
|
||||
parentHooksNode.value = debugValueHooksNodes.map(({ value }) => value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectHooks<Props>(
|
||||
renderFunction: Props => React$Node,
|
||||
props: Props,
|
||||
currentDispatcher: ReactCurrentDispatcher
|
||||
): HooksTree {
|
||||
// DevTools will pass the current renderer's injected dispatcher.
|
||||
// Other apps might compile debug hooks as part of their app though.
|
||||
//if (currentDispatcher == null) {
|
||||
//currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
//}
|
||||
|
||||
const previousDispatcher = currentDispatcher.current;
|
||||
let readHookLog;
|
||||
currentDispatcher.current = Dispatcher;
|
||||
let ancestorStackError;
|
||||
try {
|
||||
ancestorStackError = new Error();
|
||||
renderFunction(props);
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
currentDispatcher.current = previousDispatcher;
|
||||
}
|
||||
const rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
}
|
||||
|
||||
function setupContexts(contextMap: Map<any, any>, fiber: Fiber) {
|
||||
let current = fiber;
|
||||
while (current) {
|
||||
if (current.tag === ContextProvider) {
|
||||
const providerType: any = current.type;
|
||||
const context: any = providerType._context;
|
||||
if (!contextMap.has(context)) {
|
||||
// Store the current value that we're going to restore later.
|
||||
contextMap.set(context, context._currentValue);
|
||||
// Set the inner most provider value on the context.
|
||||
context._currentValue = current.memoizedProps.value;
|
||||
}
|
||||
}
|
||||
current = current.return;
|
||||
}
|
||||
}
|
||||
|
||||
function restoreContexts(contextMap: Map<any, any>) {
|
||||
contextMap.forEach((value, context) => (context._currentValue = value));
|
||||
}
|
||||
|
||||
function inspectHooksOfForwardRef<Props, Ref>(
|
||||
renderFunction: (Props, Ref) => React$Node,
|
||||
props: Props,
|
||||
ref: Ref,
|
||||
currentDispatcher: ReactCurrentDispatcher
|
||||
): HooksTree {
|
||||
const previousDispatcher = currentDispatcher.current;
|
||||
let readHookLog;
|
||||
currentDispatcher.current = Dispatcher;
|
||||
let ancestorStackError;
|
||||
try {
|
||||
ancestorStackError = new Error();
|
||||
renderFunction(props, ref);
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
currentDispatcher.current = previousDispatcher;
|
||||
}
|
||||
const rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
}
|
||||
|
||||
function resolveDefaultProps(Component, baseProps) {
|
||||
if (Component && Component.defaultProps) {
|
||||
// Resolve default props. Taken from ReactElement
|
||||
const props = Object.assign({}, baseProps);
|
||||
const defaultProps = Component.defaultProps;
|
||||
for (const propName in defaultProps) {
|
||||
if (props[propName] === undefined) {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
return baseProps;
|
||||
}
|
||||
|
||||
export function inspectHooksOfFiber(
|
||||
fiber: Fiber,
|
||||
currentDispatcher: ReactCurrentDispatcher
|
||||
) {
|
||||
// DevTools will pass the current renderer's injected dispatcher.
|
||||
// Other apps might compile debug hooks as part of their app though.
|
||||
//if (currentDispatcher == null) {
|
||||
//currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
//}
|
||||
|
||||
if (
|
||||
fiber.tag !== FunctionComponent &&
|
||||
fiber.tag !== SimpleMemoComponent &&
|
||||
fiber.tag !== ForwardRef
|
||||
) {
|
||||
throw new Error(
|
||||
'Unknown Fiber. Needs to be a function component to inspect hooks.'
|
||||
);
|
||||
}
|
||||
// Warm up the cache so that it doesn't consume the currentHook.
|
||||
getPrimitiveStackCache();
|
||||
const type = fiber.type;
|
||||
let props = fiber.memoizedProps;
|
||||
if (type !== fiber.elementType) {
|
||||
props = resolveDefaultProps(type, props);
|
||||
}
|
||||
// Set up the current hook so that we can step through and read the
|
||||
// current state from them.
|
||||
currentHook = (fiber.memoizedState: Hook);
|
||||
const contextMap = new Map();
|
||||
try {
|
||||
setupContexts(contextMap, fiber);
|
||||
if (fiber.tag === ForwardRef) {
|
||||
return inspectHooksOfForwardRef(
|
||||
type.render,
|
||||
props,
|
||||
fiber.ref,
|
||||
currentDispatcher
|
||||
);
|
||||
}
|
||||
return inspectHooks(type, props, currentDispatcher);
|
||||
} finally {
|
||||
currentHook = null;
|
||||
restoreContexts(contextMap);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import EventEmitter from 'node-events';
|
||||
import throttle from 'lodash.throttle';
|
||||
import {
|
||||
SESSION_STORAGE_LAST_SELECTION_KEY,
|
||||
|
||||
+34
-30
@@ -407,17 +407,18 @@ export function attach(
|
||||
parentID: number,
|
||||
rootID: number
|
||||
) {
|
||||
const internalInstance = idToInternalInstanceMap.get(id);
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.group('crawlAndRecordInitialMounts() id:', id);
|
||||
}
|
||||
|
||||
internalInstanceToRootIDMap.set(internalInstance, rootID);
|
||||
recordMount(internalInstance, id, parentID);
|
||||
getChildren(internalInstance).forEach(child =>
|
||||
crawlAndRecordInitialMounts(getID(child), id, rootID)
|
||||
);
|
||||
const internalInstance = idToInternalInstanceMap.get(id);
|
||||
if (internalInstance != null) {
|
||||
internalInstanceToRootIDMap.set(internalInstance, rootID);
|
||||
recordMount(internalInstance, id, parentID);
|
||||
getChildren(internalInstance).forEach(child =>
|
||||
crawlAndRecordInitialMounts(getID(child), id, rootID)
|
||||
);
|
||||
}
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.groupEnd();
|
||||
@@ -686,7 +687,12 @@ export function attach(
|
||||
|
||||
function inspectElementRaw(id: number): InspectedElement | null {
|
||||
const internalInstance = idToInternalInstanceMap.get(id);
|
||||
const displayName = getData(internalInstance).displayName;
|
||||
|
||||
if (internalInstance == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { displayName } = getData(internalInstance);
|
||||
const type = getElementType(internalInstance);
|
||||
|
||||
let context = null;
|
||||
@@ -695,33 +701,31 @@ export function attach(
|
||||
let state = null;
|
||||
let source = null;
|
||||
|
||||
if (internalInstance != null) {
|
||||
const element = internalInstance._currentElement;
|
||||
if (element !== null) {
|
||||
props = element.props;
|
||||
source = element._source != null ? element._source : null;
|
||||
const element = internalInstance._currentElement;
|
||||
if (element !== null) {
|
||||
props = element.props;
|
||||
source = element._source != null ? element._source : null;
|
||||
|
||||
let owner = element._owner;
|
||||
if (owner) {
|
||||
owners = [];
|
||||
while (owner != null) {
|
||||
owners.push({
|
||||
displayName: getData(owner).displayName || 'Unknown',
|
||||
id: getID(owner),
|
||||
type: getElementType(owner),
|
||||
});
|
||||
if (owner._currentElement) {
|
||||
owner = owner._currentElement._owner;
|
||||
}
|
||||
let owner = element._owner;
|
||||
if (owner) {
|
||||
owners = [];
|
||||
while (owner != null) {
|
||||
owners.push({
|
||||
displayName: getData(owner).displayName || 'Unknown',
|
||||
id: getID(owner),
|
||||
type: getElementType(owner),
|
||||
});
|
||||
if (owner._currentElement) {
|
||||
owner = owner._currentElement._owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const publicInstance = internalInstance._instance;
|
||||
if (publicInstance != null) {
|
||||
context = publicInstance.context || null;
|
||||
state = publicInstance.state || null;
|
||||
}
|
||||
const publicInstance = internalInstance._instance;
|
||||
if (publicInstance != null) {
|
||||
context = publicInstance.context || null;
|
||||
state = publicInstance.state || null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
+5
-2
@@ -36,7 +36,7 @@ import {
|
||||
TREE_OPERATION_REORDER_CHILDREN,
|
||||
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
|
||||
} from '../constants';
|
||||
import { inspectHooksOfFiber } from './ReactDebugHooks';
|
||||
import { inspectHooksOfFiber } from 'react-debug-tools';
|
||||
import {
|
||||
patch as patchConsole,
|
||||
registerRenderer as registerRendererWithConsole,
|
||||
@@ -285,6 +285,7 @@ export function getInternalReactConstants(
|
||||
const symbolOrNumber =
|
||||
typeof type === 'object' && type !== null ? type.$$typeof : type;
|
||||
|
||||
// $FlowFixMe Flow doesn't know about typeof "symbol"
|
||||
return typeof symbolOrNumber === 'symbol'
|
||||
? symbolOrNumber.toString()
|
||||
: symbolOrNumber;
|
||||
@@ -2746,7 +2747,9 @@ export function attach(
|
||||
}
|
||||
}
|
||||
const fiber = idToFiberMap.get(id);
|
||||
scheduleUpdate(fiber);
|
||||
if (fiber != null) {
|
||||
scheduleUpdate(fiber);
|
||||
}
|
||||
}
|
||||
|
||||
// Remember if we're trying to restore the selection after reload.
|
||||
|
||||
@@ -343,15 +343,6 @@ export type DevToolsHook = {
|
||||
) => void,
|
||||
};
|
||||
|
||||
export type HooksNode = {
|
||||
id: number,
|
||||
isStateEditable: boolean,
|
||||
name: string,
|
||||
value: mixed,
|
||||
subHooks: Array<HooksNode>,
|
||||
};
|
||||
export type HooksTree = Array<HooksNode>;
|
||||
|
||||
export type ReactEventResponder<E, C> = {
|
||||
$$typeof: Symbol | number,
|
||||
displayName: string,
|
||||
|
||||
@@ -106,6 +106,7 @@ export default function setupHighlighter(
|
||||
if (scrollIntoView && typeof node.scrollIntoView === 'function') {
|
||||
// If the node isn't visible show it before highlighting it.
|
||||
// We may want to reconsider this; it might be a little disruptive.
|
||||
// $FlowFixMe Flow only knows about 'start' | 'end'
|
||||
node.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import EventEmitter from 'node-events';
|
||||
|
||||
import type { ComponentFilter, Wall } from './types';
|
||||
import type {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import EventEmitter from 'node-events';
|
||||
import { prepareProfilingDataFrontendFromBackendAndStore } from './views/Profiler/utils';
|
||||
import ProfilingCache from './ProfilingCache';
|
||||
import Store from './store';
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import EventEmitter from 'node-events';
|
||||
import { inspect } from 'util';
|
||||
import {
|
||||
TREE_OPERATION_ADD,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from '../utils';
|
||||
import { localStorageGetItem, localStorageSetItem } from '../storage';
|
||||
import { __DEBUG__ } from '../constants';
|
||||
import { printStore } from '../__tests__/storeSerializer';
|
||||
import { printStore } from './utils';
|
||||
import ProfilerStore from './ProfilerStore';
|
||||
|
||||
import type { Element } from './views/Components/types';
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
|
||||
import type { Element } from './views/Components/types';
|
||||
import type Store from './store';
|
||||
|
||||
export function printElement(element: Element, includeWeight: boolean = false) {
|
||||
let prefix = ' ';
|
||||
if (element.children.length > 0) {
|
||||
prefix = element.isCollapsed ? '▸' : '▾';
|
||||
}
|
||||
|
||||
let key = '';
|
||||
if (element.key !== null) {
|
||||
key = ` key="${element.key}"`;
|
||||
}
|
||||
|
||||
let hocs = '';
|
||||
if (element.hocDisplayNames !== null) {
|
||||
hocs = ` [${element.hocDisplayNames.join('][')}]`;
|
||||
}
|
||||
|
||||
let suffix = '';
|
||||
if (includeWeight) {
|
||||
suffix = ` (${element.isCollapsed ? 1 : element.weight})`;
|
||||
}
|
||||
|
||||
return `${' '.repeat(element.depth + 1)}${prefix} <${element.displayName ||
|
||||
'null'}${key}>${hocs}${suffix}`;
|
||||
}
|
||||
|
||||
export function printOwnersList(elements: Array<Element>, includeWeight: boolean = false) {
|
||||
return elements
|
||||
.map(element => printElement(element, includeWeight))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
export function printStore(store: Store, includeWeight: boolean = false) {
|
||||
const snapshotLines = [];
|
||||
|
||||
let rootWeight = 0;
|
||||
|
||||
store.roots.forEach(rootID => {
|
||||
const { weight } = ((store.getElementByID(rootID): any): Element);
|
||||
|
||||
snapshotLines.push('[root]' + (includeWeight ? ` (${weight})` : ''));
|
||||
|
||||
for (let i = rootWeight; i < rootWeight + weight; i++) {
|
||||
const element = store.getElementAtIndex(i);
|
||||
|
||||
if (element == null) {
|
||||
throw Error(`Could not find element at index ${i}`);
|
||||
}
|
||||
|
||||
snapshotLines.push(printElement(element, includeWeight));
|
||||
}
|
||||
|
||||
rootWeight += weight;
|
||||
});
|
||||
|
||||
// Make sure the pretty-printed test align with the Store's reported number of total rows.
|
||||
if (rootWeight !== store.numElements) {
|
||||
throw Error(
|
||||
`Inconsistent Store state. Individual root weights (${rootWeight}) do not match total weight (${
|
||||
store.numElements
|
||||
})`
|
||||
);
|
||||
}
|
||||
|
||||
// If roots have been unmounted, verify that they've been removed from maps.
|
||||
// This helps ensure the Store doesn't leak memory.
|
||||
store.assertExpectedRootMapSizes();
|
||||
|
||||
return snapshotLines.join('\n');
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import styles from './HooksTree.css';
|
||||
import { meta } from '../../../hydration';
|
||||
|
||||
import type { InspectPath } from './SelectedElement';
|
||||
import type { HooksNode, HooksTree } from 'react-devtools-shared/src/backend/types';
|
||||
import type { HooksNode, HooksTree } from 'react-debug-tools/src/ReactDebugHooks';
|
||||
|
||||
type HooksTreeViewProps = {|
|
||||
canEditHooks: boolean,
|
||||
|
||||
+1
-1
@@ -285,7 +285,7 @@ function InspectedElementContextController({ children }: Props) {
|
||||
}
|
||||
|
||||
function hydrateHelper(
|
||||
dehydratedData: DehydratedData | null,
|
||||
dehydratedData: any | null,
|
||||
path?: Array<string | number>
|
||||
): Object | null {
|
||||
if (dehydratedData !== null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import type { Dehydrated } from 'react-devtools-shared/src/hydration';
|
||||
import type { ElementType } from 'react-devtools-shared/src/types';
|
||||
|
||||
// Each element on the frontend corresponds to a Fiber on the backend.
|
||||
@@ -83,5 +84,5 @@ export type InspectedElement = {|
|
||||
|
||||
export type DehydratedData = {|
|
||||
cleaned: Array<Array<string | number>>,
|
||||
data: Object,
|
||||
data: string | Dehydrated | Array<Dehydrated> | { [key: string]: string | Dehydrated },
|
||||
|};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import escapeStringRegExp from 'escape-string-regexp';
|
||||
import { meta } from '../../hydration';
|
||||
|
||||
import type { HooksTree } from 'react-devtools-shared/src/backend/types';
|
||||
import type { HooksTree } from 'react-debug-tools/src/ReactDebugHooks';
|
||||
|
||||
export function createRegExp(string: string): RegExp {
|
||||
// Allow /regex/ syntax with optional last /
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ export const meta = {
|
||||
type: Symbol('type'),
|
||||
};
|
||||
|
||||
type Dehydrated = {|
|
||||
export type Dehydrated = {|
|
||||
inspectable: boolean,
|
||||
name: string | null,
|
||||
readonly?: boolean,
|
||||
@@ -176,7 +176,7 @@ export function dehydrate(
|
||||
path: Array<string | number>,
|
||||
isPathWhitelisted: (path: Array<string | number>) => boolean,
|
||||
level?: number = 0
|
||||
): string | Dehydrated | { [key: string]: string | Dehydrated } {
|
||||
): string | Dehydrated | Array<Dehydrated> | { [key: string]: string | Dehydrated } {
|
||||
const type = getDataType(data);
|
||||
|
||||
switch (type) {
|
||||
|
||||
@@ -4643,6 +4643,11 @@ node-cleanup@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
|
||||
|
||||
"node-events@npm:events@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
|
||||
integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
|
||||
|
||||
node-fetch@^1.0.1, node-fetch@^1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
|
||||
Reference in New Issue
Block a user