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. 
    
  8. 'use strict';
    
  9. 
    
  10. export default function (babel, opts = {}) {
    
  11.   if (typeof babel.env === 'function') {
    
  12.     // Only available in Babel 7.
    
  13.     const env = babel.env();
    
  14.     if (env !== 'development' && !opts.skipEnvCheck) {
    
  15.       throw new Error(
    
  16.         'React Refresh Babel transform should only be enabled in development environment. ' +
    
  17.           'Instead, the environment is: "' +
    
  18.           env +
    
  19.           '". If you want to override this check, pass {skipEnvCheck: true} as plugin options.',
    
  20.       );
    
  21.     }
    
  22.   }
    
  23. 
    
  24.   const {types: t} = babel;
    
  25.   const refreshReg = t.identifier(opts.refreshReg || '$RefreshReg$');
    
  26.   const refreshSig = t.identifier(opts.refreshSig || '$RefreshSig$');
    
  27. 
    
  28.   const registrationsByProgramPath = new Map();
    
  29.   function createRegistration(programPath, persistentID) {
    
  30.     const handle = programPath.scope.generateUidIdentifier('c');
    
  31.     if (!registrationsByProgramPath.has(programPath)) {
    
  32.       registrationsByProgramPath.set(programPath, []);
    
  33.     }
    
  34.     const registrations = registrationsByProgramPath.get(programPath);
    
  35.     registrations.push({
    
  36.       handle,
    
  37.       persistentID,
    
  38.     });
    
  39.     return handle;
    
  40.   }
    
  41. 
    
  42.   function isComponentishName(name) {
    
  43.     return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z';
    
  44.   }
    
  45. 
    
  46.   function findInnerComponents(inferredName, path, callback) {
    
  47.     const node = path.node;
    
  48.     switch (node.type) {
    
  49.       case 'Identifier': {
    
  50.         if (!isComponentishName(node.name)) {
    
  51.           return false;
    
  52.         }
    
  53.         // export default hoc(Foo)
    
  54.         // const X = hoc(Foo)
    
  55.         callback(inferredName, node, null);
    
  56.         return true;
    
  57.       }
    
  58.       case 'FunctionDeclaration': {
    
  59.         // function Foo() {}
    
  60.         // export function Foo() {}
    
  61.         // export default function Foo() {}
    
  62.         callback(inferredName, node.id, null);
    
  63.         return true;
    
  64.       }
    
  65.       case 'ArrowFunctionExpression': {
    
  66.         if (node.body.type === 'ArrowFunctionExpression') {
    
  67.           return false;
    
  68.         }
    
  69.         // let Foo = () => {}
    
  70.         // export default hoc1(hoc2(() => {}))
    
  71.         callback(inferredName, node, path);
    
  72.         return true;
    
  73.       }
    
  74.       case 'FunctionExpression': {
    
  75.         // let Foo = function() {}
    
  76.         // const Foo = hoc1(forwardRef(function renderFoo() {}))
    
  77.         // export default memo(function() {})
    
  78.         callback(inferredName, node, path);
    
  79.         return true;
    
  80.       }
    
  81.       case 'CallExpression': {
    
  82.         const argsPath = path.get('arguments');
    
  83.         if (argsPath === undefined || argsPath.length === 0) {
    
  84.           return false;
    
  85.         }
    
  86.         const calleePath = path.get('callee');
    
  87.         switch (calleePath.node.type) {
    
  88.           case 'MemberExpression':
    
  89.           case 'Identifier': {
    
  90.             const calleeSource = calleePath.getSource();
    
  91.             const firstArgPath = argsPath[0];
    
  92.             const innerName = inferredName + '$' + calleeSource;
    
  93.             const foundInside = findInnerComponents(
    
  94.               innerName,
    
  95.               firstArgPath,
    
  96.               callback,
    
  97.             );
    
  98.             if (!foundInside) {
    
  99.               return false;
    
  100.             }
    
  101.             // const Foo = hoc1(hoc2(() => {}))
    
  102.             // export default memo(React.forwardRef(function() {}))
    
  103.             callback(inferredName, node, path);
    
  104.             return true;
    
  105.           }
    
  106.           default: {
    
  107.             return false;
    
  108.           }
    
  109.         }
    
  110.       }
    
  111.       case 'VariableDeclarator': {
    
  112.         const init = node.init;
    
  113.         if (init === null) {
    
  114.           return false;
    
  115.         }
    
  116.         const name = node.id.name;
    
  117.         if (!isComponentishName(name)) {
    
  118.           return false;
    
  119.         }
    
  120.         switch (init.type) {
    
  121.           case 'ArrowFunctionExpression':
    
  122.           case 'FunctionExpression':
    
  123.             // Likely component definitions.
    
  124.             break;
    
  125.           case 'CallExpression': {
    
  126.             // Maybe a HOC.
    
  127.             // Try to determine if this is some form of import.
    
  128.             const callee = init.callee;
    
  129.             const calleeType = callee.type;
    
  130.             if (calleeType === 'Import') {
    
  131.               return false;
    
  132.             } else if (calleeType === 'Identifier') {
    
  133.               if (callee.name.indexOf('require') === 0) {
    
  134.                 return false;
    
  135.               } else if (callee.name.indexOf('import') === 0) {
    
  136.                 return false;
    
  137.               }
    
  138.               // Neither require nor import. Might be a HOC.
    
  139.               // Pass through.
    
  140.             } else if (calleeType === 'MemberExpression') {
    
  141.               // Could be something like React.forwardRef(...)
    
  142.               // Pass through.
    
  143.             }
    
  144.             break;
    
  145.           }
    
  146.           case 'TaggedTemplateExpression':
    
  147.             // Maybe something like styled.div`...`
    
  148.             break;
    
  149.           default:
    
  150.             return false;
    
  151.         }
    
  152.         const initPath = path.get('init');
    
  153.         const foundInside = findInnerComponents(
    
  154.           inferredName,
    
  155.           initPath,
    
  156.           callback,
    
  157.         );
    
  158.         if (foundInside) {
    
  159.           return true;
    
  160.         }
    
  161.         // See if this identifier is used in JSX. Then it's a component.
    
  162.         const binding = path.scope.getBinding(name);
    
  163.         if (binding === undefined) {
    
  164.           return;
    
  165.         }
    
  166.         let isLikelyUsedAsType = false;
    
  167.         const referencePaths = binding.referencePaths;
    
  168.         for (let i = 0; i < referencePaths.length; i++) {
    
  169.           const ref = referencePaths[i];
    
  170.           if (
    
  171.             ref.node &&
    
  172.             ref.node.type !== 'JSXIdentifier' &&
    
  173.             ref.node.type !== 'Identifier'
    
  174.           ) {
    
  175.             continue;
    
  176.           }
    
  177.           const refParent = ref.parent;
    
  178.           if (refParent.type === 'JSXOpeningElement') {
    
  179.             isLikelyUsedAsType = true;
    
  180.           } else if (refParent.type === 'CallExpression') {
    
  181.             const callee = refParent.callee;
    
  182.             let fnName;
    
  183.             switch (callee.type) {
    
  184.               case 'Identifier':
    
  185.                 fnName = callee.name;
    
  186.                 break;
    
  187.               case 'MemberExpression':
    
  188.                 fnName = callee.property.name;
    
  189.                 break;
    
  190.             }
    
  191.             switch (fnName) {
    
  192.               case 'createElement':
    
  193.               case 'jsx':
    
  194.               case 'jsxDEV':
    
  195.               case 'jsxs':
    
  196.                 isLikelyUsedAsType = true;
    
  197.                 break;
    
  198.             }
    
  199.           }
    
  200.           if (isLikelyUsedAsType) {
    
  201.             // const X = ... + later <X />
    
  202.             callback(inferredName, init, initPath);
    
  203.             return true;
    
  204.           }
    
  205.         }
    
  206.       }
    
  207.     }
    
  208.     return false;
    
  209.   }
    
  210. 
    
  211.   function isBuiltinHook(hookName) {
    
  212.     switch (hookName) {
    
  213.       case 'useState':
    
  214.       case 'React.useState':
    
  215.       case 'useReducer':
    
  216.       case 'React.useReducer':
    
  217.       case 'useEffect':
    
  218.       case 'React.useEffect':
    
  219.       case 'useLayoutEffect':
    
  220.       case 'React.useLayoutEffect':
    
  221.       case 'useMemo':
    
  222.       case 'React.useMemo':
    
  223.       case 'useCallback':
    
  224.       case 'React.useCallback':
    
  225.       case 'useRef':
    
  226.       case 'React.useRef':
    
  227.       case 'useContext':
    
  228.       case 'React.useContext':
    
  229.       case 'useImperativeHandle':
    
  230.       case 'React.useImperativeHandle':
    
  231.       case 'useDebugValue':
    
  232.       case 'React.useDebugValue':
    
  233.         return true;
    
  234.       default:
    
  235.         return false;
    
  236.     }
    
  237.   }
    
  238. 
    
  239.   function getHookCallsSignature(functionNode) {
    
  240.     const fnHookCalls = hookCalls.get(functionNode);
    
  241.     if (fnHookCalls === undefined) {
    
  242.       return null;
    
  243.     }
    
  244.     return {
    
  245.       key: fnHookCalls.map(call => call.name + '{' + call.key + '}').join('\n'),
    
  246.       customHooks: fnHookCalls
    
  247.         .filter(call => !isBuiltinHook(call.name))
    
  248.         .map(call => t.cloneDeep(call.callee)),
    
  249.     };
    
  250.   }
    
  251. 
    
  252.   const hasForceResetCommentByFile = new WeakMap();
    
  253. 
    
  254.   // We let user do /* @refresh reset */ to reset state in the whole file.
    
  255.   function hasForceResetComment(path) {
    
  256.     const file = path.hub.file;
    
  257.     let hasForceReset = hasForceResetCommentByFile.get(file);
    
  258.     if (hasForceReset !== undefined) {
    
  259.       return hasForceReset;
    
  260.     }
    
  261. 
    
  262.     hasForceReset = false;
    
  263.     const comments = file.ast.comments;
    
  264.     for (let i = 0; i < comments.length; i++) {
    
  265.       const cmt = comments[i];
    
  266.       if (cmt.value.indexOf('@refresh reset') !== -1) {
    
  267.         hasForceReset = true;
    
  268.         break;
    
  269.       }
    
  270.     }
    
  271. 
    
  272.     hasForceResetCommentByFile.set(file, hasForceReset);
    
  273.     return hasForceReset;
    
  274.   }
    
  275. 
    
  276.   function createArgumentsForSignature(node, signature, scope) {
    
  277.     const {key, customHooks} = signature;
    
  278. 
    
  279.     let forceReset = hasForceResetComment(scope.path);
    
  280.     const customHooksInScope = [];
    
  281.     customHooks.forEach(callee => {
    
  282.       // Check if a corresponding binding exists where we emit the signature.
    
  283.       let bindingName;
    
  284.       switch (callee.type) {
    
  285.         case 'MemberExpression':
    
  286.           if (callee.object.type === 'Identifier') {
    
  287.             bindingName = callee.object.name;
    
  288.           }
    
  289.           break;
    
  290.         case 'Identifier':
    
  291.           bindingName = callee.name;
    
  292.           break;
    
  293.       }
    
  294.       if (scope.hasBinding(bindingName)) {
    
  295.         customHooksInScope.push(callee);
    
  296.       } else {
    
  297.         // We don't have anything to put in the array because Hook is out of scope.
    
  298.         // Since it could potentially have been edited, remount the component.
    
  299.         forceReset = true;
    
  300.       }
    
  301.     });
    
  302. 
    
  303.     let finalKey = key;
    
  304.     if (typeof require === 'function' && !opts.emitFullSignatures) {
    
  305.       // Prefer to hash when we can (e.g. outside of ASTExplorer).
    
  306.       // This makes it deterministically compact, even if there's
    
  307.       // e.g. a useState initializer with some code inside.
    
  308.       // We also need it for www that has transforms like cx()
    
  309.       // that don't understand if something is part of a string.
    
  310.       finalKey = require('crypto')
    
  311.         .createHash('sha1')
    
  312.         .update(key)
    
  313.         .digest('base64');
    
  314.     }
    
  315. 
    
  316.     const args = [node, t.stringLiteral(finalKey)];
    
  317.     if (forceReset || customHooksInScope.length > 0) {
    
  318.       args.push(t.booleanLiteral(forceReset));
    
  319.     }
    
  320.     if (customHooksInScope.length > 0) {
    
  321.       args.push(
    
  322.         // TODO: We could use an arrow here to be more compact.
    
  323.         // However, don't do it until AMA can run them natively.
    
  324.         t.functionExpression(
    
  325.           null,
    
  326.           [],
    
  327.           t.blockStatement([
    
  328.             t.returnStatement(t.arrayExpression(customHooksInScope)),
    
  329.           ]),
    
  330.         ),
    
  331.       );
    
  332.     }
    
  333.     return args;
    
  334.   }
    
  335. 
    
  336.   function findHOCCallPathsAbove(path) {
    
  337.     const calls = [];
    
  338.     while (true) {
    
  339.       if (!path) {
    
  340.         return calls;
    
  341.       }
    
  342.       const parentPath = path.parentPath;
    
  343.       if (!parentPath) {
    
  344.         return calls;
    
  345.       }
    
  346.       if (
    
  347.         // hoc(_c = function() { })
    
  348.         parentPath.node.type === 'AssignmentExpression' &&
    
  349.         path.node === parentPath.node.right
    
  350.       ) {
    
  351.         // Ignore registrations.
    
  352.         path = parentPath;
    
  353.         continue;
    
  354.       }
    
  355.       if (
    
  356.         // hoc1(hoc2(...))
    
  357.         parentPath.node.type === 'CallExpression' &&
    
  358.         path.node !== parentPath.node.callee
    
  359.       ) {
    
  360.         calls.push(parentPath);
    
  361.         path = parentPath;
    
  362.         continue;
    
  363.       }
    
  364.       return calls; // Stop at other types.
    
  365.     }
    
  366.   }
    
  367. 
    
  368.   const seenForRegistration = new WeakSet();
    
  369.   const seenForSignature = new WeakSet();
    
  370.   const seenForOutro = new WeakSet();
    
  371. 
    
  372.   const hookCalls = new WeakMap();
    
  373.   const HookCallsVisitor = {
    
  374.     CallExpression(path) {
    
  375.       const node = path.node;
    
  376.       const callee = node.callee;
    
  377. 
    
  378.       // Note: this visitor MUST NOT mutate the tree in any way.
    
  379.       // It runs early in a separate traversal and should be very fast.
    
  380. 
    
  381.       let name = null;
    
  382.       switch (callee.type) {
    
  383.         case 'Identifier':
    
  384.           name = callee.name;
    
  385.           break;
    
  386.         case 'MemberExpression':
    
  387.           name = callee.property.name;
    
  388.           break;
    
  389.       }
    
  390.       if (name === null || !/^use[A-Z]/.test(name)) {
    
  391.         return;
    
  392.       }
    
  393.       const fnScope = path.scope.getFunctionParent();
    
  394.       if (fnScope === null) {
    
  395.         return;
    
  396.       }
    
  397. 
    
  398.       // This is a Hook call. Record it.
    
  399.       const fnNode = fnScope.block;
    
  400.       if (!hookCalls.has(fnNode)) {
    
  401.         hookCalls.set(fnNode, []);
    
  402.       }
    
  403.       const hookCallsForFn = hookCalls.get(fnNode);
    
  404.       let key = '';
    
  405.       if (path.parent.type === 'VariableDeclarator') {
    
  406.         // TODO: if there is no LHS, consider some other heuristic.
    
  407.         key = path.parentPath.get('id').getSource();
    
  408.       }
    
  409. 
    
  410.       // Some built-in Hooks reset on edits to arguments.
    
  411.       const args = path.get('arguments');
    
  412.       if (name === 'useState' && args.length > 0) {
    
  413.         // useState second argument is initial state.
    
  414.         key += '(' + args[0].getSource() + ')';
    
  415.       } else if (name === 'useReducer' && args.length > 1) {
    
  416.         // useReducer second argument is initial state.
    
  417.         key += '(' + args[1].getSource() + ')';
    
  418.       }
    
  419. 
    
  420.       hookCallsForFn.push({
    
  421.         callee: path.node.callee,
    
  422.         name,
    
  423.         key,
    
  424.       });
    
  425.     },
    
  426.   };
    
  427. 
    
  428.   return {
    
  429.     visitor: {
    
  430.       ExportDefaultDeclaration(path) {
    
  431.         const node = path.node;
    
  432.         const decl = node.declaration;
    
  433.         const declPath = path.get('declaration');
    
  434.         if (decl.type !== 'CallExpression') {
    
  435.           // For now, we only support possible HOC calls here.
    
  436.           // Named function declarations are handled in FunctionDeclaration.
    
  437.           // Anonymous direct exports like export default function() {}
    
  438.           // are currently ignored.
    
  439.           return;
    
  440.         }
    
  441. 
    
  442.         // Make sure we're not mutating the same tree twice.
    
  443.         // This can happen if another Babel plugin replaces parents.
    
  444.         if (seenForRegistration.has(node)) {
    
  445.           return;
    
  446.         }
    
  447.         seenForRegistration.add(node);
    
  448.         // Don't mutate the tree above this point.
    
  449. 
    
  450.         // This code path handles nested cases like:
    
  451.         // export default memo(() => {})
    
  452.         // In those cases it is more plausible people will omit names
    
  453.         // so they're worth handling despite possible false positives.
    
  454.         // More importantly, it handles the named case:
    
  455.         // export default memo(function Named() {})
    
  456.         const inferredName = '%default%';
    
  457.         const programPath = path.parentPath;
    
  458.         findInnerComponents(
    
  459.           inferredName,
    
  460.           declPath,
    
  461.           (persistentID, targetExpr, targetPath) => {
    
  462.             if (targetPath === null) {
    
  463.               // For case like:
    
  464.               // export default hoc(Foo)
    
  465.               // we don't want to wrap Foo inside the call.
    
  466.               // Instead we assume it's registered at definition.
    
  467.               return;
    
  468.             }
    
  469.             const handle = createRegistration(programPath, persistentID);
    
  470.             targetPath.replaceWith(
    
  471.               t.assignmentExpression('=', handle, targetExpr),
    
  472.             );
    
  473.           },
    
  474.         );
    
  475.       },
    
  476.       FunctionDeclaration: {
    
  477.         enter(path) {
    
  478.           const node = path.node;
    
  479.           let programPath;
    
  480.           let insertAfterPath;
    
  481.           let modulePrefix = '';
    
  482.           switch (path.parent.type) {
    
  483.             case 'Program':
    
  484.               insertAfterPath = path;
    
  485.               programPath = path.parentPath;
    
  486.               break;
    
  487.             case 'TSModuleBlock':
    
  488.               insertAfterPath = path;
    
  489.               programPath = insertAfterPath.parentPath.parentPath;
    
  490.               break;
    
  491.             case 'ExportNamedDeclaration':
    
  492.               insertAfterPath = path.parentPath;
    
  493.               programPath = insertAfterPath.parentPath;
    
  494.               break;
    
  495.             case 'ExportDefaultDeclaration':
    
  496.               insertAfterPath = path.parentPath;
    
  497.               programPath = insertAfterPath.parentPath;
    
  498.               break;
    
  499.             default:
    
  500.               return;
    
  501.           }
    
  502. 
    
  503.           // These types can be nested in typescript namespace
    
  504.           // We need to find the export chain
    
  505.           // Or return if it stays local
    
  506.           if (
    
  507.             path.parent.type === 'TSModuleBlock' ||
    
  508.             path.parent.type === 'ExportNamedDeclaration'
    
  509.           ) {
    
  510.             while (programPath.type !== 'Program') {
    
  511.               if (programPath.type === 'TSModuleDeclaration') {
    
  512.                 if (
    
  513.                   programPath.parentPath.type !== 'Program' &&
    
  514.                   programPath.parentPath.type !== 'ExportNamedDeclaration'
    
  515.                 ) {
    
  516.                   return;
    
  517.                 }
    
  518.                 modulePrefix = programPath.node.id.name + '$' + modulePrefix;
    
  519.               }
    
  520.               programPath = programPath.parentPath;
    
  521.             }
    
  522.           }
    
  523. 
    
  524.           const id = node.id;
    
  525.           if (id === null) {
    
  526.             // We don't currently handle anonymous default exports.
    
  527.             return;
    
  528.           }
    
  529.           const inferredName = id.name;
    
  530.           if (!isComponentishName(inferredName)) {
    
  531.             return;
    
  532.           }
    
  533. 
    
  534.           // Make sure we're not mutating the same tree twice.
    
  535.           // This can happen if another Babel plugin replaces parents.
    
  536.           if (seenForRegistration.has(node)) {
    
  537.             return;
    
  538.           }
    
  539.           seenForRegistration.add(node);
    
  540.           // Don't mutate the tree above this point.
    
  541. 
    
  542.           const innerName = modulePrefix + inferredName;
    
  543.           // export function Named() {}
    
  544.           // function Named() {}
    
  545.           findInnerComponents(innerName, path, (persistentID, targetExpr) => {
    
  546.             const handle = createRegistration(programPath, persistentID);
    
  547.             insertAfterPath.insertAfter(
    
  548.               t.expressionStatement(
    
  549.                 t.assignmentExpression('=', handle, targetExpr),
    
  550.               ),
    
  551.             );
    
  552.           });
    
  553.         },
    
  554.         exit(path) {
    
  555.           const node = path.node;
    
  556.           const id = node.id;
    
  557.           if (id === null) {
    
  558.             return;
    
  559.           }
    
  560.           const signature = getHookCallsSignature(node);
    
  561.           if (signature === null) {
    
  562.             return;
    
  563.           }
    
  564. 
    
  565.           // Make sure we're not mutating the same tree twice.
    
  566.           // This can happen if another Babel plugin replaces parents.
    
  567.           if (seenForSignature.has(node)) {
    
  568.             return;
    
  569.           }
    
  570.           seenForSignature.add(node);
    
  571.           // Don't mutate the tree above this point.
    
  572. 
    
  573.           const sigCallID = path.scope.generateUidIdentifier('_s');
    
  574.           path.scope.parent.push({
    
  575.             id: sigCallID,
    
  576.             init: t.callExpression(refreshSig, []),
    
  577.           });
    
  578. 
    
  579.           // The signature call is split in two parts. One part is called inside the function.
    
  580.           // This is used to signal when first render happens.
    
  581.           path
    
  582.             .get('body')
    
  583.             .unshiftContainer(
    
  584.               'body',
    
  585.               t.expressionStatement(t.callExpression(sigCallID, [])),
    
  586.             );
    
  587. 
    
  588.           // The second call is around the function itself.
    
  589.           // This is used to associate a type with a signature.
    
  590. 
    
  591.           // Unlike with $RefreshReg$, this needs to work for nested
    
  592.           // declarations too. So we need to search for a path where
    
  593.           // we can insert a statement rather than hard coding it.
    
  594.           let insertAfterPath = null;
    
  595.           path.find(p => {
    
  596.             if (p.parentPath.isBlock()) {
    
  597.               insertAfterPath = p;
    
  598.               return true;
    
  599.             }
    
  600.           });
    
  601.           if (insertAfterPath === null) {
    
  602.             return;
    
  603.           }
    
  604. 
    
  605.           insertAfterPath.insertAfter(
    
  606.             t.expressionStatement(
    
  607.               t.callExpression(
    
  608.                 sigCallID,
    
  609.                 createArgumentsForSignature(
    
  610.                   id,
    
  611.                   signature,
    
  612.                   insertAfterPath.scope,
    
  613.                 ),
    
  614.               ),
    
  615.             ),
    
  616.           );
    
  617.         },
    
  618.       },
    
  619.       'ArrowFunctionExpression|FunctionExpression': {
    
  620.         exit(path) {
    
  621.           const node = path.node;
    
  622.           const signature = getHookCallsSignature(node);
    
  623.           if (signature === null) {
    
  624.             return;
    
  625.           }
    
  626. 
    
  627.           // Make sure we're not mutating the same tree twice.
    
  628.           // This can happen if another Babel plugin replaces parents.
    
  629.           if (seenForSignature.has(node)) {
    
  630.             return;
    
  631.           }
    
  632.           seenForSignature.add(node);
    
  633.           // Don't mutate the tree above this point.
    
  634. 
    
  635.           const sigCallID = path.scope.generateUidIdentifier('_s');
    
  636.           path.scope.parent.push({
    
  637.             id: sigCallID,
    
  638.             init: t.callExpression(refreshSig, []),
    
  639.           });
    
  640. 
    
  641.           // The signature call is split in two parts. One part is called inside the function.
    
  642.           // This is used to signal when first render happens.
    
  643.           if (path.node.body.type !== 'BlockStatement') {
    
  644.             path.node.body = t.blockStatement([
    
  645.               t.returnStatement(path.node.body),
    
  646.             ]);
    
  647.           }
    
  648.           path
    
  649.             .get('body')
    
  650.             .unshiftContainer(
    
  651.               'body',
    
  652.               t.expressionStatement(t.callExpression(sigCallID, [])),
    
  653.             );
    
  654. 
    
  655.           // The second call is around the function itself.
    
  656.           // This is used to associate a type with a signature.
    
  657. 
    
  658.           if (path.parent.type === 'VariableDeclarator') {
    
  659.             let insertAfterPath = null;
    
  660.             path.find(p => {
    
  661.               if (p.parentPath.isBlock()) {
    
  662.                 insertAfterPath = p;
    
  663.                 return true;
    
  664.               }
    
  665.             });
    
  666.             if (insertAfterPath === null) {
    
  667.               return;
    
  668.             }
    
  669.             // Special case when a function would get an inferred name:
    
  670.             // let Foo = () => {}
    
  671.             // let Foo = function() {}
    
  672.             // We'll add signature it on next line so that
    
  673.             // we don't mess up the inferred 'Foo' function name.
    
  674.             insertAfterPath.insertAfter(
    
  675.               t.expressionStatement(
    
  676.                 t.callExpression(
    
  677.                   sigCallID,
    
  678.                   createArgumentsForSignature(
    
  679.                     path.parent.id,
    
  680.                     signature,
    
  681.                     insertAfterPath.scope,
    
  682.                   ),
    
  683.                 ),
    
  684.               ),
    
  685.             );
    
  686.             // Result: let Foo = () => {}; __signature(Foo, ...);
    
  687.           } else {
    
  688.             // let Foo = hoc(() => {})
    
  689.             const paths = [path, ...findHOCCallPathsAbove(path)];
    
  690.             paths.forEach(p => {
    
  691.               p.replaceWith(
    
  692.                 t.callExpression(
    
  693.                   sigCallID,
    
  694.                   createArgumentsForSignature(p.node, signature, p.scope),
    
  695.                 ),
    
  696.               );
    
  697.             });
    
  698.             // Result: let Foo = __signature(hoc(__signature(() => {}, ...)), ...)
    
  699.           }
    
  700.         },
    
  701.       },
    
  702.       VariableDeclaration(path) {
    
  703.         const node = path.node;
    
  704.         let programPath;
    
  705.         let insertAfterPath;
    
  706.         let modulePrefix = '';
    
  707.         switch (path.parent.type) {
    
  708.           case 'Program':
    
  709.             insertAfterPath = path;
    
  710.             programPath = path.parentPath;
    
  711.             break;
    
  712.           case 'TSModuleBlock':
    
  713.             insertAfterPath = path;
    
  714.             programPath = insertAfterPath.parentPath.parentPath;
    
  715.             break;
    
  716.           case 'ExportNamedDeclaration':
    
  717.             insertAfterPath = path.parentPath;
    
  718.             programPath = insertAfterPath.parentPath;
    
  719.             break;
    
  720.           case 'ExportDefaultDeclaration':
    
  721.             insertAfterPath = path.parentPath;
    
  722.             programPath = insertAfterPath.parentPath;
    
  723.             break;
    
  724.           default:
    
  725.             return;
    
  726.         }
    
  727. 
    
  728.         // These types can be nested in typescript namespace
    
  729.         // We need to find the export chain
    
  730.         // Or return if it stays local
    
  731.         if (
    
  732.           path.parent.type === 'TSModuleBlock' ||
    
  733.           path.parent.type === 'ExportNamedDeclaration'
    
  734.         ) {
    
  735.           while (programPath.type !== 'Program') {
    
  736.             if (programPath.type === 'TSModuleDeclaration') {
    
  737.               if (
    
  738.                 programPath.parentPath.type !== 'Program' &&
    
  739.                 programPath.parentPath.type !== 'ExportNamedDeclaration'
    
  740.               ) {
    
  741.                 return;
    
  742.               }
    
  743.               modulePrefix = programPath.node.id.name + '$' + modulePrefix;
    
  744.             }
    
  745.             programPath = programPath.parentPath;
    
  746.           }
    
  747.         }
    
  748. 
    
  749.         // Make sure we're not mutating the same tree twice.
    
  750.         // This can happen if another Babel plugin replaces parents.
    
  751.         if (seenForRegistration.has(node)) {
    
  752.           return;
    
  753.         }
    
  754.         seenForRegistration.add(node);
    
  755.         // Don't mutate the tree above this point.
    
  756. 
    
  757.         const declPaths = path.get('declarations');
    
  758.         if (declPaths.length !== 1) {
    
  759.           return;
    
  760.         }
    
  761.         const declPath = declPaths[0];
    
  762.         const inferredName = declPath.node.id.name;
    
  763.         const innerName = modulePrefix + inferredName;
    
  764.         findInnerComponents(
    
  765.           innerName,
    
  766.           declPath,
    
  767.           (persistentID, targetExpr, targetPath) => {
    
  768.             if (targetPath === null) {
    
  769.               // For case like:
    
  770.               // export const Something = hoc(Foo)
    
  771.               // we don't want to wrap Foo inside the call.
    
  772.               // Instead we assume it's registered at definition.
    
  773.               return;
    
  774.             }
    
  775.             const handle = createRegistration(programPath, persistentID);
    
  776.             if (targetPath.parent.type === 'VariableDeclarator') {
    
  777.               // Special case when a variable would get an inferred name:
    
  778.               // let Foo = () => {}
    
  779.               // let Foo = function() {}
    
  780.               // let Foo = styled.div``;
    
  781.               // We'll register it on next line so that
    
  782.               // we don't mess up the inferred 'Foo' function name.
    
  783.               // (eg: with @babel/plugin-transform-react-display-name or
    
  784.               // babel-plugin-styled-components)
    
  785.               insertAfterPath.insertAfter(
    
  786.                 t.expressionStatement(
    
  787.                   t.assignmentExpression('=', handle, declPath.node.id),
    
  788.                 ),
    
  789.               );
    
  790.               // Result: let Foo = () => {}; _c1 = Foo;
    
  791.             } else {
    
  792.               // let Foo = hoc(() => {})
    
  793.               targetPath.replaceWith(
    
  794.                 t.assignmentExpression('=', handle, targetExpr),
    
  795.               );
    
  796.               // Result: let Foo = hoc(_c1 = () => {})
    
  797.             }
    
  798.           },
    
  799.         );
    
  800.       },
    
  801.       Program: {
    
  802.         enter(path) {
    
  803.           // This is a separate early visitor because we need to collect Hook calls
    
  804.           // and "const [foo, setFoo] = ..." signatures before the destructuring
    
  805.           // transform mangles them. This extra traversal is not ideal for perf,
    
  806.           // but it's the best we can do until we stop transpiling destructuring.
    
  807.           path.traverse(HookCallsVisitor);
    
  808.         },
    
  809.         exit(path) {
    
  810.           const registrations = registrationsByProgramPath.get(path);
    
  811.           if (registrations === undefined) {
    
  812.             return;
    
  813.           }
    
  814. 
    
  815.           // Make sure we're not mutating the same tree twice.
    
  816.           // This can happen if another Babel plugin replaces parents.
    
  817.           const node = path.node;
    
  818.           if (seenForOutro.has(node)) {
    
  819.             return;
    
  820.           }
    
  821.           seenForOutro.add(node);
    
  822.           // Don't mutate the tree above this point.
    
  823. 
    
  824.           registrationsByProgramPath.delete(path);
    
  825.           const declarators = [];
    
  826.           path.pushContainer('body', t.variableDeclaration('var', declarators));
    
  827.           registrations.forEach(({handle, persistentID}) => {
    
  828.             path.pushContainer(
    
  829.               'body',
    
  830.               t.expressionStatement(
    
  831.                 t.callExpression(refreshReg, [
    
  832.                   handle,
    
  833.                   t.stringLiteral(persistentID),
    
  834.                 ]),
    
  835.               ),
    
  836.             );
    
  837.             declarators.push(t.variableDeclarator(handle));
    
  838.           });
    
  839.         },
    
  840.       },
    
  841.     },
    
  842.   };
    
  843. }