import type {ReactComponentInfo} from 'shared/ReactTypes';
import type {LazyComponent} from 'react/src/ReactLazy';
import {
describeBuiltInComponentFrame,
describeFunctionComponentFrame,
describeClassComponentFrame,
describeDebugInfoFrame,
} from 'shared/ReactComponentStackFrame';
import {
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
REACT_SUSPENSE_LIST_TYPE,
REACT_SUSPENSE_TYPE,
} from 'shared/ReactSymbols';
import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
import {formatOwnerStack} from 'shared/ReactOwnerStackFrames';
export type ComponentStackNode = {
parent: null | ComponentStackNode,
type:
| symbol
| string
| Function
| LazyComponent<any, any>
| ReactComponentInfo,
owner?: null | ReactComponentInfo | ComponentStackNode,
stack?: null | string | Error,
};
function shouldConstruct(Component: any) {
return Component.prototype && Component.prototype.isReactComponent;
}
function describeComponentStackByType(
type:
| symbol
| string
| Function
| LazyComponent<any, any>
| ReactComponentInfo,
): string {
if (typeof type === 'string') {
return describeBuiltInComponentFrame(type);
}
if (typeof type === 'function') {
if (shouldConstruct(type)) {
return describeClassComponentFrame(type);
} else {
return describeFunctionComponentFrame(type);
}
}
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
return describeFunctionComponentFrame((type: any).render);
}
case REACT_MEMO_TYPE: {
return describeFunctionComponentFrame((type: any).type);
}
case REACT_LAZY_TYPE: {
const lazyComponent: LazyComponent<any, any> = (type: any);
const payload = lazyComponent._payload;
const init = lazyComponent._init;
try {
type = init(payload);
} catch (x) {
return describeBuiltInComponentFrame('Lazy');
}
return describeComponentStackByType(type);
}
}
if (typeof type.name === 'string') {
return describeDebugInfoFrame(type.name, type.env);
}
}
switch (type) {
case REACT_SUSPENSE_LIST_TYPE: {
return describeBuiltInComponentFrame('SuspenseList');
}
case REACT_SUSPENSE_TYPE: {
return describeBuiltInComponentFrame('Suspense');
}
}
return '';
}
export function getStackByComponentStackNode(
componentStack: ComponentStackNode,
): string {
try {
let info = '';
let node: ComponentStackNode = componentStack;
do {
info += describeComponentStackByType(node.type);
node = node.parent;
} while (node);
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}
function describeFunctionComponentFrameWithoutLineNumber(fn: Function): string {
const name = fn ? fn.displayName || fn.name : '';
return name ? describeBuiltInComponentFrame(name) : '';
}
export function getOwnerStackByComponentStackNodeInDev(
componentStack: ComponentStackNode,
): string {
if (!enableOwnerStacks || !__DEV__) {
return '';
}
try {
let info = '';
if (typeof componentStack.type === 'string') {
info += describeBuiltInComponentFrame(componentStack.type);
} else if (typeof componentStack.type === 'function') {
if (!componentStack.owner) {
info += describeFunctionComponentFrameWithoutLineNumber(
componentStack.type,
);
}
} else {
if (!componentStack.owner) {
info += describeComponentStackByType(componentStack.type);
}
}
let owner: void | null | ComponentStackNode | ReactComponentInfo =
componentStack;
while (owner) {
let ownerStack: ?string = null;
if (owner.debugStack != null) {
ownerStack = formatOwnerStack(owner.debugStack);
owner = owner.owner;
} else {
const node: ComponentStackNode = (owner: any);
if (node.stack != null) {
if (typeof node.stack !== 'string') {
ownerStack = node.stack = formatOwnerStack(node.stack);
} else {
ownerStack = node.stack;
}
}
owner = owner.owner;
}
if (owner && ownerStack) {
info += '\n' + ownerStack;
}
}
return info;
} catch (x) {
return '\nError generating stack: ' + x.message + '\n' + x.stack;
}
}