eslint-plugin-react-hooks 4.0.8 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,11 +13,81 @@ if (process.env.NODE_ENV !== "production") {
13
13
  (function() {
14
14
  'use strict';
15
15
 
16
+ function _unsupportedIterableToArray(o, minLen) {
17
+ if (!o) return;
18
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
19
+ var n = Object.prototype.toString.call(o).slice(8, -1);
20
+ if (n === "Object" && o.constructor) n = o.constructor.name;
21
+ if (n === "Map" || n === "Set") return Array.from(o);
22
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
23
+ }
24
+
25
+ function _arrayLikeToArray(arr, len) {
26
+ if (len == null || len > arr.length) len = arr.length;
27
+
28
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
29
+
30
+ return arr2;
31
+ }
32
+
33
+ function _createForOfIteratorHelper(o, allowArrayLike) {
34
+ var it;
35
+
36
+ if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) {
37
+ if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
38
+ if (it) o = it;
39
+ var i = 0;
40
+
41
+ var F = function () {};
42
+
43
+ return {
44
+ s: F,
45
+ n: function () {
46
+ if (i >= o.length) return {
47
+ done: true
48
+ };
49
+ return {
50
+ done: false,
51
+ value: o[i++]
52
+ };
53
+ },
54
+ e: function (e) {
55
+ throw e;
56
+ },
57
+ f: F
58
+ };
59
+ }
60
+
61
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
62
+ }
63
+
64
+ var normalCompletion = true,
65
+ didErr = false,
66
+ err;
67
+ return {
68
+ s: function () {
69
+ it = o[Symbol.iterator]();
70
+ },
71
+ n: function () {
72
+ var step = it.next();
73
+ normalCompletion = step.done;
74
+ return step;
75
+ },
76
+ e: function (e) {
77
+ didErr = true;
78
+ err = e;
79
+ },
80
+ f: function () {
81
+ try {
82
+ if (!normalCompletion && it.return != null) it.return();
83
+ } finally {
84
+ if (didErr) throw err;
85
+ }
86
+ }
87
+ };
88
+ }
89
+
16
90
  /* eslint-disable no-for-of-loops/no-for-of-loops */
17
- /**
18
- * Catch all identifiers that begin with "use" followed by an uppercase Latin
19
- * character to exclude identifiers like "user".
20
- */
21
91
 
22
92
  function isHookName(s) {
23
93
  return /^use[A-Z0-9].*$/.test(s);
@@ -165,28 +235,19 @@ var RulesOfHooks = {
165
235
  if (pathList.has(segment.id)) {
166
236
  var pathArray = [].concat(pathList);
167
237
  var cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
168
- var _iteratorNormalCompletion = true;
169
- var _didIteratorError = false;
170
- var _iteratorError = undefined;
238
+
239
+ var _iterator = _createForOfIteratorHelper(cyclicSegments),
240
+ _step;
171
241
 
172
242
  try {
173
- for (var _iterator = cyclicSegments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
243
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
174
244
  var cyclicSegment = _step.value;
175
245
  cyclic.add(cyclicSegment);
176
246
  }
177
247
  } catch (err) {
178
- _didIteratorError = true;
179
- _iteratorError = err;
248
+ _iterator.e(err);
180
249
  } finally {
181
- try {
182
- if (!_iteratorNormalCompletion && _iterator.return != null) {
183
- _iterator.return();
184
- }
185
- } finally {
186
- if (_didIteratorError) {
187
- throw _iteratorError;
188
- }
189
- }
250
+ _iterator.f();
190
251
  }
191
252
 
192
253
  return 0;
@@ -205,28 +266,19 @@ var RulesOfHooks = {
205
266
  paths = 1;
206
267
  } else {
207
268
  paths = 0;
208
- var _iteratorNormalCompletion2 = true;
209
- var _didIteratorError2 = false;
210
- var _iteratorError2 = undefined;
269
+
270
+ var _iterator2 = _createForOfIteratorHelper(segment.prevSegments),
271
+ _step2;
211
272
 
212
273
  try {
213
- for (var _iterator2 = segment.prevSegments[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
274
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
214
275
  var prevSegment = _step2.value;
215
276
  paths += countPathsFromStart(prevSegment, pathList);
216
277
  }
217
278
  } catch (err) {
218
- _didIteratorError2 = true;
219
- _iteratorError2 = err;
279
+ _iterator2.e(err);
220
280
  } finally {
221
- try {
222
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
223
- _iterator2.return();
224
- }
225
- } finally {
226
- if (_didIteratorError2) {
227
- throw _iteratorError2;
228
- }
229
- }
281
+ _iterator2.f();
230
282
  }
231
283
  } // If our segment is reachable then there should be at least one path
232
284
  // to it from the start of our code path.
@@ -272,28 +324,19 @@ var RulesOfHooks = {
272
324
  if (pathList.has(segment.id)) {
273
325
  var pathArray = Array.from(pathList);
274
326
  var cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
275
- var _iteratorNormalCompletion3 = true;
276
- var _didIteratorError3 = false;
277
- var _iteratorError3 = undefined;
327
+
328
+ var _iterator3 = _createForOfIteratorHelper(cyclicSegments),
329
+ _step3;
278
330
 
279
331
  try {
280
- for (var _iterator3 = cyclicSegments[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
332
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
281
333
  var cyclicSegment = _step3.value;
282
334
  cyclic.add(cyclicSegment);
283
335
  }
284
336
  } catch (err) {
285
- _didIteratorError3 = true;
286
- _iteratorError3 = err;
337
+ _iterator3.e(err);
287
338
  } finally {
288
- try {
289
- if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
290
- _iterator3.return();
291
- }
292
- } finally {
293
- if (_didIteratorError3) {
294
- throw _iteratorError3;
295
- }
296
- }
339
+ _iterator3.f();
297
340
  }
298
341
 
299
342
  return 0;
@@ -312,28 +355,19 @@ var RulesOfHooks = {
312
355
  paths = 1;
313
356
  } else {
314
357
  paths = 0;
315
- var _iteratorNormalCompletion4 = true;
316
- var _didIteratorError4 = false;
317
- var _iteratorError4 = undefined;
358
+
359
+ var _iterator4 = _createForOfIteratorHelper(segment.nextSegments),
360
+ _step4;
318
361
 
319
362
  try {
320
- for (var _iterator4 = segment.nextSegments[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
363
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
321
364
  var nextSegment = _step4.value;
322
365
  paths += countPathsToEnd(nextSegment, pathList);
323
366
  }
324
367
  } catch (err) {
325
- _didIteratorError4 = true;
326
- _iteratorError4 = err;
368
+ _iterator4.e(err);
327
369
  } finally {
328
- try {
329
- if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
330
- _iterator4.return();
331
- }
332
- } finally {
333
- if (_didIteratorError4) {
334
- throw _iteratorError4;
335
- }
336
- }
370
+ _iterator4.f();
337
371
  }
338
372
  }
339
373
 
@@ -384,12 +418,12 @@ var RulesOfHooks = {
384
418
  length = 1;
385
419
  } else {
386
420
  length = Infinity;
387
- var _iteratorNormalCompletion5 = true;
388
- var _didIteratorError5 = false;
389
- var _iteratorError5 = undefined;
421
+
422
+ var _iterator5 = _createForOfIteratorHelper(segment.prevSegments),
423
+ _step5;
390
424
 
391
425
  try {
392
- for (var _iterator5 = segment.prevSegments[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
426
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
393
427
  var prevSegment = _step5.value;
394
428
  var prevLength = shortestPathLengthToStart(prevSegment);
395
429
 
@@ -398,18 +432,9 @@ var RulesOfHooks = {
398
432
  }
399
433
  }
400
434
  } catch (err) {
401
- _didIteratorError5 = true;
402
- _iteratorError5 = err;
435
+ _iterator5.e(err);
403
436
  } finally {
404
- try {
405
- if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
406
- _iterator5.return();
407
- }
408
- } finally {
409
- if (_didIteratorError5) {
410
- throw _iteratorError5;
411
- }
412
- }
437
+ _iterator5.f();
413
438
  }
414
439
 
415
440
  length += 1;
@@ -440,12 +465,12 @@ var RulesOfHooks = {
440
465
  // after calling `visitSegment()`.
441
466
 
442
467
  var shortestFinalPathLength = Infinity;
443
- var _iteratorNormalCompletion6 = true;
444
- var _didIteratorError6 = false;
445
- var _iteratorError6 = undefined;
468
+
469
+ var _iterator6 = _createForOfIteratorHelper(codePath.finalSegments),
470
+ _step6;
446
471
 
447
472
  try {
448
- for (var _iterator6 = codePath.finalSegments[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
473
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
449
474
  var finalSegment = _step6.value;
450
475
 
451
476
  if (!finalSegment.reachable) {
@@ -461,26 +486,16 @@ var RulesOfHooks = {
461
486
  // if not.
462
487
 
463
488
  } catch (err) {
464
- _didIteratorError6 = true;
465
- _iteratorError6 = err;
489
+ _iterator6.e(err);
466
490
  } finally {
467
- try {
468
- if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
469
- _iterator6.return();
470
- }
471
- } finally {
472
- if (_didIteratorError6) {
473
- throw _iteratorError6;
474
- }
475
- }
491
+ _iterator6.f();
476
492
  }
477
493
 
478
- var _iteratorNormalCompletion7 = true;
479
- var _didIteratorError7 = false;
480
- var _iteratorError7 = undefined;
494
+ var _iterator7 = _createForOfIteratorHelper(reactHooksMap),
495
+ _step7;
481
496
 
482
497
  try {
483
- for (var _iterator7 = reactHooksMap[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
498
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
484
499
  var _step7$value = _step7.value,
485
500
  segment = _step7$value[0],
486
501
  reactHooks = _step7$value[1];
@@ -536,12 +551,12 @@ var RulesOfHooks = {
536
551
  var pathsFromStartToEnd = countPathsFromStart(segment) * countPathsToEnd(segment); // Is this hook a part of a cyclic segment?
537
552
 
538
553
  var cycled = cyclic.has(segment.id);
539
- var _iteratorNormalCompletion8 = true;
540
- var _didIteratorError8 = false;
541
- var _iteratorError8 = undefined;
554
+
555
+ var _iterator8 = _createForOfIteratorHelper(reactHooks),
556
+ _step8;
542
557
 
543
558
  try {
544
- for (var _iterator8 = reactHooks[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {
559
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
545
560
  var hook = _step8.value;
546
561
 
547
562
  // Report an error if a hook may be called more then once.
@@ -610,33 +625,15 @@ var RulesOfHooks = {
610
625
  }
611
626
  }
612
627
  } catch (err) {
613
- _didIteratorError8 = true;
614
- _iteratorError8 = err;
628
+ _iterator8.e(err);
615
629
  } finally {
616
- try {
617
- if (!_iteratorNormalCompletion8 && _iterator8.return != null) {
618
- _iterator8.return();
619
- }
620
- } finally {
621
- if (_didIteratorError8) {
622
- throw _iteratorError8;
623
- }
624
- }
630
+ _iterator8.f();
625
631
  }
626
632
  }
627
633
  } catch (err) {
628
- _didIteratorError7 = true;
629
- _iteratorError7 = err;
634
+ _iterator7.e(err);
630
635
  } finally {
631
- try {
632
- if (!_iteratorNormalCompletion7 && _iterator7.return != null) {
633
- _iterator7.return();
634
- }
635
- } finally {
636
- if (_didIteratorError7) {
637
- throw _iteratorError7;
638
- }
639
- }
636
+ _iterator7.f();
640
637
  }
641
638
  },
642
639
  // Missed opportunity...We could visit all `Identifier`s instead of all
@@ -718,7 +715,6 @@ function last(array) {
718
715
  }
719
716
 
720
717
  /* eslint-disable no-for-of-loops/no-for-of-loops */
721
-
722
718
  var ExhaustiveDeps = {
723
719
  meta: {
724
720
  type: 'suggestion',
@@ -784,139 +780,6 @@ var ExhaustiveDeps = {
784
780
  return result;
785
781
  };
786
782
  }
787
-
788
- return {
789
- CallExpression: visitCallExpression
790
- };
791
-
792
- function visitCallExpression(node) {
793
- var callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
794
-
795
- if (callbackIndex === -1) {
796
- // Not a React Hook call that needs deps.
797
- return;
798
- }
799
-
800
- var callback = node.arguments[callbackIndex];
801
- var reactiveHook = node.callee;
802
- var reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
803
- var declaredDependenciesNode = node.arguments[callbackIndex + 1];
804
- var isEffect = /Effect($|[^a-z])/g.test(reactiveHookName); // Check the declared dependencies for this reactive hook. If there is no
805
- // second argument then the reactive callback will re-run on every render.
806
- // So no need to check for dependency inclusion.
807
-
808
- if (!declaredDependenciesNode && !isEffect) {
809
- // These are only used for optimization.
810
- if (reactiveHookName === 'useMemo' || reactiveHookName === 'useCallback') {
811
- // TODO: Can this have a suggestion?
812
- reportProblem({
813
- node: reactiveHook,
814
- message: "React Hook " + reactiveHookName + " does nothing when called with " + "only one argument. Did you forget to pass an array of " + "dependencies?"
815
- });
816
- }
817
-
818
- return;
819
- }
820
-
821
- switch (callback.type) {
822
- case 'FunctionExpression':
823
- case 'ArrowFunctionExpression':
824
- visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
825
- return;
826
- // Handled
827
-
828
- case 'Identifier':
829
- if (!declaredDependenciesNode) {
830
- // No deps, no problems.
831
- return; // Handled
832
- } // The function passed as a callback is not written inline.
833
- // But perhaps it's in the dependencies array?
834
-
835
-
836
- if (declaredDependenciesNode.elements && declaredDependenciesNode.elements.some(function (el) {
837
- return el && el.type === 'Identifier' && el.name === callback.name;
838
- })) {
839
- // If it's already in the list of deps, we don't care because
840
- // this is valid regardless.
841
- return; // Handled
842
- } // We'll do our best effort to find it, complain otherwise.
843
-
844
-
845
- var variable = context.getScope().set.get(callback.name);
846
-
847
- if (variable == null || variable.defs == null) {
848
- // If it's not in scope, we don't care.
849
- return; // Handled
850
- } // The function passed as a callback is not written inline.
851
- // But it's defined somewhere in the render scope.
852
- // We'll do our best effort to find and check it, complain otherwise.
853
-
854
-
855
- var def = variable.defs[0];
856
-
857
- if (!def || !def.node) {
858
- break; // Unhandled
859
- }
860
-
861
- if (def.type !== 'Variable' && def.type !== 'FunctionName') {
862
- // Parameter or an unusual pattern. Bail out.
863
- break; // Unhandled
864
- }
865
-
866
- switch (def.node.type) {
867
- case 'FunctionDeclaration':
868
- // useEffect(() => { ... }, []);
869
- visitFunctionWithDependencies(def.node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
870
- return;
871
- // Handled
872
-
873
- case 'VariableDeclarator':
874
- var init = def.node.init;
875
-
876
- if (!init) {
877
- break; // Unhandled
878
- }
879
-
880
- switch (init.type) {
881
- // const effectBody = () => {...};
882
- // useEffect(effectBody, []);
883
- case 'ArrowFunctionExpression':
884
- case 'FunctionExpression':
885
- // We can inspect this function as if it were inline.
886
- visitFunctionWithDependencies(init, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
887
- return;
888
- // Handled
889
- }
890
-
891
- break;
892
- // Unhandled
893
- }
894
-
895
- break;
896
- // Unhandled
897
-
898
- default:
899
- // useEffect(generateEffectBody(), []);
900
- reportProblem({
901
- node: reactiveHook,
902
- message: "React Hook " + reactiveHookName + " received a function whose dependencies " + "are unknown. Pass an inline function instead."
903
- });
904
- return;
905
- // Handled
906
- } // Something unusual. Fall back to suggesting to add the body itself as a dep.
907
-
908
-
909
- reportProblem({
910
- node: reactiveHook,
911
- message: "React Hook " + reactiveHookName + " has a missing dependency: '" + callback.name + "'. " + "Either include it or remove the dependency array.",
912
- suggest: [{
913
- desc: "Update the dependencies array to be: [" + callback.name + "]",
914
- fix: function (fixer) {
915
- return fixer.replaceText(declaredDependenciesNode, "[" + callback.name + "]");
916
- }
917
- }]
918
- });
919
- }
920
783
  /**
921
784
  * Visitor for both function expressions and arrow function expressions.
922
785
  */
@@ -926,7 +789,7 @@ var ExhaustiveDeps = {
926
789
  if (isEffect && node.async) {
927
790
  reportProblem({
928
791
  node: node,
929
- 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://fb.me/react-hooks-data-fetching'
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'
930
793
  });
931
794
  } // Get the current scope.
932
795
 
@@ -1072,6 +935,14 @@ var ExhaustiveDeps = {
1072
935
  return false;
1073
936
  }
1074
937
  }
938
+ } else if (name === 'useTransition') {
939
+ if (id.type === 'ArrayPattern' && Array.isArray(resolved.identifiers)) {
940
+ // Is first tuple value the same reference we're checking?
941
+ if (id.elements[0] === resolved.identifiers[0]) {
942
+ // Setter is stable.
943
+ return true;
944
+ }
945
+ }
1075
946
  } // By default assume it's dynamic.
1076
947
 
1077
948
 
@@ -1170,12 +1041,11 @@ var ExhaustiveDeps = {
1170
1041
  gatherDependenciesRecursively(scope);
1171
1042
 
1172
1043
  function gatherDependenciesRecursively(currentScope) {
1173
- var _iteratorNormalCompletion = true;
1174
- var _didIteratorError = false;
1175
- var _iteratorError = undefined;
1044
+ var _iterator = _createForOfIteratorHelper(currentScope.references),
1045
+ _step;
1176
1046
 
1177
1047
  try {
1178
- for (var _iterator = currentScope.references[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
1048
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1179
1049
  var reference = _step.value;
1180
1050
 
1181
1051
  // If this reference is not resolved or it is not declared in a pure
@@ -1204,7 +1074,7 @@ var ExhaustiveDeps = {
1204
1074
  });
1205
1075
  }
1206
1076
 
1207
- if (dependencyNode.parent.type === 'TSTypeQuery') {
1077
+ if (dependencyNode.parent.type === 'TSTypeQuery' || dependencyNode.parent.type === 'TSTypeReference') {
1208
1078
  continue;
1209
1079
  }
1210
1080
 
@@ -1238,42 +1108,23 @@ var ExhaustiveDeps = {
1238
1108
  }
1239
1109
  }
1240
1110
  } catch (err) {
1241
- _didIteratorError = true;
1242
- _iteratorError = err;
1111
+ _iterator.e(err);
1243
1112
  } finally {
1244
- try {
1245
- if (!_iteratorNormalCompletion && _iterator.return != null) {
1246
- _iterator.return();
1247
- }
1248
- } finally {
1249
- if (_didIteratorError) {
1250
- throw _iteratorError;
1251
- }
1252
- }
1113
+ _iterator.f();
1253
1114
  }
1254
1115
 
1255
- var _iteratorNormalCompletion2 = true;
1256
- var _didIteratorError2 = false;
1257
- var _iteratorError2 = undefined;
1116
+ var _iterator2 = _createForOfIteratorHelper(currentScope.childScopes),
1117
+ _step2;
1258
1118
 
1259
1119
  try {
1260
- for (var _iterator2 = currentScope.childScopes[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
1120
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1261
1121
  var childScope = _step2.value;
1262
1122
  gatherDependenciesRecursively(childScope);
1263
1123
  }
1264
1124
  } catch (err) {
1265
- _didIteratorError2 = true;
1266
- _iteratorError2 = err;
1125
+ _iterator2.e(err);
1267
1126
  } finally {
1268
- try {
1269
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
1270
- _iterator2.return();
1271
- }
1272
- } finally {
1273
- if (_didIteratorError2) {
1274
- throw _iteratorError2;
1275
- }
1276
- }
1127
+ _iterator2.f();
1277
1128
  }
1278
1129
  } // Warn about accessing .current in cleanup effects.
1279
1130
 
@@ -1473,8 +1324,8 @@ var ExhaustiveDeps = {
1473
1324
 
1474
1325
  var maybeID = declaredDependencyNode;
1475
1326
 
1476
- while (maybeID.type === 'MemberExpression' || maybeID.type === 'OptionalMemberExpression') {
1477
- maybeID = maybeID.object;
1327
+ while (maybeID.type === 'MemberExpression' || maybeID.type === 'OptionalMemberExpression' || maybeID.type === 'ChainExpression') {
1328
+ maybeID = maybeID.object || maybeID.expression.object;
1478
1329
  }
1479
1330
 
1480
1331
  var isDeclaredInComponent = !componentScope.through.some(function (ref) {
@@ -1508,38 +1359,44 @@ var ExhaustiveDeps = {
1508
1359
  var problemCount = duplicateDependencies.size + missingDependencies.size + unnecessaryDependencies.size;
1509
1360
 
1510
1361
  if (problemCount === 0) {
1511
- // If nothing else to report, check if some callbacks
1512
- // are bare and would invalidate on every render.
1513
- var bareFunctions = scanForDeclaredBareFunctions({
1362
+ // If nothing else to report, check if some dependencies would
1363
+ // invalidate on every render.
1364
+ var constructions = scanForConstructions({
1514
1365
  declaredDependencies: declaredDependencies,
1515
1366
  declaredDependenciesNode: declaredDependenciesNode,
1516
1367
  componentScope: componentScope,
1517
1368
  scope: scope
1518
1369
  });
1519
- bareFunctions.forEach(function (_ref4) {
1520
- var fn = _ref4.fn,
1521
- suggestUseCallback = _ref4.suggestUseCallback;
1522
- var message = "The '" + fn.name.name + "' function makes the dependencies of " + (reactiveHookName + " Hook (at line " + declaredDependenciesNode.loc.start.line + ") ") + "change on every render.";
1523
-
1524
- if (suggestUseCallback) {
1525
- message += " To fix this, " + ("wrap the '" + fn.name.name + "' definition into its own useCallback() Hook.");
1526
- } else {
1527
- message += " Move it inside the " + reactiveHookName + " callback. " + ("Alternatively, wrap the '" + fn.name.name + "' definition into its own useCallback() Hook.");
1528
- }
1529
-
1530
- var suggest; // Only handle the simple case: arrow functions.
1370
+ constructions.forEach(function (_ref4) {
1371
+ var construction = _ref4.construction,
1372
+ isUsedOutsideOfHook = _ref4.isUsedOutsideOfHook,
1373
+ depType = _ref4.depType;
1374
+ var wrapperHook = depType === 'function' ? 'useCallback' : 'useMemo';
1375
+ var constructionType = depType === 'function' ? 'definition' : 'initialization';
1376
+ var defaultAdvice = "wrap the " + constructionType + " of '" + construction.name.name + "' in its own " + wrapperHook + "() Hook.";
1377
+ var advice = isUsedOutsideOfHook ? "To fix this, " + defaultAdvice : "Move it inside the " + reactiveHookName + " callback. Alternatively, " + defaultAdvice;
1378
+ var causation = depType === 'conditional' || depType === 'logical expression' ? 'could make' : 'makes';
1379
+ var message = "The '" + construction.name.name + "' " + depType + " " + causation + " the dependencies of " + (reactiveHookName + " Hook (at line " + declaredDependenciesNode.loc.start.line + ") ") + ("change on every render. " + advice);
1380
+ var suggest; // Only handle the simple case of variable assignments.
1531
1381
  // Wrapping function declarations can mess up hoisting.
1532
1382
 
1533
- if (suggestUseCallback && fn.type === 'Variable') {
1383
+ if (isUsedOutsideOfHook && construction.type === 'Variable' && // Objects may be mutated ater construction, which would make this
1384
+ // fix unsafe. Functions _probably_ won't be mutated, so we'll
1385
+ // allow this fix for them.
1386
+ depType === 'function') {
1534
1387
  suggest = [{
1535
- desc: "Wrap the '" + fn.name.name + "' definition into its own useCallback() Hook.",
1388
+ desc: "Wrap the " + constructionType + " of '" + construction.name.name + "' in its own " + wrapperHook + "() Hook.",
1536
1389
  fix: function (fixer) {
1390
+ var _ref5 = wrapperHook === 'useMemo' ? ["useMemo(() => { return ", '; })'] : ['useCallback(', ')'],
1391
+ before = _ref5[0],
1392
+ after = _ref5[1];
1393
+
1537
1394
  return [// TODO: also add an import?
1538
- fixer.insertTextBefore(fn.node.init, 'useCallback('), // TODO: ideally we'd gather deps here but it would require
1395
+ fixer.insertTextBefore(construction.node.init, before), // TODO: ideally we'd gather deps here but it would require
1539
1396
  // restructuring the rule code. This will cause a new lint
1540
1397
  // error to appear immediately for useCallback. Note we're
1541
1398
  // not adding [] because would that changes semantics.
1542
- fixer.insertTextAfter(fn.node.init, ')')];
1399
+ fixer.insertTextAfter(construction.node.init, after)];
1543
1400
  }
1544
1401
  }];
1545
1402
  } // TODO: What if the function needs to change on every render anyway?
@@ -1548,7 +1405,7 @@ var ExhaustiveDeps = {
1548
1405
 
1549
1406
  reportProblem({
1550
1407
  // TODO: Why not report this at the dependency site?
1551
- node: fn.node,
1408
+ node: construction.node,
1552
1409
  message: message,
1553
1410
  suggest: suggest
1554
1411
  });
@@ -1844,15 +1701,148 @@ var ExhaustiveDeps = {
1844
1701
  }]
1845
1702
  });
1846
1703
  }
1704
+
1705
+ function visitCallExpression(node) {
1706
+ var callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
1707
+
1708
+ if (callbackIndex === -1) {
1709
+ // Not a React Hook call that needs deps.
1710
+ return;
1711
+ }
1712
+
1713
+ var callback = node.arguments[callbackIndex];
1714
+ var reactiveHook = node.callee;
1715
+ var reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
1716
+ var declaredDependenciesNode = node.arguments[callbackIndex + 1];
1717
+ var isEffect = /Effect($|[^a-z])/g.test(reactiveHookName); // Check the declared dependencies for this reactive hook. If there is no
1718
+ // second argument then the reactive callback will re-run on every render.
1719
+ // So no need to check for dependency inclusion.
1720
+
1721
+ if (!declaredDependenciesNode && !isEffect) {
1722
+ // These are only used for optimization.
1723
+ if (reactiveHookName === 'useMemo' || reactiveHookName === 'useCallback') {
1724
+ // TODO: Can this have a suggestion?
1725
+ reportProblem({
1726
+ node: reactiveHook,
1727
+ message: "React Hook " + reactiveHookName + " does nothing when called with " + "only one argument. Did you forget to pass an array of " + "dependencies?"
1728
+ });
1729
+ }
1730
+
1731
+ return;
1732
+ }
1733
+
1734
+ switch (callback.type) {
1735
+ case 'FunctionExpression':
1736
+ case 'ArrowFunctionExpression':
1737
+ visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
1738
+ return;
1739
+ // Handled
1740
+
1741
+ case 'Identifier':
1742
+ if (!declaredDependenciesNode) {
1743
+ // No deps, no problems.
1744
+ return; // Handled
1745
+ } // The function passed as a callback is not written inline.
1746
+ // But perhaps it's in the dependencies array?
1747
+
1748
+
1749
+ if (declaredDependenciesNode.elements && declaredDependenciesNode.elements.some(function (el) {
1750
+ return el && el.type === 'Identifier' && el.name === callback.name;
1751
+ })) {
1752
+ // If it's already in the list of deps, we don't care because
1753
+ // this is valid regardless.
1754
+ return; // Handled
1755
+ } // We'll do our best effort to find it, complain otherwise.
1756
+
1757
+
1758
+ var variable = context.getScope().set.get(callback.name);
1759
+
1760
+ if (variable == null || variable.defs == null) {
1761
+ // If it's not in scope, we don't care.
1762
+ return; // Handled
1763
+ } // The function passed as a callback is not written inline.
1764
+ // But it's defined somewhere in the render scope.
1765
+ // We'll do our best effort to find and check it, complain otherwise.
1766
+
1767
+
1768
+ var def = variable.defs[0];
1769
+
1770
+ if (!def || !def.node) {
1771
+ break; // Unhandled
1772
+ }
1773
+
1774
+ if (def.type !== 'Variable' && def.type !== 'FunctionName') {
1775
+ // Parameter or an unusual pattern. Bail out.
1776
+ break; // Unhandled
1777
+ }
1778
+
1779
+ switch (def.node.type) {
1780
+ case 'FunctionDeclaration':
1781
+ // useEffect(() => { ... }, []);
1782
+ visitFunctionWithDependencies(def.node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
1783
+ return;
1784
+ // Handled
1785
+
1786
+ case 'VariableDeclarator':
1787
+ var init = def.node.init;
1788
+
1789
+ if (!init) {
1790
+ break; // Unhandled
1791
+ }
1792
+
1793
+ switch (init.type) {
1794
+ // const effectBody = () => {...};
1795
+ // useEffect(effectBody, []);
1796
+ case 'ArrowFunctionExpression':
1797
+ case 'FunctionExpression':
1798
+ // We can inspect this function as if it were inline.
1799
+ visitFunctionWithDependencies(init, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
1800
+ return;
1801
+ // Handled
1802
+ }
1803
+
1804
+ break;
1805
+ // Unhandled
1806
+ }
1807
+
1808
+ break;
1809
+ // Unhandled
1810
+
1811
+ default:
1812
+ // useEffect(generateEffectBody(), []);
1813
+ reportProblem({
1814
+ node: reactiveHook,
1815
+ message: "React Hook " + reactiveHookName + " received a function whose dependencies " + "are unknown. Pass an inline function instead."
1816
+ });
1817
+ return;
1818
+ // Handled
1819
+ } // Something unusual. Fall back to suggesting to add the body itself as a dep.
1820
+
1821
+
1822
+ reportProblem({
1823
+ node: reactiveHook,
1824
+ message: "React Hook " + reactiveHookName + " has a missing dependency: '" + callback.name + "'. " + "Either include it or remove the dependency array.",
1825
+ suggest: [{
1826
+ desc: "Update the dependencies array to be: [" + callback.name + "]",
1827
+ fix: function (fixer) {
1828
+ return fixer.replaceText(declaredDependenciesNode, "[" + callback.name + "]");
1829
+ }
1830
+ }]
1831
+ });
1832
+ }
1833
+
1834
+ return {
1835
+ CallExpression: visitCallExpression
1836
+ };
1847
1837
  }
1848
1838
  }; // The meat of the logic.
1849
1839
 
1850
- function collectRecommendations(_ref5) {
1851
- var dependencies = _ref5.dependencies,
1852
- declaredDependencies = _ref5.declaredDependencies,
1853
- stableDependencies = _ref5.stableDependencies,
1854
- externalDependencies = _ref5.externalDependencies,
1855
- isEffect = _ref5.isEffect;
1840
+ function collectRecommendations(_ref6) {
1841
+ var dependencies = _ref6.dependencies,
1842
+ declaredDependencies = _ref6.declaredDependencies,
1843
+ stableDependencies = _ref6.stableDependencies,
1844
+ externalDependencies = _ref6.externalDependencies,
1845
+ isEffect = _ref6.isEffect;
1856
1846
  // Our primary data structure.
1857
1847
  // It is a logical representation of property chains:
1858
1848
  // `props` -> `props.foo` -> `props.foo.bar` -> `props.foo.bar.baz`
@@ -1888,8 +1878,8 @@ function collectRecommendations(_ref5) {
1888
1878
  }); // Mark all satisfied nodes.
1889
1879
  // Imagine checkmarks next to each declared dependency.
1890
1880
 
1891
- declaredDependencies.forEach(function (_ref6) {
1892
- var key = _ref6.key;
1881
+ declaredDependencies.forEach(function (_ref7) {
1882
+ var key = _ref7.key;
1893
1883
  var node = getOrCreateNodeByPath(depTree, key);
1894
1884
  node.isSatisfiedRecursively = true;
1895
1885
  });
@@ -1901,12 +1891,12 @@ function collectRecommendations(_ref5) {
1901
1891
  function getOrCreateNodeByPath(rootNode, path) {
1902
1892
  var keys = path.split('.');
1903
1893
  var node = rootNode;
1904
- var _iteratorNormalCompletion3 = true;
1905
- var _didIteratorError3 = false;
1906
- var _iteratorError3 = undefined;
1894
+
1895
+ var _iterator3 = _createForOfIteratorHelper(keys),
1896
+ _step3;
1907
1897
 
1908
1898
  try {
1909
- for (var _iterator3 = keys[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
1899
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1910
1900
  var key = _step3.value;
1911
1901
  var child = node.children.get(key);
1912
1902
 
@@ -1918,18 +1908,9 @@ function collectRecommendations(_ref5) {
1918
1908
  node = child;
1919
1909
  }
1920
1910
  } catch (err) {
1921
- _didIteratorError3 = true;
1922
- _iteratorError3 = err;
1911
+ _iterator3.e(err);
1923
1912
  } finally {
1924
- try {
1925
- if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
1926
- _iterator3.return();
1927
- }
1928
- } finally {
1929
- if (_didIteratorError3) {
1930
- throw _iteratorError3;
1931
- }
1932
- }
1913
+ _iterator3.f();
1933
1914
  }
1934
1915
 
1935
1916
  return node;
@@ -1938,12 +1919,12 @@ function collectRecommendations(_ref5) {
1938
1919
  function markAllParentsByPath(rootNode, path, fn) {
1939
1920
  var keys = path.split('.');
1940
1921
  var node = rootNode;
1941
- var _iteratorNormalCompletion4 = true;
1942
- var _didIteratorError4 = false;
1943
- var _iteratorError4 = undefined;
1922
+
1923
+ var _iterator4 = _createForOfIteratorHelper(keys),
1924
+ _step4;
1944
1925
 
1945
1926
  try {
1946
- for (var _iterator4 = keys[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
1927
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1947
1928
  var key = _step4.value;
1948
1929
  var child = node.children.get(key);
1949
1930
 
@@ -1955,18 +1936,9 @@ function collectRecommendations(_ref5) {
1955
1936
  node = child;
1956
1937
  }
1957
1938
  } catch (err) {
1958
- _didIteratorError4 = true;
1959
- _iteratorError4 = err;
1939
+ _iterator4.e(err);
1960
1940
  } finally {
1961
- try {
1962
- if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
1963
- _iterator4.return();
1964
- }
1965
- } finally {
1966
- if (_didIteratorError4) {
1967
- throw _iteratorError4;
1968
- }
1969
- }
1941
+ _iterator4.f();
1970
1942
  }
1971
1943
  } // Now we can learn which dependencies are missing or necessary.
1972
1944
 
@@ -2011,8 +1983,8 @@ function collectRecommendations(_ref5) {
2011
1983
  var suggestedDependencies = [];
2012
1984
  var unnecessaryDependencies = new Set();
2013
1985
  var duplicateDependencies = new Set();
2014
- declaredDependencies.forEach(function (_ref7) {
2015
- var key = _ref7.key;
1986
+ declaredDependencies.forEach(function (_ref8) {
1987
+ var key = _ref8.key;
2016
1988
 
2017
1989
  // Does this declared dep satisfy a real need?
2018
1990
  if (satisfyingDependencies.has(key)) {
@@ -2048,48 +2020,127 @@ function collectRecommendations(_ref5) {
2048
2020
  duplicateDependencies: duplicateDependencies,
2049
2021
  missingDependencies: missingDependencies
2050
2022
  };
2051
- } // Finds functions declared as dependencies
2023
+ } // If the node will result in constructing a referentially unique value, return
2024
+ // its human readable type name, else return null.
2025
+
2026
+
2027
+ function getConstructionExpressionType(node) {
2028
+ switch (node.type) {
2029
+ case 'ObjectExpression':
2030
+ return 'object';
2031
+
2032
+ case 'ArrayExpression':
2033
+ return 'array';
2034
+
2035
+ case 'ArrowFunctionExpression':
2036
+ case 'FunctionExpression':
2037
+ return 'function';
2038
+
2039
+ case 'ClassExpression':
2040
+ return 'class';
2041
+
2042
+ case 'ConditionalExpression':
2043
+ if (getConstructionExpressionType(node.consequent) != null || getConstructionExpressionType(node.alternate) != null) {
2044
+ return 'conditional';
2045
+ }
2046
+
2047
+ return null;
2048
+
2049
+ case 'LogicalExpression':
2050
+ if (getConstructionExpressionType(node.left) != null || getConstructionExpressionType(node.right) != null) {
2051
+ return 'logical expression';
2052
+ }
2053
+
2054
+ return null;
2055
+
2056
+ case 'JSXFragment':
2057
+ return 'JSX fragment';
2058
+
2059
+ case 'JSXElement':
2060
+ return 'JSX element';
2061
+
2062
+ case 'AssignmentExpression':
2063
+ if (getConstructionExpressionType(node.right) != null) {
2064
+ return 'assignment expression';
2065
+ }
2066
+
2067
+ return null;
2068
+
2069
+ case 'NewExpression':
2070
+ return 'object construction';
2071
+
2072
+ case 'Literal':
2073
+ if (node.value instanceof RegExp) {
2074
+ return 'regular expression';
2075
+ }
2076
+
2077
+ return null;
2078
+
2079
+ case 'TypeCastExpression':
2080
+ return getConstructionExpressionType(node.expression);
2081
+
2082
+ case 'TSAsExpression':
2083
+ return getConstructionExpressionType(node.expression);
2084
+ }
2085
+
2086
+ return null;
2087
+ } // Finds variables declared as dependencies
2052
2088
  // that would invalidate on every render.
2053
2089
 
2054
2090
 
2055
- function scanForDeclaredBareFunctions(_ref8) {
2056
- var declaredDependencies = _ref8.declaredDependencies,
2057
- declaredDependenciesNode = _ref8.declaredDependenciesNode,
2058
- componentScope = _ref8.componentScope,
2059
- scope = _ref8.scope;
2060
- var bareFunctions = declaredDependencies.map(function (_ref9) {
2061
- var key = _ref9.key;
2062
- var fnRef = componentScope.set.get(key);
2091
+ function scanForConstructions(_ref9) {
2092
+ var declaredDependencies = _ref9.declaredDependencies,
2093
+ declaredDependenciesNode = _ref9.declaredDependenciesNode,
2094
+ componentScope = _ref9.componentScope,
2095
+ scope = _ref9.scope;
2096
+ var constructions = declaredDependencies.map(function (_ref10) {
2097
+ var key = _ref10.key;
2098
+ var ref = componentScope.variables.find(function (v) {
2099
+ return v.name === key;
2100
+ });
2063
2101
 
2064
- if (fnRef == null) {
2102
+ if (ref == null) {
2065
2103
  return null;
2066
2104
  }
2067
2105
 
2068
- var fnNode = fnRef.defs[0];
2106
+ var node = ref.defs[0];
2069
2107
 
2070
- if (fnNode == null) {
2108
+ if (node == null) {
2071
2109
  return null;
2072
2110
  } // const handleChange = function () {}
2073
2111
  // const handleChange = () => {}
2112
+ // const foo = {}
2113
+ // const foo = []
2114
+ // etc.
2115
+
2074
2116
 
2117
+ if (node.type === 'Variable' && node.node.type === 'VariableDeclarator' && node.node.id.type === 'Identifier' && // Ensure this is not destructed assignment
2118
+ node.node.init != null) {
2119
+ var constantExpressionType = getConstructionExpressionType(node.node.init);
2075
2120
 
2076
- if (fnNode.type === 'Variable' && fnNode.node.type === 'VariableDeclarator' && fnNode.node.init != null && (fnNode.node.init.type === 'ArrowFunctionExpression' || fnNode.node.init.type === 'FunctionExpression')) {
2077
- return fnRef;
2121
+ if (constantExpressionType != null) {
2122
+ return [ref, constantExpressionType];
2123
+ }
2078
2124
  } // function handleChange() {}
2079
2125
 
2080
2126
 
2081
- if (fnNode.type === 'FunctionName' && fnNode.node.type === 'FunctionDeclaration') {
2082
- return fnRef;
2127
+ if (node.type === 'FunctionName' && node.node.type === 'FunctionDeclaration') {
2128
+ return [ref, 'function'];
2129
+ } // class Foo {}
2130
+
2131
+
2132
+ if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
2133
+ return [ref, 'class'];
2083
2134
  }
2084
2135
 
2085
2136
  return null;
2086
2137
  }).filter(Boolean);
2087
2138
 
2088
- function isUsedOutsideOfHook(fnRef) {
2139
+ function isUsedOutsideOfHook(ref) {
2089
2140
  var foundWriteExpr = false;
2090
2141
 
2091
- for (var i = 0; i < fnRef.references.length; i++) {
2092
- var reference = fnRef.references[i];
2142
+ for (var i = 0; i < ref.references.length; i++) {
2143
+ var reference = ref.references[i];
2093
2144
 
2094
2145
  if (reference.writeExpr) {
2095
2146
  if (foundWriteExpr) {
@@ -2120,10 +2171,13 @@ function scanForDeclaredBareFunctions(_ref8) {
2120
2171
  return false;
2121
2172
  }
2122
2173
 
2123
- return bareFunctions.map(function (fnRef) {
2174
+ return constructions.map(function (_ref11) {
2175
+ var ref = _ref11[0],
2176
+ depType = _ref11[1];
2124
2177
  return {
2125
- fn: fnRef.defs[0],
2126
- suggestUseCallback: isUsedOutsideOfHook(fnRef)
2178
+ construction: ref.defs[0],
2179
+ depType: depType,
2180
+ isUsedOutsideOfHook: isUsedOutsideOfHook(ref)
2127
2181
  };
2128
2182
  });
2129
2183
  }
@@ -2146,6 +2200,28 @@ function getDependency(node) {
2146
2200
  return node;
2147
2201
  }
2148
2202
  }
2203
+ /**
2204
+ * Mark a node as either optional or required.
2205
+ * Note: If the node argument is an OptionalMemberExpression, it doesn't necessarily mean it is optional.
2206
+ * It just means there is an optional member somewhere inside.
2207
+ * This particular node might still represent a required member, so check .optional field.
2208
+ */
2209
+
2210
+
2211
+ function markNode(node, optionalChains, result) {
2212
+ if (optionalChains) {
2213
+ if (node.optional) {
2214
+ // We only want to consider it optional if *all* usages were optional.
2215
+ if (!optionalChains.has(result)) {
2216
+ // Mark as (maybe) optional. If there's a required usage, this will be overridden.
2217
+ optionalChains.set(result, true);
2218
+ }
2219
+ } else {
2220
+ // Mark as required.
2221
+ optionalChains.set(result, false);
2222
+ }
2223
+ }
2224
+ }
2149
2225
  /**
2150
2226
  * Assuming () means the passed node.
2151
2227
  * (foo) -> 'foo'
@@ -2156,7 +2232,7 @@ function getDependency(node) {
2156
2232
 
2157
2233
 
2158
2234
  function analyzePropertyChain(node, optionalChains) {
2159
- if (node.type === 'Identifier') {
2235
+ if (node.type === 'Identifier' || node.type === 'JSXIdentifier') {
2160
2236
  var result = node.name;
2161
2237
 
2162
2238
  if (optionalChains) {
@@ -2171,11 +2247,7 @@ function analyzePropertyChain(node, optionalChains) {
2171
2247
 
2172
2248
  var _result = object + "." + property;
2173
2249
 
2174
- if (optionalChains) {
2175
- // Mark as required.
2176
- optionalChains.set(_result, false);
2177
- }
2178
-
2250
+ markNode(node, optionalChains, _result);
2179
2251
  return _result;
2180
2252
  } else if (node.type === 'OptionalMemberExpression' && !node.computed) {
2181
2253
  var _object = analyzePropertyChain(node.object, optionalChains);
@@ -2184,23 +2256,19 @@ function analyzePropertyChain(node, optionalChains) {
2184
2256
 
2185
2257
  var _result2 = _object + "." + _property;
2186
2258
 
2187
- if (optionalChains) {
2188
- // Note: OptionalMemberExpression doesn't necessarily mean this node is optional.
2189
- // It just means there is an optional member somewhere inside.
2190
- // This particular node might still represent a required member, so check .optional field.
2191
- if (node.optional) {
2192
- // We only want to consider it optional if *all* usages were optional.
2193
- if (!optionalChains.has(_result2)) {
2194
- // Mark as (maybe) optional. If there's a required usage, this will be overridden.
2195
- optionalChains.set(_result2, true);
2196
- }
2197
- } else {
2198
- // Mark as required.
2199
- optionalChains.set(_result2, false);
2200
- }
2201
- }
2202
-
2259
+ markNode(node, optionalChains, _result2);
2203
2260
  return _result2;
2261
+ } else if (node.type === 'ChainExpression' && !node.computed) {
2262
+ var expression = node.expression;
2263
+
2264
+ var _object2 = analyzePropertyChain(expression.object, optionalChains);
2265
+
2266
+ var _property2 = analyzePropertyChain(expression.property, null);
2267
+
2268
+ var _result3 = _object2 + "." + _property2;
2269
+
2270
+ markNode(expression, optionalChains, _result3);
2271
+ return _result3;
2204
2272
  } else {
2205
2273
  throw new Error("Unsupported node type: " + node.type);
2206
2274
  }
@@ -2337,7 +2405,7 @@ function isNodeLike(val) {
2337
2405
  }
2338
2406
 
2339
2407
  function isSameIdentifier(a, b) {
2340
- return a.type === 'Identifier' && a.name === b.name && a.range[0] === b.range[0] && a.range[1] === b.range[1];
2408
+ 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];
2341
2409
  }
2342
2410
 
2343
2411
  function isAncestorNodeOf(a, b) {