mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[DevTools] Fix crash when inspecting Components suspended on data awaited in anonymous functions (#34234)
This commit is contained in:
committed by
GitHub
parent
06b5050d65
commit
e9dda80e60
+44
-17
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {StackFrame as ParsedStackFrame} from 'error-stack-parser';
|
||||
import type {
|
||||
Awaited,
|
||||
ReactContext,
|
||||
@@ -844,7 +844,11 @@ export type HooksTree = Array<HooksNode>;
|
||||
|
||||
let mostLikelyAncestorIndex = 0;
|
||||
|
||||
function findSharedIndex(hookStack: any, rootStack: any, rootIndex: number) {
|
||||
function findSharedIndex(
|
||||
hookStack: ParsedStackFrame[],
|
||||
rootStack: ParsedStackFrame[],
|
||||
rootIndex: number,
|
||||
) {
|
||||
const source = rootStack[rootIndex].source;
|
||||
hookSearch: for (let i = 0; i < hookStack.length; i++) {
|
||||
if (hookStack[i].source === source) {
|
||||
@@ -865,7 +869,10 @@ function findSharedIndex(hookStack: any, rootStack: any, rootIndex: number) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findCommonAncestorIndex(rootStack: any, hookStack: any) {
|
||||
function findCommonAncestorIndex(
|
||||
rootStack: ParsedStackFrame[],
|
||||
hookStack: ParsedStackFrame[],
|
||||
) {
|
||||
let rootIndex = findSharedIndex(
|
||||
hookStack,
|
||||
rootStack,
|
||||
@@ -886,7 +893,7 @@ function findCommonAncestorIndex(rootStack: any, hookStack: any) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function isReactWrapper(functionName: any, wrapperName: string) {
|
||||
function isReactWrapper(functionName: void | string, wrapperName: string) {
|
||||
const hookName = parseHookName(functionName);
|
||||
if (wrapperName === 'HostTransitionStatus') {
|
||||
return hookName === wrapperName || hookName === 'FormStatus';
|
||||
@@ -895,7 +902,7 @@ function isReactWrapper(functionName: any, wrapperName: string) {
|
||||
return hookName === wrapperName;
|
||||
}
|
||||
|
||||
function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
|
||||
function findPrimitiveIndex(hookStack: ParsedStackFrame[], hook: HookLogEntry) {
|
||||
const stackCache = getPrimitiveStackCache();
|
||||
const primitiveStack = stackCache.get(hook.primitive);
|
||||
if (primitiveStack === undefined) {
|
||||
@@ -926,7 +933,7 @@ function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
|
||||
function parseTrimmedStack(rootStack: ParsedStackFrame[], hook: HookLogEntry) {
|
||||
// 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);
|
||||
@@ -987,7 +994,7 @@ function parseHookName(functionName: void | string): string {
|
||||
}
|
||||
|
||||
function buildTree(
|
||||
rootStack: any,
|
||||
rootStack: ParsedStackFrame[],
|
||||
readHookLog: Array<HookLogEntry>,
|
||||
): HooksTree {
|
||||
const rootChildren: Array<HooksNode> = [];
|
||||
@@ -1044,10 +1051,20 @@ function buildTree(
|
||||
subHooks: children,
|
||||
debugInfo: null,
|
||||
hookSource: {
|
||||
lineNumber: stackFrame.lineNumber,
|
||||
columnNumber: stackFrame.columnNumber,
|
||||
functionName: stackFrame.functionName,
|
||||
fileName: stackFrame.fileName,
|
||||
lineNumber:
|
||||
stackFrame.lineNumber === undefined
|
||||
? null
|
||||
: stackFrame.lineNumber,
|
||||
columnNumber:
|
||||
stackFrame.columnNumber === undefined
|
||||
? null
|
||||
: stackFrame.columnNumber,
|
||||
functionName:
|
||||
stackFrame.functionName === undefined
|
||||
? null
|
||||
: stackFrame.functionName,
|
||||
fileName:
|
||||
stackFrame.fileName === undefined ? null : stackFrame.fileName,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1092,10 +1109,14 @@ function buildTree(
|
||||
};
|
||||
if (stack && stack.length >= 1) {
|
||||
const stackFrame = stack[0];
|
||||
hookSource.lineNumber = stackFrame.lineNumber;
|
||||
hookSource.functionName = stackFrame.functionName;
|
||||
hookSource.fileName = stackFrame.fileName;
|
||||
hookSource.columnNumber = stackFrame.columnNumber;
|
||||
hookSource.lineNumber =
|
||||
stackFrame.lineNumber === undefined ? null : stackFrame.lineNumber;
|
||||
hookSource.functionName =
|
||||
stackFrame.functionName === undefined ? null : stackFrame.functionName;
|
||||
hookSource.fileName =
|
||||
stackFrame.fileName === undefined ? null : stackFrame.fileName;
|
||||
hookSource.columnNumber =
|
||||
stackFrame.columnNumber === undefined ? null : stackFrame.columnNumber;
|
||||
}
|
||||
|
||||
levelChild.hookSource = hookSource;
|
||||
@@ -1201,7 +1222,10 @@ export function inspectHooks<Props>(
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
currentDispatcher.H = previousDispatcher;
|
||||
}
|
||||
const rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
const rootStack =
|
||||
ancestorStackError === undefined
|
||||
? ([]: ParsedStackFrame[])
|
||||
: ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
}
|
||||
|
||||
@@ -1249,7 +1273,10 @@ function inspectHooksOfForwardRef<Props, Ref>(
|
||||
hookLog = [];
|
||||
currentDispatcher.H = previousDispatcher;
|
||||
}
|
||||
const rootStack = ErrorStackParser.parse(ancestorStackError);
|
||||
const rootStack =
|
||||
ancestorStackError === undefined
|
||||
? ([]: ParsedStackFrame[])
|
||||
: ErrorStackParser.parse(ancestorStackError);
|
||||
return buildTree(rootStack, readHookLog);
|
||||
}
|
||||
|
||||
|
||||
@@ -720,6 +720,53 @@ describe('ReactHooksInspection', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should inspect use() calls in anonymous loops', () => {
|
||||
function Foo({entries}) {
|
||||
const values = Object.fromEntries(
|
||||
Object.entries(entries).map(([key, value]) => {
|
||||
return [key, React.use(value)];
|
||||
}),
|
||||
);
|
||||
return <div>{values}</div>;
|
||||
}
|
||||
const tree = ReactDebugTools.inspectHooks(Foo, {
|
||||
entries: {one: Promise.resolve('one'), two: Promise.resolve('two')},
|
||||
});
|
||||
const results = normalizeSourceLoc(tree);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0]).toMatchInlineSnapshot(`
|
||||
{
|
||||
"debugInfo": null,
|
||||
"hookSource": {
|
||||
"columnNumber": 0,
|
||||
"fileName": "**",
|
||||
"functionName": "Foo",
|
||||
"lineNumber": 0,
|
||||
},
|
||||
"id": null,
|
||||
"isStateEditable": false,
|
||||
"name": "",
|
||||
"subHooks": [
|
||||
{
|
||||
"debugInfo": null,
|
||||
"hookSource": {
|
||||
"columnNumber": 0,
|
||||
"fileName": "**",
|
||||
"functionName": null,
|
||||
"lineNumber": 0,
|
||||
},
|
||||
"id": null,
|
||||
"isStateEditable": false,
|
||||
"name": "Use",
|
||||
"subHooks": [],
|
||||
"value": Promise {},
|
||||
},
|
||||
],
|
||||
"value": undefined,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
describe('useDebugValue', () => {
|
||||
it('should be ignored when called outside of a custom hook', () => {
|
||||
function Foo(props) {
|
||||
|
||||
+1
-1
@@ -885,7 +885,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
||||
"hookSource": {
|
||||
"columnNumber": 0,
|
||||
"fileName": "**",
|
||||
"functionName": undefined,
|
||||
"functionName": null,
|
||||
"lineNumber": 0,
|
||||
},
|
||||
"id": 0,
|
||||
|
||||
@@ -51,12 +51,16 @@ export function isInternalModule(
|
||||
const [startStackFrame, stopStackFrame] = ranges[i];
|
||||
|
||||
const isAfterStart =
|
||||
// $FlowFixMe[invalid-compare] -- TODO: Revealed when adding types to error-stack-parser
|
||||
locationLine > startStackFrame.lineNumber ||
|
||||
(locationLine === startStackFrame.lineNumber &&
|
||||
// $FlowFixMe[invalid-compare]
|
||||
locationColumn >= startStackFrame.columnNumber);
|
||||
const isBeforeStop =
|
||||
// $FlowFixMe[invalid-compare]
|
||||
locationLine < stopStackFrame.lineNumber ||
|
||||
(locationLine === stopStackFrame.lineNumber &&
|
||||
// $FlowFixMe[invalid-compare]
|
||||
locationColumn <= stopStackFrame.columnNumber);
|
||||
|
||||
if (isAfterStart && isBeforeStop) {
|
||||
|
||||
+4
-9
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {StackFrame as ErrorStackFrame} from 'error-stack-parser';
|
||||
import type {ScrollState} from './view-base/utils/scrollState';
|
||||
|
||||
// Source: https://github.com/facebook/flow/issues/4002#issuecomment-323612798
|
||||
@@ -16,12 +16,7 @@ type Return_<R, F: (...args: Array<any>) => R> = R;
|
||||
export type Return<T> = Return_<mixed, T>;
|
||||
|
||||
// Project types
|
||||
|
||||
export type ErrorStackFrame = {
|
||||
fileName: string,
|
||||
lineNumber: number,
|
||||
columnNumber: number,
|
||||
};
|
||||
export type {ErrorStackFrame};
|
||||
|
||||
export type Milliseconds = number;
|
||||
|
||||
@@ -192,7 +187,7 @@ export type ViewState = {
|
||||
};
|
||||
|
||||
export type InternalModuleSourceToRanges = Map<
|
||||
string,
|
||||
string | void,
|
||||
Array<[ErrorStackFrame, ErrorStackFrame]>,
|
||||
>;
|
||||
|
||||
@@ -224,7 +219,7 @@ export type TimelineDataExport = {
|
||||
duration: number,
|
||||
flamechart: Flamechart,
|
||||
internalModuleSourceToRanges: Array<
|
||||
[string, Array<[ErrorStackFrame, ErrorStackFrame]>],
|
||||
[string | void, Array<[ErrorStackFrame, ErrorStackFrame]>],
|
||||
>,
|
||||
laneToLabelKeyValueArray: Array<[ReactLane, string]>,
|
||||
laneToReactMeasureKeyValueArray: Array<[ReactLane, ReactMeasure[]]>,
|
||||
|
||||
@@ -29,6 +29,67 @@ declare module 'create-react-class' {
|
||||
declare const exports: $FlowFixMe;
|
||||
}
|
||||
|
||||
declare module 'error-stack-parser' {
|
||||
// flow-typed signature: 132e48034ef4756600e1d98681a166b5
|
||||
// flow-typed version: c6154227d1/error-stack-parser_v2.x.x/flow_>=v0.104.x
|
||||
|
||||
declare interface StackFrame {
|
||||
constructor(object: StackFrame): StackFrame;
|
||||
|
||||
isConstructor?: boolean;
|
||||
getIsConstructor(): boolean;
|
||||
setIsConstructor(): void;
|
||||
|
||||
isEval?: boolean;
|
||||
getIsEval(): boolean;
|
||||
setIsEval(): void;
|
||||
|
||||
isNative?: boolean;
|
||||
getIsNative(): boolean;
|
||||
setIsNative(): void;
|
||||
|
||||
isTopLevel?: boolean;
|
||||
getIsTopLevel(): boolean;
|
||||
setIsTopLevel(): void;
|
||||
|
||||
columnNumber?: number;
|
||||
getColumnNumber(): number;
|
||||
setColumnNumber(): void;
|
||||
|
||||
lineNumber?: number;
|
||||
getLineNumber(): number;
|
||||
setLineNumber(): void;
|
||||
|
||||
fileName?: string;
|
||||
getFileName(): string;
|
||||
setFileName(): void;
|
||||
|
||||
functionName?: string;
|
||||
getFunctionName(): string;
|
||||
setFunctionName(): void;
|
||||
|
||||
source?: string;
|
||||
getSource(): string;
|
||||
setSource(): void;
|
||||
|
||||
args?: any[];
|
||||
getArgs(): any[];
|
||||
setArgs(): void;
|
||||
|
||||
evalOrigin?: StackFrame;
|
||||
getEvalOrigin(): StackFrame;
|
||||
setEvalOrigin(): void;
|
||||
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
declare class ErrorStackParser {
|
||||
parse(error: Error): Array<StackFrame>;
|
||||
}
|
||||
|
||||
declare module.exports: ErrorStackParser;
|
||||
}
|
||||
|
||||
declare interface ConsoleTask {
|
||||
run<T>(f: () => T): T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user