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. /* global BigInt */
    
  9. /* eslint-disable no-for-of-loops/no-for-of-loops */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. /**
    
  14.  * Catch all identifiers that begin with "use" followed by an uppercase Latin
    
  15.  * character to exclude identifiers like "user".
    
  16.  */
    
  17. 
    
  18. function isHookName(s) {
    
  19.   if (__EXPERIMENTAL__) {
    
  20.     return s === 'use' || /^use[A-Z0-9]/.test(s);
    
  21.   }
    
  22.   return /^use[A-Z0-9]/.test(s);
    
  23. }
    
  24. 
    
  25. /**
    
  26.  * We consider hooks to be a hook name identifier or a member expression
    
  27.  * containing a hook name.
    
  28.  */
    
  29. 
    
  30. function isHook(node) {
    
  31.   if (node.type === 'Identifier') {
    
  32.     return isHookName(node.name);
    
  33.   } else if (
    
  34.     node.type === 'MemberExpression' &&
    
  35.     !node.computed &&
    
  36.     isHook(node.property)
    
  37.   ) {
    
  38.     const obj = node.object;
    
  39.     const isPascalCaseNameSpace = /^[A-Z].*/;
    
  40.     return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
    
  41.   } else {
    
  42.     return false;
    
  43.   }
    
  44. }
    
  45. 
    
  46. /**
    
  47.  * Checks if the node is a React component name. React component names must
    
  48.  * always start with an uppercase letter.
    
  49.  */
    
  50. 
    
  51. function isComponentName(node) {
    
  52.   return node.type === 'Identifier' && /^[A-Z]/.test(node.name);
    
  53. }
    
  54. 
    
  55. function isReactFunction(node, functionName) {
    
  56.   return (
    
  57.     node.name === functionName ||
    
  58.     (node.type === 'MemberExpression' &&
    
  59.       node.object.name === 'React' &&
    
  60.       node.property.name === functionName)
    
  61.   );
    
  62. }
    
  63. 
    
  64. /**
    
  65.  * Checks if the node is a callback argument of forwardRef. This render function
    
  66.  * should follow the rules of hooks.
    
  67.  */
    
  68. 
    
  69. function isForwardRefCallback(node) {
    
  70.   return !!(
    
  71.     node.parent &&
    
  72.     node.parent.callee &&
    
  73.     isReactFunction(node.parent.callee, 'forwardRef')
    
  74.   );
    
  75. }
    
  76. 
    
  77. /**
    
  78.  * Checks if the node is a callback argument of React.memo. This anonymous
    
  79.  * functional component should follow the rules of hooks.
    
  80.  */
    
  81. 
    
  82. function isMemoCallback(node) {
    
  83.   return !!(
    
  84.     node.parent &&
    
  85.     node.parent.callee &&
    
  86.     isReactFunction(node.parent.callee, 'memo')
    
  87.   );
    
  88. }
    
  89. 
    
  90. function isInsideComponentOrHook(node) {
    
  91.   while (node) {
    
  92.     const functionName = getFunctionName(node);
    
  93.     if (functionName) {
    
  94.       if (isComponentName(functionName) || isHook(functionName)) {
    
  95.         return true;
    
  96.       }
    
  97.     }
    
  98.     if (isForwardRefCallback(node) || isMemoCallback(node)) {
    
  99.       return true;
    
  100.     }
    
  101.     node = node.parent;
    
  102.   }
    
  103.   return false;
    
  104. }
    
  105. 
    
  106. function isUseEffectEventIdentifier(node) {
    
  107.   if (__EXPERIMENTAL__) {
    
  108.     return node.type === 'Identifier' && node.name === 'useEffectEvent';
    
  109.   }
    
  110.   return false;
    
  111. }
    
  112. 
    
  113. function isUseIdentifier(node) {
    
  114.   if (__EXPERIMENTAL__) {
    
  115.     return node.type === 'Identifier' && node.name === 'use';
    
  116.   }
    
  117.   return false;
    
  118. }
    
  119. 
    
  120. export default {
    
  121.   meta: {
    
  122.     type: 'problem',
    
  123.     docs: {
    
  124.       description: 'enforces the Rules of Hooks',
    
  125.       recommended: true,
    
  126.       url: 'https://reactjs.org/docs/hooks-rules.html',
    
  127.     },
    
  128.   },
    
  129.   create(context) {
    
  130.     let lastEffect = null;
    
  131.     const codePathReactHooksMapStack = [];
    
  132.     const codePathSegmentStack = [];
    
  133.     const useEffectEventFunctions = new WeakSet();
    
  134. 
    
  135.     // For a given scope, iterate through the references and add all useEffectEvent definitions. We can
    
  136.     // do this in non-Program nodes because we can rely on the assumption that useEffectEvent functions
    
  137.     // can only be declared within a component or hook at its top level.
    
  138.     function recordAllUseEffectEventFunctions(scope) {
    
  139.       for (const reference of scope.references) {
    
  140.         const parent = reference.identifier.parent;
    
  141.         if (
    
  142.           parent.type === 'VariableDeclarator' &&
    
  143.           parent.init &&
    
  144.           parent.init.type === 'CallExpression' &&
    
  145.           parent.init.callee &&
    
  146.           isUseEffectEventIdentifier(parent.init.callee)
    
  147.         ) {
    
  148.           for (const ref of reference.resolved.references) {
    
  149.             if (ref !== reference) {
    
  150.               useEffectEventFunctions.add(ref.identifier);
    
  151.             }
    
  152.           }
    
  153.         }
    
  154.       }
    
  155.     }
    
  156. 
    
  157.     return {
    
  158.       // Maintain code segment path stack as we traverse.
    
  159.       onCodePathSegmentStart: segment => codePathSegmentStack.push(segment),
    
  160.       onCodePathSegmentEnd: () => codePathSegmentStack.pop(),
    
  161. 
    
  162.       // Maintain code path stack as we traverse.
    
  163.       onCodePathStart: () => codePathReactHooksMapStack.push(new Map()),
    
  164. 
    
  165.       // Process our code path.
    
  166.       //
    
  167.       // Everything is ok if all React Hooks are both reachable from the initial
    
  168.       // segment and reachable from every final segment.
    
  169.       onCodePathEnd(codePath, codePathNode) {
    
  170.         const reactHooksMap = codePathReactHooksMapStack.pop();
    
  171.         if (reactHooksMap.size === 0) {
    
  172.           return;
    
  173.         }
    
  174. 
    
  175.         // All of the segments which are cyclic are recorded in this set.
    
  176.         const cyclic = new Set();
    
  177. 
    
  178.         /**
    
  179.          * Count the number of code paths from the start of the function to this
    
  180.          * segment. For example:
    
  181.          *
    
  182.          * ```js
    
  183.          * function MyComponent() {
    
  184.          *   if (condition) {
    
  185.          *     // Segment 1
    
  186.          *   } else {
    
  187.          *     // Segment 2
    
  188.          *   }
    
  189.          *   // Segment 3
    
  190.          * }
    
  191.          * ```
    
  192.          *
    
  193.          * Segments 1 and 2 have one path to the beginning of `MyComponent` and
    
  194.          * segment 3 has two paths to the beginning of `MyComponent` since we
    
  195.          * could have either taken the path of segment 1 or segment 2.
    
  196.          *
    
  197.          * Populates `cyclic` with cyclic segments.
    
  198.          */
    
  199. 
    
  200.         function countPathsFromStart(segment, pathHistory) {
    
  201.           const {cache} = countPathsFromStart;
    
  202.           let paths = cache.get(segment.id);
    
  203.           const pathList = new Set(pathHistory);
    
  204. 
    
  205.           // If `pathList` includes the current segment then we've found a cycle!
    
  206.           // We need to fill `cyclic` with all segments inside cycle
    
  207.           if (pathList.has(segment.id)) {
    
  208.             const pathArray = [...pathList];
    
  209.             const cyclicSegments = pathArray.slice(
    
  210.               pathArray.indexOf(segment.id) + 1,
    
  211.             );
    
  212.             for (const cyclicSegment of cyclicSegments) {
    
  213.               cyclic.add(cyclicSegment);
    
  214.             }
    
  215. 
    
  216.             return BigInt('0');
    
  217.           }
    
  218. 
    
  219.           // add the current segment to pathList
    
  220.           pathList.add(segment.id);
    
  221. 
    
  222.           // We have a cached `paths`. Return it.
    
  223.           if (paths !== undefined) {
    
  224.             return paths;
    
  225.           }
    
  226. 
    
  227.           if (codePath.thrownSegments.includes(segment)) {
    
  228.             paths = BigInt('0');
    
  229.           } else if (segment.prevSegments.length === 0) {
    
  230.             paths = BigInt('1');
    
  231.           } else {
    
  232.             paths = BigInt('0');
    
  233.             for (const prevSegment of segment.prevSegments) {
    
  234.               paths += countPathsFromStart(prevSegment, pathList);
    
  235.             }
    
  236.           }
    
  237. 
    
  238.           // If our segment is reachable then there should be at least one path
    
  239.           // to it from the start of our code path.
    
  240.           if (segment.reachable && paths === BigInt('0')) {
    
  241.             cache.delete(segment.id);
    
  242.           } else {
    
  243.             cache.set(segment.id, paths);
    
  244.           }
    
  245. 
    
  246.           return paths;
    
  247.         }
    
  248. 
    
  249.         /**
    
  250.          * Count the number of code paths from this segment to the end of the
    
  251.          * function. For example:
    
  252.          *
    
  253.          * ```js
    
  254.          * function MyComponent() {
    
  255.          *   // Segment 1
    
  256.          *   if (condition) {
    
  257.          *     // Segment 2
    
  258.          *   } else {
    
  259.          *     // Segment 3
    
  260.          *   }
    
  261.          * }
    
  262.          * ```
    
  263.          *
    
  264.          * Segments 2 and 3 have one path to the end of `MyComponent` and
    
  265.          * segment 1 has two paths to the end of `MyComponent` since we could
    
  266.          * either take the path of segment 1 or segment 2.
    
  267.          *
    
  268.          * Populates `cyclic` with cyclic segments.
    
  269.          */
    
  270. 
    
  271.         function countPathsToEnd(segment, pathHistory) {
    
  272.           const {cache} = countPathsToEnd;
    
  273.           let paths = cache.get(segment.id);
    
  274.           const pathList = new Set(pathHistory);
    
  275. 
    
  276.           // If `pathList` includes the current segment then we've found a cycle!
    
  277.           // We need to fill `cyclic` with all segments inside cycle
    
  278.           if (pathList.has(segment.id)) {
    
  279.             const pathArray = Array.from(pathList);
    
  280.             const cyclicSegments = pathArray.slice(
    
  281.               pathArray.indexOf(segment.id) + 1,
    
  282.             );
    
  283.             for (const cyclicSegment of cyclicSegments) {
    
  284.               cyclic.add(cyclicSegment);
    
  285.             }
    
  286. 
    
  287.             return BigInt('0');
    
  288.           }
    
  289. 
    
  290.           // add the current segment to pathList
    
  291.           pathList.add(segment.id);
    
  292. 
    
  293.           // We have a cached `paths`. Return it.
    
  294.           if (paths !== undefined) {
    
  295.             return paths;
    
  296.           }
    
  297. 
    
  298.           if (codePath.thrownSegments.includes(segment)) {
    
  299.             paths = BigInt('0');
    
  300.           } else if (segment.nextSegments.length === 0) {
    
  301.             paths = BigInt('1');
    
  302.           } else {
    
  303.             paths = BigInt('0');
    
  304.             for (const nextSegment of segment.nextSegments) {
    
  305.               paths += countPathsToEnd(nextSegment, pathList);
    
  306.             }
    
  307.           }
    
  308. 
    
  309.           cache.set(segment.id, paths);
    
  310.           return paths;
    
  311.         }
    
  312. 
    
  313.         /**
    
  314.          * Gets the shortest path length to the start of a code path.
    
  315.          * For example:
    
  316.          *
    
  317.          * ```js
    
  318.          * function MyComponent() {
    
  319.          *   if (condition) {
    
  320.          *     // Segment 1
    
  321.          *   }
    
  322.          *   // Segment 2
    
  323.          * }
    
  324.          * ```
    
  325.          *
    
  326.          * There is only one path from segment 1 to the code path start. Its
    
  327.          * length is one so that is the shortest path.
    
  328.          *
    
  329.          * There are two paths from segment 2 to the code path start. One
    
  330.          * through segment 1 with a length of two and another directly to the
    
  331.          * start with a length of one. The shortest path has a length of one
    
  332.          * so we would return that.
    
  333.          */
    
  334. 
    
  335.         function shortestPathLengthToStart(segment) {
    
  336.           const {cache} = shortestPathLengthToStart;
    
  337.           let length = cache.get(segment.id);
    
  338. 
    
  339.           // If `length` is null then we found a cycle! Return infinity since
    
  340.           // the shortest path is definitely not the one where we looped.
    
  341.           if (length === null) {
    
  342.             return Infinity;
    
  343.           }
    
  344. 
    
  345.           // We have a cached `length`. Return it.
    
  346.           if (length !== undefined) {
    
  347.             return length;
    
  348.           }
    
  349. 
    
  350.           // Compute `length` and cache it. Guarding against cycles.
    
  351.           cache.set(segment.id, null);
    
  352.           if (segment.prevSegments.length === 0) {
    
  353.             length = 1;
    
  354.           } else {
    
  355.             length = Infinity;
    
  356.             for (const prevSegment of segment.prevSegments) {
    
  357.               const prevLength = shortestPathLengthToStart(prevSegment);
    
  358.               if (prevLength < length) {
    
  359.                 length = prevLength;
    
  360.               }
    
  361.             }
    
  362.             length += 1;
    
  363.           }
    
  364.           cache.set(segment.id, length);
    
  365.           return length;
    
  366.         }
    
  367. 
    
  368.         countPathsFromStart.cache = new Map();
    
  369.         countPathsToEnd.cache = new Map();
    
  370.         shortestPathLengthToStart.cache = new Map();
    
  371. 
    
  372.         // Count all code paths to the end of our component/hook. Also primes
    
  373.         // the `countPathsToEnd` cache.
    
  374.         const allPathsFromStartToEnd = countPathsToEnd(codePath.initialSegment);
    
  375. 
    
  376.         // Gets the function name for our code path. If the function name is
    
  377.         // `undefined` then we know either that we have an anonymous function
    
  378.         // expression or our code path is not in a function. In both cases we
    
  379.         // will want to error since neither are React function components or
    
  380.         // hook functions - unless it is an anonymous function argument to
    
  381.         // forwardRef or memo.
    
  382.         const codePathFunctionName = getFunctionName(codePathNode);
    
  383. 
    
  384.         // This is a valid code path for React hooks if we are directly in a React
    
  385.         // function component or we are in a hook function.
    
  386.         const isSomewhereInsideComponentOrHook =
    
  387.           isInsideComponentOrHook(codePathNode);
    
  388.         const isDirectlyInsideComponentOrHook = codePathFunctionName
    
  389.           ? isComponentName(codePathFunctionName) ||
    
  390.             isHook(codePathFunctionName)
    
  391.           : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode);
    
  392. 
    
  393.         // Compute the earliest finalizer level using information from the
    
  394.         // cache. We expect all reachable final segments to have a cache entry
    
  395.         // after calling `visitSegment()`.
    
  396.         let shortestFinalPathLength = Infinity;
    
  397.         for (const finalSegment of codePath.finalSegments) {
    
  398.           if (!finalSegment.reachable) {
    
  399.             continue;
    
  400.           }
    
  401.           const length = shortestPathLengthToStart(finalSegment);
    
  402.           if (length < shortestFinalPathLength) {
    
  403.             shortestFinalPathLength = length;
    
  404.           }
    
  405.         }
    
  406. 
    
  407.         // Make sure all React Hooks pass our lint invariants. Log warnings
    
  408.         // if not.
    
  409.         for (const [segment, reactHooks] of reactHooksMap) {
    
  410.           // NOTE: We could report here that the hook is not reachable, but
    
  411.           // that would be redundant with more general "no unreachable"
    
  412.           // lint rules.
    
  413.           if (!segment.reachable) {
    
  414.             continue;
    
  415.           }
    
  416. 
    
  417.           // If there are any final segments with a shorter path to start then
    
  418.           // we possibly have an early return.
    
  419.           //
    
  420.           // If our segment is a final segment itself then siblings could
    
  421.           // possibly be early returns.
    
  422.           const possiblyHasEarlyReturn =
    
  423.             segment.nextSegments.length === 0
    
  424.               ? shortestFinalPathLength <= shortestPathLengthToStart(segment)
    
  425.               : shortestFinalPathLength < shortestPathLengthToStart(segment);
    
  426. 
    
  427.           // Count all the paths from the start of our code path to the end of
    
  428.           // our code path that go _through_ this segment. The critical piece
    
  429.           // of this is _through_. If we just call `countPathsToEnd(segment)`
    
  430.           // then we neglect that we may have gone through multiple paths to get
    
  431.           // to this point! Consider:
    
  432.           //
    
  433.           // ```js
    
  434.           // function MyComponent() {
    
  435.           //   if (a) {
    
  436.           //     // Segment 1
    
  437.           //   } else {
    
  438.           //     // Segment 2
    
  439.           //   }
    
  440.           //   // Segment 3
    
  441.           //   if (b) {
    
  442.           //     // Segment 4
    
  443.           //   } else {
    
  444.           //     // Segment 5
    
  445.           //   }
    
  446.           // }
    
  447.           // ```
    
  448.           //
    
  449.           // In this component we have four code paths:
    
  450.           //
    
  451.           // 1. `a = true; b = true`
    
  452.           // 2. `a = true; b = false`
    
  453.           // 3. `a = false; b = true`
    
  454.           // 4. `a = false; b = false`
    
  455.           //
    
  456.           // From segment 3 there are two code paths to the end through segment
    
  457.           // 4 and segment 5. However, we took two paths to get here through
    
  458.           // segment 1 and segment 2.
    
  459.           //
    
  460.           // If we multiply the paths from start (two) by the paths to end (two)
    
  461.           // for segment 3 we get four. Which is our desired count.
    
  462.           const pathsFromStartToEnd =
    
  463.             countPathsFromStart(segment) * countPathsToEnd(segment);
    
  464. 
    
  465.           // Is this hook a part of a cyclic segment?
    
  466.           const cycled = cyclic.has(segment.id);
    
  467. 
    
  468.           for (const hook of reactHooks) {
    
  469.             // Report an error if a hook may be called more then once.
    
  470.             // `use(...)` can be called in loops.
    
  471.             if (cycled && !isUseIdentifier(hook)) {
    
  472.               context.report({
    
  473.                 node: hook,
    
  474.                 message:
    
  475.                   `React Hook "${context.getSource(hook)}" may be executed ` +
    
  476.                   'more than once. Possibly because it is called in a loop. ' +
    
  477.                   'React Hooks must be called in the exact same order in ' +
    
  478.                   'every component render.',
    
  479.               });
    
  480.             }
    
  481. 
    
  482.             // If this is not a valid code path for React hooks then we need to
    
  483.             // log a warning for every hook in this code path.
    
  484.             //
    
  485.             // Pick a special message depending on the scope this hook was
    
  486.             // called in.
    
  487.             if (isDirectlyInsideComponentOrHook) {
    
  488.               // Report an error if the hook is called inside an async function.
    
  489.               const isAsyncFunction = codePathNode.async;
    
  490.               if (isAsyncFunction) {
    
  491.                 context.report({
    
  492.                   node: hook,
    
  493.                   message:
    
  494.                     `React Hook "${context.getSource(hook)}" cannot be ` +
    
  495.                     'called in an async function.',
    
  496.                 });
    
  497.               }
    
  498. 
    
  499.               // Report an error if a hook does not reach all finalizing code
    
  500.               // path segments.
    
  501.               //
    
  502.               // Special case when we think there might be an early return.
    
  503.               if (
    
  504.                 !cycled &&
    
  505.                 pathsFromStartToEnd !== allPathsFromStartToEnd &&
    
  506.                 !isUseIdentifier(hook) // `use(...)` can be called conditionally.
    
  507.               ) {
    
  508.                 const message =
    
  509.                   `React Hook "${context.getSource(hook)}" is called ` +
    
  510.                   'conditionally. React Hooks must be called in the exact ' +
    
  511.                   'same order in every component render.' +
    
  512.                   (possiblyHasEarlyReturn
    
  513.                     ? ' Did you accidentally call a React Hook after an' +
    
  514.                       ' early return?'
    
  515.                     : '');
    
  516.                 context.report({node: hook, message});
    
  517.               }
    
  518.             } else if (
    
  519.               codePathNode.parent &&
    
  520.               (codePathNode.parent.type === 'MethodDefinition' ||
    
  521.                 codePathNode.parent.type === 'ClassProperty') &&
    
  522.               codePathNode.parent.value === codePathNode
    
  523.             ) {
    
  524.               // Custom message for hooks inside a class
    
  525.               const message =
    
  526.                 `React Hook "${context.getSource(hook)}" cannot be called ` +
    
  527.                 'in a class component. React Hooks must be called in a ' +
    
  528.                 'React function component or a custom React Hook function.';
    
  529.               context.report({node: hook, message});
    
  530.             } else if (codePathFunctionName) {
    
  531.               // Custom message if we found an invalid function name.
    
  532.               const message =
    
  533.                 `React Hook "${context.getSource(hook)}" is called in ` +
    
  534.                 `function "${context.getSource(codePathFunctionName)}" ` +
    
  535.                 'that is neither a React function component nor a custom ' +
    
  536.                 'React Hook function.' +
    
  537.                 ' React component names must start with an uppercase letter.' +
    
  538.                 ' React Hook names must start with the word "use".';
    
  539.               context.report({node: hook, message});
    
  540.             } else if (codePathNode.type === 'Program') {
    
  541.               // These are dangerous if you have inline requires enabled.
    
  542.               const message =
    
  543.                 `React Hook "${context.getSource(hook)}" cannot be called ` +
    
  544.                 'at the top level. React Hooks must be called in a ' +
    
  545.                 'React function component or a custom React Hook function.';
    
  546.               context.report({node: hook, message});
    
  547.             } else {
    
  548.               // Assume in all other cases the user called a hook in some
    
  549.               // random function callback. This should usually be true for
    
  550.               // anonymous function expressions. Hopefully this is clarifying
    
  551.               // enough in the common case that the incorrect message in
    
  552.               // uncommon cases doesn't matter.
    
  553.               // `use(...)` can be called in callbacks.
    
  554.               if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
    
  555.                 const message =
    
  556.                   `React Hook "${context.getSource(hook)}" cannot be called ` +
    
  557.                   'inside a callback. React Hooks must be called in a ' +
    
  558.                   'React function component or a custom React Hook function.';
    
  559.                 context.report({node: hook, message});
    
  560.               }
    
  561.             }
    
  562.           }
    
  563.         }
    
  564.       },
    
  565. 
    
  566.       // Missed opportunity...We could visit all `Identifier`s instead of all
    
  567.       // `CallExpression`s and check that _every use_ of a hook name is valid.
    
  568.       // But that gets complicated and enters type-system territory, so we're
    
  569.       // only being strict about hook calls for now.
    
  570.       CallExpression(node) {
    
  571.         if (isHook(node.callee)) {
    
  572.           // Add the hook node to a map keyed by the code path segment. We will
    
  573.           // do full code path analysis at the end of our code path.
    
  574.           const reactHooksMap = last(codePathReactHooksMapStack);
    
  575.           const codePathSegment = last(codePathSegmentStack);
    
  576.           let reactHooks = reactHooksMap.get(codePathSegment);
    
  577.           if (!reactHooks) {
    
  578.             reactHooks = [];
    
  579.             reactHooksMap.set(codePathSegment, reactHooks);
    
  580.           }
    
  581.           reactHooks.push(node.callee);
    
  582.         }
    
  583. 
    
  584.         // useEffectEvent: useEffectEvent functions can be passed by reference within useEffect as well as in
    
  585.         // another useEffectEvent
    
  586.         if (
    
  587.           node.callee.type === 'Identifier' &&
    
  588.           (node.callee.name === 'useEffect' ||
    
  589.             isUseEffectEventIdentifier(node.callee)) &&
    
  590.           node.arguments.length > 0
    
  591.         ) {
    
  592.           // Denote that we have traversed into a useEffect call, and stash the CallExpr for
    
  593.           // comparison later when we exit
    
  594.           lastEffect = node;
    
  595.         }
    
  596.       },
    
  597. 
    
  598.       Identifier(node) {
    
  599.         // This identifier resolves to a useEffectEvent function, but isn't being referenced in an
    
  600.         // effect or another event function. It isn't being called either.
    
  601.         if (
    
  602.           lastEffect == null &&
    
  603.           useEffectEventFunctions.has(node) &&
    
  604.           node.parent.type !== 'CallExpression'
    
  605.         ) {
    
  606.           context.report({
    
  607.             node,
    
  608.             message:
    
  609.               `\`${context.getSource(
    
  610.                 node,
    
  611.               )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
    
  612.               'the same component. They cannot be assigned to variables or passed down.',
    
  613.           });
    
  614.         }
    
  615.       },
    
  616. 
    
  617.       'CallExpression:exit'(node) {
    
  618.         if (node === lastEffect) {
    
  619.           lastEffect = null;
    
  620.         }
    
  621.       },
    
  622. 
    
  623.       FunctionDeclaration(node) {
    
  624.         // function MyComponent() { const onClick = useEffectEvent(...) }
    
  625.         if (isInsideComponentOrHook(node)) {
    
  626.           recordAllUseEffectEventFunctions(context.getScope());
    
  627.         }
    
  628.       },
    
  629. 
    
  630.       ArrowFunctionExpression(node) {
    
  631.         // const MyComponent = () => { const onClick = useEffectEvent(...) }
    
  632.         if (isInsideComponentOrHook(node)) {
    
  633.           recordAllUseEffectEventFunctions(context.getScope());
    
  634.         }
    
  635.       },
    
  636.     };
    
  637.   },
    
  638. };
    
  639. 
    
  640. /**
    
  641.  * Gets the static name of a function AST node. For function declarations it is
    
  642.  * easy. For anonymous function expressions it is much harder. If you search for
    
  643.  * `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
    
  644.  * where JS gives anonymous function expressions names. We roughly detect the
    
  645.  * same AST nodes with some exceptions to better fit our use case.
    
  646.  */
    
  647. 
    
  648. function getFunctionName(node) {
    
  649.   if (
    
  650.     node.type === 'FunctionDeclaration' ||
    
  651.     (node.type === 'FunctionExpression' && node.id)
    
  652.   ) {
    
  653.     // function useHook() {}
    
  654.     // const whatever = function useHook() {};
    
  655.     //
    
  656.     // Function declaration or function expression names win over any
    
  657.     // assignment statements or other renames.
    
  658.     return node.id;
    
  659.   } else if (
    
  660.     node.type === 'FunctionExpression' ||
    
  661.     node.type === 'ArrowFunctionExpression'
    
  662.   ) {
    
  663.     if (
    
  664.       node.parent.type === 'VariableDeclarator' &&
    
  665.       node.parent.init === node
    
  666.     ) {
    
  667.       // const useHook = () => {};
    
  668.       return node.parent.id;
    
  669.     } else if (
    
  670.       node.parent.type === 'AssignmentExpression' &&
    
  671.       node.parent.right === node &&
    
  672.       node.parent.operator === '='
    
  673.     ) {
    
  674.       // useHook = () => {};
    
  675.       return node.parent.left;
    
  676.     } else if (
    
  677.       node.parent.type === 'Property' &&
    
  678.       node.parent.value === node &&
    
  679.       !node.parent.computed
    
  680.     ) {
    
  681.       // {useHook: () => {}}
    
  682.       // {useHook() {}}
    
  683.       return node.parent.key;
    
  684. 
    
  685.       // NOTE: We could also support `ClassProperty` and `MethodDefinition`
    
  686.       // here to be pedantic. However, hooks in a class are an anti-pattern. So
    
  687.       // we don't allow it to error early.
    
  688.       //
    
  689.       // class {useHook = () => {}}
    
  690.       // class {useHook() {}}
    
  691.     } else if (
    
  692.       node.parent.type === 'AssignmentPattern' &&
    
  693.       node.parent.right === node &&
    
  694.       !node.parent.computed
    
  695.     ) {
    
  696.       // const {useHook = () => {}} = {};
    
  697.       // ({useHook = () => {}} = {});
    
  698.       //
    
  699.       // Kinda clowny, but we'd said we'd follow spec convention for
    
  700.       // `IsAnonymousFunctionDefinition()` usage.
    
  701.       return node.parent.left;
    
  702.     } else {
    
  703.       return undefined;
    
  704.     }
    
  705.   } else {
    
  706.     return undefined;
    
  707.   }
    
  708. }
    
  709. 
    
  710. /**
    
  711.  * Convenience function for peeking the last item in a stack.
    
  712.  */
    
  713. 
    
  714. function last(array) {
    
  715.   return array[array.length - 1];
    
  716. }