Skip to content

Get Server Component Function Location for Parent Stacks using Child's Owner Stack #33629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 24, 2025
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
10 changes: 8 additions & 2 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion packages/react-devtools-shared/src/backend/fiber/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion packages/react-reconciler/src/ReactFiberComponentStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server/src/ReactFizzComponentStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 20 additions & 1 deletion packages/shared/ReactComponentStackFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 + ']' : ''));
}

Expand Down
1 change: 1 addition & 0 deletions packages/shared/ReactTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading