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
|
-
|
|
169
|
-
var
|
|
170
|
-
|
|
238
|
+
|
|
239
|
+
var _iterator = _createForOfIteratorHelper(cyclicSegments),
|
|
240
|
+
_step;
|
|
171
241
|
|
|
172
242
|
try {
|
|
173
|
-
for (
|
|
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
|
-
|
|
179
|
-
_iteratorError = err;
|
|
248
|
+
_iterator.e(err);
|
|
180
249
|
} finally {
|
|
181
|
-
|
|
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
|
-
|
|
209
|
-
var
|
|
210
|
-
|
|
269
|
+
|
|
270
|
+
var _iterator2 = _createForOfIteratorHelper(segment.prevSegments),
|
|
271
|
+
_step2;
|
|
211
272
|
|
|
212
273
|
try {
|
|
213
|
-
for (
|
|
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
|
-
|
|
219
|
-
_iteratorError2 = err;
|
|
279
|
+
_iterator2.e(err);
|
|
220
280
|
} finally {
|
|
221
|
-
|
|
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
|
-
|
|
276
|
-
var
|
|
277
|
-
|
|
327
|
+
|
|
328
|
+
var _iterator3 = _createForOfIteratorHelper(cyclicSegments),
|
|
329
|
+
_step3;
|
|
278
330
|
|
|
279
331
|
try {
|
|
280
|
-
for (
|
|
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
|
-
|
|
286
|
-
_iteratorError3 = err;
|
|
337
|
+
_iterator3.e(err);
|
|
287
338
|
} finally {
|
|
288
|
-
|
|
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
|
-
|
|
316
|
-
var
|
|
317
|
-
|
|
358
|
+
|
|
359
|
+
var _iterator4 = _createForOfIteratorHelper(segment.nextSegments),
|
|
360
|
+
_step4;
|
|
318
361
|
|
|
319
362
|
try {
|
|
320
|
-
for (
|
|
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
|
-
|
|
326
|
-
_iteratorError4 = err;
|
|
368
|
+
_iterator4.e(err);
|
|
327
369
|
} finally {
|
|
328
|
-
|
|
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
|
-
|
|
388
|
-
var
|
|
389
|
-
|
|
421
|
+
|
|
422
|
+
var _iterator5 = _createForOfIteratorHelper(segment.prevSegments),
|
|
423
|
+
_step5;
|
|
390
424
|
|
|
391
425
|
try {
|
|
392
|
-
for (
|
|
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
|
-
|
|
402
|
-
_iteratorError5 = err;
|
|
435
|
+
_iterator5.e(err);
|
|
403
436
|
} finally {
|
|
404
|
-
|
|
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
|
-
|
|
444
|
-
var
|
|
445
|
-
|
|
468
|
+
|
|
469
|
+
var _iterator6 = _createForOfIteratorHelper(codePath.finalSegments),
|
|
470
|
+
_step6;
|
|
446
471
|
|
|
447
472
|
try {
|
|
448
|
-
for (
|
|
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
|
-
|
|
465
|
-
_iteratorError6 = err;
|
|
489
|
+
_iterator6.e(err);
|
|
466
490
|
} finally {
|
|
467
|
-
|
|
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
|
|
479
|
-
|
|
480
|
-
var _iteratorError7 = undefined;
|
|
494
|
+
var _iterator7 = _createForOfIteratorHelper(reactHooksMap),
|
|
495
|
+
_step7;
|
|
481
496
|
|
|
482
497
|
try {
|
|
483
|
-
for (
|
|
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
|
-
|
|
540
|
-
var
|
|
541
|
-
|
|
554
|
+
|
|
555
|
+
var _iterator8 = _createForOfIteratorHelper(reactHooks),
|
|
556
|
+
_step8;
|
|
542
557
|
|
|
543
558
|
try {
|
|
544
|
-
for (
|
|
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
|
-
|
|
614
|
-
_iteratorError8 = err;
|
|
628
|
+
_iterator8.e(err);
|
|
615
629
|
} finally {
|
|
616
|
-
|
|
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
|
-
|
|
629
|
-
_iteratorError7 = err;
|
|
634
|
+
_iterator7.e(err);
|
|
630
635
|
} finally {
|
|
631
|
-
|
|
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://
|
|
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
|
|
1174
|
-
|
|
1175
|
-
var _iteratorError = undefined;
|
|
1044
|
+
var _iterator = _createForOfIteratorHelper(currentScope.references),
|
|
1045
|
+
_step;
|
|
1176
1046
|
|
|
1177
1047
|
try {
|
|
1178
|
-
for (
|
|
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
|
-
|
|
1242
|
-
_iteratorError = err;
|
|
1111
|
+
_iterator.e(err);
|
|
1243
1112
|
} finally {
|
|
1244
|
-
|
|
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
|
|
1256
|
-
|
|
1257
|
-
var _iteratorError2 = undefined;
|
|
1116
|
+
var _iterator2 = _createForOfIteratorHelper(currentScope.childScopes),
|
|
1117
|
+
_step2;
|
|
1258
1118
|
|
|
1259
1119
|
try {
|
|
1260
|
-
for (
|
|
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
|
-
|
|
1266
|
-
_iteratorError2 = err;
|
|
1125
|
+
_iterator2.e(err);
|
|
1267
1126
|
} finally {
|
|
1268
|
-
|
|
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
|
|
1512
|
-
//
|
|
1513
|
-
var
|
|
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
|
-
|
|
1520
|
-
var
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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 (
|
|
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 '" +
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
1851
|
-
var dependencies =
|
|
1852
|
-
declaredDependencies =
|
|
1853
|
-
stableDependencies =
|
|
1854
|
-
externalDependencies =
|
|
1855
|
-
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 (
|
|
1892
|
-
var 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
|
-
|
|
1905
|
-
var
|
|
1906
|
-
|
|
1894
|
+
|
|
1895
|
+
var _iterator3 = _createForOfIteratorHelper(keys),
|
|
1896
|
+
_step3;
|
|
1907
1897
|
|
|
1908
1898
|
try {
|
|
1909
|
-
for (
|
|
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
|
-
|
|
1922
|
-
_iteratorError3 = err;
|
|
1911
|
+
_iterator3.e(err);
|
|
1923
1912
|
} finally {
|
|
1924
|
-
|
|
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
|
-
|
|
1942
|
-
var
|
|
1943
|
-
|
|
1922
|
+
|
|
1923
|
+
var _iterator4 = _createForOfIteratorHelper(keys),
|
|
1924
|
+
_step4;
|
|
1944
1925
|
|
|
1945
1926
|
try {
|
|
1946
|
-
for (
|
|
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
|
-
|
|
1959
|
-
_iteratorError4 = err;
|
|
1939
|
+
_iterator4.e(err);
|
|
1960
1940
|
} finally {
|
|
1961
|
-
|
|
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 (
|
|
2015
|
-
var 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
|
-
} //
|
|
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
|
|
2056
|
-
var declaredDependencies =
|
|
2057
|
-
declaredDependenciesNode =
|
|
2058
|
-
componentScope =
|
|
2059
|
-
scope =
|
|
2060
|
-
var
|
|
2061
|
-
var key =
|
|
2062
|
-
var
|
|
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 (
|
|
2102
|
+
if (ref == null) {
|
|
2065
2103
|
return null;
|
|
2066
2104
|
}
|
|
2067
2105
|
|
|
2068
|
-
var
|
|
2106
|
+
var node = ref.defs[0];
|
|
2069
2107
|
|
|
2070
|
-
if (
|
|
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
|
-
|
|
2077
|
-
|
|
2121
|
+
if (constantExpressionType != null) {
|
|
2122
|
+
return [ref, constantExpressionType];
|
|
2123
|
+
}
|
|
2078
2124
|
} // function handleChange() {}
|
|
2079
2125
|
|
|
2080
2126
|
|
|
2081
|
-
if (
|
|
2082
|
-
return
|
|
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(
|
|
2139
|
+
function isUsedOutsideOfHook(ref) {
|
|
2089
2140
|
var foundWriteExpr = false;
|
|
2090
2141
|
|
|
2091
|
-
for (var i = 0; i <
|
|
2092
|
-
var reference =
|
|
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
|
|
2174
|
+
return constructions.map(function (_ref11) {
|
|
2175
|
+
var ref = _ref11[0],
|
|
2176
|
+
depType = _ref11[1];
|
|
2124
2177
|
return {
|
|
2125
|
-
|
|
2126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|