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 {Instance} from 'react-reconciler/src/ReactFiberConfig';
    
  11. import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
    
  12. import type {
    
  13.   Family,
    
  14.   RefreshUpdate,
    
  15.   ScheduleRefresh,
    
  16.   ScheduleRoot,
    
  17.   FindHostInstancesForRefresh,
    
  18.   SetRefreshHandler,
    
  19. } from 'react-reconciler/src/ReactFiberHotReloading';
    
  20. import type {ReactNodeList} from 'shared/ReactTypes';
    
  21. 
    
  22. import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';
    
  23. 
    
  24. type Signature = {
    
  25.   ownKey: string,
    
  26.   forceReset: boolean,
    
  27.   fullKey: string | null, // Contains keys of nested Hooks. Computed lazily.
    
  28.   getCustomHooks: () => Array<Function>,
    
  29. };
    
  30. 
    
  31. type RendererHelpers = {
    
  32.   findHostInstancesForRefresh: FindHostInstancesForRefresh,
    
  33.   scheduleRefresh: ScheduleRefresh,
    
  34.   scheduleRoot: ScheduleRoot,
    
  35.   setRefreshHandler: SetRefreshHandler,
    
  36. };
    
  37. 
    
  38. if (!__DEV__) {
    
  39.   throw new Error(
    
  40.     'React Refresh runtime should not be included in the production bundle.',
    
  41.   );
    
  42. }
    
  43. 
    
  44. // In old environments, we'll leak previous types after every edit.
    
  45. const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
    
  46. 
    
  47. // We never remove these associations.
    
  48. // It's OK to reference families, but use WeakMap/Set for types.
    
  49. const allFamiliesByID: Map<string, Family> = new Map();
    
  50. const allFamiliesByType: WeakMap<any, Family> | Map<any, Family> =
    
  51.   new PossiblyWeakMap();
    
  52. const allSignaturesByType: WeakMap<any, Signature> | Map<any, Signature> =
    
  53.   new PossiblyWeakMap();
    
  54. // This WeakMap is read by React, so we only put families
    
  55. // that have actually been edited here. This keeps checks fast.
    
  56. const updatedFamiliesByType: WeakMap<any, Family> | Map<any, Family> =
    
  57.   new PossiblyWeakMap();
    
  58. 
    
  59. // This is cleared on every performReactRefresh() call.
    
  60. // It is an array of [Family, NextType] tuples.
    
  61. let pendingUpdates: Array<[Family, any]> = [];
    
  62. 
    
  63. // This is injected by the renderer via DevTools global hook.
    
  64. const helpersByRendererID: Map<number, RendererHelpers> = new Map();
    
  65. 
    
  66. const helpersByRoot: Map<FiberRoot, RendererHelpers> = new Map();
    
  67. 
    
  68. // We keep track of mounted roots so we can schedule updates.
    
  69. const mountedRoots: Set<FiberRoot> = new Set();
    
  70. // If a root captures an error, we remember it so we can retry on edit.
    
  71. const failedRoots: Set<FiberRoot> = new Set();
    
  72. 
    
  73. // In environments that support WeakMap, we also remember the last element for every root.
    
  74. // It needs to be weak because we do this even for roots that failed to mount.
    
  75. // If there is no WeakMap, we won't attempt to do retrying.
    
  76. const rootElements: WeakMap<any, ReactNodeList> | null =
    
  77.   typeof WeakMap === 'function' ? new WeakMap() : null;
    
  78. 
    
  79. let isPerformingRefresh = false;
    
  80. 
    
  81. function computeFullKey(signature: Signature): string {
    
  82.   if (signature.fullKey !== null) {
    
  83.     return signature.fullKey;
    
  84.   }
    
  85. 
    
  86.   let fullKey: string = signature.ownKey;
    
  87.   let hooks;
    
  88.   try {
    
  89.     hooks = signature.getCustomHooks();
    
  90.   } catch (err) {
    
  91.     // This can happen in an edge case, e.g. if expression like Foo.useSomething
    
  92.     // depends on Foo which is lazily initialized during rendering.
    
  93.     // In that case just assume we'll have to remount.
    
  94.     signature.forceReset = true;
    
  95.     signature.fullKey = fullKey;
    
  96.     return fullKey;
    
  97.   }
    
  98. 
    
  99.   for (let i = 0; i < hooks.length; i++) {
    
  100.     const hook = hooks[i];
    
  101.     if (typeof hook !== 'function') {
    
  102.       // Something's wrong. Assume we need to remount.
    
  103.       signature.forceReset = true;
    
  104.       signature.fullKey = fullKey;
    
  105.       return fullKey;
    
  106.     }
    
  107.     const nestedHookSignature = allSignaturesByType.get(hook);
    
  108.     if (nestedHookSignature === undefined) {
    
  109.       // No signature means Hook wasn't in the source code, e.g. in a library.
    
  110.       // We'll skip it because we can assume it won't change during this session.
    
  111.       continue;
    
  112.     }
    
  113.     const nestedHookKey = computeFullKey(nestedHookSignature);
    
  114.     if (nestedHookSignature.forceReset) {
    
  115.       signature.forceReset = true;
    
  116.     }
    
  117.     fullKey += '\n---\n' + nestedHookKey;
    
  118.   }
    
  119. 
    
  120.   signature.fullKey = fullKey;
    
  121.   return fullKey;
    
  122. }
    
  123. 
    
  124. function haveEqualSignatures(prevType: any, nextType: any) {
    
  125.   const prevSignature = allSignaturesByType.get(prevType);
    
  126.   const nextSignature = allSignaturesByType.get(nextType);
    
  127. 
    
  128.   if (prevSignature === undefined && nextSignature === undefined) {
    
  129.     return true;
    
  130.   }
    
  131.   if (prevSignature === undefined || nextSignature === undefined) {
    
  132.     return false;
    
  133.   }
    
  134.   if (computeFullKey(prevSignature) !== computeFullKey(nextSignature)) {
    
  135.     return false;
    
  136.   }
    
  137.   if (nextSignature.forceReset) {
    
  138.     return false;
    
  139.   }
    
  140. 
    
  141.   return true;
    
  142. }
    
  143. 
    
  144. function isReactClass(type: any) {
    
  145.   return type.prototype && type.prototype.isReactComponent;
    
  146. }
    
  147. 
    
  148. function canPreserveStateBetween(prevType: any, nextType: any) {
    
  149.   if (isReactClass(prevType) || isReactClass(nextType)) {
    
  150.     return false;
    
  151.   }
    
  152.   if (haveEqualSignatures(prevType, nextType)) {
    
  153.     return true;
    
  154.   }
    
  155.   return false;
    
  156. }
    
  157. 
    
  158. function resolveFamily(type: any) {
    
  159.   // Only check updated types to keep lookups fast.
    
  160.   return updatedFamiliesByType.get(type);
    
  161. }
    
  162. 
    
  163. // If we didn't care about IE11, we could use new Map/Set(iterable).
    
  164. function cloneMap<K, V>(map: Map<K, V>): Map<K, V> {
    
  165.   const clone = new Map<K, V>();
    
  166.   map.forEach((value, key) => {
    
  167.     clone.set(key, value);
    
  168.   });
    
  169.   return clone;
    
  170. }
    
  171. function cloneSet<T>(set: Set<T>): Set<T> {
    
  172.   const clone = new Set<T>();
    
  173.   set.forEach(value => {
    
  174.     clone.add(value);
    
  175.   });
    
  176.   return clone;
    
  177. }
    
  178. 
    
  179. // This is a safety mechanism to protect against rogue getters and Proxies.
    
  180. function getProperty(object: any, property: string) {
    
  181.   try {
    
  182.     return object[property];
    
  183.   } catch (err) {
    
  184.     // Intentionally ignore.
    
  185.     return undefined;
    
  186.   }
    
  187. }
    
  188. 
    
  189. export function performReactRefresh(): RefreshUpdate | null {
    
  190.   if (!__DEV__) {
    
  191.     throw new Error(
    
  192.       'Unexpected call to React Refresh in a production environment.',
    
  193.     );
    
  194.   }
    
  195.   if (pendingUpdates.length === 0) {
    
  196.     return null;
    
  197.   }
    
  198.   if (isPerformingRefresh) {
    
  199.     return null;
    
  200.   }
    
  201. 
    
  202.   isPerformingRefresh = true;
    
  203.   try {
    
  204.     const staleFamilies = new Set<Family>();
    
  205.     const updatedFamilies = new Set<Family>();
    
  206. 
    
  207.     const updates = pendingUpdates;
    
  208.     pendingUpdates = [];
    
  209.     updates.forEach(([family, nextType]) => {
    
  210.       // Now that we got a real edit, we can create associations
    
  211.       // that will be read by the React reconciler.
    
  212.       const prevType = family.current;
    
  213.       updatedFamiliesByType.set(prevType, family);
    
  214.       updatedFamiliesByType.set(nextType, family);
    
  215.       family.current = nextType;
    
  216. 
    
  217.       // Determine whether this should be a re-render or a re-mount.
    
  218.       if (canPreserveStateBetween(prevType, nextType)) {
    
  219.         updatedFamilies.add(family);
    
  220.       } else {
    
  221.         staleFamilies.add(family);
    
  222.       }
    
  223.     });
    
  224. 
    
  225.     // TODO: rename these fields to something more meaningful.
    
  226.     const update: RefreshUpdate = {
    
  227.       updatedFamilies, // Families that will re-render preserving state
    
  228.       staleFamilies, // Families that will be remounted
    
  229.     };
    
  230. 
    
  231.     helpersByRendererID.forEach(helpers => {
    
  232.       // Even if there are no roots, set the handler on first update.
    
  233.       // This ensures that if *new* roots are mounted, they'll use the resolve handler.
    
  234.       helpers.setRefreshHandler(resolveFamily);
    
  235.     });
    
  236. 
    
  237.     let didError = false;
    
  238.     let firstError = null;
    
  239. 
    
  240.     // We snapshot maps and sets that are mutated during commits.
    
  241.     // If we don't do this, there is a risk they will be mutated while
    
  242.     // we iterate over them. For example, trying to recover a failed root
    
  243.     // may cause another root to be added to the failed list -- an infinite loop.
    
  244.     const failedRootsSnapshot = cloneSet(failedRoots);
    
  245.     const mountedRootsSnapshot = cloneSet(mountedRoots);
    
  246.     const helpersByRootSnapshot = cloneMap(helpersByRoot);
    
  247. 
    
  248.     failedRootsSnapshot.forEach(root => {
    
  249.       const helpers = helpersByRootSnapshot.get(root);
    
  250.       if (helpers === undefined) {
    
  251.         throw new Error(
    
  252.           'Could not find helpers for a root. This is a bug in React Refresh.',
    
  253.         );
    
  254.       }
    
  255.       if (!failedRoots.has(root)) {
    
  256.         // No longer failed.
    
  257.       }
    
  258.       if (rootElements === null) {
    
  259.         return;
    
  260.       }
    
  261.       if (!rootElements.has(root)) {
    
  262.         return;
    
  263.       }
    
  264.       const element = rootElements.get(root);
    
  265.       try {
    
  266.         helpers.scheduleRoot(root, element);
    
  267.       } catch (err) {
    
  268.         if (!didError) {
    
  269.           didError = true;
    
  270.           firstError = err;
    
  271.         }
    
  272.         // Keep trying other roots.
    
  273.       }
    
  274.     });
    
  275.     mountedRootsSnapshot.forEach(root => {
    
  276.       const helpers = helpersByRootSnapshot.get(root);
    
  277.       if (helpers === undefined) {
    
  278.         throw new Error(
    
  279.           'Could not find helpers for a root. This is a bug in React Refresh.',
    
  280.         );
    
  281.       }
    
  282.       if (!mountedRoots.has(root)) {
    
  283.         // No longer mounted.
    
  284.       }
    
  285.       try {
    
  286.         helpers.scheduleRefresh(root, update);
    
  287.       } catch (err) {
    
  288.         if (!didError) {
    
  289.           didError = true;
    
  290.           firstError = err;
    
  291.         }
    
  292.         // Keep trying other roots.
    
  293.       }
    
  294.     });
    
  295.     if (didError) {
    
  296.       throw firstError;
    
  297.     }
    
  298.     return update;
    
  299.   } finally {
    
  300.     isPerformingRefresh = false;
    
  301.   }
    
  302. }
    
  303. 
    
  304. export function register(type: any, id: string): void {
    
  305.   if (__DEV__) {
    
  306.     if (type === null) {
    
  307.       return;
    
  308.     }
    
  309.     if (typeof type !== 'function' && typeof type !== 'object') {
    
  310.       return;
    
  311.     }
    
  312. 
    
  313.     // This can happen in an edge case, e.g. if we register
    
  314.     // return value of a HOC but it returns a cached component.
    
  315.     // Ignore anything but the first registration for each type.
    
  316.     if (allFamiliesByType.has(type)) {
    
  317.       return;
    
  318.     }
    
  319.     // Create family or remember to update it.
    
  320.     // None of this bookkeeping affects reconciliation
    
  321.     // until the first performReactRefresh() call above.
    
  322.     let family = allFamiliesByID.get(id);
    
  323.     if (family === undefined) {
    
  324.       family = {current: type};
    
  325.       allFamiliesByID.set(id, family);
    
  326.     } else {
    
  327.       pendingUpdates.push([family, type]);
    
  328.     }
    
  329.     allFamiliesByType.set(type, family);
    
  330. 
    
  331.     // Visit inner types because we might not have registered them.
    
  332.     if (typeof type === 'object' && type !== null) {
    
  333.       switch (getProperty(type, '$$typeof')) {
    
  334.         case REACT_FORWARD_REF_TYPE:
    
  335.           register(type.render, id + '$render');
    
  336.           break;
    
  337.         case REACT_MEMO_TYPE:
    
  338.           register(type.type, id + '$type');
    
  339.           break;
    
  340.       }
    
  341.     }
    
  342.   } else {
    
  343.     throw new Error(
    
  344.       'Unexpected call to React Refresh in a production environment.',
    
  345.     );
    
  346.   }
    
  347. }
    
  348. 
    
  349. export function setSignature(
    
  350.   type: any,
    
  351.   key: string,
    
  352.   forceReset?: boolean = false,
    
  353.   getCustomHooks?: () => Array<Function>,
    
  354. ): void {
    
  355.   if (__DEV__) {
    
  356.     if (!allSignaturesByType.has(type)) {
    
  357.       allSignaturesByType.set(type, {
    
  358.         forceReset,
    
  359.         ownKey: key,
    
  360.         fullKey: null,
    
  361.         getCustomHooks: getCustomHooks || (() => []),
    
  362.       });
    
  363.     }
    
  364.     // Visit inner types because we might not have signed them.
    
  365.     if (typeof type === 'object' && type !== null) {
    
  366.       switch (getProperty(type, '$$typeof')) {
    
  367.         case REACT_FORWARD_REF_TYPE:
    
  368.           setSignature(type.render, key, forceReset, getCustomHooks);
    
  369.           break;
    
  370.         case REACT_MEMO_TYPE:
    
  371.           setSignature(type.type, key, forceReset, getCustomHooks);
    
  372.           break;
    
  373.       }
    
  374.     }
    
  375.   } else {
    
  376.     throw new Error(
    
  377.       'Unexpected call to React Refresh in a production environment.',
    
  378.     );
    
  379.   }
    
  380. }
    
  381. 
    
  382. // This is lazily called during first render for a type.
    
  383. // It captures Hook list at that time so inline requires don't break comparisons.
    
  384. export function collectCustomHooksForSignature(type: any) {
    
  385.   if (__DEV__) {
    
  386.     const signature = allSignaturesByType.get(type);
    
  387.     if (signature !== undefined) {
    
  388.       computeFullKey(signature);
    
  389.     }
    
  390.   } else {
    
  391.     throw new Error(
    
  392.       'Unexpected call to React Refresh in a production environment.',
    
  393.     );
    
  394.   }
    
  395. }
    
  396. 
    
  397. export function getFamilyByID(id: string): Family | void {
    
  398.   if (__DEV__) {
    
  399.     return allFamiliesByID.get(id);
    
  400.   } else {
    
  401.     throw new Error(
    
  402.       'Unexpected call to React Refresh in a production environment.',
    
  403.     );
    
  404.   }
    
  405. }
    
  406. 
    
  407. export function getFamilyByType(type: any): Family | void {
    
  408.   if (__DEV__) {
    
  409.     return allFamiliesByType.get(type);
    
  410.   } else {
    
  411.     throw new Error(
    
  412.       'Unexpected call to React Refresh in a production environment.',
    
  413.     );
    
  414.   }
    
  415. }
    
  416. 
    
  417. export function findAffectedHostInstances(
    
  418.   families: Array<Family>,
    
  419. ): Set<Instance> {
    
  420.   if (__DEV__) {
    
  421.     const affectedInstances = new Set<Instance>();
    
  422.     mountedRoots.forEach(root => {
    
  423.       const helpers = helpersByRoot.get(root);
    
  424.       if (helpers === undefined) {
    
  425.         throw new Error(
    
  426.           'Could not find helpers for a root. This is a bug in React Refresh.',
    
  427.         );
    
  428.       }
    
  429.       const instancesForRoot = helpers.findHostInstancesForRefresh(
    
  430.         root,
    
  431.         families,
    
  432.       );
    
  433.       instancesForRoot.forEach(inst => {
    
  434.         affectedInstances.add(inst);
    
  435.       });
    
  436.     });
    
  437.     return affectedInstances;
    
  438.   } else {
    
  439.     throw new Error(
    
  440.       'Unexpected call to React Refresh in a production environment.',
    
  441.     );
    
  442.   }
    
  443. }
    
  444. 
    
  445. export function injectIntoGlobalHook(globalObject: any): void {
    
  446.   if (__DEV__) {
    
  447.     // For React Native, the global hook will be set up by require('react-devtools-core').
    
  448.     // That code will run before us. So we need to monkeypatch functions on existing hook.
    
  449. 
    
  450.     // For React Web, the global hook will be set up by the extension.
    
  451.     // This will also run before us.
    
  452.     let hook = globalObject.__REACT_DEVTOOLS_GLOBAL_HOOK__;
    
  453.     if (hook === undefined) {
    
  454.       // However, if there is no DevTools extension, we'll need to set up the global hook ourselves.
    
  455.       // Note that in this case it's important that renderer code runs *after* this method call.
    
  456.       // Otherwise, the renderer will think that there is no global hook, and won't do the injection.
    
  457.       let nextID = 0;
    
  458.       globalObject.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook = {
    
  459.         renderers: new Map(),
    
  460.         supportsFiber: true,
    
  461.         inject: injected => nextID++,
    
  462.         onScheduleFiberRoot: (
    
  463.           id: number,
    
  464.           root: FiberRoot,
    
  465.           children: ReactNodeList,
    
  466.         ) => {},
    
  467.         onCommitFiberRoot: (
    
  468.           id: number,
    
  469.           root: FiberRoot,
    
  470.           maybePriorityLevel: mixed,
    
  471.           didError: boolean,
    
  472.         ) => {},
    
  473.         onCommitFiberUnmount() {},
    
  474.       };
    
  475.     }
    
  476. 
    
  477.     if (hook.isDisabled) {
    
  478.       // This isn't a real property on the hook, but it can be set to opt out
    
  479.       // of DevTools integration and associated warnings and logs.
    
  480.       // Using console['warn'] to evade Babel and ESLint
    
  481.       console['warn'](
    
  482.         'Something has shimmed the React DevTools global hook (__REACT_DEVTOOLS_GLOBAL_HOOK__). ' +
    
  483.           'Fast Refresh is not compatible with this shim and will be disabled.',
    
  484.       );
    
  485.       return;
    
  486.     }
    
  487. 
    
  488.     // Here, we just want to get a reference to scheduleRefresh.
    
  489.     const oldInject = hook.inject;
    
  490.     hook.inject = function (this: mixed, injected) {
    
  491.       const id = oldInject.apply(this, arguments);
    
  492.       if (
    
  493.         typeof injected.scheduleRefresh === 'function' &&
    
  494.         typeof injected.setRefreshHandler === 'function'
    
  495.       ) {
    
  496.         // This version supports React Refresh.
    
  497.         helpersByRendererID.set(id, ((injected: any): RendererHelpers));
    
  498.       }
    
  499.       return id;
    
  500.     };
    
  501. 
    
  502.     // Do the same for any already injected roots.
    
  503.     // This is useful if ReactDOM has already been initialized.
    
  504.     // https://github.com/facebook/react/issues/17626
    
  505.     hook.renderers.forEach((injected, id) => {
    
  506.       if (
    
  507.         typeof injected.scheduleRefresh === 'function' &&
    
  508.         typeof injected.setRefreshHandler === 'function'
    
  509.       ) {
    
  510.         // This version supports React Refresh.
    
  511.         helpersByRendererID.set(id, ((injected: any): RendererHelpers));
    
  512.       }
    
  513.     });
    
  514. 
    
  515.     // We also want to track currently mounted roots.
    
  516.     const oldOnCommitFiberRoot = hook.onCommitFiberRoot;
    
  517.     const oldOnScheduleFiberRoot = hook.onScheduleFiberRoot || (() => {});
    
  518.     hook.onScheduleFiberRoot = function (
    
  519.       this: mixed,
    
  520.       id: number,
    
  521.       root: FiberRoot,
    
  522.       children: ReactNodeList,
    
  523.     ) {
    
  524.       if (!isPerformingRefresh) {
    
  525.         // If it was intentionally scheduled, don't attempt to restore.
    
  526.         // This includes intentionally scheduled unmounts.
    
  527.         failedRoots.delete(root);
    
  528.         if (rootElements !== null) {
    
  529.           rootElements.set(root, children);
    
  530.         }
    
  531.       }
    
  532.       return oldOnScheduleFiberRoot.apply(this, arguments);
    
  533.     };
    
  534.     hook.onCommitFiberRoot = function (
    
  535.       this: mixed,
    
  536.       id: number,
    
  537.       root: FiberRoot,
    
  538.       maybePriorityLevel: mixed,
    
  539.       didError: boolean,
    
  540.     ) {
    
  541.       const helpers = helpersByRendererID.get(id);
    
  542.       if (helpers !== undefined) {
    
  543.         helpersByRoot.set(root, helpers);
    
  544. 
    
  545.         const current = root.current;
    
  546.         const alternate = current.alternate;
    
  547. 
    
  548.         // We need to determine whether this root has just (un)mounted.
    
  549.         // This logic is copy-pasted from similar logic in the DevTools backend.
    
  550.         // If this breaks with some refactoring, you'll want to update DevTools too.
    
  551. 
    
  552.         if (alternate !== null) {
    
  553.           const wasMounted =
    
  554.             alternate.memoizedState != null &&
    
  555.             alternate.memoizedState.element != null &&
    
  556.             mountedRoots.has(root);
    
  557. 
    
  558.           const isMounted =
    
  559.             current.memoizedState != null &&
    
  560.             current.memoizedState.element != null;
    
  561. 
    
  562.           if (!wasMounted && isMounted) {
    
  563.             // Mount a new root.
    
  564.             mountedRoots.add(root);
    
  565.             failedRoots.delete(root);
    
  566.           } else if (wasMounted && isMounted) {
    
  567.             // Update an existing root.
    
  568.             // This doesn't affect our mounted root Set.
    
  569.           } else if (wasMounted && !isMounted) {
    
  570.             // Unmount an existing root.
    
  571.             mountedRoots.delete(root);
    
  572.             if (didError) {
    
  573.               // We'll remount it on future edits.
    
  574.               failedRoots.add(root);
    
  575.             } else {
    
  576.               helpersByRoot.delete(root);
    
  577.             }
    
  578.           } else if (!wasMounted && !isMounted) {
    
  579.             if (didError) {
    
  580.               // We'll remount it on future edits.
    
  581.               failedRoots.add(root);
    
  582.             }
    
  583.           }
    
  584.         } else {
    
  585.           // Mount a new root.
    
  586.           mountedRoots.add(root);
    
  587.         }
    
  588.       }
    
  589. 
    
  590.       // Always call the decorated DevTools hook.
    
  591.       return oldOnCommitFiberRoot.apply(this, arguments);
    
  592.     };
    
  593.   } else {
    
  594.     throw new Error(
    
  595.       'Unexpected call to React Refresh in a production environment.',
    
  596.     );
    
  597.   }
    
  598. }
    
  599. 
    
  600. export function hasUnrecoverableErrors(): boolean {
    
  601.   // TODO: delete this after removing dependency in RN.
    
  602.   return false;
    
  603. }
    
  604. 
    
  605. // Exposed for testing.
    
  606. export function _getMountedRootCount(): number {
    
  607.   if (__DEV__) {
    
  608.     return mountedRoots.size;
    
  609.   } else {
    
  610.     throw new Error(
    
  611.       'Unexpected call to React Refresh in a production environment.',
    
  612.     );
    
  613.   }
    
  614. }
    
  615. 
    
  616. // This is a wrapper over more primitive functions for setting signature.
    
  617. // Signatures let us decide whether the Hook order has changed on refresh.
    
  618. //
    
  619. // This function is intended to be used as a transform target, e.g.:
    
  620. // var _s = createSignatureFunctionForTransform()
    
  621. //
    
  622. // function Hello() {
    
  623. //   const [foo, setFoo] = useState(0);
    
  624. //   const value = useCustomHook();
    
  625. //   _s(); /* Call without arguments triggers collecting the custom Hook list.
    
  626. //          * This doesn't happen during the module evaluation because we
    
  627. //          * don't want to change the module order with inline requires.
    
  628. //          * Next calls are noops. */
    
  629. //   return <h1>Hi</h1>;
    
  630. // }
    
  631. //
    
  632. // /* Call with arguments attaches the signature to the type: */
    
  633. // _s(
    
  634. //   Hello,
    
  635. //   'useState{[foo, setFoo]}(0)',
    
  636. //   () => [useCustomHook], /* Lazy to avoid triggering inline requires */
    
  637. // );
    
  638. export function createSignatureFunctionForTransform(): <T>(
    
  639.   type: T,
    
  640.   key: string,
    
  641.   forceReset?: boolean,
    
  642.   getCustomHooks?: () => Array<Function>,
    
  643. ) => T | void {
    
  644.   if (__DEV__) {
    
  645.     let savedType: mixed;
    
  646.     let hasCustomHooks: boolean;
    
  647.     let didCollectHooks = false;
    
  648.     return function <T>(
    
  649.       type: T,
    
  650.       key: string,
    
  651.       forceReset?: boolean,
    
  652.       getCustomHooks?: () => Array<Function>,
    
  653.     ): T | void {
    
  654.       if (typeof key === 'string') {
    
  655.         // We're in the initial phase that associates signatures
    
  656.         // with the functions. Note this may be called multiple times
    
  657.         // in HOC chains like _s(hoc1(_s(hoc2(_s(actualFunction))))).
    
  658.         if (!savedType) {
    
  659.           // We're in the innermost call, so this is the actual type.
    
  660.           savedType = type;
    
  661.           hasCustomHooks = typeof getCustomHooks === 'function';
    
  662.         }
    
  663.         // Set the signature for all types (even wrappers!) in case
    
  664.         // they have no signatures of their own. This is to prevent
    
  665.         // problems like https://github.com/facebook/react/issues/20417.
    
  666.         if (
    
  667.           type != null &&
    
  668.           (typeof type === 'function' || typeof type === 'object')
    
  669.         ) {
    
  670.           setSignature(type, key, forceReset, getCustomHooks);
    
  671.         }
    
  672.         return type;
    
  673.       } else {
    
  674.         // We're in the _s() call without arguments, which means
    
  675.         // this is the time to collect custom Hook signatures.
    
  676.         // Only do this once. This path is hot and runs *inside* every render!
    
  677.         if (!didCollectHooks && hasCustomHooks) {
    
  678.           didCollectHooks = true;
    
  679.           collectCustomHooksForSignature(savedType);
    
  680.         }
    
  681.       }
    
  682.     };
    
  683.   } else {
    
  684.     throw new Error(
    
  685.       'Unexpected call to React Refresh in a production environment.',
    
  686.     );
    
  687.   }
    
  688. }
    
  689. 
    
  690. export function isLikelyComponentType(type: any): boolean {
    
  691.   if (__DEV__) {
    
  692.     switch (typeof type) {
    
  693.       case 'function': {
    
  694.         // First, deal with classes.
    
  695.         if (type.prototype != null) {
    
  696.           if (type.prototype.isReactComponent) {
    
  697.             // React class.
    
  698.             return true;
    
  699.           }
    
  700.           const ownNames = Object.getOwnPropertyNames(type.prototype);
    
  701.           if (ownNames.length > 1 || ownNames[0] !== 'constructor') {
    
  702.             // This looks like a class.
    
  703.             return false;
    
  704.           }
    
  705.           // eslint-disable-next-line no-proto
    
  706.           if (type.prototype.__proto__ !== Object.prototype) {
    
  707.             // It has a superclass.
    
  708.             return false;
    
  709.           }
    
  710.           // Pass through.
    
  711.           // This looks like a regular function with empty prototype.
    
  712.         }
    
  713.         // For plain functions and arrows, use name as a heuristic.
    
  714.         const name = type.name || type.displayName;
    
  715.         return typeof name === 'string' && /^[A-Z]/.test(name);
    
  716.       }
    
  717.       case 'object': {
    
  718.         if (type != null) {
    
  719.           switch (getProperty(type, '$$typeof')) {
    
  720.             case REACT_FORWARD_REF_TYPE:
    
  721.             case REACT_MEMO_TYPE:
    
  722.               // Definitely React components.
    
  723.               return true;
    
  724.             default:
    
  725.               return false;
    
  726.           }
    
  727.         }
    
  728.         return false;
    
  729.       }
    
  730.       default: {
    
  731.         return false;
    
  732.       }
    
  733.     }
    
  734.   } else {
    
  735.     throw new Error(
    
  736.       'Unexpected call to React Refresh in a production environment.',
    
  737.     );
    
  738.   }
    
  739. }