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 {
    
  11.   ReactContext,
    
  12.   ReactProviderType,
    
  13.   StartTransitionOptions,
    
  14. } from 'shared/ReactTypes';
    
  15. import type {
    
  16.   Fiber,
    
  17.   Dispatcher as DispatcherType,
    
  18. } from 'react-reconciler/src/ReactInternalTypes';
    
  19. 
    
  20. import ErrorStackParser from 'error-stack-parser';
    
  21. import assign from 'shared/assign';
    
  22. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  23. import {
    
  24.   FunctionComponent,
    
  25.   SimpleMemoComponent,
    
  26.   ContextProvider,
    
  27.   ForwardRef,
    
  28. } from 'react-reconciler/src/ReactWorkTags';
    
  29. 
    
  30. type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
    
  31. 
    
  32. // Used to track hooks called during a render
    
  33. 
    
  34. type HookLogEntry = {
    
  35.   primitive: string,
    
  36.   stackError: Error,
    
  37.   value: mixed,
    
  38.   ...
    
  39. };
    
  40. 
    
  41. let hookLog: Array<HookLogEntry> = [];
    
  42. 
    
  43. // Primitives
    
  44. 
    
  45. type BasicStateAction<S> = (S => S) | S;
    
  46. 
    
  47. type Dispatch<A> = A => void;
    
  48. 
    
  49. let primitiveStackCache: null | Map<string, Array<any>> = null;
    
  50. 
    
  51. type Hook = {
    
  52.   memoizedState: any,
    
  53.   next: Hook | null,
    
  54. };
    
  55. 
    
  56. function getPrimitiveStackCache(): Map<string, Array<any>> {
    
  57.   // This initializes a cache of all primitive hooks so that the top
    
  58.   // most stack frames added by calling the primitive hook can be removed.
    
  59.   if (primitiveStackCache === null) {
    
  60.     const cache = new Map<string, Array<any>>();
    
  61.     let readHookLog;
    
  62.     try {
    
  63.       // Use all hooks here to add them to the hook log.
    
  64.       Dispatcher.useContext(({_currentValue: null}: any));
    
  65.       Dispatcher.useState(null);
    
  66.       Dispatcher.useReducer((s: mixed, a: mixed) => s, null);
    
  67.       Dispatcher.useRef(null);
    
  68.       if (typeof Dispatcher.useCacheRefresh === 'function') {
    
  69.         // This type check is for Flow only.
    
  70.         Dispatcher.useCacheRefresh();
    
  71.       }
    
  72.       Dispatcher.useLayoutEffect(() => {});
    
  73.       Dispatcher.useInsertionEffect(() => {});
    
  74.       Dispatcher.useEffect(() => {});
    
  75.       Dispatcher.useImperativeHandle(undefined, () => null);
    
  76.       Dispatcher.useDebugValue(null);
    
  77.       Dispatcher.useCallback(() => {});
    
  78.       Dispatcher.useMemo(() => null);
    
  79.       if (typeof Dispatcher.useMemoCache === 'function') {
    
  80.         // This type check is for Flow only.
    
  81.         Dispatcher.useMemoCache(0);
    
  82.       }
    
  83.     } finally {
    
  84.       readHookLog = hookLog;
    
  85.       hookLog = [];
    
  86.     }
    
  87.     for (let i = 0; i < readHookLog.length; i++) {
    
  88.       const hook = readHookLog[i];
    
  89.       cache.set(hook.primitive, ErrorStackParser.parse(hook.stackError));
    
  90.     }
    
  91.     primitiveStackCache = cache;
    
  92.   }
    
  93.   return primitiveStackCache;
    
  94. }
    
  95. 
    
  96. let currentHook: null | Hook = null;
    
  97. function nextHook(): null | Hook {
    
  98.   const hook = currentHook;
    
  99.   if (hook !== null) {
    
  100.     currentHook = hook.next;
    
  101.   }
    
  102.   return hook;
    
  103. }
    
  104. 
    
  105. function readContext<T>(context: ReactContext<T>): T {
    
  106.   // For now we don't expose readContext usage in the hooks debugging info.
    
  107.   return context._currentValue;
    
  108. }
    
  109. 
    
  110. function use<T>(): T {
    
  111.   // TODO: What should this do if it receives an unresolved promise?
    
  112.   throw new Error(
    
  113.     'Support for `use` not yet implemented in react-debug-tools.',
    
  114.   );
    
  115. }
    
  116. 
    
  117. function useContext<T>(context: ReactContext<T>): T {
    
  118.   hookLog.push({
    
  119.     primitive: 'Context',
    
  120.     stackError: new Error(),
    
  121.     value: context._currentValue,
    
  122.   });
    
  123.   return context._currentValue;
    
  124. }
    
  125. 
    
  126. function useState<S>(
    
  127.   initialState: (() => S) | S,
    
  128. ): [S, Dispatch<BasicStateAction<S>>] {
    
  129.   const hook = nextHook();
    
  130.   const state: S =
    
  131.     hook !== null
    
  132.       ? hook.memoizedState
    
  133.       : typeof initialState === 'function'
    
  134.       ? // $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
    
  135.         initialState()
    
  136.       : initialState;
    
  137.   hookLog.push({primitive: 'State', stackError: new Error(), value: state});
    
  138.   return [state, (action: BasicStateAction<S>) => {}];
    
  139. }
    
  140. 
    
  141. function useReducer<S, I, A>(
    
  142.   reducer: (S, A) => S,
    
  143.   initialArg: I,
    
  144.   init?: I => S,
    
  145. ): [S, Dispatch<A>] {
    
  146.   const hook = nextHook();
    
  147.   let state;
    
  148.   if (hook !== null) {
    
  149.     state = hook.memoizedState;
    
  150.   } else {
    
  151.     state = init !== undefined ? init(initialArg) : ((initialArg: any): S);
    
  152.   }
    
  153.   hookLog.push({
    
  154.     primitive: 'Reducer',
    
  155.     stackError: new Error(),
    
  156.     value: state,
    
  157.   });
    
  158.   return [state, (action: A) => {}];
    
  159. }
    
  160. 
    
  161. function useRef<T>(initialValue: T): {current: T} {
    
  162.   const hook = nextHook();
    
  163.   const ref = hook !== null ? hook.memoizedState : {current: initialValue};
    
  164.   hookLog.push({
    
  165.     primitive: 'Ref',
    
  166.     stackError: new Error(),
    
  167.     value: ref.current,
    
  168.   });
    
  169.   return ref;
    
  170. }
    
  171. 
    
  172. function useCacheRefresh(): () => void {
    
  173.   const hook = nextHook();
    
  174.   hookLog.push({
    
  175.     primitive: 'CacheRefresh',
    
  176.     stackError: new Error(),
    
  177.     value: hook !== null ? hook.memoizedState : function refresh() {},
    
  178.   });
    
  179.   return () => {};
    
  180. }
    
  181. 
    
  182. function useLayoutEffect(
    
  183.   create: () => (() => void) | void,
    
  184.   inputs: Array<mixed> | void | null,
    
  185. ): void {
    
  186.   nextHook();
    
  187.   hookLog.push({
    
  188.     primitive: 'LayoutEffect',
    
  189.     stackError: new Error(),
    
  190.     value: create,
    
  191.   });
    
  192. }
    
  193. 
    
  194. function useInsertionEffect(
    
  195.   create: () => mixed,
    
  196.   inputs: Array<mixed> | void | null,
    
  197. ): void {
    
  198.   nextHook();
    
  199.   hookLog.push({
    
  200.     primitive: 'InsertionEffect',
    
  201.     stackError: new Error(),
    
  202.     value: create,
    
  203.   });
    
  204. }
    
  205. 
    
  206. function useEffect(
    
  207.   create: () => (() => void) | void,
    
  208.   inputs: Array<mixed> | void | null,
    
  209. ): void {
    
  210.   nextHook();
    
  211.   hookLog.push({primitive: 'Effect', stackError: new Error(), value: create});
    
  212. }
    
  213. 
    
  214. function useImperativeHandle<T>(
    
  215.   ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
    
  216.   create: () => T,
    
  217.   inputs: Array<mixed> | void | null,
    
  218. ): void {
    
  219.   nextHook();
    
  220.   // We don't actually store the instance anywhere if there is no ref callback
    
  221.   // and if there is a ref callback it might not store it but if it does we
    
  222.   // have no way of knowing where. So let's only enable introspection of the
    
  223.   // ref itself if it is using the object form.
    
  224.   let instance: ?T = undefined;
    
  225.   if (ref !== null && typeof ref === 'object') {
    
  226.     instance = ref.current;
    
  227.   }
    
  228.   hookLog.push({
    
  229.     primitive: 'ImperativeHandle',
    
  230.     stackError: new Error(),
    
  231.     value: instance,
    
  232.   });
    
  233. }
    
  234. 
    
  235. function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
    
  236.   hookLog.push({
    
  237.     primitive: 'DebugValue',
    
  238.     stackError: new Error(),
    
  239.     value: typeof formatterFn === 'function' ? formatterFn(value) : value,
    
  240.   });
    
  241. }
    
  242. 
    
  243. function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
    
  244.   const hook = nextHook();
    
  245.   hookLog.push({
    
  246.     primitive: 'Callback',
    
  247.     stackError: new Error(),
    
  248.     value: hook !== null ? hook.memoizedState[0] : callback,
    
  249.   });
    
  250.   return callback;
    
  251. }
    
  252. 
    
  253. function useMemo<T>(
    
  254.   nextCreate: () => T,
    
  255.   inputs: Array<mixed> | void | null,
    
  256. ): T {
    
  257.   const hook = nextHook();
    
  258.   const value = hook !== null ? hook.memoizedState[0] : nextCreate();
    
  259.   hookLog.push({primitive: 'Memo', stackError: new Error(), value});
    
  260.   return value;
    
  261. }
    
  262. 
    
  263. function useSyncExternalStore<T>(
    
  264.   subscribe: (() => void) => () => void,
    
  265.   getSnapshot: () => T,
    
  266.   getServerSnapshot?: () => T,
    
  267. ): T {
    
  268.   // useSyncExternalStore() composes multiple hooks internally.
    
  269.   // Advance the current hook index the same number of times
    
  270.   // so that subsequent hooks have the right memoized state.
    
  271.   nextHook(); // SyncExternalStore
    
  272.   nextHook(); // Effect
    
  273.   const value = getSnapshot();
    
  274.   hookLog.push({
    
  275.     primitive: 'SyncExternalStore',
    
  276.     stackError: new Error(),
    
  277.     value,
    
  278.   });
    
  279.   return value;
    
  280. }
    
  281. 
    
  282. function useTransition(): [
    
  283.   boolean,
    
  284.   (callback: () => void, options?: StartTransitionOptions) => void,
    
  285. ] {
    
  286.   // useTransition() composes multiple hooks internally.
    
  287.   // Advance the current hook index the same number of times
    
  288.   // so that subsequent hooks have the right memoized state.
    
  289.   nextHook(); // State
    
  290.   nextHook(); // Callback
    
  291.   hookLog.push({
    
  292.     primitive: 'Transition',
    
  293.     stackError: new Error(),
    
  294.     value: undefined,
    
  295.   });
    
  296.   return [false, callback => {}];
    
  297. }
    
  298. 
    
  299. function useDeferredValue<T>(value: T, initialValue?: T): T {
    
  300.   const hook = nextHook();
    
  301.   hookLog.push({
    
  302.     primitive: 'DeferredValue',
    
  303.     stackError: new Error(),
    
  304.     value: hook !== null ? hook.memoizedState : value,
    
  305.   });
    
  306.   return value;
    
  307. }
    
  308. 
    
  309. function useId(): string {
    
  310.   const hook = nextHook();
    
  311.   const id = hook !== null ? hook.memoizedState : '';
    
  312.   hookLog.push({
    
  313.     primitive: 'Id',
    
  314.     stackError: new Error(),
    
  315.     value: id,
    
  316.   });
    
  317.   return id;
    
  318. }
    
  319. 
    
  320. // useMemoCache is an implementation detail of Forget's memoization
    
  321. // it should not be called directly in user-generated code
    
  322. // we keep it as a stub for dispatcher
    
  323. function useMemoCache(size: number): Array<any> {
    
  324.   return [];
    
  325. }
    
  326. 
    
  327. const Dispatcher: DispatcherType = {
    
  328.   use,
    
  329.   readContext,
    
  330.   useCacheRefresh,
    
  331.   useCallback,
    
  332.   useContext,
    
  333.   useEffect,
    
  334.   useImperativeHandle,
    
  335.   useDebugValue,
    
  336.   useLayoutEffect,
    
  337.   useInsertionEffect,
    
  338.   useMemo,
    
  339.   useMemoCache,
    
  340.   useReducer,
    
  341.   useRef,
    
  342.   useState,
    
  343.   useTransition,
    
  344.   useSyncExternalStore,
    
  345.   useDeferredValue,
    
  346.   useId,
    
  347. };
    
  348. 
    
  349. // create a proxy to throw a custom error
    
  350. // in case future versions of React adds more hooks
    
  351. const DispatcherProxyHandler = {
    
  352.   get(target: DispatcherType, prop: string) {
    
  353.     if (target.hasOwnProperty(prop)) {
    
  354.       return target[prop];
    
  355.     }
    
  356.     const error = new Error('Missing method in Dispatcher: ' + prop);
    
  357.     // Note: This error name needs to stay in sync with react-devtools-shared
    
  358.     // TODO: refactor this if we ever combine the devtools and debug tools packages
    
  359.     error.name = 'ReactDebugToolsUnsupportedHookError';
    
  360.     throw error;
    
  361.   },
    
  362. };
    
  363. 
    
  364. // `Proxy` may not exist on some platforms
    
  365. const DispatcherProxy =
    
  366.   typeof Proxy === 'undefined'
    
  367.     ? Dispatcher
    
  368.     : new Proxy(Dispatcher, DispatcherProxyHandler);
    
  369. 
    
  370. // Inspect
    
  371. 
    
  372. export type HookSource = {
    
  373.   lineNumber: number | null,
    
  374.   columnNumber: number | null,
    
  375.   fileName: string | null,
    
  376.   functionName: string | null,
    
  377. };
    
  378. 
    
  379. export type HooksNode = {
    
  380.   id: number | null,
    
  381.   isStateEditable: boolean,
    
  382.   name: string,
    
  383.   value: mixed,
    
  384.   subHooks: Array<HooksNode>,
    
  385.   hookSource?: HookSource,
    
  386.   ...
    
  387. };
    
  388. export type HooksTree = Array<HooksNode>;
    
  389. 
    
  390. // Don't assume
    
  391. //
    
  392. // We can't assume that stack frames are nth steps away from anything.
    
  393. // E.g. we can't assume that the root call shares all frames with the stack
    
  394. // of a hook call. A simple way to demonstrate this is wrapping `new Error()`
    
  395. // in a wrapper constructor like a polyfill. That'll add an extra frame.
    
  396. // Similar things can happen with the call to the dispatcher. The top frame
    
  397. // may not be the primitive. Likewise the primitive can have fewer stack frames
    
  398. // such as when a call to useState got inlined to use dispatcher.useState.
    
  399. //
    
  400. // We also can't assume that the last frame of the root call is the same
    
  401. // frame as the last frame of the hook call because long stack traces can be
    
  402. // truncated to a stack trace limit.
    
  403. 
    
  404. let mostLikelyAncestorIndex = 0;
    
  405. 
    
  406. function findSharedIndex(hookStack: any, rootStack: any, rootIndex: number) {
    
  407.   const source = rootStack[rootIndex].source;
    
  408.   hookSearch: for (let i = 0; i < hookStack.length; i++) {
    
  409.     if (hookStack[i].source === source) {
    
  410.       // This looks like a match. Validate that the rest of both stack match up.
    
  411.       for (
    
  412.         let a = rootIndex + 1, b = i + 1;
    
  413.         a < rootStack.length && b < hookStack.length;
    
  414.         a++, b++
    
  415.       ) {
    
  416.         if (hookStack[b].source !== rootStack[a].source) {
    
  417.           // If not, give up and try a different match.
    
  418.           continue hookSearch;
    
  419.         }
    
  420.       }
    
  421.       return i;
    
  422.     }
    
  423.   }
    
  424.   return -1;
    
  425. }
    
  426. 
    
  427. function findCommonAncestorIndex(rootStack: any, hookStack: any) {
    
  428.   let rootIndex = findSharedIndex(
    
  429.     hookStack,
    
  430.     rootStack,
    
  431.     mostLikelyAncestorIndex,
    
  432.   );
    
  433.   if (rootIndex !== -1) {
    
  434.     return rootIndex;
    
  435.   }
    
  436.   // If the most likely one wasn't a hit, try any other frame to see if it is shared.
    
  437.   // If that takes more than 5 frames, something probably went wrong.
    
  438.   for (let i = 0; i < rootStack.length && i < 5; i++) {
    
  439.     rootIndex = findSharedIndex(hookStack, rootStack, i);
    
  440.     if (rootIndex !== -1) {
    
  441.       mostLikelyAncestorIndex = i;
    
  442.       return rootIndex;
    
  443.     }
    
  444.   }
    
  445.   return -1;
    
  446. }
    
  447. 
    
  448. function isReactWrapper(functionName: any, primitiveName: string) {
    
  449.   if (!functionName) {
    
  450.     return false;
    
  451.   }
    
  452.   const expectedPrimitiveName = 'use' + primitiveName;
    
  453.   if (functionName.length < expectedPrimitiveName.length) {
    
  454.     return false;
    
  455.   }
    
  456.   return (
    
  457.     functionName.lastIndexOf(expectedPrimitiveName) ===
    
  458.     functionName.length - expectedPrimitiveName.length
    
  459.   );
    
  460. }
    
  461. 
    
  462. function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
    
  463.   const stackCache = getPrimitiveStackCache();
    
  464.   const primitiveStack = stackCache.get(hook.primitive);
    
  465.   if (primitiveStack === undefined) {
    
  466.     return -1;
    
  467.   }
    
  468.   for (let i = 0; i < primitiveStack.length && i < hookStack.length; i++) {
    
  469.     if (primitiveStack[i].source !== hookStack[i].source) {
    
  470.       // If the next two frames are functions called `useX` then we assume that they're part of the
    
  471.       // wrappers that the React packager or other packages adds around the dispatcher.
    
  472.       if (
    
  473.         i < hookStack.length - 1 &&
    
  474.         isReactWrapper(hookStack[i].functionName, hook.primitive)
    
  475.       ) {
    
  476.         i++;
    
  477.       }
    
  478.       if (
    
  479.         i < hookStack.length - 1 &&
    
  480.         isReactWrapper(hookStack[i].functionName, hook.primitive)
    
  481.       ) {
    
  482.         i++;
    
  483.       }
    
  484.       return i;
    
  485.     }
    
  486.   }
    
  487.   return -1;
    
  488. }
    
  489. 
    
  490. function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
    
  491.   // Get the stack trace between the primitive hook function and
    
  492.   // the root function call. I.e. the stack frames of custom hooks.
    
  493.   const hookStack = ErrorStackParser.parse(hook.stackError);
    
  494.   const rootIndex = findCommonAncestorIndex(rootStack, hookStack);
    
  495.   const primitiveIndex = findPrimitiveIndex(hookStack, hook);
    
  496.   if (
    
  497.     rootIndex === -1 ||
    
  498.     primitiveIndex === -1 ||
    
  499.     rootIndex - primitiveIndex < 2
    
  500.   ) {
    
  501.     // Something went wrong. Give up.
    
  502.     return null;
    
  503.   }
    
  504.   return hookStack.slice(primitiveIndex, rootIndex - 1);
    
  505. }
    
  506. 
    
  507. function parseCustomHookName(functionName: void | string): string {
    
  508.   if (!functionName) {
    
  509.     return '';
    
  510.   }
    
  511.   let startIndex = functionName.lastIndexOf('.');
    
  512.   if (startIndex === -1) {
    
  513.     startIndex = 0;
    
  514.   }
    
  515.   if (functionName.slice(startIndex, startIndex + 3) === 'use') {
    
  516.     startIndex += 3;
    
  517.   }
    
  518.   return functionName.slice(startIndex);
    
  519. }
    
  520. 
    
  521. function buildTree(
    
  522.   rootStack: any,
    
  523.   readHookLog: Array<HookLogEntry>,
    
  524.   includeHooksSource: boolean,
    
  525. ): HooksTree {
    
  526.   const rootChildren: Array<HooksNode> = [];
    
  527.   let prevStack = null;
    
  528.   let levelChildren = rootChildren;
    
  529.   let nativeHookID = 0;
    
  530.   const stackOfChildren = [];
    
  531.   for (let i = 0; i < readHookLog.length; i++) {
    
  532.     const hook = readHookLog[i];
    
  533.     const stack = parseTrimmedStack(rootStack, hook);
    
  534.     if (stack !== null) {
    
  535.       // Note: The indices 0 <= n < length-1 will contain the names.
    
  536.       // The indices 1 <= n < length will contain the source locations.
    
  537.       // That's why we get the name from n - 1 and don't check the source
    
  538.       // of index 0.
    
  539.       let commonSteps = 0;
    
  540.       if (prevStack !== null) {
    
  541.         // Compare the current level's stack to the new stack.
    
  542.         while (commonSteps < stack.length && commonSteps < prevStack.length) {
    
  543.           const stackSource = stack[stack.length - commonSteps - 1].source;
    
  544.           const prevSource =
    
  545.             prevStack[prevStack.length - commonSteps - 1].source;
    
  546.           if (stackSource !== prevSource) {
    
  547.             break;
    
  548.           }
    
  549.           commonSteps++;
    
  550.         }
    
  551.         // Pop back the stack as many steps as were not common.
    
  552.         for (let j = prevStack.length - 1; j > commonSteps; j--) {
    
  553.           levelChildren = stackOfChildren.pop();
    
  554.         }
    
  555.       }
    
  556.       // The remaining part of the new stack are custom hooks. Push them
    
  557.       // to the tree.
    
  558.       for (let j = stack.length - commonSteps - 1; j >= 1; j--) {
    
  559.         const children: Array<HooksNode> = [];
    
  560.         const stackFrame = stack[j];
    
  561.         const levelChild: HooksNode = {
    
  562.           id: null,
    
  563.           isStateEditable: false,
    
  564.           name: parseCustomHookName(stack[j - 1].functionName),
    
  565.           value: undefined,
    
  566.           subHooks: children,
    
  567.         };
    
  568. 
    
  569.         if (includeHooksSource) {
    
  570.           levelChild.hookSource = {
    
  571.             lineNumber: stackFrame.lineNumber,
    
  572.             columnNumber: stackFrame.columnNumber,
    
  573.             functionName: stackFrame.functionName,
    
  574.             fileName: stackFrame.fileName,
    
  575.           };
    
  576.         }
    
  577. 
    
  578.         levelChildren.push(levelChild);
    
  579.         stackOfChildren.push(levelChildren);
    
  580.         levelChildren = children;
    
  581.       }
    
  582.       prevStack = stack;
    
  583.     }
    
  584.     const {primitive} = hook;
    
  585. 
    
  586.     // For now, the "id" of stateful hooks is just the stateful hook index.
    
  587.     // Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
    
  588.     const id =
    
  589.       primitive === 'Context' || primitive === 'DebugValue'
    
  590.         ? null
    
  591.         : nativeHookID++;
    
  592. 
    
  593.     // For the time being, only State and Reducer hooks support runtime overrides.
    
  594.     const isStateEditable = primitive === 'Reducer' || primitive === 'State';
    
  595.     const levelChild: HooksNode = {
    
  596.       id,
    
  597.       isStateEditable,
    
  598.       name: primitive,
    
  599.       value: hook.value,
    
  600.       subHooks: [],
    
  601.     };
    
  602. 
    
  603.     if (includeHooksSource) {
    
  604.       const hookSource: HookSource = {
    
  605.         lineNumber: null,
    
  606.         functionName: null,
    
  607.         fileName: null,
    
  608.         columnNumber: null,
    
  609.       };
    
  610.       if (stack && stack.length >= 1) {
    
  611.         const stackFrame = stack[0];
    
  612.         hookSource.lineNumber = stackFrame.lineNumber;
    
  613.         hookSource.functionName = stackFrame.functionName;
    
  614.         hookSource.fileName = stackFrame.fileName;
    
  615.         hookSource.columnNumber = stackFrame.columnNumber;
    
  616.       }
    
  617. 
    
  618.       levelChild.hookSource = hookSource;
    
  619.     }
    
  620. 
    
  621.     levelChildren.push(levelChild);
    
  622.   }
    
  623. 
    
  624.   // Associate custom hook values (useDebugValue() hook entries) with the correct hooks.
    
  625.   processDebugValues(rootChildren, null);
    
  626. 
    
  627.   return rootChildren;
    
  628. }
    
  629. 
    
  630. // Custom hooks support user-configurable labels (via the special useDebugValue() hook).
    
  631. // That hook adds user-provided values to the hooks tree,
    
  632. // but these values aren't intended to appear alongside of the other hooks.
    
  633. // Instead they should be attributed to their parent custom hook.
    
  634. // This method walks the tree and assigns debug values to their custom hook owners.
    
  635. function processDebugValues(
    
  636.   hooksTree: HooksTree,
    
  637.   parentHooksNode: HooksNode | null,
    
  638. ): void {
    
  639.   const debugValueHooksNodes: Array<HooksNode> = [];
    
  640. 
    
  641.   for (let i = 0; i < hooksTree.length; i++) {
    
  642.     const hooksNode = hooksTree[i];
    
  643.     if (hooksNode.name === 'DebugValue' && hooksNode.subHooks.length === 0) {
    
  644.       hooksTree.splice(i, 1);
    
  645.       i--;
    
  646.       debugValueHooksNodes.push(hooksNode);
    
  647.     } else {
    
  648.       processDebugValues(hooksNode.subHooks, hooksNode);
    
  649.     }
    
  650.   }
    
  651. 
    
  652.   // Bubble debug value labels to their custom hook owner.
    
  653.   // If there is no parent hook, just ignore them for now.
    
  654.   // (We may warn about this in the future.)
    
  655.   if (parentHooksNode !== null) {
    
  656.     if (debugValueHooksNodes.length === 1) {
    
  657.       parentHooksNode.value = debugValueHooksNodes[0].value;
    
  658.     } else if (debugValueHooksNodes.length > 1) {
    
  659.       parentHooksNode.value = debugValueHooksNodes.map(({value}) => value);
    
  660.     }
    
  661.   }
    
  662. }
    
  663. 
    
  664. function handleRenderFunctionError(error: any): void {
    
  665.   // original error might be any type.
    
  666.   if (
    
  667.     error instanceof Error &&
    
  668.     error.name === 'ReactDebugToolsUnsupportedHookError'
    
  669.   ) {
    
  670.     throw error;
    
  671.   }
    
  672.   // If the error is not caused by an unsupported feature, it means
    
  673.   // that the error is caused by user's code in renderFunction.
    
  674.   // In this case, we should wrap the original error inside a custom error
    
  675.   // so that devtools can give a clear message about it.
    
  676.   // $FlowFixMe[extra-arg]: Flow doesn't know about 2nd argument of Error constructor
    
  677.   const wrapperError = new Error('Error rendering inspected component', {
    
  678.     cause: error,
    
  679.   });
    
  680.   // Note: This error name needs to stay in sync with react-devtools-shared
    
  681.   // TODO: refactor this if we ever combine the devtools and debug tools packages
    
  682.   wrapperError.name = 'ReactDebugToolsRenderError';
    
  683.   // this stage-4 proposal is not supported by all environments yet.
    
  684.   // $FlowFixMe[prop-missing] Flow doesn't have this type yet.
    
  685.   wrapperError.cause = error;
    
  686.   throw wrapperError;
    
  687. }
    
  688. 
    
  689. export function inspectHooks<Props>(
    
  690.   renderFunction: Props => React$Node,
    
  691.   props: Props,
    
  692.   currentDispatcher: ?CurrentDispatcherRef,
    
  693.   includeHooksSource: boolean = false,
    
  694. ): HooksTree {
    
  695.   // DevTools will pass the current renderer's injected dispatcher.
    
  696.   // Other apps might compile debug hooks as part of their app though.
    
  697.   if (currentDispatcher == null) {
    
  698.     currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
    
  699.   }
    
  700. 
    
  701.   const previousDispatcher = currentDispatcher.current;
    
  702.   let readHookLog;
    
  703.   currentDispatcher.current = DispatcherProxy;
    
  704.   let ancestorStackError;
    
  705.   try {
    
  706.     ancestorStackError = new Error();
    
  707.     renderFunction(props);
    
  708.   } catch (error) {
    
  709.     handleRenderFunctionError(error);
    
  710.   } finally {
    
  711.     readHookLog = hookLog;
    
  712.     hookLog = [];
    
  713.     // $FlowFixMe[incompatible-use] found when upgrading Flow
    
  714.     currentDispatcher.current = previousDispatcher;
    
  715.   }
    
  716.   const rootStack = ErrorStackParser.parse(ancestorStackError);
    
  717.   return buildTree(rootStack, readHookLog, includeHooksSource);
    
  718. }
    
  719. 
    
  720. function setupContexts(contextMap: Map<ReactContext<any>, any>, fiber: Fiber) {
    
  721.   let current: null | Fiber = fiber;
    
  722.   while (current) {
    
  723.     if (current.tag === ContextProvider) {
    
  724.       const providerType: ReactProviderType<any> = current.type;
    
  725.       const context: ReactContext<any> = providerType._context;
    
  726.       if (!contextMap.has(context)) {
    
  727.         // Store the current value that we're going to restore later.
    
  728.         contextMap.set(context, context._currentValue);
    
  729.         // Set the inner most provider value on the context.
    
  730.         context._currentValue = current.memoizedProps.value;
    
  731.       }
    
  732.     }
    
  733.     current = current.return;
    
  734.   }
    
  735. }
    
  736. 
    
  737. function restoreContexts(contextMap: Map<ReactContext<any>, any>) {
    
  738.   contextMap.forEach((value, context) => (context._currentValue = value));
    
  739. }
    
  740. 
    
  741. function inspectHooksOfForwardRef<Props, Ref>(
    
  742.   renderFunction: (Props, Ref) => React$Node,
    
  743.   props: Props,
    
  744.   ref: Ref,
    
  745.   currentDispatcher: CurrentDispatcherRef,
    
  746.   includeHooksSource: boolean,
    
  747. ): HooksTree {
    
  748.   const previousDispatcher = currentDispatcher.current;
    
  749.   let readHookLog;
    
  750.   currentDispatcher.current = DispatcherProxy;
    
  751.   let ancestorStackError;
    
  752.   try {
    
  753.     ancestorStackError = new Error();
    
  754.     renderFunction(props, ref);
    
  755.   } catch (error) {
    
  756.     handleRenderFunctionError(error);
    
  757.   } finally {
    
  758.     readHookLog = hookLog;
    
  759.     hookLog = [];
    
  760.     currentDispatcher.current = previousDispatcher;
    
  761.   }
    
  762.   const rootStack = ErrorStackParser.parse(ancestorStackError);
    
  763.   return buildTree(rootStack, readHookLog, includeHooksSource);
    
  764. }
    
  765. 
    
  766. function resolveDefaultProps(Component: any, baseProps: any) {
    
  767.   if (Component && Component.defaultProps) {
    
  768.     // Resolve default props. Taken from ReactElement
    
  769.     const props = assign({}, baseProps);
    
  770.     const defaultProps = Component.defaultProps;
    
  771.     for (const propName in defaultProps) {
    
  772.       if (props[propName] === undefined) {
    
  773.         props[propName] = defaultProps[propName];
    
  774.       }
    
  775.     }
    
  776.     return props;
    
  777.   }
    
  778.   return baseProps;
    
  779. }
    
  780. 
    
  781. export function inspectHooksOfFiber(
    
  782.   fiber: Fiber,
    
  783.   currentDispatcher: ?CurrentDispatcherRef,
    
  784.   includeHooksSource: boolean = false,
    
  785. ): HooksTree {
    
  786.   // DevTools will pass the current renderer's injected dispatcher.
    
  787.   // Other apps might compile debug hooks as part of their app though.
    
  788.   if (currentDispatcher == null) {
    
  789.     currentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
    
  790.   }
    
  791. 
    
  792.   if (
    
  793.     fiber.tag !== FunctionComponent &&
    
  794.     fiber.tag !== SimpleMemoComponent &&
    
  795.     fiber.tag !== ForwardRef
    
  796.   ) {
    
  797.     throw new Error(
    
  798.       'Unknown Fiber. Needs to be a function component to inspect hooks.',
    
  799.     );
    
  800.   }
    
  801.   // Warm up the cache so that it doesn't consume the currentHook.
    
  802.   getPrimitiveStackCache();
    
  803.   const type = fiber.type;
    
  804.   let props = fiber.memoizedProps;
    
  805.   if (type !== fiber.elementType) {
    
  806.     props = resolveDefaultProps(type, props);
    
  807.   }
    
  808.   // Set up the current hook so that we can step through and read the
    
  809.   // current state from them.
    
  810.   currentHook = (fiber.memoizedState: Hook);
    
  811.   const contextMap = new Map<ReactContext<$FlowFixMe>, $FlowFixMe>();
    
  812.   try {
    
  813.     setupContexts(contextMap, fiber);
    
  814.     if (fiber.tag === ForwardRef) {
    
  815.       return inspectHooksOfForwardRef(
    
  816.         type.render,
    
  817.         props,
    
  818.         fiber.ref,
    
  819.         currentDispatcher,
    
  820.         includeHooksSource,
    
  821.       );
    
  822.     }
    
  823.     return inspectHooks(type, props, currentDispatcher, includeHooksSource);
    
  824.   } finally {
    
  825.     currentHook = null;
    
  826.     restoreContexts(contextMap);
    
  827.   }
    
  828. }