github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/node_modules/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js (about)

     1  /**
     2   * @license React
     3   * eslint-plugin-react-hooks.development.js
     4   *
     5   * Copyright (c) Facebook, Inc. and its affiliates.
     6   *
     7   * This source code is licensed under the MIT license found in the
     8   * LICENSE file in the root directory of this source tree.
     9   */
    10  
    11  'use strict';
    12  
    13  if (process.env.NODE_ENV !== "production") {
    14    (function() {
    15  'use strict';
    16  
    17  function _unsupportedIterableToArray(o, minLen) {
    18    if (!o) return;
    19    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    20    var n = Object.prototype.toString.call(o).slice(8, -1);
    21    if (n === "Object" && o.constructor) n = o.constructor.name;
    22    if (n === "Map" || n === "Set") return Array.from(o);
    23    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
    24  }
    25  
    26  function _arrayLikeToArray(arr, len) {
    27    if (len == null || len > arr.length) len = arr.length;
    28  
    29    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
    30  
    31    return arr2;
    32  }
    33  
    34  function _createForOfIteratorHelper(o, allowArrayLike) {
    35    var it;
    36  
    37    if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
    38      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
    39        if (it) o = it;
    40        var i = 0;
    41  
    42        var F = function () {};
    43  
    44        return {
    45          s: F,
    46          n: function () {
    47            if (i >= o.length) return {
    48              done: true
    49            };
    50            return {
    51              done: false,
    52              value: o[i++]
    53            };
    54          },
    55          e: function (e) {
    56            throw e;
    57          },
    58          f: F
    59        };
    60      }
    61  
    62      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    63    }
    64  
    65    var normalCompletion = true,
    66        didErr = false,
    67        err;
    68    return {
    69      s: function () {
    70        it = o[Symbol.iterator]();
    71      },
    72      n: function () {
    73        var step = it.next();
    74        normalCompletion = step.done;
    75        return step;
    76      },
    77      e: function (e) {
    78        didErr = true;
    79        err = e;
    80      },
    81      f: function () {
    82        try {
    83          if (!normalCompletion && it.return != null) it.return();
    84        } finally {
    85          if (didErr) throw err;
    86        }
    87      }
    88    };
    89  }
    90  
    91  /* global BigInt */
    92  
    93  function isHookName(s) {
    94    return /^use[A-Z0-9].*$/.test(s);
    95  }
    96  /**
    97   * We consider hooks to be a hook name identifier or a member expression
    98   * containing a hook name.
    99   */
   100  
   101  
   102  function isHook(node) {
   103    if (node.type === 'Identifier') {
   104      return isHookName(node.name);
   105    } else if (node.type === 'MemberExpression' && !node.computed && isHook(node.property)) {
   106      var obj = node.object;
   107      var isPascalCaseNameSpace = /^[A-Z].*/;
   108      return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
   109    } else {
   110      return false;
   111    }
   112  }
   113  /**
   114   * Checks if the node is a React component name. React component names must
   115   * always start with a non-lowercase letter. So `MyComponent` or `_MyComponent`
   116   * are valid component names for instance.
   117   */
   118  
   119  
   120  function isComponentName(node) {
   121    if (node.type === 'Identifier') {
   122      return !/^[a-z]/.test(node.name);
   123    } else {
   124      return false;
   125    }
   126  }
   127  
   128  function isReactFunction(node, functionName) {
   129    return node.name === functionName || node.type === 'MemberExpression' && node.object.name === 'React' && node.property.name === functionName;
   130  }
   131  /**
   132   * Checks if the node is a callback argument of forwardRef. This render function
   133   * should follow the rules of hooks.
   134   */
   135  
   136  
   137  function isForwardRefCallback(node) {
   138    return !!(node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'forwardRef'));
   139  }
   140  /**
   141   * Checks if the node is a callback argument of React.memo. This anonymous
   142   * functional component should follow the rules of hooks.
   143   */
   144  
   145  
   146  function isMemoCallback(node) {
   147    return !!(node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'memo'));
   148  }
   149  
   150  function isInsideComponentOrHook(node) {
   151    while (node) {
   152      var functionName = getFunctionName(node);
   153  
   154      if (functionName) {
   155        if (isComponentName(functionName) || isHook(functionName)) {
   156          return true;
   157        }
   158      }
   159  
   160      if (isForwardRefCallback(node) || isMemoCallback(node)) {
   161        return true;
   162      }
   163  
   164      node = node.parent;
   165    }
   166  
   167    return false;
   168  }
   169  
   170  var RulesOfHooks = {
   171    meta: {
   172      type: 'problem',
   173      docs: {
   174        description: 'enforces the Rules of Hooks',
   175        recommended: true,
   176        url: 'https://reactjs.org/docs/hooks-rules.html'
   177      }
   178    },
   179    create: function (context) {
   180      var codePathReactHooksMapStack = [];
   181      var codePathSegmentStack = [];
   182      return {
   183        // Maintain code segment path stack as we traverse.
   184        onCodePathSegmentStart: function (segment) {
   185          return codePathSegmentStack.push(segment);
   186        },
   187        onCodePathSegmentEnd: function () {
   188          return codePathSegmentStack.pop();
   189        },
   190        // Maintain code path stack as we traverse.
   191        onCodePathStart: function () {
   192          return codePathReactHooksMapStack.push(new Map());
   193        },
   194        // Process our code path.
   195        //
   196        // Everything is ok if all React Hooks are both reachable from the initial
   197        // segment and reachable from every final segment.
   198        onCodePathEnd: function (codePath, codePathNode) {
   199          var reactHooksMap = codePathReactHooksMapStack.pop();
   200  
   201          if (reactHooksMap.size === 0) {
   202            return;
   203          } // All of the segments which are cyclic are recorded in this set.
   204  
   205  
   206          var cyclic = new Set();
   207          /**
   208           * Count the number of code paths from the start of the function to this
   209           * segment. For example:
   210           *
   211           * ```js
   212           * function MyComponent() {
   213           *   if (condition) {
   214           *     // Segment 1
   215           *   } else {
   216           *     // Segment 2
   217           *   }
   218           *   // Segment 3
   219           * }
   220           * ```
   221           *
   222           * Segments 1 and 2 have one path to the beginning of `MyComponent` and
   223           * segment 3 has two paths to the beginning of `MyComponent` since we
   224           * could have either taken the path of segment 1 or segment 2.
   225           *
   226           * Populates `cyclic` with cyclic segments.
   227           */
   228  
   229          function countPathsFromStart(segment, pathHistory) {
   230            var cache = countPathsFromStart.cache;
   231            var paths = cache.get(segment.id);
   232            var pathList = new Set(pathHistory); // If `pathList` includes the current segment then we've found a cycle!
   233            // We need to fill `cyclic` with all segments inside cycle
   234  
   235            if (pathList.has(segment.id)) {
   236              var pathArray = [].concat(pathList);
   237              var cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
   238  
   239              var _iterator = _createForOfIteratorHelper(cyclicSegments),
   240                  _step;
   241  
   242              try {
   243                for (_iterator.s(); !(_step = _iterator.n()).done;) {
   244                  var cyclicSegment = _step.value;
   245                  cyclic.add(cyclicSegment);
   246                }
   247              } catch (err) {
   248                _iterator.e(err);
   249              } finally {
   250                _iterator.f();
   251              }
   252  
   253              return BigInt('0');
   254            } // add the current segment to pathList
   255  
   256  
   257            pathList.add(segment.id); // We have a cached `paths`. Return it.
   258  
   259            if (paths !== undefined) {
   260              return paths;
   261            }
   262  
   263            if (codePath.thrownSegments.includes(segment)) {
   264              paths = BigInt('0');
   265            } else if (segment.prevSegments.length === 0) {
   266              paths = BigInt('1');
   267            } else {
   268              paths = BigInt('0');
   269  
   270              var _iterator2 = _createForOfIteratorHelper(segment.prevSegments),
   271                  _step2;
   272  
   273              try {
   274                for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
   275                  var prevSegment = _step2.value;
   276                  paths += countPathsFromStart(prevSegment, pathList);
   277                }
   278              } catch (err) {
   279                _iterator2.e(err);
   280              } finally {
   281                _iterator2.f();
   282              }
   283            } // If our segment is reachable then there should be at least one path
   284            // to it from the start of our code path.
   285  
   286  
   287            if (segment.reachable && paths === BigInt('0')) {
   288              cache.delete(segment.id);
   289            } else {
   290              cache.set(segment.id, paths);
   291            }
   292  
   293            return paths;
   294          }
   295          /**
   296           * Count the number of code paths from this segment to the end of the
   297           * function. For example:
   298           *
   299           * ```js
   300           * function MyComponent() {
   301           *   // Segment 1
   302           *   if (condition) {
   303           *     // Segment 2
   304           *   } else {
   305           *     // Segment 3
   306           *   }
   307           * }
   308           * ```
   309           *
   310           * Segments 2 and 3 have one path to the end of `MyComponent` and
   311           * segment 1 has two paths to the end of `MyComponent` since we could
   312           * either take the path of segment 1 or segment 2.
   313           *
   314           * Populates `cyclic` with cyclic segments.
   315           */
   316  
   317  
   318          function countPathsToEnd(segment, pathHistory) {
   319            var cache = countPathsToEnd.cache;
   320            var paths = cache.get(segment.id);
   321            var pathList = new Set(pathHistory); // If `pathList` includes the current segment then we've found a cycle!
   322            // We need to fill `cyclic` with all segments inside cycle
   323  
   324            if (pathList.has(segment.id)) {
   325              var pathArray = Array.from(pathList);
   326              var cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
   327  
   328              var _iterator3 = _createForOfIteratorHelper(cyclicSegments),
   329                  _step3;
   330  
   331              try {
   332                for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
   333                  var cyclicSegment = _step3.value;
   334                  cyclic.add(cyclicSegment);
   335                }
   336              } catch (err) {
   337                _iterator3.e(err);
   338              } finally {
   339                _iterator3.f();
   340              }
   341  
   342              return BigInt('0');
   343            } // add the current segment to pathList
   344  
   345  
   346            pathList.add(segment.id); // We have a cached `paths`. Return it.
   347  
   348            if (paths !== undefined) {
   349              return paths;
   350            }
   351  
   352            if (codePath.thrownSegments.includes(segment)) {
   353              paths = BigInt('0');
   354            } else if (segment.nextSegments.length === 0) {
   355              paths = BigInt('1');
   356            } else {
   357              paths = BigInt('0');
   358  
   359              var _iterator4 = _createForOfIteratorHelper(segment.nextSegments),
   360                  _step4;
   361  
   362              try {
   363                for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
   364                  var nextSegment = _step4.value;
   365                  paths += countPathsToEnd(nextSegment, pathList);
   366                }
   367              } catch (err) {
   368                _iterator4.e(err);
   369              } finally {
   370                _iterator4.f();
   371              }
   372            }
   373  
   374            cache.set(segment.id, paths);
   375            return paths;
   376          }
   377          /**
   378           * Gets the shortest path length to the start of a code path.
   379           * For example:
   380           *
   381           * ```js
   382           * function MyComponent() {
   383           *   if (condition) {
   384           *     // Segment 1
   385           *   }
   386           *   // Segment 2
   387           * }
   388           * ```
   389           *
   390           * There is only one path from segment 1 to the code path start. Its
   391           * length is one so that is the shortest path.
   392           *
   393           * There are two paths from segment 2 to the code path start. One
   394           * through segment 1 with a length of two and another directly to the
   395           * start with a length of one. The shortest path has a length of one
   396           * so we would return that.
   397           */
   398  
   399  
   400          function shortestPathLengthToStart(segment) {
   401            var cache = shortestPathLengthToStart.cache;
   402            var length = cache.get(segment.id); // If `length` is null then we found a cycle! Return infinity since
   403            // the shortest path is definitely not the one where we looped.
   404  
   405            if (length === null) {
   406              return Infinity;
   407            } // We have a cached `length`. Return it.
   408  
   409  
   410            if (length !== undefined) {
   411              return length;
   412            } // Compute `length` and cache it. Guarding against cycles.
   413  
   414  
   415            cache.set(segment.id, null);
   416  
   417            if (segment.prevSegments.length === 0) {
   418              length = 1;
   419            } else {
   420              length = Infinity;
   421  
   422              var _iterator5 = _createForOfIteratorHelper(segment.prevSegments),
   423                  _step5;
   424  
   425              try {
   426                for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
   427                  var prevSegment = _step5.value;
   428                  var prevLength = shortestPathLengthToStart(prevSegment);
   429  
   430                  if (prevLength < length) {
   431                    length = prevLength;
   432                  }
   433                }
   434              } catch (err) {
   435                _iterator5.e(err);
   436              } finally {
   437                _iterator5.f();
   438              }
   439  
   440              length += 1;
   441            }
   442  
   443            cache.set(segment.id, length);
   444            return length;
   445          }
   446  
   447          countPathsFromStart.cache = new Map();
   448          countPathsToEnd.cache = new Map();
   449          shortestPathLengthToStart.cache = new Map(); // Count all code paths to the end of our component/hook. Also primes
   450          // the `countPathsToEnd` cache.
   451  
   452          var allPathsFromStartToEnd = countPathsToEnd(codePath.initialSegment); // Gets the function name for our code path. If the function name is
   453          // `undefined` then we know either that we have an anonymous function
   454          // expression or our code path is not in a function. In both cases we
   455          // will want to error since neither are React function components or
   456          // hook functions - unless it is an anonymous function argument to
   457          // forwardRef or memo.
   458  
   459          var codePathFunctionName = getFunctionName(codePathNode); // This is a valid code path for React hooks if we are directly in a React
   460          // function component or we are in a hook function.
   461  
   462          var isSomewhereInsideComponentOrHook = isInsideComponentOrHook(codePathNode);
   463          var isDirectlyInsideComponentOrHook = codePathFunctionName ? isComponentName(codePathFunctionName) || isHook(codePathFunctionName) : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode); // Compute the earliest finalizer level using information from the
   464          // cache. We expect all reachable final segments to have a cache entry
   465          // after calling `visitSegment()`.
   466  
   467          var shortestFinalPathLength = Infinity;
   468  
   469          var _iterator6 = _createForOfIteratorHelper(codePath.finalSegments),
   470              _step6;
   471  
   472          try {
   473            for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
   474              var finalSegment = _step6.value;
   475  
   476              if (!finalSegment.reachable) {
   477                continue;
   478              }
   479  
   480              var length = shortestPathLengthToStart(finalSegment);
   481  
   482              if (length < shortestFinalPathLength) {
   483                shortestFinalPathLength = length;
   484              }
   485            } // Make sure all React Hooks pass our lint invariants. Log warnings
   486            // if not.
   487  
   488          } catch (err) {
   489            _iterator6.e(err);
   490          } finally {
   491            _iterator6.f();
   492          }
   493  
   494          var _iterator7 = _createForOfIteratorHelper(reactHooksMap),
   495              _step7;
   496  
   497          try {
   498            for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
   499              var _step7$value = _step7.value,
   500                  segment = _step7$value[0],
   501                  reactHooks = _step7$value[1];
   502  
   503              // NOTE: We could report here that the hook is not reachable, but
   504              // that would be redundant with more general "no unreachable"
   505              // lint rules.
   506              if (!segment.reachable) {
   507                continue;
   508              } // If there are any final segments with a shorter path to start then
   509              // we possibly have an early return.
   510              //
   511              // If our segment is a final segment itself then siblings could
   512              // possibly be early returns.
   513  
   514  
   515              var possiblyHasEarlyReturn = segment.nextSegments.length === 0 ? shortestFinalPathLength <= shortestPathLengthToStart(segment) : shortestFinalPathLength < shortestPathLengthToStart(segment); // Count all the paths from the start of our code path to the end of
   516              // our code path that go _through_ this segment. The critical piece
   517              // of this is _through_. If we just call `countPathsToEnd(segment)`
   518              // then we neglect that we may have gone through multiple paths to get
   519              // to this point! Consider:
   520              //
   521              // ```js
   522              // function MyComponent() {
   523              //   if (a) {
   524              //     // Segment 1
   525              //   } else {
   526              //     // Segment 2
   527              //   }
   528              //   // Segment 3
   529              //   if (b) {
   530              //     // Segment 4
   531              //   } else {
   532              //     // Segment 5
   533              //   }
   534              // }
   535              // ```
   536              //
   537              // In this component we have four code paths:
   538              //
   539              // 1. `a = true; b = true`
   540              // 2. `a = true; b = false`
   541              // 3. `a = false; b = true`
   542              // 4. `a = false; b = false`
   543              //
   544              // From segment 3 there are two code paths to the end through segment
   545              // 4 and segment 5. However, we took two paths to get here through
   546              // segment 1 and segment 2.
   547              //
   548              // If we multiply the paths from start (two) by the paths to end (two)
   549              // for segment 3 we get four. Which is our desired count.
   550  
   551              var pathsFromStartToEnd = countPathsFromStart(segment) * countPathsToEnd(segment); // Is this hook a part of a cyclic segment?
   552  
   553              var cycled = cyclic.has(segment.id);
   554  
   555              var _iterator8 = _createForOfIteratorHelper(reactHooks),
   556                  _step8;
   557  
   558              try {
   559                for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
   560                  var hook = _step8.value;
   561  
   562                  // Report an error if a hook may be called more then once.
   563                  if (cycled) {
   564                    context.report({
   565                      node: hook,
   566                      message: "React Hook \"" + context.getSource(hook) + "\" may be executed " + 'more than once. Possibly because it is called in a loop. ' + 'React Hooks must be called in the exact same order in ' + 'every component render.'
   567                    });
   568                  } // If this is not a valid code path for React hooks then we need to
   569                  // log a warning for every hook in this code path.
   570                  //
   571                  // Pick a special message depending on the scope this hook was
   572                  // called in.
   573  
   574  
   575                  if (isDirectlyInsideComponentOrHook) {
   576                    // Report an error if a hook does not reach all finalizing code
   577                    // path segments.
   578                    //
   579                    // Special case when we think there might be an early return.
   580                    if (!cycled && pathsFromStartToEnd !== allPathsFromStartToEnd) {
   581                      var message = "React Hook \"" + context.getSource(hook) + "\" is called " + 'conditionally. React Hooks must be called in the exact ' + 'same order in every component render.' + (possiblyHasEarlyReturn ? ' Did you accidentally call a React Hook after an' + ' early return?' : '');
   582                      context.report({
   583                        node: hook,
   584                        message: message
   585                      });
   586                    }
   587                  } else if (codePathNode.parent && (codePathNode.parent.type === 'MethodDefinition' || codePathNode.parent.type === 'ClassProperty') && codePathNode.parent.value === codePathNode) {
   588                    // Custom message for hooks inside a class
   589                    var _message = "React Hook \"" + context.getSource(hook) + "\" cannot be called " + 'in a class component. React Hooks must be called in a ' + 'React function component or a custom React Hook function.';
   590  
   591                    context.report({
   592                      node: hook,
   593                      message: _message
   594                    });
   595                  } else if (codePathFunctionName) {
   596                    // Custom message if we found an invalid function name.
   597                    var _message2 = "React Hook \"" + context.getSource(hook) + "\" is called in " + ("function \"" + context.getSource(codePathFunctionName) + "\" ") + 'that is neither a React function component nor a custom ' + 'React Hook function.' + ' React component names must start with an uppercase letter.' + ' React Hook names must start with the word "use".';
   598  
   599                    context.report({
   600                      node: hook,
   601                      message: _message2
   602                    });
   603                  } else if (codePathNode.type === 'Program') {
   604                    // These are dangerous if you have inline requires enabled.
   605                    var _message3 = "React Hook \"" + context.getSource(hook) + "\" cannot be called " + 'at the top level. React Hooks must be called in a ' + 'React function component or a custom React Hook function.';
   606  
   607                    context.report({
   608                      node: hook,
   609                      message: _message3
   610                    });
   611                  } else {
   612                    // Assume in all other cases the user called a hook in some
   613                    // random function callback. This should usually be true for
   614                    // anonymous function expressions. Hopefully this is clarifying
   615                    // enough in the common case that the incorrect message in
   616                    // uncommon cases doesn't matter.
   617                    if (isSomewhereInsideComponentOrHook) {
   618                      var _message4 = "React Hook \"" + context.getSource(hook) + "\" cannot be called " + 'inside a callback. React Hooks must be called in a ' + 'React function component or a custom React Hook function.';
   619  
   620                      context.report({
   621                        node: hook,
   622                        message: _message4
   623                      });
   624                    }
   625                  }
   626                }
   627              } catch (err) {
   628                _iterator8.e(err);
   629              } finally {
   630                _iterator8.f();
   631              }
   632            }
   633          } catch (err) {
   634            _iterator7.e(err);
   635          } finally {
   636            _iterator7.f();
   637          }
   638        },
   639        // Missed opportunity...We could visit all `Identifier`s instead of all
   640        // `CallExpression`s and check that _every use_ of a hook name is valid.
   641        // But that gets complicated and enters type-system territory, so we're
   642        // only being strict about hook calls for now.
   643        CallExpression: function (node) {
   644          if (isHook(node.callee)) {
   645            // Add the hook node to a map keyed by the code path segment. We will
   646            // do full code path analysis at the end of our code path.
   647            var reactHooksMap = last(codePathReactHooksMapStack);
   648            var codePathSegment = last(codePathSegmentStack);
   649            var reactHooks = reactHooksMap.get(codePathSegment);
   650  
   651            if (!reactHooks) {
   652              reactHooks = [];
   653              reactHooksMap.set(codePathSegment, reactHooks);
   654            }
   655  
   656            reactHooks.push(node.callee);
   657          }
   658        }
   659      };
   660    }
   661  };
   662  /**
   663   * Gets the static name of a function AST node. For function declarations it is
   664   * easy. For anonymous function expressions it is much harder. If you search for
   665   * `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
   666   * where JS gives anonymous function expressions names. We roughly detect the
   667   * same AST nodes with some exceptions to better fit our use case.
   668   */
   669  
   670  function getFunctionName(node) {
   671    if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' && node.id) {
   672      // function useHook() {}
   673      // const whatever = function useHook() {};
   674      //
   675      // Function declaration or function expression names win over any
   676      // assignment statements or other renames.
   677      return node.id;
   678    } else if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
   679      if (node.parent.type === 'VariableDeclarator' && node.parent.init === node) {
   680        // const useHook = () => {};
   681        return node.parent.id;
   682      } else if (node.parent.type === 'AssignmentExpression' && node.parent.right === node && node.parent.operator === '=') {
   683        // useHook = () => {};
   684        return node.parent.left;
   685      } else if (node.parent.type === 'Property' && node.parent.value === node && !node.parent.computed) {
   686        // {useHook: () => {}}
   687        // {useHook() {}}
   688        return node.parent.key; // NOTE: We could also support `ClassProperty` and `MethodDefinition`
   689        // here to be pedantic. However, hooks in a class are an anti-pattern. So
   690        // we don't allow it to error early.
   691        //
   692        // class {useHook = () => {}}
   693        // class {useHook() {}}
   694      } else if (node.parent.type === 'AssignmentPattern' && node.parent.right === node && !node.parent.computed) {
   695        // const {useHook = () => {}} = {};
   696        // ({useHook = () => {}} = {});
   697        //
   698        // Kinda clowny, but we'd said we'd follow spec convention for
   699        // `IsAnonymousFunctionDefinition()` usage.
   700        return node.parent.left;
   701      } else {
   702        return undefined;
   703      }
   704    } else {
   705      return undefined;
   706    }
   707  }
   708  /**
   709   * Convenience function for peeking the last item in a stack.
   710   */
   711  
   712  
   713  function last(array) {
   714    return array[array.length - 1];
   715  }
   716  
   717  /* eslint-disable no-for-of-loops/no-for-of-loops */
   718  var ExhaustiveDeps = {
   719    meta: {
   720      type: 'suggestion',
   721      docs: {
   722        description: 'verifies the list of dependencies for Hooks like useEffect and similar',
   723        recommended: true,
   724        url: 'https://github.com/facebook/react/issues/14920'
   725      },
   726      fixable: 'code',
   727      hasSuggestions: true,
   728      schema: [{
   729        type: 'object',
   730        additionalProperties: false,
   731        enableDangerousAutofixThisMayCauseInfiniteLoops: false,
   732        properties: {
   733          additionalHooks: {
   734            type: 'string'
   735          },
   736          enableDangerousAutofixThisMayCauseInfiniteLoops: {
   737            type: 'boolean'
   738          }
   739        }
   740      }]
   741    },
   742    create: function (context) {
   743      // Parse the `additionalHooks` regex.
   744      var additionalHooks = context.options && context.options[0] && context.options[0].additionalHooks ? new RegExp(context.options[0].additionalHooks) : undefined;
   745      var enableDangerousAutofixThisMayCauseInfiniteLoops = context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops || false;
   746      var options = {
   747        additionalHooks: additionalHooks,
   748        enableDangerousAutofixThisMayCauseInfiniteLoops: enableDangerousAutofixThisMayCauseInfiniteLoops
   749      };
   750  
   751      function reportProblem(problem) {
   752        if (enableDangerousAutofixThisMayCauseInfiniteLoops) {
   753          // Used to enable legacy behavior. Dangerous.
   754          // Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension).
   755          if (Array.isArray(problem.suggest) && problem.suggest.length > 0) {
   756            problem.fix = problem.suggest[0].fix;
   757          }
   758        }
   759  
   760        context.report(problem);
   761      }
   762  
   763      var scopeManager = context.getSourceCode().scopeManager; // Should be shared between visitors.
   764  
   765      var setStateCallSites = new WeakMap();
   766      var stateVariables = new WeakSet();
   767      var stableKnownValueCache = new WeakMap();
   768      var functionWithoutCapturedValueCache = new WeakMap();
   769  
   770      function memoizeWithWeakMap(fn, map) {
   771        return function (arg) {
   772          if (map.has(arg)) {
   773            // to verify cache hits:
   774            // console.log(arg.name)
   775            return map.get(arg);
   776          }
   777  
   778          var result = fn(arg);
   779          map.set(arg, result);
   780          return result;
   781        };
   782      }
   783      /**
   784       * Visitor for both function expressions and arrow function expressions.
   785       */
   786  
   787  
   788      function visitFunctionWithDependencies(node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect) {
   789        if (isEffect && node.async) {
   790          reportProblem({
   791            node: node,
   792            message: "Effect callbacks are synchronous to prevent race conditions. " + "Put the async function inside:\n\n" + 'useEffect(() => {\n' + '  async function fetchData() {\n' + '    // You can await here\n' + '    const response = await MyAPI.getData(someId);\n' + '    // ...\n' + '  }\n' + '  fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching'
   793          });
   794        } // Get the current scope.
   795  
   796  
   797        var scope = scopeManager.acquire(node); // Find all our "pure scopes". On every re-render of a component these
   798        // pure scopes may have changes to the variables declared within. So all
   799        // variables used in our reactive hook callback but declared in a pure
   800        // scope need to be listed as dependencies of our reactive hook callback.
   801        //
   802        // According to the rules of React you can't read a mutable value in pure
   803        // scope. We can't enforce this in a lint so we trust that all variables
   804        // declared outside of pure scope are indeed frozen.
   805  
   806        var pureScopes = new Set();
   807        var componentScope = null;
   808        {
   809          var currentScope = scope.upper;
   810  
   811          while (currentScope) {
   812            pureScopes.add(currentScope);
   813  
   814            if (currentScope.type === 'function') {
   815              break;
   816            }
   817  
   818            currentScope = currentScope.upper;
   819          } // If there is no parent function scope then there are no pure scopes.
   820          // The ones we've collected so far are incorrect. So don't continue with
   821          // the lint.
   822  
   823  
   824          if (!currentScope) {
   825            return;
   826          }
   827  
   828          componentScope = currentScope;
   829        }
   830        var isArray = Array.isArray; // Next we'll define a few helpers that helps us
   831        // tell if some values don't have to be declared as deps.
   832        // Some are known to be stable based on Hook calls.
   833        // const [state, setState] = useState() / React.useState()
   834        //               ^^^ true for this reference
   835        // const [state, dispatch] = useReducer() / React.useReducer()
   836        //               ^^^ true for this reference
   837        // const ref = useRef()
   838        //       ^^^ true for this reference
   839        // False for everything else.
   840  
   841        function isStableKnownHookValue(resolved) {
   842          if (!isArray(resolved.defs)) {
   843            return false;
   844          }
   845  
   846          var def = resolved.defs[0];
   847  
   848          if (def == null) {
   849            return false;
   850          } // Look for `let stuff = ...`
   851  
   852  
   853          if (def.node.type !== 'VariableDeclarator') {
   854            return false;
   855          }
   856  
   857          var init = def.node.init;
   858  
   859          if (init == null) {
   860            return false;
   861          }
   862  
   863          while (init.type === 'TSAsExpression') {
   864            init = init.expression;
   865          } // Detect primitive constants
   866          // const foo = 42
   867  
   868  
   869          var declaration = def.node.parent;
   870  
   871          if (declaration == null) {
   872            // This might happen if variable is declared after the callback.
   873            // In that case ESLint won't set up .parent refs.
   874            // So we'll set them up manually.
   875            fastFindReferenceWithParent(componentScope.block, def.node.id);
   876            declaration = def.node.parent;
   877  
   878            if (declaration == null) {
   879              return false;
   880            }
   881          }
   882  
   883          if (declaration.kind === 'const' && init.type === 'Literal' && (typeof init.value === 'string' || typeof init.value === 'number' || init.value === null)) {
   884            // Definitely stable
   885            return true;
   886          } // Detect known Hook calls
   887          // const [_, setState] = useState()
   888  
   889  
   890          if (init.type !== 'CallExpression') {
   891            return false;
   892          }
   893  
   894          var callee = init.callee; // Step into `= React.something` initializer.
   895  
   896          if (callee.type === 'MemberExpression' && callee.object.name === 'React' && callee.property != null && !callee.computed) {
   897            callee = callee.property;
   898          }
   899  
   900          if (callee.type !== 'Identifier') {
   901            return false;
   902          }
   903  
   904          var id = def.node.id;
   905          var _callee = callee,
   906              name = _callee.name;
   907  
   908          if (name === 'useRef' && id.type === 'Identifier') {
   909            // useRef() return value is stable.
   910            return true;
   911          } else if (name === 'useState' || name === 'useReducer') {
   912            // Only consider second value in initializing tuple stable.
   913            if (id.type === 'ArrayPattern' && id.elements.length === 2 && isArray(resolved.identifiers)) {
   914              // Is second tuple value the same reference we're checking?
   915              if (id.elements[1] === resolved.identifiers[0]) {
   916                if (name === 'useState') {
   917                  var references = resolved.references;
   918                  var writeCount = 0;
   919  
   920                  for (var i = 0; i < references.length; i++) {
   921                    if (references[i].isWrite()) {
   922                      writeCount++;
   923                    }
   924  
   925                    if (writeCount > 1) {
   926                      return false;
   927                    }
   928  
   929                    setStateCallSites.set(references[i].identifier, id.elements[0]);
   930                  }
   931                } // Setter is stable.
   932  
   933  
   934                return true;
   935              } else if (id.elements[0] === resolved.identifiers[0]) {
   936                if (name === 'useState') {
   937                  var _references = resolved.references;
   938  
   939                  for (var _i = 0; _i < _references.length; _i++) {
   940                    stateVariables.add(_references[_i].identifier);
   941                  }
   942                } // State variable itself is dynamic.
   943  
   944  
   945                return false;
   946              }
   947            }
   948          } else if (name === 'useTransition') {
   949            // Only consider second value in initializing tuple stable.
   950            if (id.type === 'ArrayPattern' && id.elements.length === 2 && Array.isArray(resolved.identifiers)) {
   951              // Is second tuple value the same reference we're checking?
   952              if (id.elements[1] === resolved.identifiers[0]) {
   953                // Setter is stable.
   954                return true;
   955              }
   956            }
   957          } // By default assume it's dynamic.
   958  
   959  
   960          return false;
   961        } // Some are just functions that don't reference anything dynamic.
   962  
   963  
   964        function isFunctionWithoutCapturedValues(resolved) {
   965          if (!isArray(resolved.defs)) {
   966            return false;
   967          }
   968  
   969          var def = resolved.defs[0];
   970  
   971          if (def == null) {
   972            return false;
   973          }
   974  
   975          if (def.node == null || def.node.id == null) {
   976            return false;
   977          } // Search the direct component subscopes for
   978          // top-level function definitions matching this reference.
   979  
   980  
   981          var fnNode = def.node;
   982          var childScopes = componentScope.childScopes;
   983          var fnScope = null;
   984          var i;
   985  
   986          for (i = 0; i < childScopes.length; i++) {
   987            var childScope = childScopes[i];
   988            var childScopeBlock = childScope.block;
   989  
   990            if ( // function handleChange() {}
   991            fnNode.type === 'FunctionDeclaration' && childScopeBlock === fnNode || // const handleChange = () => {}
   992            // const handleChange = function() {}
   993            fnNode.type === 'VariableDeclarator' && childScopeBlock.parent === fnNode) {
   994              // Found it!
   995              fnScope = childScope;
   996              break;
   997            }
   998          }
   999  
  1000          if (fnScope == null) {
  1001            return false;
  1002          } // Does this function capture any values
  1003          // that are in pure scopes (aka render)?
  1004  
  1005  
  1006          for (i = 0; i < fnScope.through.length; i++) {
  1007            var ref = fnScope.through[i];
  1008  
  1009            if (ref.resolved == null) {
  1010              continue;
  1011            }
  1012  
  1013            if (pureScopes.has(ref.resolved.scope) && // Stable values are fine though,
  1014            // although we won't check functions deeper.
  1015            !memoizedIsStableKnownHookValue(ref.resolved)) {
  1016              return false;
  1017            }
  1018          } // If we got here, this function doesn't capture anything
  1019          // from render--or everything it captures is known stable.
  1020  
  1021  
  1022          return true;
  1023        } // Remember such values. Avoid re-running extra checks on them.
  1024  
  1025  
  1026        var memoizedIsStableKnownHookValue = memoizeWithWeakMap(isStableKnownHookValue, stableKnownValueCache);
  1027        var memoizedIsFunctionWithoutCapturedValues = memoizeWithWeakMap(isFunctionWithoutCapturedValues, functionWithoutCapturedValueCache); // These are usually mistaken. Collect them.
  1028  
  1029        var currentRefsInEffectCleanup = new Map(); // Is this reference inside a cleanup function for this effect node?
  1030        // We can check by traversing scopes upwards  from the reference, and checking
  1031        // if the last "return () => " we encounter is located directly inside the effect.
  1032  
  1033        function isInsideEffectCleanup(reference) {
  1034          var curScope = reference.from;
  1035          var isInReturnedFunction = false;
  1036  
  1037          while (curScope.block !== node) {
  1038            if (curScope.type === 'function') {
  1039              isInReturnedFunction = curScope.block.parent != null && curScope.block.parent.type === 'ReturnStatement';
  1040            }
  1041  
  1042            curScope = curScope.upper;
  1043          }
  1044  
  1045          return isInReturnedFunction;
  1046        } // Get dependencies from all our resolved references in pure scopes.
  1047        // Key is dependency string, value is whether it's stable.
  1048  
  1049  
  1050        var dependencies = new Map();
  1051        var optionalChains = new Map();
  1052        gatherDependenciesRecursively(scope);
  1053  
  1054        function gatherDependenciesRecursively(currentScope) {
  1055          var _iterator = _createForOfIteratorHelper(currentScope.references),
  1056              _step;
  1057  
  1058          try {
  1059            for (_iterator.s(); !(_step = _iterator.n()).done;) {
  1060              var reference = _step.value;
  1061  
  1062              // If this reference is not resolved or it is not declared in a pure
  1063              // scope then we don't care about this reference.
  1064              if (!reference.resolved) {
  1065                continue;
  1066              }
  1067  
  1068              if (!pureScopes.has(reference.resolved.scope)) {
  1069                continue;
  1070              } // Narrow the scope of a dependency if it is, say, a member expression.
  1071              // Then normalize the narrowed dependency.
  1072  
  1073  
  1074              var referenceNode = fastFindReferenceWithParent(node, reference.identifier);
  1075              var dependencyNode = getDependency(referenceNode);
  1076              var dependency = analyzePropertyChain(dependencyNode, optionalChains); // Accessing ref.current inside effect cleanup is bad.
  1077  
  1078              if ( // We're in an effect...
  1079              isEffect && // ... and this look like accessing .current...
  1080              dependencyNode.type === 'Identifier' && (dependencyNode.parent.type === 'MemberExpression' || dependencyNode.parent.type === 'OptionalMemberExpression') && !dependencyNode.parent.computed && dependencyNode.parent.property.type === 'Identifier' && dependencyNode.parent.property.name === 'current' && // ...in a cleanup function or below...
  1081              isInsideEffectCleanup(reference)) {
  1082                currentRefsInEffectCleanup.set(dependency, {
  1083                  reference: reference,
  1084                  dependencyNode: dependencyNode
  1085                });
  1086              }
  1087  
  1088              if (dependencyNode.parent.type === 'TSTypeQuery' || dependencyNode.parent.type === 'TSTypeReference') {
  1089                continue;
  1090              }
  1091  
  1092              var def = reference.resolved.defs[0];
  1093  
  1094              if (def == null) {
  1095                continue;
  1096              } // Ignore references to the function itself as it's not defined yet.
  1097  
  1098  
  1099              if (def.node != null && def.node.init === node.parent) {
  1100                continue;
  1101              } // Ignore Flow type parameters
  1102  
  1103  
  1104              if (def.type === 'TypeParameter') {
  1105                continue;
  1106              } // Add the dependency to a map so we can make sure it is referenced
  1107              // again in our dependencies array. Remember whether it's stable.
  1108  
  1109  
  1110              if (!dependencies.has(dependency)) {
  1111                var resolved = reference.resolved;
  1112                var isStable = memoizedIsStableKnownHookValue(resolved) || memoizedIsFunctionWithoutCapturedValues(resolved);
  1113                dependencies.set(dependency, {
  1114                  isStable: isStable,
  1115                  references: [reference]
  1116                });
  1117              } else {
  1118                dependencies.get(dependency).references.push(reference);
  1119              }
  1120            }
  1121          } catch (err) {
  1122            _iterator.e(err);
  1123          } finally {
  1124            _iterator.f();
  1125          }
  1126  
  1127          var _iterator2 = _createForOfIteratorHelper(currentScope.childScopes),
  1128              _step2;
  1129  
  1130          try {
  1131            for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
  1132              var childScope = _step2.value;
  1133              gatherDependenciesRecursively(childScope);
  1134            }
  1135          } catch (err) {
  1136            _iterator2.e(err);
  1137          } finally {
  1138            _iterator2.f();
  1139          }
  1140        } // Warn about accessing .current in cleanup effects.
  1141  
  1142  
  1143        currentRefsInEffectCleanup.forEach(function (_ref, dependency) {
  1144          var reference = _ref.reference,
  1145              dependencyNode = _ref.dependencyNode;
  1146          var references = reference.resolved.references; // Is React managing this ref or us?
  1147          // Let's see if we can find a .current assignment.
  1148  
  1149          var foundCurrentAssignment = false;
  1150  
  1151          for (var i = 0; i < references.length; i++) {
  1152            var identifier = references[i].identifier;
  1153            var parent = identifier.parent;
  1154  
  1155            if (parent != null && // ref.current
  1156            // Note: no need to handle OptionalMemberExpression because it can't be LHS.
  1157            parent.type === 'MemberExpression' && !parent.computed && parent.property.type === 'Identifier' && parent.property.name === 'current' && // ref.current = <something>
  1158            parent.parent.type === 'AssignmentExpression' && parent.parent.left === parent) {
  1159              foundCurrentAssignment = true;
  1160              break;
  1161            }
  1162          } // We only want to warn about React-managed refs.
  1163  
  1164  
  1165          if (foundCurrentAssignment) {
  1166            return;
  1167          }
  1168  
  1169          reportProblem({
  1170            node: dependencyNode.parent.property,
  1171            message: "The ref value '" + dependency + ".current' will likely have " + "changed by the time this effect cleanup function runs. If " + "this ref points to a node rendered by React, copy " + ("'" + dependency + ".current' to a variable inside the effect, and ") + "use that variable in the cleanup function."
  1172          });
  1173        }); // Warn about assigning to variables in the outer scope.
  1174        // Those are usually bugs.
  1175  
  1176        var staleAssignments = new Set();
  1177  
  1178        function reportStaleAssignment(writeExpr, key) {
  1179          if (staleAssignments.has(key)) {
  1180            return;
  1181          }
  1182  
  1183          staleAssignments.add(key);
  1184          reportProblem({
  1185            node: writeExpr,
  1186            message: "Assignments to the '" + key + "' variable from inside React Hook " + (context.getSource(reactiveHook) + " will be lost after each ") + "render. To preserve the value over time, store it in a useRef " + "Hook and keep the mutable value in the '.current' property. " + "Otherwise, you can move this variable directly inside " + (context.getSource(reactiveHook) + ".")
  1187          });
  1188        } // Remember which deps are stable and report bad usage first.
  1189  
  1190  
  1191        var stableDependencies = new Set();
  1192        dependencies.forEach(function (_ref2, key) {
  1193          var isStable = _ref2.isStable,
  1194              references = _ref2.references;
  1195  
  1196          if (isStable) {
  1197            stableDependencies.add(key);
  1198          }
  1199  
  1200          references.forEach(function (reference) {
  1201            if (reference.writeExpr) {
  1202              reportStaleAssignment(reference.writeExpr, key);
  1203            }
  1204          });
  1205        });
  1206  
  1207        if (staleAssignments.size > 0) {
  1208          // The intent isn't clear so we'll wait until you fix those first.
  1209          return;
  1210        }
  1211  
  1212        if (!declaredDependenciesNode) {
  1213          // Check if there are any top-level setState() calls.
  1214          // Those tend to lead to infinite loops.
  1215          var setStateInsideEffectWithoutDeps = null;
  1216          dependencies.forEach(function (_ref3, key) {
  1217            var isStable = _ref3.isStable,
  1218                references = _ref3.references;
  1219  
  1220            if (setStateInsideEffectWithoutDeps) {
  1221              return;
  1222            }
  1223  
  1224            references.forEach(function (reference) {
  1225              if (setStateInsideEffectWithoutDeps) {
  1226                return;
  1227              }
  1228  
  1229              var id = reference.identifier;
  1230              var isSetState = setStateCallSites.has(id);
  1231  
  1232              if (!isSetState) {
  1233                return;
  1234              }
  1235  
  1236              var fnScope = reference.from;
  1237  
  1238              while (fnScope.type !== 'function') {
  1239                fnScope = fnScope.upper;
  1240              }
  1241  
  1242              var isDirectlyInsideEffect = fnScope.block === node;
  1243  
  1244              if (isDirectlyInsideEffect) {
  1245                // TODO: we could potentially ignore early returns.
  1246                setStateInsideEffectWithoutDeps = key;
  1247              }
  1248            });
  1249          });
  1250  
  1251          if (setStateInsideEffectWithoutDeps) {
  1252            var _collectRecommendatio = collectRecommendations({
  1253              dependencies: dependencies,
  1254              declaredDependencies: [],
  1255              stableDependencies: stableDependencies,
  1256              externalDependencies: new Set(),
  1257              isEffect: true
  1258            }),
  1259                _suggestedDependencies = _collectRecommendatio.suggestedDependencies;
  1260  
  1261            reportProblem({
  1262              node: reactiveHook,
  1263              message: "React Hook " + reactiveHookName + " contains a call to '" + setStateInsideEffectWithoutDeps + "'. " + "Without a list of dependencies, this can lead to an infinite chain of updates. " + "To fix this, pass [" + _suggestedDependencies.join(', ') + ("] as a second argument to the " + reactiveHookName + " Hook."),
  1264              suggest: [{
  1265                desc: "Add dependencies array: [" + _suggestedDependencies.join(', ') + "]",
  1266                fix: function (fixer) {
  1267                  return fixer.insertTextAfter(node, ", [" + _suggestedDependencies.join(', ') + "]");
  1268                }
  1269              }]
  1270            });
  1271          }
  1272  
  1273          return;
  1274        }
  1275  
  1276        var declaredDependencies = [];
  1277        var externalDependencies = new Set();
  1278  
  1279        if (declaredDependenciesNode.type !== 'ArrayExpression') {
  1280          // If the declared dependencies are not an array expression then we
  1281          // can't verify that the user provided the correct dependencies. Tell
  1282          // the user this in an error.
  1283          reportProblem({
  1284            node: declaredDependenciesNode,
  1285            message: "React Hook " + context.getSource(reactiveHook) + " was passed a " + 'dependency list that is not an array literal. This means we ' + "can't statically verify whether you've passed the correct " + 'dependencies.'
  1286          });
  1287        } else {
  1288          declaredDependenciesNode.elements.forEach(function (declaredDependencyNode) {
  1289            // Skip elided elements.
  1290            if (declaredDependencyNode === null) {
  1291              return;
  1292            } // If we see a spread element then add a special warning.
  1293  
  1294  
  1295            if (declaredDependencyNode.type === 'SpreadElement') {
  1296              reportProblem({
  1297                node: declaredDependencyNode,
  1298                message: "React Hook " + context.getSource(reactiveHook) + " has a spread " + "element in its dependency array. This means we can't " + "statically verify whether you've passed the " + 'correct dependencies.'
  1299              });
  1300              return;
  1301            } // Try to normalize the declared dependency. If we can't then an error
  1302            // will be thrown. We will catch that error and report an error.
  1303  
  1304  
  1305            var declaredDependency;
  1306  
  1307            try {
  1308              declaredDependency = analyzePropertyChain(declaredDependencyNode, null);
  1309            } catch (error) {
  1310              if (/Unsupported node type/.test(error.message)) {
  1311                if (declaredDependencyNode.type === 'Literal') {
  1312                  if (dependencies.has(declaredDependencyNode.value)) {
  1313                    reportProblem({
  1314                      node: declaredDependencyNode,
  1315                      message: "The " + declaredDependencyNode.raw + " literal is not a valid dependency " + "because it never changes. " + ("Did you mean to include " + declaredDependencyNode.value + " in the array instead?")
  1316                    });
  1317                  } else {
  1318                    reportProblem({
  1319                      node: declaredDependencyNode,
  1320                      message: "The " + declaredDependencyNode.raw + " literal is not a valid dependency " + 'because it never changes. You can safely remove it.'
  1321                    });
  1322                  }
  1323                } else {
  1324                  reportProblem({
  1325                    node: declaredDependencyNode,
  1326                    message: "React Hook " + context.getSource(reactiveHook) + " has a " + "complex expression in the dependency array. " + 'Extract it to a separate variable so it can be statically checked.'
  1327                  });
  1328                }
  1329  
  1330                return;
  1331              } else {
  1332                throw error;
  1333              }
  1334            }
  1335  
  1336            var maybeID = declaredDependencyNode;
  1337  
  1338            while (maybeID.type === 'MemberExpression' || maybeID.type === 'OptionalMemberExpression' || maybeID.type === 'ChainExpression') {
  1339              maybeID = maybeID.object || maybeID.expression.object;
  1340            }
  1341  
  1342            var isDeclaredInComponent = !componentScope.through.some(function (ref) {
  1343              return ref.identifier === maybeID;
  1344            }); // Add the dependency to our declared dependency map.
  1345  
  1346            declaredDependencies.push({
  1347              key: declaredDependency,
  1348              node: declaredDependencyNode
  1349            });
  1350  
  1351            if (!isDeclaredInComponent) {
  1352              externalDependencies.add(declaredDependency);
  1353            }
  1354          });
  1355        }
  1356  
  1357        var _collectRecommendatio2 = collectRecommendations({
  1358          dependencies: dependencies,
  1359          declaredDependencies: declaredDependencies,
  1360          stableDependencies: stableDependencies,
  1361          externalDependencies: externalDependencies,
  1362          isEffect: isEffect
  1363        }),
  1364            suggestedDependencies = _collectRecommendatio2.suggestedDependencies,
  1365            unnecessaryDependencies = _collectRecommendatio2.unnecessaryDependencies,
  1366            missingDependencies = _collectRecommendatio2.missingDependencies,
  1367            duplicateDependencies = _collectRecommendatio2.duplicateDependencies;
  1368  
  1369        var suggestedDeps = suggestedDependencies;
  1370        var problemCount = duplicateDependencies.size + missingDependencies.size + unnecessaryDependencies.size;
  1371  
  1372        if (problemCount === 0) {
  1373          // If nothing else to report, check if some dependencies would
  1374          // invalidate on every render.
  1375          var constructions = scanForConstructions({
  1376            declaredDependencies: declaredDependencies,
  1377            declaredDependenciesNode: declaredDependenciesNode,
  1378            componentScope: componentScope,
  1379            scope: scope
  1380          });
  1381          constructions.forEach(function (_ref4) {
  1382            var construction = _ref4.construction,
  1383                isUsedOutsideOfHook = _ref4.isUsedOutsideOfHook,
  1384                depType = _ref4.depType;
  1385            var wrapperHook = depType === 'function' ? 'useCallback' : 'useMemo';
  1386            var constructionType = depType === 'function' ? 'definition' : 'initialization';
  1387            var defaultAdvice = "wrap the " + constructionType + " of '" + construction.name.name + "' in its own " + wrapperHook + "() Hook.";
  1388            var advice = isUsedOutsideOfHook ? "To fix this, " + defaultAdvice : "Move it inside the " + reactiveHookName + " callback. Alternatively, " + defaultAdvice;
  1389            var causation = depType === 'conditional' || depType === 'logical expression' ? 'could make' : 'makes';
  1390            var message = "The '" + construction.name.name + "' " + depType + " " + causation + " the dependencies of " + (reactiveHookName + " Hook (at line " + declaredDependenciesNode.loc.start.line + ") ") + ("change on every render. " + advice);
  1391            var suggest; // Only handle the simple case of variable assignments.
  1392            // Wrapping function declarations can mess up hoisting.
  1393  
  1394            if (isUsedOutsideOfHook && construction.type === 'Variable' && // Objects may be mutated after construction, which would make this
  1395            // fix unsafe. Functions _probably_ won't be mutated, so we'll
  1396            // allow this fix for them.
  1397            depType === 'function') {
  1398              suggest = [{
  1399                desc: "Wrap the " + constructionType + " of '" + construction.name.name + "' in its own " + wrapperHook + "() Hook.",
  1400                fix: function (fixer) {
  1401                  var _ref5 = wrapperHook === 'useMemo' ? ["useMemo(() => { return ", '; })'] : ['useCallback(', ')'],
  1402                      before = _ref5[0],
  1403                      after = _ref5[1];
  1404  
  1405                  return [// TODO: also add an import?
  1406                  fixer.insertTextBefore(construction.node.init, before), // TODO: ideally we'd gather deps here but it would require
  1407                  // restructuring the rule code. This will cause a new lint
  1408                  // error to appear immediately for useCallback. Note we're
  1409                  // not adding [] because would that changes semantics.
  1410                  fixer.insertTextAfter(construction.node.init, after)];
  1411                }
  1412              }];
  1413            } // TODO: What if the function needs to change on every render anyway?
  1414            // Should we suggest removing effect deps as an appropriate fix too?
  1415  
  1416  
  1417            reportProblem({
  1418              // TODO: Why not report this at the dependency site?
  1419              node: construction.node,
  1420              message: message,
  1421              suggest: suggest
  1422            });
  1423          });
  1424          return;
  1425        } // If we're going to report a missing dependency,
  1426        // we might as well recalculate the list ignoring
  1427        // the currently specified deps. This can result
  1428        // in some extra deduplication. We can't do this
  1429        // for effects though because those have legit
  1430        // use cases for over-specifying deps.
  1431  
  1432  
  1433        if (!isEffect && missingDependencies.size > 0) {
  1434          suggestedDeps = collectRecommendations({
  1435            dependencies: dependencies,
  1436            declaredDependencies: [],
  1437            // Pretend we don't know
  1438            stableDependencies: stableDependencies,
  1439            externalDependencies: externalDependencies,
  1440            isEffect: isEffect
  1441          }).suggestedDependencies;
  1442        } // Alphabetize the suggestions, but only if deps were already alphabetized.
  1443  
  1444  
  1445        function areDeclaredDepsAlphabetized() {
  1446          if (declaredDependencies.length === 0) {
  1447            return true;
  1448          }
  1449  
  1450          var declaredDepKeys = declaredDependencies.map(function (dep) {
  1451            return dep.key;
  1452          });
  1453          var sortedDeclaredDepKeys = declaredDepKeys.slice().sort();
  1454          return declaredDepKeys.join(',') === sortedDeclaredDepKeys.join(',');
  1455        }
  1456  
  1457        if (areDeclaredDepsAlphabetized()) {
  1458          suggestedDeps.sort();
  1459        } // Most of our algorithm deals with dependency paths with optional chaining stripped.
  1460        // This function is the last step before printing a dependency, so now is a good time to
  1461        // check whether any members in our path are always used as optional-only. In that case,
  1462        // we will use ?. instead of . to concatenate those parts of the path.
  1463  
  1464  
  1465        function formatDependency(path) {
  1466          var members = path.split('.');
  1467          var finalPath = '';
  1468  
  1469          for (var i = 0; i < members.length; i++) {
  1470            if (i !== 0) {
  1471              var pathSoFar = members.slice(0, i + 1).join('.');
  1472              var isOptional = optionalChains.get(pathSoFar) === true;
  1473              finalPath += isOptional ? '?.' : '.';
  1474            }
  1475  
  1476            finalPath += members[i];
  1477          }
  1478  
  1479          return finalPath;
  1480        }
  1481  
  1482        function getWarningMessage(deps, singlePrefix, label, fixVerb) {
  1483          if (deps.size === 0) {
  1484            return null;
  1485          }
  1486  
  1487          return (deps.size > 1 ? '' : singlePrefix + ' ') + label + ' ' + (deps.size > 1 ? 'dependencies' : 'dependency') + ': ' + joinEnglish(Array.from(deps).sort().map(function (name) {
  1488            return "'" + formatDependency(name) + "'";
  1489          })) + (". Either " + fixVerb + " " + (deps.size > 1 ? 'them' : 'it') + " or remove the dependency array.");
  1490        }
  1491  
  1492        var extraWarning = '';
  1493  
  1494        if (unnecessaryDependencies.size > 0) {
  1495          var badRef = null;
  1496          Array.from(unnecessaryDependencies.keys()).forEach(function (key) {
  1497            if (badRef !== null) {
  1498              return;
  1499            }
  1500  
  1501            if (key.endsWith('.current')) {
  1502              badRef = key;
  1503            }
  1504          });
  1505  
  1506          if (badRef !== null) {
  1507            extraWarning = " Mutable values like '" + badRef + "' aren't valid dependencies " + "because mutating them doesn't re-render the component.";
  1508          } else if (externalDependencies.size > 0) {
  1509            var dep = Array.from(externalDependencies)[0]; // Don't show this warning for things that likely just got moved *inside* the callback
  1510            // because in that case they're clearly not referring to globals.
  1511  
  1512            if (!scope.set.has(dep)) {
  1513              extraWarning = " Outer scope values like '" + dep + "' aren't valid dependencies " + "because mutating them doesn't re-render the component.";
  1514            }
  1515          }
  1516        } // `props.foo()` marks `props` as a dependency because it has
  1517        // a `this` value. This warning can be confusing.
  1518        // So if we're going to show it, append a clarification.
  1519  
  1520  
  1521        if (!extraWarning && missingDependencies.has('props')) {
  1522          var propDep = dependencies.get('props');
  1523  
  1524          if (propDep == null) {
  1525            return;
  1526          }
  1527  
  1528          var refs = propDep.references;
  1529  
  1530          if (!Array.isArray(refs)) {
  1531            return;
  1532          }
  1533  
  1534          var isPropsOnlyUsedInMembers = true;
  1535  
  1536          for (var i = 0; i < refs.length; i++) {
  1537            var ref = refs[i];
  1538            var id = fastFindReferenceWithParent(componentScope.block, ref.identifier);
  1539  
  1540            if (!id) {
  1541              isPropsOnlyUsedInMembers = false;
  1542              break;
  1543            }
  1544  
  1545            var parent = id.parent;
  1546  
  1547            if (parent == null) {
  1548              isPropsOnlyUsedInMembers = false;
  1549              break;
  1550            }
  1551  
  1552            if (parent.type !== 'MemberExpression' && parent.type !== 'OptionalMemberExpression') {
  1553              isPropsOnlyUsedInMembers = false;
  1554              break;
  1555            }
  1556          }
  1557  
  1558          if (isPropsOnlyUsedInMembers) {
  1559            extraWarning = " However, 'props' will change when *any* prop changes, so the " + "preferred fix is to destructure the 'props' object outside of " + ("the " + reactiveHookName + " call and refer to those specific props ") + ("inside " + context.getSource(reactiveHook) + ".");
  1560          }
  1561        }
  1562  
  1563        if (!extraWarning && missingDependencies.size > 0) {
  1564          // See if the user is trying to avoid specifying a callable prop.
  1565          // This usually means they're unaware of useCallback.
  1566          var missingCallbackDep = null;
  1567          missingDependencies.forEach(function (missingDep) {
  1568            if (missingCallbackDep) {
  1569              return;
  1570            } // Is this a variable from top scope?
  1571  
  1572  
  1573            var topScopeRef = componentScope.set.get(missingDep);
  1574            var usedDep = dependencies.get(missingDep);
  1575  
  1576            if (usedDep.references[0].resolved !== topScopeRef) {
  1577              return;
  1578            } // Is this a destructured prop?
  1579  
  1580  
  1581            var def = topScopeRef.defs[0];
  1582  
  1583            if (def == null || def.name == null || def.type !== 'Parameter') {
  1584              return;
  1585            } // Was it called in at least one case? Then it's a function.
  1586  
  1587  
  1588            var isFunctionCall = false;
  1589            var id;
  1590  
  1591            for (var _i2 = 0; _i2 < usedDep.references.length; _i2++) {
  1592              id = usedDep.references[_i2].identifier;
  1593  
  1594              if (id != null && id.parent != null && (id.parent.type === 'CallExpression' || id.parent.type === 'OptionalCallExpression') && id.parent.callee === id) {
  1595                isFunctionCall = true;
  1596                break;
  1597              }
  1598            }
  1599  
  1600            if (!isFunctionCall) {
  1601              return;
  1602            } // If it's missing (i.e. in component scope) *and* it's a parameter
  1603            // then it is definitely coming from props destructuring.
  1604            // (It could also be props itself but we wouldn't be calling it then.)
  1605  
  1606  
  1607            missingCallbackDep = missingDep;
  1608          });
  1609  
  1610          if (missingCallbackDep !== null) {
  1611            extraWarning = " If '" + missingCallbackDep + "' changes too often, " + "find the parent component that defines it " + "and wrap that definition in useCallback.";
  1612          }
  1613        }
  1614  
  1615        if (!extraWarning && missingDependencies.size > 0) {
  1616          var setStateRecommendation = null;
  1617          missingDependencies.forEach(function (missingDep) {
  1618            if (setStateRecommendation !== null) {
  1619              return;
  1620            }
  1621  
  1622            var usedDep = dependencies.get(missingDep);
  1623            var references = usedDep.references;
  1624            var id;
  1625            var maybeCall;
  1626  
  1627            for (var _i3 = 0; _i3 < references.length; _i3++) {
  1628              id = references[_i3].identifier;
  1629              maybeCall = id.parent; // Try to see if we have setState(someExpr(missingDep)).
  1630  
  1631              while (maybeCall != null && maybeCall !== componentScope.block) {
  1632                if (maybeCall.type === 'CallExpression') {
  1633                  var correspondingStateVariable = setStateCallSites.get(maybeCall.callee);
  1634  
  1635                  if (correspondingStateVariable != null) {
  1636                    if (correspondingStateVariable.name === missingDep) {
  1637                      // setCount(count + 1)
  1638                      setStateRecommendation = {
  1639                        missingDep: missingDep,
  1640                        setter: maybeCall.callee.name,
  1641                        form: 'updater'
  1642                      };
  1643                    } else if (stateVariables.has(id)) {
  1644                      // setCount(count + increment)
  1645                      setStateRecommendation = {
  1646                        missingDep: missingDep,
  1647                        setter: maybeCall.callee.name,
  1648                        form: 'reducer'
  1649                      };
  1650                    } else {
  1651                      var resolved = references[_i3].resolved;
  1652  
  1653                      if (resolved != null) {
  1654                        // If it's a parameter *and* a missing dep,
  1655                        // it must be a prop or something inside a prop.
  1656                        // Therefore, recommend an inline reducer.
  1657                        var def = resolved.defs[0];
  1658  
  1659                        if (def != null && def.type === 'Parameter') {
  1660                          setStateRecommendation = {
  1661                            missingDep: missingDep,
  1662                            setter: maybeCall.callee.name,
  1663                            form: 'inlineReducer'
  1664                          };
  1665                        }
  1666                      }
  1667                    }
  1668  
  1669                    break;
  1670                  }
  1671                }
  1672  
  1673                maybeCall = maybeCall.parent;
  1674              }
  1675  
  1676              if (setStateRecommendation !== null) {
  1677                break;
  1678              }
  1679            }
  1680          });
  1681  
  1682          if (setStateRecommendation !== null) {
  1683            switch (setStateRecommendation.form) {
  1684              case 'reducer':
  1685                extraWarning = " You can also replace multiple useState variables with useReducer " + ("if '" + setStateRecommendation.setter + "' needs the ") + ("current value of '" + setStateRecommendation.missingDep + "'.");
  1686                break;
  1687  
  1688              case 'inlineReducer':
  1689                extraWarning = " If '" + setStateRecommendation.setter + "' needs the " + ("current value of '" + setStateRecommendation.missingDep + "', ") + "you can also switch to useReducer instead of useState and " + ("read '" + setStateRecommendation.missingDep + "' in the reducer.");
  1690                break;
  1691  
  1692              case 'updater':
  1693                extraWarning = " You can also do a functional update '" + setStateRecommendation.setter + "(" + setStateRecommendation.missingDep.substring(0, 1) + " => ...)' if you only need '" + setStateRecommendation.missingDep + "'" + (" in the '" + setStateRecommendation.setter + "' call.");
  1694                break;
  1695  
  1696              default:
  1697                throw new Error('Unknown case.');
  1698            }
  1699          }
  1700        }
  1701  
  1702        reportProblem({
  1703          node: declaredDependenciesNode,
  1704          message: "React Hook " + context.getSource(reactiveHook) + " has " + ( // To avoid a long message, show the next actionable item.
  1705          getWarningMessage(missingDependencies, 'a', 'missing', 'include') || getWarningMessage(unnecessaryDependencies, 'an', 'unnecessary', 'exclude') || getWarningMessage(duplicateDependencies, 'a', 'duplicate', 'omit')) + extraWarning,
  1706          suggest: [{
  1707            desc: "Update the dependencies array to be: [" + suggestedDeps.map(formatDependency).join(', ') + "]",
  1708            fix: function (fixer) {
  1709              // TODO: consider preserving the comments or formatting?
  1710              return fixer.replaceText(declaredDependenciesNode, "[" + suggestedDeps.map(formatDependency).join(', ') + "]");
  1711            }
  1712          }]
  1713        });
  1714      }
  1715  
  1716      function visitCallExpression(node) {
  1717        var callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
  1718  
  1719        if (callbackIndex === -1) {
  1720          // Not a React Hook call that needs deps.
  1721          return;
  1722        }
  1723  
  1724        var callback = node.arguments[callbackIndex];
  1725        var reactiveHook = node.callee;
  1726        var reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
  1727        var declaredDependenciesNode = node.arguments[callbackIndex + 1];
  1728        var isEffect = /Effect($|[^a-z])/g.test(reactiveHookName); // Check whether a callback is supplied. If there is no callback supplied
  1729        // then the hook will not work and React will throw a TypeError.
  1730        // So no need to check for dependency inclusion.
  1731  
  1732        if (!callback) {
  1733          reportProblem({
  1734            node: reactiveHook,
  1735            message: "React Hook " + reactiveHookName + " requires an effect callback. " + "Did you forget to pass a callback to the hook?"
  1736          });
  1737          return;
  1738        } // Check the declared dependencies for this reactive hook. If there is no
  1739        // second argument then the reactive callback will re-run on every render.
  1740        // So no need to check for dependency inclusion.
  1741  
  1742  
  1743        if (!declaredDependenciesNode && !isEffect) {
  1744          // These are only used for optimization.
  1745          if (reactiveHookName === 'useMemo' || reactiveHookName === 'useCallback') {
  1746            // TODO: Can this have a suggestion?
  1747            reportProblem({
  1748              node: reactiveHook,
  1749              message: "React Hook " + reactiveHookName + " does nothing when called with " + "only one argument. Did you forget to pass an array of " + "dependencies?"
  1750            });
  1751          }
  1752  
  1753          return;
  1754        }
  1755  
  1756        switch (callback.type) {
  1757          case 'FunctionExpression':
  1758          case 'ArrowFunctionExpression':
  1759            visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
  1760            return;
  1761          // Handled
  1762  
  1763          case 'Identifier':
  1764            if (!declaredDependenciesNode) {
  1765              // No deps, no problems.
  1766              return; // Handled
  1767            } // The function passed as a callback is not written inline.
  1768            // But perhaps it's in the dependencies array?
  1769  
  1770  
  1771            if (declaredDependenciesNode.elements && declaredDependenciesNode.elements.some(function (el) {
  1772              return el && el.type === 'Identifier' && el.name === callback.name;
  1773            })) {
  1774              // If it's already in the list of deps, we don't care because
  1775              // this is valid regardless.
  1776              return; // Handled
  1777            } // We'll do our best effort to find it, complain otherwise.
  1778  
  1779  
  1780            var variable = context.getScope().set.get(callback.name);
  1781  
  1782            if (variable == null || variable.defs == null) {
  1783              // If it's not in scope, we don't care.
  1784              return; // Handled
  1785            } // The function passed as a callback is not written inline.
  1786            // But it's defined somewhere in the render scope.
  1787            // We'll do our best effort to find and check it, complain otherwise.
  1788  
  1789  
  1790            var def = variable.defs[0];
  1791  
  1792            if (!def || !def.node) {
  1793              break; // Unhandled
  1794            }
  1795  
  1796            if (def.type !== 'Variable' && def.type !== 'FunctionName') {
  1797              // Parameter or an unusual pattern. Bail out.
  1798              break; // Unhandled
  1799            }
  1800  
  1801            switch (def.node.type) {
  1802              case 'FunctionDeclaration':
  1803                // useEffect(() => { ... }, []);
  1804                visitFunctionWithDependencies(def.node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
  1805                return;
  1806              // Handled
  1807  
  1808              case 'VariableDeclarator':
  1809                var init = def.node.init;
  1810  
  1811                if (!init) {
  1812                  break; // Unhandled
  1813                }
  1814  
  1815                switch (init.type) {
  1816                  // const effectBody = () => {...};
  1817                  // useEffect(effectBody, []);
  1818                  case 'ArrowFunctionExpression':
  1819                  case 'FunctionExpression':
  1820                    // We can inspect this function as if it were inline.
  1821                    visitFunctionWithDependencies(init, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
  1822                    return;
  1823                  // Handled
  1824                }
  1825  
  1826                break;
  1827              // Unhandled
  1828            }
  1829  
  1830            break;
  1831          // Unhandled
  1832  
  1833          default:
  1834            // useEffect(generateEffectBody(), []);
  1835            reportProblem({
  1836              node: reactiveHook,
  1837              message: "React Hook " + reactiveHookName + " received a function whose dependencies " + "are unknown. Pass an inline function instead."
  1838            });
  1839            return;
  1840          // Handled
  1841        } // Something unusual. Fall back to suggesting to add the body itself as a dep.
  1842  
  1843  
  1844        reportProblem({
  1845          node: reactiveHook,
  1846          message: "React Hook " + reactiveHookName + " has a missing dependency: '" + callback.name + "'. " + "Either include it or remove the dependency array.",
  1847          suggest: [{
  1848            desc: "Update the dependencies array to be: [" + callback.name + "]",
  1849            fix: function (fixer) {
  1850              return fixer.replaceText(declaredDependenciesNode, "[" + callback.name + "]");
  1851            }
  1852          }]
  1853        });
  1854      }
  1855  
  1856      return {
  1857        CallExpression: visitCallExpression
  1858      };
  1859    }
  1860  }; // The meat of the logic.
  1861  
  1862  function collectRecommendations(_ref6) {
  1863    var dependencies = _ref6.dependencies,
  1864        declaredDependencies = _ref6.declaredDependencies,
  1865        stableDependencies = _ref6.stableDependencies,
  1866        externalDependencies = _ref6.externalDependencies,
  1867        isEffect = _ref6.isEffect;
  1868    // Our primary data structure.
  1869    // It is a logical representation of property chains:
  1870    // `props` -> `props.foo` -> `props.foo.bar` -> `props.foo.bar.baz`
  1871    //         -> `props.lol`
  1872    //         -> `props.huh` -> `props.huh.okay`
  1873    //         -> `props.wow`
  1874    // We'll use it to mark nodes that are *used* by the programmer,
  1875    // and the nodes that were *declared* as deps. Then we will
  1876    // traverse it to learn which deps are missing or unnecessary.
  1877    var depTree = createDepTree();
  1878  
  1879    function createDepTree() {
  1880      return {
  1881        isUsed: false,
  1882        // True if used in code
  1883        isSatisfiedRecursively: false,
  1884        // True if specified in deps
  1885        isSubtreeUsed: false,
  1886        // True if something deeper is used by code
  1887        children: new Map() // Nodes for properties
  1888  
  1889      };
  1890    } // Mark all required nodes first.
  1891    // Imagine exclamation marks next to each used deep property.
  1892  
  1893  
  1894    dependencies.forEach(function (_, key) {
  1895      var node = getOrCreateNodeByPath(depTree, key);
  1896      node.isUsed = true;
  1897      markAllParentsByPath(depTree, key, function (parent) {
  1898        parent.isSubtreeUsed = true;
  1899      });
  1900    }); // Mark all satisfied nodes.
  1901    // Imagine checkmarks next to each declared dependency.
  1902  
  1903    declaredDependencies.forEach(function (_ref7) {
  1904      var key = _ref7.key;
  1905      var node = getOrCreateNodeByPath(depTree, key);
  1906      node.isSatisfiedRecursively = true;
  1907    });
  1908    stableDependencies.forEach(function (key) {
  1909      var node = getOrCreateNodeByPath(depTree, key);
  1910      node.isSatisfiedRecursively = true;
  1911    }); // Tree manipulation helpers.
  1912  
  1913    function getOrCreateNodeByPath(rootNode, path) {
  1914      var keys = path.split('.');
  1915      var node = rootNode;
  1916  
  1917      var _iterator3 = _createForOfIteratorHelper(keys),
  1918          _step3;
  1919  
  1920      try {
  1921        for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
  1922          var key = _step3.value;
  1923          var child = node.children.get(key);
  1924  
  1925          if (!child) {
  1926            child = createDepTree();
  1927            node.children.set(key, child);
  1928          }
  1929  
  1930          node = child;
  1931        }
  1932      } catch (err) {
  1933        _iterator3.e(err);
  1934      } finally {
  1935        _iterator3.f();
  1936      }
  1937  
  1938      return node;
  1939    }
  1940  
  1941    function markAllParentsByPath(rootNode, path, fn) {
  1942      var keys = path.split('.');
  1943      var node = rootNode;
  1944  
  1945      var _iterator4 = _createForOfIteratorHelper(keys),
  1946          _step4;
  1947  
  1948      try {
  1949        for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
  1950          var key = _step4.value;
  1951          var child = node.children.get(key);
  1952  
  1953          if (!child) {
  1954            return;
  1955          }
  1956  
  1957          fn(child);
  1958          node = child;
  1959        }
  1960      } catch (err) {
  1961        _iterator4.e(err);
  1962      } finally {
  1963        _iterator4.f();
  1964      }
  1965    } // Now we can learn which dependencies are missing or necessary.
  1966  
  1967  
  1968    var missingDependencies = new Set();
  1969    var satisfyingDependencies = new Set();
  1970    scanTreeRecursively(depTree, missingDependencies, satisfyingDependencies, function (key) {
  1971      return key;
  1972    });
  1973  
  1974    function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
  1975      node.children.forEach(function (child, key) {
  1976        var path = keyToPath(key);
  1977  
  1978        if (child.isSatisfiedRecursively) {
  1979          if (child.isSubtreeUsed) {
  1980            // Remember this dep actually satisfied something.
  1981            satisfyingPaths.add(path);
  1982          } // It doesn't matter if there's something deeper.
  1983          // It would be transitively satisfied since we assume immutability.
  1984          // `props.foo` is enough if you read `props.foo.id`.
  1985  
  1986  
  1987          return;
  1988        }
  1989  
  1990        if (child.isUsed) {
  1991          // Remember that no declared deps satisfied this node.
  1992          missingPaths.add(path); // If we got here, nothing in its subtree was satisfied.
  1993          // No need to search further.
  1994  
  1995          return;
  1996        }
  1997  
  1998        scanTreeRecursively(child, missingPaths, satisfyingPaths, function (childKey) {
  1999          return path + '.' + childKey;
  2000        });
  2001      });
  2002    } // Collect suggestions in the order they were originally specified.
  2003  
  2004  
  2005    var suggestedDependencies = [];
  2006    var unnecessaryDependencies = new Set();
  2007    var duplicateDependencies = new Set();
  2008    declaredDependencies.forEach(function (_ref8) {
  2009      var key = _ref8.key;
  2010  
  2011      // Does this declared dep satisfy a real need?
  2012      if (satisfyingDependencies.has(key)) {
  2013        if (suggestedDependencies.indexOf(key) === -1) {
  2014          // Good one.
  2015          suggestedDependencies.push(key);
  2016        } else {
  2017          // Duplicate.
  2018          duplicateDependencies.add(key);
  2019        }
  2020      } else {
  2021        if (isEffect && !key.endsWith('.current') && !externalDependencies.has(key)) {
  2022          // Effects are allowed extra "unnecessary" deps.
  2023          // Such as resetting scroll when ID changes.
  2024          // Consider them legit.
  2025          // The exception is ref.current which is always wrong.
  2026          if (suggestedDependencies.indexOf(key) === -1) {
  2027            suggestedDependencies.push(key);
  2028          }
  2029        } else {
  2030          // It's definitely not needed.
  2031          unnecessaryDependencies.add(key);
  2032        }
  2033      }
  2034    }); // Then add the missing ones at the end.
  2035  
  2036    missingDependencies.forEach(function (key) {
  2037      suggestedDependencies.push(key);
  2038    });
  2039    return {
  2040      suggestedDependencies: suggestedDependencies,
  2041      unnecessaryDependencies: unnecessaryDependencies,
  2042      duplicateDependencies: duplicateDependencies,
  2043      missingDependencies: missingDependencies
  2044    };
  2045  } // If the node will result in constructing a referentially unique value, return
  2046  // its human readable type name, else return null.
  2047  
  2048  
  2049  function getConstructionExpressionType(node) {
  2050    switch (node.type) {
  2051      case 'ObjectExpression':
  2052        return 'object';
  2053  
  2054      case 'ArrayExpression':
  2055        return 'array';
  2056  
  2057      case 'ArrowFunctionExpression':
  2058      case 'FunctionExpression':
  2059        return 'function';
  2060  
  2061      case 'ClassExpression':
  2062        return 'class';
  2063  
  2064      case 'ConditionalExpression':
  2065        if (getConstructionExpressionType(node.consequent) != null || getConstructionExpressionType(node.alternate) != null) {
  2066          return 'conditional';
  2067        }
  2068  
  2069        return null;
  2070  
  2071      case 'LogicalExpression':
  2072        if (getConstructionExpressionType(node.left) != null || getConstructionExpressionType(node.right) != null) {
  2073          return 'logical expression';
  2074        }
  2075  
  2076        return null;
  2077  
  2078      case 'JSXFragment':
  2079        return 'JSX fragment';
  2080  
  2081      case 'JSXElement':
  2082        return 'JSX element';
  2083  
  2084      case 'AssignmentExpression':
  2085        if (getConstructionExpressionType(node.right) != null) {
  2086          return 'assignment expression';
  2087        }
  2088  
  2089        return null;
  2090  
  2091      case 'NewExpression':
  2092        return 'object construction';
  2093  
  2094      case 'Literal':
  2095        if (node.value instanceof RegExp) {
  2096          return 'regular expression';
  2097        }
  2098  
  2099        return null;
  2100  
  2101      case 'TypeCastExpression':
  2102        return getConstructionExpressionType(node.expression);
  2103  
  2104      case 'TSAsExpression':
  2105        return getConstructionExpressionType(node.expression);
  2106    }
  2107  
  2108    return null;
  2109  } // Finds variables declared as dependencies
  2110  // that would invalidate on every render.
  2111  
  2112  
  2113  function scanForConstructions(_ref9) {
  2114    var declaredDependencies = _ref9.declaredDependencies,
  2115        declaredDependenciesNode = _ref9.declaredDependenciesNode,
  2116        componentScope = _ref9.componentScope,
  2117        scope = _ref9.scope;
  2118    var constructions = declaredDependencies.map(function (_ref10) {
  2119      var key = _ref10.key;
  2120      var ref = componentScope.variables.find(function (v) {
  2121        return v.name === key;
  2122      });
  2123  
  2124      if (ref == null) {
  2125        return null;
  2126      }
  2127  
  2128      var node = ref.defs[0];
  2129  
  2130      if (node == null) {
  2131        return null;
  2132      } // const handleChange = function () {}
  2133      // const handleChange = () => {}
  2134      // const foo = {}
  2135      // const foo = []
  2136      // etc.
  2137  
  2138  
  2139      if (node.type === 'Variable' && node.node.type === 'VariableDeclarator' && node.node.id.type === 'Identifier' && // Ensure this is not destructed assignment
  2140      node.node.init != null) {
  2141        var constantExpressionType = getConstructionExpressionType(node.node.init);
  2142  
  2143        if (constantExpressionType != null) {
  2144          return [ref, constantExpressionType];
  2145        }
  2146      } // function handleChange() {}
  2147  
  2148  
  2149      if (node.type === 'FunctionName' && node.node.type === 'FunctionDeclaration') {
  2150        return [ref, 'function'];
  2151      } // class Foo {}
  2152  
  2153  
  2154      if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
  2155        return [ref, 'class'];
  2156      }
  2157  
  2158      return null;
  2159    }).filter(Boolean);
  2160  
  2161    function isUsedOutsideOfHook(ref) {
  2162      var foundWriteExpr = false;
  2163  
  2164      for (var i = 0; i < ref.references.length; i++) {
  2165        var reference = ref.references[i];
  2166  
  2167        if (reference.writeExpr) {
  2168          if (foundWriteExpr) {
  2169            // Two writes to the same function.
  2170            return true;
  2171          } else {
  2172            // Ignore first write as it's not usage.
  2173            foundWriteExpr = true;
  2174            continue;
  2175          }
  2176        }
  2177  
  2178        var currentScope = reference.from;
  2179  
  2180        while (currentScope !== scope && currentScope != null) {
  2181          currentScope = currentScope.upper;
  2182        }
  2183  
  2184        if (currentScope !== scope) {
  2185          // This reference is outside the Hook callback.
  2186          // It can only be legit if it's the deps array.
  2187          if (!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)) {
  2188            return true;
  2189          }
  2190        }
  2191      }
  2192  
  2193      return false;
  2194    }
  2195  
  2196    return constructions.map(function (_ref11) {
  2197      var ref = _ref11[0],
  2198          depType = _ref11[1];
  2199      return {
  2200        construction: ref.defs[0],
  2201        depType: depType,
  2202        isUsedOutsideOfHook: isUsedOutsideOfHook(ref)
  2203      };
  2204    });
  2205  }
  2206  /**
  2207   * Assuming () means the passed/returned node:
  2208   * (props) => (props)
  2209   * props.(foo) => (props.foo)
  2210   * props.foo.(bar) => (props).foo.bar
  2211   * props.foo.bar.(baz) => (props).foo.bar.baz
  2212   */
  2213  
  2214  
  2215  function getDependency(node) {
  2216    if ((node.parent.type === 'MemberExpression' || node.parent.type === 'OptionalMemberExpression') && node.parent.object === node && node.parent.property.name !== 'current' && !node.parent.computed && !(node.parent.parent != null && (node.parent.parent.type === 'CallExpression' || node.parent.parent.type === 'OptionalCallExpression') && node.parent.parent.callee === node.parent)) {
  2217      return getDependency(node.parent);
  2218    } else if ( // Note: we don't check OptionalMemberExpression because it can't be LHS.
  2219    node.type === 'MemberExpression' && node.parent && node.parent.type === 'AssignmentExpression' && node.parent.left === node) {
  2220      return node.object;
  2221    } else {
  2222      return node;
  2223    }
  2224  }
  2225  /**
  2226   * Mark a node as either optional or required.
  2227   * Note: If the node argument is an OptionalMemberExpression, it doesn't necessarily mean it is optional.
  2228   * It just means there is an optional member somewhere inside.
  2229   * This particular node might still represent a required member, so check .optional field.
  2230   */
  2231  
  2232  
  2233  function markNode(node, optionalChains, result) {
  2234    if (optionalChains) {
  2235      if (node.optional) {
  2236        // We only want to consider it optional if *all* usages were optional.
  2237        if (!optionalChains.has(result)) {
  2238          // Mark as (maybe) optional. If there's a required usage, this will be overridden.
  2239          optionalChains.set(result, true);
  2240        }
  2241      } else {
  2242        // Mark as required.
  2243        optionalChains.set(result, false);
  2244      }
  2245    }
  2246  }
  2247  /**
  2248   * Assuming () means the passed node.
  2249   * (foo) -> 'foo'
  2250   * foo(.)bar -> 'foo.bar'
  2251   * foo.bar(.)baz -> 'foo.bar.baz'
  2252   * Otherwise throw.
  2253   */
  2254  
  2255  
  2256  function analyzePropertyChain(node, optionalChains) {
  2257    if (node.type === 'Identifier' || node.type === 'JSXIdentifier') {
  2258      var result = node.name;
  2259  
  2260      if (optionalChains) {
  2261        // Mark as required.
  2262        optionalChains.set(result, false);
  2263      }
  2264  
  2265      return result;
  2266    } else if (node.type === 'MemberExpression' && !node.computed) {
  2267      var object = analyzePropertyChain(node.object, optionalChains);
  2268      var property = analyzePropertyChain(node.property, null);
  2269  
  2270      var _result = object + "." + property;
  2271  
  2272      markNode(node, optionalChains, _result);
  2273      return _result;
  2274    } else if (node.type === 'OptionalMemberExpression' && !node.computed) {
  2275      var _object = analyzePropertyChain(node.object, optionalChains);
  2276  
  2277      var _property = analyzePropertyChain(node.property, null);
  2278  
  2279      var _result2 = _object + "." + _property;
  2280  
  2281      markNode(node, optionalChains, _result2);
  2282      return _result2;
  2283    } else if (node.type === 'ChainExpression' && !node.computed) {
  2284      var expression = node.expression;
  2285  
  2286      if (expression.type === 'CallExpression') {
  2287        throw new Error("Unsupported node type: " + expression.type);
  2288      }
  2289  
  2290      var _object2 = analyzePropertyChain(expression.object, optionalChains);
  2291  
  2292      var _property2 = analyzePropertyChain(expression.property, null);
  2293  
  2294      var _result3 = _object2 + "." + _property2;
  2295  
  2296      markNode(expression, optionalChains, _result3);
  2297      return _result3;
  2298    } else {
  2299      throw new Error("Unsupported node type: " + node.type);
  2300    }
  2301  }
  2302  
  2303  function getNodeWithoutReactNamespace(node, options) {
  2304    if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'React' && node.property.type === 'Identifier' && !node.computed) {
  2305      return node.property;
  2306    }
  2307  
  2308    return node;
  2309  } // What's the index of callback that needs to be analyzed for a given Hook?
  2310  // -1 if it's not a Hook we care about (e.g. useState).
  2311  // 0 for useEffect/useMemo/useCallback(fn).
  2312  // 1 for useImperativeHandle(ref, fn).
  2313  // For additionally configured Hooks, assume that they're like useEffect (0).
  2314  
  2315  
  2316  function getReactiveHookCallbackIndex(calleeNode, options) {
  2317    var node = getNodeWithoutReactNamespace(calleeNode);
  2318  
  2319    if (node.type !== 'Identifier') {
  2320      return -1;
  2321    }
  2322  
  2323    switch (node.name) {
  2324      case 'useEffect':
  2325      case 'useLayoutEffect':
  2326      case 'useCallback':
  2327      case 'useMemo':
  2328        // useEffect(fn)
  2329        return 0;
  2330  
  2331      case 'useImperativeHandle':
  2332        // useImperativeHandle(ref, fn)
  2333        return 1;
  2334  
  2335      default:
  2336        if (node === calleeNode && options && options.additionalHooks) {
  2337          // Allow the user to provide a regular expression which enables the lint to
  2338          // target custom reactive hooks.
  2339          var name;
  2340  
  2341          try {
  2342            name = analyzePropertyChain(node, null);
  2343          } catch (error) {
  2344            if (/Unsupported node type/.test(error.message)) {
  2345              return 0;
  2346            } else {
  2347              throw error;
  2348            }
  2349          }
  2350  
  2351          return options.additionalHooks.test(name) ? 0 : -1;
  2352        } else {
  2353          return -1;
  2354        }
  2355  
  2356    }
  2357  }
  2358  /**
  2359   * ESLint won't assign node.parent to references from context.getScope()
  2360   *
  2361   * So instead we search for the node from an ancestor assigning node.parent
  2362   * as we go. This mutates the AST.
  2363   *
  2364   * This traversal is:
  2365   * - optimized by only searching nodes with a range surrounding our target node
  2366   * - agnostic to AST node types, it looks for `{ type: string, ... }`
  2367   */
  2368  
  2369  
  2370  function fastFindReferenceWithParent(start, target) {
  2371    var queue = [start];
  2372    var item = null;
  2373  
  2374    while (queue.length) {
  2375      item = queue.shift();
  2376  
  2377      if (isSameIdentifier(item, target)) {
  2378        return item;
  2379      }
  2380  
  2381      if (!isAncestorNodeOf(item, target)) {
  2382        continue;
  2383      }
  2384  
  2385      for (var _i4 = 0, _Object$entries = Object.entries(item); _i4 < _Object$entries.length; _i4++) {
  2386        var _Object$entries$_i = _Object$entries[_i4],
  2387            key = _Object$entries$_i[0],
  2388            value = _Object$entries$_i[1];
  2389  
  2390        if (key === 'parent') {
  2391          continue;
  2392        }
  2393  
  2394        if (isNodeLike(value)) {
  2395          value.parent = item;
  2396          queue.push(value);
  2397        } else if (Array.isArray(value)) {
  2398          value.forEach(function (val) {
  2399            if (isNodeLike(val)) {
  2400              val.parent = item;
  2401              queue.push(val);
  2402            }
  2403          });
  2404        }
  2405      }
  2406    }
  2407  
  2408    return null;
  2409  }
  2410  
  2411  function joinEnglish(arr) {
  2412    var s = '';
  2413  
  2414    for (var i = 0; i < arr.length; i++) {
  2415      s += arr[i];
  2416  
  2417      if (i === 0 && arr.length === 2) {
  2418        s += ' and ';
  2419      } else if (i === arr.length - 2 && arr.length > 2) {
  2420        s += ', and ';
  2421      } else if (i < arr.length - 1) {
  2422        s += ', ';
  2423      }
  2424    }
  2425  
  2426    return s;
  2427  }
  2428  
  2429  function isNodeLike(val) {
  2430    return typeof val === 'object' && val !== null && !Array.isArray(val) && typeof val.type === 'string';
  2431  }
  2432  
  2433  function isSameIdentifier(a, b) {
  2434    return (a.type === 'Identifier' || a.type === 'JSXIdentifier') && a.type === b.type && a.name === b.name && a.range[0] === b.range[0] && a.range[1] === b.range[1];
  2435  }
  2436  
  2437  function isAncestorNodeOf(a, b) {
  2438    return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
  2439  }
  2440  
  2441  var configs = {
  2442    recommended: {
  2443      plugins: ['react-hooks'],
  2444      rules: {
  2445        'react-hooks/rules-of-hooks': 'error',
  2446        'react-hooks/exhaustive-deps': 'warn'
  2447      }
  2448    }
  2449  };
  2450  var rules = {
  2451    'rules-of-hooks': RulesOfHooks,
  2452    'exhaustive-deps': ExhaustiveDeps
  2453  };
  2454  
  2455  exports.configs = configs;
  2456  exports.rules = rules;
  2457    })();
  2458  }