main
js 201 lines 6.5 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 type {ReactComponentInfo, ReactAsyncInfo} from 'shared/ReactTypes';
11 import type {LazyComponent} from 'react/src/ReactLazy';
12
13 import {
14 describeBuiltInComponentFrame,
15 describeFunctionComponentFrame,
16 describeClassComponentFrame,
17 describeDebugInfoFrame,
18 } from 'shared/ReactComponentStackFrame';
19
20 import {
21 REACT_FORWARD_REF_TYPE,
22 REACT_MEMO_TYPE,
23 REACT_LAZY_TYPE,
24 REACT_SUSPENSE_LIST_TYPE,
25 REACT_SUSPENSE_TYPE,
26 REACT_VIEW_TRANSITION_TYPE,
27 } from 'shared/ReactSymbols';
28
29 import {enableViewTransition} from 'shared/ReactFeatureFlags';
30
31 import {formatOwnerStack} from 'shared/ReactOwnerStackFrames';
32
33 export type ComponentStackNode = {
34 parent: null | ComponentStackNode,
35 type:
36 | symbol
37 | string
38 | Function
39 | LazyComponent<any, any>
40 | ReactComponentInfo
41 | ReactAsyncInfo,
42 owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
43 stack?: null | string | Error, // DEV only
44 };
45
46 function shouldConstruct(Component: any) {
47 return Component.prototype && Component.prototype.isReactComponent;
48 }
49
50 function describeComponentStackByType(
51 type:
52 | symbol
53 | string
54 | Function
55 | LazyComponent<any, any>
56 | ReactComponentInfo,
57 ): string {
58 if (typeof type === 'string') {
59 return describeBuiltInComponentFrame(type);
60 }
61 if (typeof type === 'function') {
62 if (shouldConstruct(type)) {
63 return describeClassComponentFrame(type);
64 } else {
65 return describeFunctionComponentFrame(type);
66 }
67 }
68 // $FlowFixMe[invalid-compare]
69 if (typeof type === 'object' && type !== null) {
70 switch (type.$$typeof) {
71 case REACT_FORWARD_REF_TYPE: {
72 return describeFunctionComponentFrame((type as any).render);
73 }
74 case REACT_MEMO_TYPE: {
75 return describeFunctionComponentFrame((type as any).type);
76 }
77 case REACT_LAZY_TYPE: {
78 const lazyComponent: LazyComponent<any, any> = type as any;
79 const payload = lazyComponent._payload;
80 const init = lazyComponent._init;
81 try {
82 type = init(payload);
83 } catch (x) {
84 // TODO: When we support Thenables as component types we should rename this.
85 return describeBuiltInComponentFrame('Lazy');
86 }
87 return describeComponentStackByType(type);
88 }
89 }
90 if (typeof type.name === 'string') {
91 return describeDebugInfoFrame(type.name, type.env, type.debugLocation);
92 }
93 }
94 switch (type) {
95 case REACT_SUSPENSE_LIST_TYPE: {
96 return describeBuiltInComponentFrame('SuspenseList');
97 }
98 case REACT_SUSPENSE_TYPE: {
99 return describeBuiltInComponentFrame('Suspense');
100 }
101 case REACT_VIEW_TRANSITION_TYPE:
102 if (enableViewTransition) {
103 return describeBuiltInComponentFrame('ViewTransition');
104 }
105 }
106 return '';
107 }
108
109 export function getStackByComponentStackNode(
110 componentStack: ComponentStackNode,
111 ): string {
112 try {
113 let info = '';
114 let node: ComponentStackNode = componentStack;
115 do {
116 info += describeComponentStackByType(node.type);
117 // $FlowFixMe[incompatible-type] we bail out when we get a null
118 node = node.parent;
119 } while (node);
120 return info;
121 } catch (x) {
122 return '\nError generating stack: ' + x.message + '\n' + x.stack;
123 }
124 }
125
126 function describeFunctionComponentFrameWithoutLineNumber(fn: Function): string {
127 // We use this because we don't actually want to describe the line of the component
128 // but just the component name.
129 const name = fn ? fn.displayName || fn.name : '';
130 return name ? describeBuiltInComponentFrame(name) : '';
131 }
132
133 export function getOwnerStackByComponentStackNodeInDev(
134 componentStack: ComponentStackNode,
135 ): string {
136 if (!__DEV__) {
137 return '';
138 }
139 try {
140 let info = '';
141
142 // The owner stack of the current component will be where it was created, i.e. inside its owner.
143 // There's no actual name of the currently executing component. Instead, that is available
144 // on the regular stack that's currently executing. However, for built-ins there is no such
145 // named stack frame and it would be ignored as being internal anyway. Therefore we add
146 // add one extra frame just to describe the "current" built-in component by name.
147 // Similarly, if there is no owner at all, then there's no stack frame so we add the name
148 // of the root component to the stack to know which component is currently executing.
149 if (typeof componentStack.type === 'string') {
150 info += describeBuiltInComponentFrame(componentStack.type);
151 } else if (typeof componentStack.type === 'function') {
152 if (!componentStack.owner) {
153 // Only if we have no other data about the callsite do we add
154 // the component name as the single stack frame.
155 info += describeFunctionComponentFrameWithoutLineNumber(
156 componentStack.type,
157 );
158 }
159 } else {
160 if (!componentStack.owner) {
161 info += describeComponentStackByType(componentStack.type);
162 }
163 }
164
165 let owner: void | null | ComponentStackNode | ReactComponentInfo =
166 componentStack;
167
168 while (owner) {
169 let ownerStack: ?string = null;
170 if (owner.debugStack != null) {
171 // Server Component
172 // TODO: Should we stash this somewhere for caching purposes?
173 ownerStack = formatOwnerStack(owner.debugStack);
174 owner = owner.owner;
175 } else {
176 // Client Component
177 const node: ComponentStackNode = owner as any;
178 if (node.stack != null) {
179 if (typeof node.stack !== 'string') {
180 ownerStack = node.stack = formatOwnerStack(node.stack);
181 } else {
182 ownerStack = node.stack;
183 }
184 }
185 owner = owner.owner;
186 }
187 // If we don't actually print the stack if there is no owner of this JSX element.
188 // In a real app it's typically not useful since the root app is always controlled
189 // by the framework. These also tend to have noisy stacks because they're not rooted
190 // in a React render but in some imperative bootstrapping code. It could be useful
191 // if the element was created in module scope. E.g. hoisted. We could add a a single
192 // stack frame for context for example but it doesn't say much if that's a wrapper.
193 if (owner && ownerStack) {
194 info += '\n' + ownerStack;
195 }
196 }
197 return info;
198 } catch (x) {
199 return '\nError generating stack: ' + x.message + '\n' + x.stack;
200 }
201 }