import type { Fiber, ContextDependency, MemoizedState, ReactContext } from "../types.js"; import { parseStack, type StackFrame } from "./parse-stack.js"; import { getRDTHook, _renderers } from "../rdt-hook.js"; const REACT_CONTEXT_TYPE = Symbol.for("react.context"); const REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel"); const FUNCTION_COMPONENT_TAG = 0; const CONTEXT_PROVIDER_TAG = 10; const FORWARD_REF_TAG = 11; const SIMPLE_MEMO_COMPONENT_TAG = 15; export interface HookSource { lineNumber: number | null; columnNumber: number | null; fileName: string | null; functionName: string | null; } export interface HooksNode { id: number | null; isStateEditable: boolean; name: string; value: unknown; subHooks: HooksNode[]; hookSource: HookSource | null; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface HooksTree extends Array {} interface HookLogEntry { displayName: string | null; primitive: string; stackError: Error; value: unknown; dispatcherHookName: string; } interface DispatcherRefContainer { H?: unknown; current?: unknown; [key: string]: unknown; } let hookLog: HookLogEntry[] = []; let primitiveStackCache: Map | null = null; let currentFiber: Fiber | null = null; let currentHook: MemoizedState | null = null; let currentContextDependency: ContextDependency | null = null; let currentThenableIndex = 0; // eslint-disable-next-line @typescript-eslint/no-explicit-any let currentThenableState: any[] | null = null; const SuspenseException: unknown = new Error( "Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render.", ); const parseErrorStack = (error: Error): StackFrame[] => parseStack(error.stack || "", { includeInElement: false }); const nextHook = (): MemoizedState | null => { const hook = currentHook; if (hook !== null) currentHook = hook.next; return hook; }; const readContext = (context: ReactContext): T => { if (currentFiber === null) return context._currentValue; if (currentContextDependency === null) { throw new Error("Context reads do not line up with context dependencies."); } if (Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")) { const value = currentContextDependency.memoizedValue as T; currentContextDependency = currentContextDependency.next; return value; } return context._currentValue; }; const getDispatcherRef = (): DispatcherRefContainer | null => { const rdtHook = getRDTHook(); const allRenderers = [..._renderers, ...rdtHook.renderers.values()]; for (const renderer of allRenderers) { const ref = renderer.currentDispatcherRef; if (ref && typeof ref === "object") return ref as DispatcherRefContainer; } return null; }; const getDispatcherFromRef = (ref: DispatcherRefContainer): unknown => "H" in ref ? ref.H : ref.current; const setDispatcherOnRef = (ref: DispatcherRefContainer, dispatcher: unknown): void => { if ("H" in ref) { ref.H = dispatcher; } else { ref.current = dispatcher; } }; const pushHookLogEntry = ( primitive: string, value: unknown, dispatcherHookName: string, displayName: string | null = null, ): void => { hookLog.push({ displayName, primitive, stackError: new Error(), value, dispatcherHookName, }); }; const dispatcherUse = (usable: unknown): unknown => { if (usable !== null && typeof usable === "object") { // eslint-disable-next-line @typescript-eslint/no-explicit-any const asThenable = usable as any; if (typeof asThenable.then === "function") { const thenable = currentThenableState !== null && currentThenableIndex < currentThenableState.length ? currentThenableState[currentThenableIndex++] : asThenable; switch (thenable.status) { case "fulfilled": { pushHookLogEntry("Promise", thenable.value, "Use"); return thenable.value; } case "rejected": throw thenable.reason; } pushHookLogEntry("Unresolved", thenable, "Use"); throw SuspenseException; } if (asThenable.$$typeof === REACT_CONTEXT_TYPE && "_currentValue" in asThenable) { const context: ReactContext = asThenable; const value = readContext(context); pushHookLogEntry("Context (use)", value, "Use", context.displayName || "Context"); return value; } } throw new Error("An unsupported type was passed to use(): " + String(usable)); }; const dispatcherUseContext = (context: ReactContext): unknown => { const value = readContext(context); pushHookLogEntry("Context", value, "Context", context.displayName || null); return value; }; const dispatcherUseState = (initialState: unknown): [unknown, () => void] => { const hook = nextHook(); const state = hook !== null ? hook.memoizedState : typeof initialState === "function" ? (initialState as () => unknown)() : initialState; pushHookLogEntry("State", state, "State"); return [state, () => {}]; }; const dispatcherUseReducer = ( _reducer: unknown, initialArg: unknown, init?: (arg: unknown) => unknown, ): [unknown, () => void] => { const hook = nextHook(); const state = hook !== null ? hook.memoizedState : init !== undefined ? init(initialArg) : initialArg; pushHookLogEntry("Reducer", state, "Reducer"); return [state, () => {}]; }; const dispatcherUseRef = (initialValue: unknown): { current: unknown } => { const hook = nextHook(); const ref = hook !== null ? hook.memoizedState : { current: initialValue }; pushHookLogEntry("Ref", (ref as { current: unknown }).current, "Ref"); return ref as { current: unknown }; }; const dispatcherUseCacheRefresh = (): (() => void) => { const hook = nextHook(); pushHookLogEntry("CacheRefresh", hook !== null ? hook.memoizedState : () => {}, "CacheRefresh"); return () => {}; }; const dispatcherUseLayoutEffect = (create: () => void): void => { nextHook(); pushHookLogEntry("LayoutEffect", create, "LayoutEffect"); }; const dispatcherUseInsertionEffect = (create: () => unknown): void => { nextHook(); pushHookLogEntry("InsertionEffect", create, "InsertionEffect"); }; const dispatcherUseEffect = (create: () => void): void => { nextHook(); pushHookLogEntry("Effect", create, "Effect"); }; const dispatcherUseImperativeHandle = (ref: unknown): void => { nextHook(); let instance: unknown; if (ref !== null && typeof ref === "object" && "current" in ref) { instance = ref.current; } pushHookLogEntry("ImperativeHandle", instance, "ImperativeHandle"); }; const dispatcherUseDebugValue = ( value: unknown, formatterFn?: (value: unknown) => unknown, ): void => { pushHookLogEntry( "DebugValue", typeof formatterFn === "function" ? formatterFn(value) : value, "DebugValue", ); }; const dispatcherUseCallback = (callback: unknown): unknown => { const hook = nextHook(); pushHookLogEntry( "Callback", hook !== null ? (hook.memoizedState as unknown[])[0] : callback, "Callback", ); return callback; }; const dispatcherUseMemo = (nextCreate: () => unknown): unknown => { const hook = nextHook(); const value = hook !== null ? (hook.memoizedState as unknown[])[0] : nextCreate(); pushHookLogEntry("Memo", value, "Memo"); return value; }; const dispatcherUseSyncExternalStore = ( _subscribe: unknown, getSnapshot: () => unknown, ): unknown => { const hook = nextHook(); nextHook(); const value = hook !== null ? hook.memoizedState : getSnapshot(); pushHookLogEntry("SyncExternalStore", value, "SyncExternalStore"); return value; }; const dispatcherUseTransition = (): [boolean, () => void] => { const stateHook = nextHook(); nextHook(); const isPending = stateHook !== null ? (stateHook.memoizedState as boolean) : false; pushHookLogEntry("Transition", isPending, "Transition"); return [isPending, () => {}]; }; const dispatcherUseDeferredValue = (value: unknown): unknown => { const hook = nextHook(); const previousValue = hook !== null ? hook.memoizedState : value; pushHookLogEntry("DeferredValue", previousValue, "DeferredValue"); return previousValue; }; const dispatcherUseId = (): string => { const hook = nextHook(); const identifier = hook !== null ? (hook.memoizedState as string) : ""; pushHookLogEntry("Id", identifier, "Id"); return identifier; }; const dispatcherUseMemoCache = (size: number): unknown[] => { const fiber = currentFiber; if (fiber === null || fiber === undefined) return []; const memoCache = ( fiber.updateQueue as { memoCache?: { data: unknown[][]; index: number } } | null )?.memoCache; if (memoCache === null || memoCache === undefined) return []; let data = memoCache.data[memoCache.index]; if (data === undefined) { data = memoCache.data[memoCache.index] = Array.from( { length: size }, () => REACT_MEMO_CACHE_SENTINEL, ); } memoCache.index++; return data; }; const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] => { const hook = nextHook(); const state = hook !== null ? hook.memoizedState : passthrough; pushHookLogEntry("Optimistic", state, "Optimistic"); return [state, () => {}]; }; const inspectActionStateHook = ( hook: MemoizedState | null, initialState: unknown, ): { value: unknown; error: unknown } => { let value: unknown; let error: unknown = null; if (hook !== null) { const actionResult = hook.memoizedState; if ( typeof actionResult === "object" && actionResult !== null && "then" in actionResult && typeof actionResult.then === "function" ) { const thenable = actionResult as { status?: string; value?: unknown; reason?: unknown }; switch (thenable.status) { case "fulfilled": value = thenable.value; break; case "rejected": error = thenable.reason; break; default: error = SuspenseException; value = thenable; } } else { value = actionResult; } } else { value = initialState; } return { value, error }; }; const createActionStateDispatcher = (primitive: string) => (_action: unknown, initialState: unknown): [unknown, () => void, boolean] => { const hook = nextHook(); nextHook(); nextHook(); const stackError = new Error(); const { value, error } = inspectActionStateHook(hook, initialState); hookLog.push({ displayName: null, primitive, stackError, value, dispatcherHookName: primitive, }); if (error !== null) throw error; return [value, () => {}, false]; }; const dispatcherUseActionState = createActionStateDispatcher("ActionState"); const dispatcherUseFormState = createActionStateDispatcher("FormState"); const dispatcherUseHostTransitionStatus = (): unknown => { // HACK: creating a minimal fake context because useHostTransitionStatus reads from an internal context not available outside React const status = readContext({ _currentValue: null } as unknown as ReactContext); pushHookLogEntry("HostTransitionStatus", status, "HostTransitionStatus"); return status; }; const dispatcherUseEffectEvent = (callback: (...args: unknown[]) => unknown): typeof callback => { nextHook(); pushHookLogEntry("EffectEvent", callback, "EffectEvent"); return callback; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const Dispatcher: Record any> = { readContext, use: dispatcherUse, useCallback: dispatcherUseCallback, useContext: dispatcherUseContext, useEffect: dispatcherUseEffect, useImperativeHandle: dispatcherUseImperativeHandle, useLayoutEffect: dispatcherUseLayoutEffect, useInsertionEffect: dispatcherUseInsertionEffect, useMemo: dispatcherUseMemo, useReducer: dispatcherUseReducer, useRef: dispatcherUseRef, useState: dispatcherUseState, useDebugValue: dispatcherUseDebugValue, useDeferredValue: dispatcherUseDeferredValue, useTransition: dispatcherUseTransition, useSyncExternalStore: dispatcherUseSyncExternalStore, useId: dispatcherUseId, useHostTransitionStatus: dispatcherUseHostTransitionStatus, useFormState: dispatcherUseFormState, useActionState: dispatcherUseActionState, useOptimistic: dispatcherUseOptimistic, useMemoCache: dispatcherUseMemoCache, useCacheRefresh: dispatcherUseCacheRefresh, useEffectEvent: dispatcherUseEffectEvent, }; const DispatcherProxy = typeof Proxy === "undefined" ? Dispatcher : new Proxy(Dispatcher, { get(target, prop: string) { if (Object.prototype.hasOwnProperty.call(target, prop)) return target[prop]; const error = new Error("Missing method in Dispatcher: " + prop); error.name = "ReactDebugToolsUnsupportedHookError"; throw error; }, }); const getPrimitiveStackCache = (): Map => { if (primitiveStackCache !== null) return primitiveStackCache; const cache = new Map(); let capturedHookLog: HookLogEntry[]; try { Dispatcher.useContext({ _currentValue: null }); Dispatcher.useState(null); Dispatcher.useReducer((state: unknown) => state, null); Dispatcher.useRef(null); if (typeof Dispatcher.useCacheRefresh === "function") Dispatcher.useCacheRefresh(); Dispatcher.useLayoutEffect(() => {}); Dispatcher.useInsertionEffect(() => {}); Dispatcher.useEffect(() => {}); Dispatcher.useImperativeHandle(undefined, () => null); Dispatcher.useDebugValue(null); Dispatcher.useCallback(() => {}); Dispatcher.useTransition(); Dispatcher.useSyncExternalStore( () => () => {}, () => null, () => null, ); Dispatcher.useDeferredValue(null); Dispatcher.useMemo(() => null); Dispatcher.useOptimistic(null, (state: unknown) => state); Dispatcher.useFormState((state: unknown) => state, null); Dispatcher.useActionState((state: unknown) => state, null); Dispatcher.useHostTransitionStatus(); if (typeof Dispatcher.useMemoCache === "function") Dispatcher.useMemoCache(0); if (typeof Dispatcher.use === "function") { Dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null }); Dispatcher.use({ then() {}, status: "fulfilled", value: null }); try { Dispatcher.use({ then() {} }); } catch { /* noop */ } } Dispatcher.useId(); if (typeof Dispatcher.useEffectEvent === "function") Dispatcher.useEffectEvent(() => {}); } finally { capturedHookLog = hookLog; hookLog = []; } for (const hook of capturedHookLog) { cache.set(hook.primitive, parseErrorStack(hook.stackError)); } primitiveStackCache = cache; return primitiveStackCache; }; let mostLikelyAncestorIndex = 0; const findSharedIndex = ( hookStack: StackFrame[], rootStack: StackFrame[], rootIndex: number, ): number => { const source = rootStack[rootIndex].source; hookSearch: for (let hookIndex = 0; hookIndex < hookStack.length; hookIndex++) { if (hookStack[hookIndex].source === source) { for ( let rootOffset = rootIndex + 1, hookOffset = hookIndex + 1; rootOffset < rootStack.length && hookOffset < hookStack.length; rootOffset++, hookOffset++ ) { if (hookStack[hookOffset].source !== rootStack[rootOffset].source) continue hookSearch; } return hookIndex; } } return -1; }; const findCommonAncestorIndex = (rootStack: StackFrame[], hookStack: StackFrame[]): number => { let rootIndex = findSharedIndex(hookStack, rootStack, mostLikelyAncestorIndex); if (rootIndex !== -1) return rootIndex; for ( let candidateIndex = 0; candidateIndex < rootStack.length && candidateIndex < 5; candidateIndex++ ) { rootIndex = findSharedIndex(hookStack, rootStack, candidateIndex); if (rootIndex !== -1) { mostLikelyAncestorIndex = candidateIndex; return rootIndex; } } return -1; }; const parseHookName = (functionName: string | undefined): string => { if (!functionName) return ""; let startIndex = functionName.lastIndexOf("[as "); if (startIndex !== -1) { return parseHookName(functionName.slice(startIndex + "[as ".length, -1)); } startIndex = functionName.lastIndexOf("."); startIndex = startIndex === -1 ? 0 : startIndex + 1; if (functionName.slice(startIndex).startsWith("unstable_")) startIndex += "unstable_".length; if (functionName.slice(startIndex).startsWith("experimental_")) startIndex += "experimental_".length; if (functionName.slice(startIndex, startIndex + 3) === "use") { if (functionName.length - startIndex === 3) return "Use"; startIndex += 3; } return functionName.slice(startIndex); }; const isReactWrapper = (functionName: string | undefined, wrapperName: string): boolean => { const hookName = parseHookName(functionName); if (wrapperName === "HostTransitionStatus") { return hookName === wrapperName || hookName === "FormStatus"; } return hookName === wrapperName; }; const findPrimitiveIndex = (hookStack: StackFrame[], hook: HookLogEntry): number => { const stackCache = getPrimitiveStackCache(); const primitiveStack = stackCache.get(hook.primitive); if (primitiveStack === undefined) return -1; for ( let frameIndex = 0; frameIndex < primitiveStack.length && frameIndex < hookStack.length; frameIndex++ ) { if (primitiveStack[frameIndex].source !== hookStack[frameIndex].source) { if ( frameIndex < hookStack.length - 1 && isReactWrapper(hookStack[frameIndex].functionName, hook.dispatcherHookName) ) { frameIndex++; } if ( frameIndex < hookStack.length - 1 && isReactWrapper(hookStack[frameIndex].functionName, hook.dispatcherHookName) ) { frameIndex++; } return frameIndex; } } return -1; }; const parseTrimmedStack = ( rootStack: StackFrame[], hook: HookLogEntry, ): [StackFrame | null, StackFrame[] | null] => { const hookStack = parseErrorStack(hook.stackError); const rootIndex = findCommonAncestorIndex(rootStack, hookStack); const primitiveIndex = findPrimitiveIndex(hookStack, hook); if (rootIndex === -1 || primitiveIndex === -1 || rootIndex - primitiveIndex < 2) { if (primitiveIndex === -1) return [null, null]; return [hookStack[primitiveIndex - 1] ?? null, null]; } return [hookStack[primitiveIndex - 1] ?? null, hookStack.slice(primitiveIndex, rootIndex - 1)]; }; const NON_ID_HOOK_PRIMITIVES = new Set([ "Context", "Context (use)", "DebugValue", "Promise", "Unresolved", "HostTransitionStatus", ]); const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): HooksTree => { const rootChildren: HooksNode[] = []; let previousStack: StackFrame[] | null = null; let levelChildren = rootChildren; let nativeHookID = 0; const childrenStack: HooksNode[][] = []; for (const hook of capturedHookLog) { const [primitiveFrame, stack] = parseTrimmedStack(rootStack, hook); let displayName = hook.displayName; if (displayName === null && primitiveFrame !== null) { displayName = parseHookName(primitiveFrame.functionName) || parseHookName(hook.dispatcherHookName); } if (stack !== null) { let commonSteps = 0; if (previousStack !== null) { while (commonSteps < stack.length && commonSteps < previousStack.length) { const stackSource = stack[stack.length - commonSteps - 1].source; const previousSource = previousStack[previousStack.length - commonSteps - 1].source; if (stackSource !== previousSource) break; commonSteps++; } for (let popIndex = previousStack.length - 1; popIndex > commonSteps; popIndex--) { levelChildren = childrenStack.pop() ?? rootChildren; } } for (let stackIndex = stack.length - commonSteps - 1; stackIndex >= 1; stackIndex--) { const children: HooksNode[] = []; const stackFrame = stack[stackIndex]; const levelChild: HooksNode = { id: null, isStateEditable: false, name: parseHookName(stack[stackIndex - 1].functionName), value: undefined, subHooks: children, hookSource: { lineNumber: stackFrame.lineNumber ?? null, columnNumber: stackFrame.columnNumber ?? null, functionName: stackFrame.functionName ?? null, fileName: stackFrame.fileName ?? null, }, }; levelChildren.push(levelChild); childrenStack.push(levelChildren); levelChildren = children; } previousStack = stack; } const { primitive } = hook; const id = NON_ID_HOOK_PRIMITIVES.has(primitive) ? null : nativeHookID++; const isStateEditable = primitive === "Reducer" || primitive === "State"; const name = displayName || primitive; const firstStackFrame = stack?.[0]; const hookSource: HookSource = { lineNumber: firstStackFrame?.lineNumber ?? null, columnNumber: firstStackFrame?.columnNumber ?? null, functionName: firstStackFrame?.functionName ?? null, fileName: firstStackFrame?.fileName ?? null, }; levelChildren.push({ id, isStateEditable, name, value: hook.value, subHooks: [], hookSource }); } processDebugValues(rootChildren, null); return rootChildren; }; const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | null): void => { const debugValueNodes: HooksNode[] = []; for (let nodeIndex = 0; nodeIndex < hooksTree.length; nodeIndex++) { const hooksNode = hooksTree[nodeIndex]; if (hooksNode.name === "DebugValue" && hooksNode.subHooks.length === 0) { hooksTree.splice(nodeIndex, 1); nodeIndex--; debugValueNodes.push(hooksNode); } else { processDebugValues(hooksNode.subHooks, hooksNode); } } if (parentHooksNode !== null) { if (debugValueNodes.length === 1) { parentHooksNode.value = debugValueNodes[0].value; } else if (debugValueNodes.length > 1) { parentHooksNode.value = debugValueNodes.map(({ value }) => value); } } }; const setupContexts = (contextMap: Map, unknown>, fiber: Fiber): void => { let current: Fiber | null = fiber; while (current) { if (current.tag === CONTEXT_PROVIDER_TAG) { let context = current.type as ReactContext; if ("_context" in context && context._context !== undefined) { context = context._context as ReactContext; } if (!contextMap.has(context)) { contextMap.set(context, context._currentValue); context._currentValue = (current.memoizedProps as { value: unknown }).value; } } current = current.return; } }; const restoreContexts = (contextMap: Map, unknown>): void => { contextMap.forEach((value, context) => { context._currentValue = value; }); }; const handleRenderFunctionError = (error: unknown): void => { if (error === SuspenseException) return; if (error instanceof Error && error.name === "ReactDebugToolsUnsupportedHookError") throw error; const wrapperError = new Error("Error rendering inspected component", { cause: error }); wrapperError.name = "ReactDebugToolsRenderError"; (wrapperError as { cause: unknown }).cause = error; throw wrapperError; }; const resolveDefaultProps = ( Component: unknown, baseProps: Record, ): Record => { if ( Component && typeof Component === "object" && "defaultProps" in Component && Component.defaultProps ) { const props = { ...baseProps }; const defaultProps = Component.defaultProps as Record; for (const propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } return props; } return baseProps; }; const suppressConsole = (): Record => { const originalMethods: Record = {}; for (const method in console) { try { originalMethods[method] = (console as Record)[method]; (console as Record)[method] = () => {}; } catch { /* noop */ } } return originalMethods; }; const restoreConsole = (originalMethods: Record): void => { for (const method in originalMethods) { try { (console as Record)[method] = originalMethods[method]; } catch { /* noop */ } } }; const performDispatcherInspection = ( dispatcherRef: DispatcherRefContainer, renderFn: () => void, ): HooksTree => { const previousDispatcher = getDispatcherFromRef(dispatcherRef); setDispatcherOnRef(dispatcherRef, DispatcherProxy); let capturedHookLog: HookLogEntry[] = []; let ancestorStackError: Error | undefined; try { ancestorStackError = new Error(); renderFn(); } catch (renderError) { handleRenderFunctionError(renderError); } finally { capturedHookLog = hookLog; hookLog = []; setDispatcherOnRef(dispatcherRef, previousDispatcher); } const rootStack = ancestorStackError !== undefined ? parseErrorStack(ancestorStackError) : []; return buildTree(rootStack, capturedHookLog); }; const requireDispatcherRef = (): DispatcherRefContainer => { const dispatcherRef = getDispatcherRef(); if (!dispatcherRef) { throw new Error( "No React renderer found. Make sure React is loaded and bippy's hook is installed.", ); } return dispatcherRef; }; const resolveContextDependency = (fiber: Fiber): void => { if (Object.prototype.hasOwnProperty.call(fiber, "dependencies")) { const dependencies = fiber.dependencies; currentContextDependency = dependencies !== null ? dependencies.firstContext : null; } else if (Object.prototype.hasOwnProperty.call(fiber, "dependencies_old")) { const dependencies = (fiber as unknown as { dependencies_old: typeof fiber.dependencies }) .dependencies_old; currentContextDependency = dependencies !== null ? dependencies!.firstContext : null; } else if (Object.prototype.hasOwnProperty.call(fiber, "dependencies_new")) { const dependencies = (fiber as unknown as { dependencies_new: typeof fiber.dependencies }) .dependencies_new; currentContextDependency = dependencies !== null ? dependencies!.firstContext : null; } else if (Object.prototype.hasOwnProperty.call(fiber, "contextDependencies")) { const contextDependencies = ( fiber as unknown as { contextDependencies: { first: ContextDependency | null } | null; } ).contextDependencies; currentContextDependency = contextDependencies !== null ? contextDependencies.first : null; } else { throw new Error("Unsupported React version."); } }; export const getFiberHooks = (fiber: Fiber): HooksTree => { const dispatcherRef = requireDispatcherRef(); if ( fiber.tag !== FUNCTION_COMPONENT_TAG && fiber.tag !== SIMPLE_MEMO_COMPONENT_TAG && fiber.tag !== FORWARD_REF_TAG ) { throw new Error("Unknown Fiber. Needs to be a function component to inspect hooks."); } getPrimitiveStackCache(); currentHook = fiber.memoizedState; currentFiber = fiber; const debugThenableState = fiber.dependencies && (fiber.dependencies as { _debugThenableState?: { thenables?: unknown[] } })._debugThenableState; const usedThenables = debugThenableState ? debugThenableState.thenables || debugThenableState : null; currentThenableState = Array.isArray(usedThenables) ? usedThenables : null; currentThenableIndex = 0; resolveContextDependency(fiber); const type = fiber.type; let props = fiber.memoizedProps as Record; if (type !== fiber.elementType) { props = resolveDefaultProps(type, props); } const originalConsoleMethods = suppressConsole(); const contextMap = new Map, unknown>(); try { if ( currentContextDependency !== null && !Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue") ) { setupContexts(contextMap, fiber); } if (fiber.tag === FORWARD_REF_TAG) { return performDispatcherInspection(dispatcherRef, () => { (type as { render: (props: Record, ref: unknown) => unknown }).render( props, fiber.ref, ); }); } return performDispatcherInspection(dispatcherRef, () => { (type as (props: Record) => unknown)(props); }); } finally { currentFiber = null; currentHook = null; currentContextDependency = null; currentThenableState = null; currentThenableIndex = 0; restoreContexts(contextMap); restoreConsole(originalConsoleMethods); } };