Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 44 additions & 17 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* @flow
*/

import type {StackFrame as ParsedStackFrame} from 'error-stack-parser';
import type {
Awaited,
ReactContext,
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -987,7 +994,7 @@ function parseHookName(functionName: void | string): string {
}

function buildTree(
rootStack: any,
rootStack: ParsedStackFrame[],
readHookLog: Array<HookLogEntry>,
): HooksTree {
const rootChildren: Array<HooksNode> = [];
Expand Down Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,7 @@ describe('ReactHooksInspectionIntegration', () => {
"hookSource": {
"columnNumber": 0,
"fileName": "**",
"functionName": undefined,
"functionName": null,
"lineNumber": 0,
},
"id": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
13 changes: 4 additions & 9 deletions packages/react-devtools-timeline/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -192,7 +187,7 @@ export type ViewState = {
};

export type InternalModuleSourceToRanges = Map<
string,
string | void,
Array<[ErrorStackFrame, ErrorStackFrame]>,
>;

Expand Down Expand Up @@ -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[]]>,
Expand Down
61 changes: 61 additions & 0 deletions scripts/flow/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading