diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index cb19755734078..baca176659abb 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -2715,9 +2715,15 @@ function initializeFakeStack( // $FlowFixMe[cannot-write] debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env); } - if (debugInfo.owner != null) { + const owner = debugInfo.owner; + if (owner != null) { // Initialize any owners not yet initialized. - initializeFakeStack(response, debugInfo.owner); + initializeFakeStack(response, owner); + if (owner.debugLocation === undefined && debugInfo.debugStack != null) { + // If we are the child of this owner, then the owner should be the bottom frame + // our stack. We can use it as the implied location of the owner. + owner.debugLocation = debugInfo.debugStack; + } } } diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 5fb3c28600d6f..d77bcea346b6c 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -69,7 +69,7 @@ function getErrorForJestMatcher(error) { function normalizeComponentInfo(debugInfo) { if (Array.isArray(debugInfo.stack)) { - const {debugTask, debugStack, ...copy} = debugInfo; + const {debugTask, debugStack, debugLocation, ...copy} = debugInfo; copy.stack = formatV8Stack(debugInfo.stack); if (debugInfo.owner) { copy.owner = normalizeComponentInfo(debugInfo.owner); diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 94246df6485e4..e24734b0ab032 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -5831,13 +5831,21 @@ export function attach( } function getSourceForInstance(instance: DevToolsInstance): Source | null { - const unresolvedSource = instance.source; + let unresolvedSource = instance.source; if (unresolvedSource === null) { // We don't have any source yet. We can try again later in case an owned child mounts later. // TODO: We won't have any information here if the child is filtered. return null; } + if (instance.kind === VIRTUAL_INSTANCE) { + // We might have found one on the virtual instance. + const debugLocation = instance.data.debugLocation; + if (debugLocation != null) { + unresolvedSource = debugLocation; + } + } + // If we have the debug stack (the creation stack of the JSX) for any owned child of this // component, then at the bottom of that stack will be a stack frame that is somewhere within // the component's function body. Typically it would be the callsite of the JSX unless there's diff --git a/packages/react-reconciler/src/ReactFiberComponentStack.js b/packages/react-reconciler/src/ReactFiberComponentStack.js index edb84b7e6de69..6cd4896dea91b 100644 --- a/packages/react-reconciler/src/ReactFiberComponentStack.js +++ b/packages/react-reconciler/src/ReactFiberComponentStack.js @@ -79,7 +79,11 @@ export function getStackByFiberInDevAndProd(workInProgress: Fiber): string { for (let i = debugInfo.length - 1; i >= 0; i--) { const entry = debugInfo[i]; if (typeof entry.name === 'string') { - info += describeDebugInfoFrame(entry.name, entry.env); + info += describeDebugInfoFrame( + entry.name, + entry.env, + entry.debugLocation, + ); } } } diff --git a/packages/react-server/src/ReactFizzComponentStack.js b/packages/react-server/src/ReactFizzComponentStack.js index c9d3998500e17..8b6147292d182 100644 --- a/packages/react-server/src/ReactFizzComponentStack.js +++ b/packages/react-server/src/ReactFizzComponentStack.js @@ -86,7 +86,7 @@ function describeComponentStackByType( } } if (typeof type.name === 'string') { - return describeDebugInfoFrame(type.name, type.env); + return describeDebugInfoFrame(type.name, type.env, type.debugLocation); } } switch (type) { diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 7b5a284bd916d..24f65902e540f 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -37,7 +37,7 @@ function normalizeStack(stack) { } function normalizeIOInfo(ioInfo) { - const {debugTask, debugStack, ...copy} = ioInfo; + const {debugTask, debugStack, debugLocation, ...copy} = ioInfo; if (ioInfo.stack) { copy.stack = normalizeStack(ioInfo.stack); } @@ -72,7 +72,7 @@ function normalizeIOInfo(ioInfo) { function normalizeDebugInfo(debugInfo) { if (Array.isArray(debugInfo.stack)) { - const {debugTask, debugStack, ...copy} = debugInfo; + const {debugTask, debugStack, debugLocation, ...copy} = debugInfo; copy.stack = normalizeStack(debugInfo.stack); if (debugInfo.owner) { copy.owner = normalizeDebugInfo(debugInfo.owner); diff --git a/packages/shared/ReactComponentStackFrame.js b/packages/shared/ReactComponentStackFrame.js index f8a27546b027a..1951ab07c7d16 100644 --- a/packages/shared/ReactComponentStackFrame.js +++ b/packages/shared/ReactComponentStackFrame.js @@ -13,6 +13,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import DefaultPrepareStackTrace from 'shared/DefaultPrepareStackTrace'; +import {formatOwnerStack} from './ReactOwnerStackFrames'; + let prefix; let suffix; export function describeBuiltInComponentFrame(name: string): string { @@ -38,7 +40,24 @@ export function describeBuiltInComponentFrame(name: string): string { return '\n' + prefix + name + suffix; } -export function describeDebugInfoFrame(name: string, env: ?string): string { +export function describeDebugInfoFrame( + name: string, + env: ?string, + location: ?Error, +): string { + if (location != null) { + // If we have a location, it's the child's owner stack. Treat the bottom most frame as + // the location of this function. + const childStack = formatOwnerStack(location); + const idx = childStack.lastIndexOf('\n'); + const lastLine = idx === -1 ? childStack : childStack.slice(idx + 1); + if (lastLine.indexOf(name) !== -1) { + // For async stacks it's possible we don't have the owner on it. As a precaution only + // use this frame if it has the name of the function in it. + return '\n' + lastLine; + } + } + return describeBuiltInComponentFrame(name + (env ? ' [' + env + ']' : '')); } diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 04a30dbf8b300..64eb0524a41df 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -209,6 +209,7 @@ export type ReactComponentInfo = { // Stashed Data for the Specific Execution Environment. Not part of the transport protocol +debugStack?: null | Error, +debugTask?: null | ConsoleTask, + debugLocation?: null | Error, }; export type ReactEnvironmentInfo = {