mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
DevTools: Rely on sourcemaps to compute hook name of built-in hooks in newer versions (#28593)
This commit is contained in:
committed by
Rick Hanlon
parent
921749c22e
commit
efecc43e7f
+71
-35
@@ -47,6 +47,7 @@ type HookLogEntry = {
|
||||
stackError: Error,
|
||||
value: mixed,
|
||||
debugInfo: ReactDebugInfo | null,
|
||||
dispatcherHookName: string,
|
||||
};
|
||||
|
||||
let hookLog: Array<HookLogEntry> = [];
|
||||
@@ -131,6 +132,8 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
||||
);
|
||||
} catch (x) {}
|
||||
}
|
||||
|
||||
Dispatcher.useId();
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
@@ -207,6 +210,7 @@ function use<T>(usable: Usable<T>): T {
|
||||
value: fulfilledValue,
|
||||
debugInfo:
|
||||
thenable._debugInfo === undefined ? null : thenable._debugInfo,
|
||||
dispatcherHookName: 'Use',
|
||||
});
|
||||
return fulfilledValue;
|
||||
}
|
||||
@@ -224,6 +228,7 @@ function use<T>(usable: Usable<T>): T {
|
||||
value: thenable,
|
||||
debugInfo:
|
||||
thenable._debugInfo === undefined ? null : thenable._debugInfo,
|
||||
dispatcherHookName: 'Use',
|
||||
});
|
||||
throw SuspenseException;
|
||||
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
|
||||
@@ -236,6 +241,7 @@ function use<T>(usable: Usable<T>): T {
|
||||
stackError: new Error(),
|
||||
value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Use',
|
||||
});
|
||||
|
||||
return value;
|
||||
@@ -254,6 +260,7 @@ function useContext<T>(context: ReactContext<T>): T {
|
||||
stackError: new Error(),
|
||||
value: value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Context',
|
||||
});
|
||||
return value;
|
||||
}
|
||||
@@ -275,6 +282,7 @@ function useState<S>(
|
||||
stackError: new Error(),
|
||||
value: state,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'State',
|
||||
});
|
||||
return [state, (action: BasicStateAction<S>) => {}];
|
||||
}
|
||||
@@ -297,6 +305,7 @@ function useReducer<S, I, A>(
|
||||
stackError: new Error(),
|
||||
value: state,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Reducer',
|
||||
});
|
||||
return [state, (action: A) => {}];
|
||||
}
|
||||
@@ -310,6 +319,7 @@ function useRef<T>(initialValue: T): {current: T} {
|
||||
stackError: new Error(),
|
||||
value: ref.current,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Ref',
|
||||
});
|
||||
return ref;
|
||||
}
|
||||
@@ -322,6 +332,7 @@ function useCacheRefresh(): () => void {
|
||||
stackError: new Error(),
|
||||
value: hook !== null ? hook.memoizedState : function refresh() {},
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'CacheRefresh',
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
@@ -337,6 +348,7 @@ function useLayoutEffect(
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'LayoutEffect',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -351,6 +363,7 @@ function useInsertionEffect(
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'InsertionEffect',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,6 +378,7 @@ function useEffect(
|
||||
stackError: new Error(),
|
||||
value: create,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Effect',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -388,6 +402,7 @@ function useImperativeHandle<T>(
|
||||
stackError: new Error(),
|
||||
value: instance,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'ImperativeHandle',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -398,6 +413,7 @@ function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
|
||||
stackError: new Error(),
|
||||
value: typeof formatterFn === 'function' ? formatterFn(value) : value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'DebugValue',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -409,6 +425,7 @@ function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
|
||||
stackError: new Error(),
|
||||
value: hook !== null ? hook.memoizedState[0] : callback,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Callback',
|
||||
});
|
||||
return callback;
|
||||
}
|
||||
@@ -425,6 +442,7 @@ function useMemo<T>(
|
||||
stackError: new Error(),
|
||||
value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Memo',
|
||||
});
|
||||
return value;
|
||||
}
|
||||
@@ -446,6 +464,7 @@ function useSyncExternalStore<T>(
|
||||
stackError: new Error(),
|
||||
value,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'SyncExternalStore',
|
||||
});
|
||||
return value;
|
||||
}
|
||||
@@ -468,6 +487,7 @@ function useTransition(): [
|
||||
stackError: new Error(),
|
||||
value: isPending,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Transition',
|
||||
});
|
||||
return [isPending, () => {}];
|
||||
}
|
||||
@@ -481,6 +501,7 @@ function useDeferredValue<T>(value: T, initialValue?: T): T {
|
||||
stackError: new Error(),
|
||||
value: prevValue,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'DeferredValue',
|
||||
});
|
||||
return prevValue;
|
||||
}
|
||||
@@ -494,6 +515,7 @@ function useId(): string {
|
||||
stackError: new Error(),
|
||||
value: id,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Id',
|
||||
});
|
||||
return id;
|
||||
}
|
||||
@@ -544,6 +566,7 @@ function useOptimistic<S, A>(
|
||||
stackError: new Error(),
|
||||
value: state,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'Optimistic',
|
||||
});
|
||||
return [state, (action: A) => {}];
|
||||
}
|
||||
@@ -603,6 +626,7 @@ function useFormState<S, P>(
|
||||
stackError: stackError,
|
||||
value: value,
|
||||
debugInfo: debugInfo,
|
||||
dispatcherHookName: 'FormState',
|
||||
});
|
||||
|
||||
if (error !== null) {
|
||||
@@ -672,6 +696,7 @@ function useActionState<S, P>(
|
||||
stackError: stackError,
|
||||
value: value,
|
||||
debugInfo: debugInfo,
|
||||
dispatcherHookName: 'ActionState',
|
||||
});
|
||||
|
||||
if (error !== null) {
|
||||
@@ -759,8 +784,7 @@ export type HooksTree = Array<HooksNode>;
|
||||
// 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.
|
||||
// may not be the primitive.
|
||||
//
|
||||
// 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
|
||||
@@ -810,27 +834,8 @@ function findCommonAncestorIndex(rootStack: any, hookStack: any) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isReactWrapper(functionName: any, primitiveName: string) {
|
||||
if (!functionName) {
|
||||
return false;
|
||||
}
|
||||
switch (primitiveName) {
|
||||
case 'Context':
|
||||
case 'Context (use)':
|
||||
case 'Promise':
|
||||
case 'Unresolved':
|
||||
if (functionName.endsWith('use')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
const expectedPrimitiveName = 'use' + primitiveName;
|
||||
if (functionName.length < expectedPrimitiveName.length) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
functionName.lastIndexOf(expectedPrimitiveName) ===
|
||||
functionName.length - expectedPrimitiveName.length
|
||||
);
|
||||
function isReactWrapper(functionName: any, wrapperName: string) {
|
||||
return parseHookName(functionName) === wrapperName;
|
||||
}
|
||||
|
||||
function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
|
||||
@@ -841,17 +846,18 @@ function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
|
||||
}
|
||||
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 the next frame is a method from the dispatcher, we
|
||||
// assume that the next frame after that is the actual public API call.
|
||||
// This prohibits nesting dispatcher calls in hooks.
|
||||
if (
|
||||
i < hookStack.length - 1 &&
|
||||
isReactWrapper(hookStack[i].functionName, hook.primitive)
|
||||
isReactWrapper(hookStack[i].functionName, hook.dispatcherHookName)
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
if (
|
||||
i < hookStack.length - 1 &&
|
||||
isReactWrapper(hookStack[i].functionName, hook.primitive)
|
||||
isReactWrapper(hookStack[i].functionName, hook.dispatcherHookName)
|
||||
) {
|
||||
i++;
|
||||
}
|
||||
@@ -872,21 +878,41 @@ function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
|
||||
primitiveIndex === -1 ||
|
||||
rootIndex - primitiveIndex < 2
|
||||
) {
|
||||
// Something went wrong. Give up.
|
||||
return null;
|
||||
if (primitiveIndex === -1) {
|
||||
// Something went wrong. Give up.
|
||||
return [null, null];
|
||||
} else {
|
||||
return [hookStack[primitiveIndex - 1], null];
|
||||
}
|
||||
}
|
||||
return hookStack.slice(primitiveIndex, rootIndex - 1);
|
||||
return [
|
||||
hookStack[primitiveIndex - 1],
|
||||
hookStack.slice(primitiveIndex, rootIndex - 1),
|
||||
];
|
||||
}
|
||||
|
||||
function parseCustomHookName(functionName: void | string): string {
|
||||
function parseHookName(functionName: void | string): string {
|
||||
if (!functionName) {
|
||||
return '';
|
||||
}
|
||||
let startIndex = functionName.lastIndexOf('.');
|
||||
let startIndex = functionName.lastIndexOf('[as ');
|
||||
|
||||
if (startIndex !== -1) {
|
||||
// Workaround for sourcemaps in Jest and Chrome.
|
||||
// In `node --enable-source-maps`, we don't see "Object.useHostTransitionStatus [as useFormStatus]" but "Object.useFormStatus"
|
||||
// "Object.useHostTransitionStatus [as useFormStatus]" -> "useFormStatus"
|
||||
return parseHookName(functionName.slice(startIndex + '[as '.length, -1));
|
||||
}
|
||||
startIndex = functionName.lastIndexOf('.');
|
||||
if (startIndex === -1) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
startIndex += 1;
|
||||
}
|
||||
if (functionName.slice(startIndex, startIndex + 3) === 'use') {
|
||||
if (functionName.length - startIndex === 3) {
|
||||
return 'Use';
|
||||
}
|
||||
startIndex += 3;
|
||||
}
|
||||
return functionName.slice(startIndex);
|
||||
@@ -903,7 +929,17 @@ function buildTree(
|
||||
const stackOfChildren = [];
|
||||
for (let i = 0; i < readHookLog.length; i++) {
|
||||
const hook = readHookLog[i];
|
||||
const stack = parseTrimmedStack(rootStack, hook);
|
||||
const parseResult = parseTrimmedStack(rootStack, hook);
|
||||
const primitiveFrame = parseResult[0];
|
||||
const stack = parseResult[1];
|
||||
let displayName = hook.displayName;
|
||||
if (displayName === null && primitiveFrame !== null) {
|
||||
displayName =
|
||||
parseHookName(primitiveFrame.functionName) ||
|
||||
// Older versions of React do not have sourcemaps.
|
||||
// In those versions there was always a 1:1 mapping between wrapper and dispatcher method.
|
||||
parseHookName(hook.dispatcherHookName);
|
||||
}
|
||||
if (stack !== null) {
|
||||
// Note: The indices 0 <= n < length-1 will contain the names.
|
||||
// The indices 1 <= n < length will contain the source locations.
|
||||
@@ -934,7 +970,7 @@ function buildTree(
|
||||
const levelChild: HooksNode = {
|
||||
id: null,
|
||||
isStateEditable: false,
|
||||
name: parseCustomHookName(stack[j - 1].functionName),
|
||||
name: parseHookName(stack[j - 1].functionName),
|
||||
value: undefined,
|
||||
subHooks: children,
|
||||
debugInfo: null,
|
||||
@@ -952,7 +988,7 @@ function buildTree(
|
||||
}
|
||||
prevStack = stack;
|
||||
}
|
||||
const {displayName, primitive, debugInfo} = hook;
|
||||
const {primitive, debugInfo} = 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).
|
||||
|
||||
@@ -518,7 +518,7 @@ describe('ReactHooksInspection', () => {
|
||||
},
|
||||
"id": null,
|
||||
"isStateEditable": false,
|
||||
"name": "Promise",
|
||||
"name": "Use",
|
||||
"subHooks": [],
|
||||
"value": "world",
|
||||
},
|
||||
@@ -568,7 +568,7 @@ describe('ReactHooksInspection', () => {
|
||||
},
|
||||
"id": null,
|
||||
"isStateEditable": false,
|
||||
"name": "Unresolved",
|
||||
"name": "Use",
|
||||
"subHooks": [],
|
||||
"value": Any<Promise>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user