main
js 209 lines 7.28 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import {enableViewTransition} from 'shared/ReactFeatureFlags';
11 import type {Fiber} from './ReactInternalTypes';
12 import type {ReactComponentInfo} from 'shared/ReactTypes';
13
14 import {
15 HostComponent,
16 HostHoistable,
17 HostSingleton,
18 LazyComponent,
19 SuspenseComponent,
20 SuspenseListComponent,
21 FunctionComponent,
22 ForwardRef,
23 SimpleMemoComponent,
24 ClassComponent,
25 HostText,
26 ViewTransitionComponent,
27 ActivityComponent,
28 } from './ReactWorkTags';
29 import {
30 describeBuiltInComponentFrame,
31 describeFunctionComponentFrame,
32 describeClassComponentFrame,
33 describeDebugInfoFrame,
34 } from 'shared/ReactComponentStackFrame';
35 import {formatOwnerStack} from 'shared/ReactOwnerStackFrames';
36
37 function describeFiber(fiber: Fiber, childFiber: null | Fiber): string {
38 switch (fiber.tag) {
39 case HostHoistable:
40 case HostSingleton:
41 case HostComponent:
42 return describeBuiltInComponentFrame(fiber.type);
43 case LazyComponent:
44 // TODO: When we support Thenables as component types we should rename this.
45 return describeBuiltInComponentFrame('Lazy');
46 case SuspenseComponent:
47 if (fiber.child !== childFiber && childFiber !== null) {
48 // If we came from the second Fiber then we're in the Suspense Fallback.
49 return describeBuiltInComponentFrame('Suspense Fallback');
50 }
51 return describeBuiltInComponentFrame('Suspense');
52 case SuspenseListComponent:
53 return describeBuiltInComponentFrame('SuspenseList');
54 case FunctionComponent:
55 case SimpleMemoComponent:
56 return describeFunctionComponentFrame(fiber.type);
57 case ForwardRef:
58 return describeFunctionComponentFrame(fiber.type.render);
59 case ClassComponent:
60 return describeClassComponentFrame(fiber.type);
61 case ActivityComponent:
62 return describeBuiltInComponentFrame('Activity');
63 case ViewTransitionComponent:
64 if (enableViewTransition) {
65 return describeBuiltInComponentFrame('ViewTransition');
66 }
67 // Fallthrough
68 default:
69 return '';
70 }
71 }
72
73 export function getStackByFiberInDevAndProd(workInProgress: Fiber): string {
74 try {
75 let info = '';
76 let node: Fiber = workInProgress;
77 let previous: null | Fiber = null;
78 do {
79 info += describeFiber(node, previous);
80 if (__DEV__) {
81 // Add any Server Component stack frames in reverse order.
82 const debugInfo = node._debugInfo;
83 if (debugInfo) {
84 for (let i = debugInfo.length - 1; i >= 0; i--) {
85 const entry = debugInfo[i];
86 if (typeof entry.name === 'string') {
87 info += describeDebugInfoFrame(
88 entry.name,
89 entry.env,
90 entry.debugLocation,
91 );
92 }
93 }
94 }
95 }
96 previous = node;
97 // $FlowFixMe[incompatible-type] we bail out when we get a null
98 node = node.return;
99 } while (node);
100 return info;
101 } catch (x) {
102 return '\nError generating stack: ' + x.message + '\n' + x.stack;
103 }
104 }
105
106 function describeFunctionComponentFrameWithoutLineNumber(fn: Function): string {
107 // We use this because we don't actually want to describe the line of the component
108 // but just the component name.
109 const name = fn ? fn.displayName || fn.name : '';
110 return name ? describeBuiltInComponentFrame(name) : '';
111 }
112
113 export function getOwnerStackByFiberInDev(workInProgress: Fiber): string {
114 if (!__DEV__) {
115 return '';
116 }
117 try {
118 let info = '';
119
120 if (workInProgress.tag === HostText) {
121 // Text nodes never have an owner/stack because they're not created through JSX.
122 // We use the parent since text nodes are always created through a host parent.
123 workInProgress = workInProgress.return as any;
124 }
125
126 // The owner stack of the current fiber will be where it was created, i.e. inside its owner.
127 // There's no actual name of the currently executing component. Instead, that is available
128 // on the regular stack that's currently executing. However, for built-ins there is no such
129 // named stack frame and it would be ignored as being internal anyway. Therefore we add
130 // add one extra frame just to describe the "current" built-in component by name.
131 // Similarly, if there is no owner at all, then there's no stack frame so we add the name
132 // of the root component to the stack to know which component is currently executing.
133 switch (workInProgress.tag) {
134 case HostHoistable:
135 case HostSingleton:
136 case HostComponent:
137 info += describeBuiltInComponentFrame(workInProgress.type);
138 break;
139 case SuspenseComponent:
140 info += describeBuiltInComponentFrame('Suspense');
141 break;
142 case SuspenseListComponent:
143 info += describeBuiltInComponentFrame('SuspenseList');
144 break;
145 case ActivityComponent:
146 info += describeBuiltInComponentFrame('Activity');
147 break;
148 case ViewTransitionComponent:
149 if (enableViewTransition) {
150 info += describeBuiltInComponentFrame('ViewTransition');
151 break;
152 }
153 // Fallthrough
154 case FunctionComponent:
155 case SimpleMemoComponent:
156 case ClassComponent:
157 if (!workInProgress._debugOwner && info === '') {
158 // Only if we have no other data about the callsite do we add
159 // the component name as the single stack frame.
160 info += describeFunctionComponentFrameWithoutLineNumber(
161 workInProgress.type,
162 );
163 }
164 break;
165 case ForwardRef:
166 if (!workInProgress._debugOwner && info === '') {
167 info += describeFunctionComponentFrameWithoutLineNumber(
168 workInProgress.type.render,
169 );
170 }
171 break;
172 }
173
174 let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
175
176 while (owner) {
177 if (typeof owner.tag === 'number') {
178 const fiber: Fiber = owner as any;
179 owner = fiber._debugOwner;
180 const debugStack = fiber._debugStack;
181 // If we don't actually print the stack if there is no owner of this JSX element.
182 // In a real app it's typically not useful since the root app is always controlled
183 // by the framework. These also tend to have noisy stacks because they're not rooted
184 // in a React render but in some imperative bootstrapping code. It could be useful
185 // if the element was created in module scope. E.g. hoisted. We could add a a single
186 // stack frame for context for example but it doesn't say much if that's a wrapper.
187 if (owner && debugStack) {
188 const formattedStack = formatOwnerStack(debugStack);
189 if (formattedStack !== '') {
190 info += '\n' + formattedStack;
191 }
192 }
193 } else if (owner.debugStack != null) {
194 // Server Component
195 const ownerStack: Error = owner.debugStack;
196 owner = owner.owner;
197 if (owner && ownerStack) {
198 // TODO: Should we stash this somewhere for caching purposes?
199 info += '\n' + formatOwnerStack(ownerStack);
200 }
201 } else {
202 break;
203 }
204 }
205 return info;
206 } catch (x) {
207 return '\nError generating stack: ' + x.message + '\n' + x.stack;
208 }
209 }