eslint-plugin-react-hooks 3.0.0 → 4.0.0
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.
|
@@ -34,10 +34,9 @@ function isHook(node) {
|
|
|
34
34
|
if (node.type === 'Identifier') {
|
|
35
35
|
return isHookName(node.name);
|
|
36
36
|
} else if (node.type === 'MemberExpression' && !node.computed && isHook(node.property)) {
|
|
37
|
-
// Only consider React.useFoo() to be namespace hooks for now to avoid false positives.
|
|
38
|
-
// We can expand this check later.
|
|
39
37
|
var obj = node.object;
|
|
40
|
-
|
|
38
|
+
var isPascalCaseNameSpace = /^[A-Z].*/;
|
|
39
|
+
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
|
|
41
40
|
} else {
|
|
42
41
|
return false;
|
|
43
42
|
}
|
|
@@ -99,7 +98,16 @@ function isInsideComponentOrHook(node) {
|
|
|
99
98
|
return false;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
var
|
|
101
|
+
var RulesOfHooks = {
|
|
102
|
+
meta: {
|
|
103
|
+
type: 'problem',
|
|
104
|
+
docs: {
|
|
105
|
+
description: 'enforces the Rules of Hooks',
|
|
106
|
+
category: 'Possible Errors',
|
|
107
|
+
recommended: true,
|
|
108
|
+
url: 'https://reactjs.org/docs/hooks-rules.html'
|
|
109
|
+
}
|
|
110
|
+
},
|
|
103
111
|
create: function (context) {
|
|
104
112
|
var codePathReactHooksMapStack = [];
|
|
105
113
|
var codePathSegmentStack = [];
|
|
@@ -715,12 +723,24 @@ function last(array) {
|
|
|
715
723
|
|
|
716
724
|
var ExhaustiveDeps = {
|
|
717
725
|
meta: {
|
|
726
|
+
type: 'suggestion',
|
|
727
|
+
docs: {
|
|
728
|
+
description: 'verifies the list of dependencies for Hooks like useEffect and similar',
|
|
729
|
+
category: 'Best Practices',
|
|
730
|
+
recommended: true,
|
|
731
|
+
url: 'https://github.com/facebook/react/issues/14920'
|
|
732
|
+
},
|
|
733
|
+
fixable: 'code',
|
|
718
734
|
schema: [{
|
|
719
735
|
type: 'object',
|
|
720
736
|
additionalProperties: false,
|
|
737
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: false,
|
|
721
738
|
properties: {
|
|
722
739
|
additionalHooks: {
|
|
723
740
|
type: 'string'
|
|
741
|
+
},
|
|
742
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
|
743
|
+
type: 'boolean'
|
|
724
744
|
}
|
|
725
745
|
}
|
|
726
746
|
}]
|
|
@@ -728,9 +748,25 @@ var ExhaustiveDeps = {
|
|
|
728
748
|
create: function (context) {
|
|
729
749
|
// Parse the `additionalHooks` regex.
|
|
730
750
|
var additionalHooks = context.options && context.options[0] && context.options[0].additionalHooks ? new RegExp(context.options[0].additionalHooks) : undefined;
|
|
751
|
+
var enableDangerousAutofixThisMayCauseInfiniteLoops = context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops || false;
|
|
731
752
|
var options = {
|
|
732
|
-
additionalHooks: additionalHooks
|
|
733
|
-
|
|
753
|
+
additionalHooks: additionalHooks,
|
|
754
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: enableDangerousAutofixThisMayCauseInfiniteLoops
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
function reportProblem(problem) {
|
|
758
|
+
if (enableDangerousAutofixThisMayCauseInfiniteLoops) {
|
|
759
|
+
// Used to enable legacy behavior. Dangerous.
|
|
760
|
+
// Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension).
|
|
761
|
+
if (Array.isArray(problem.suggest) && problem.suggest.length > 0) {
|
|
762
|
+
problem.fix = problem.suggest[0].fix;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
context.report(problem);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
var scopeManager = context.getSourceCode().scopeManager; // Should be shared between visitors.
|
|
734
770
|
|
|
735
771
|
var setStateCallSites = new WeakMap();
|
|
736
772
|
var stateVariables = new WeakSet();
|
|
@@ -752,41 +788,31 @@ var ExhaustiveDeps = {
|
|
|
752
788
|
}
|
|
753
789
|
|
|
754
790
|
return {
|
|
755
|
-
|
|
756
|
-
ArrowFunctionExpression: visitFunctionExpression
|
|
791
|
+
CallExpression: visitCallExpression
|
|
757
792
|
};
|
|
758
|
-
/**
|
|
759
|
-
* Visitor for both function expressions and arrow function expressions.
|
|
760
|
-
*/
|
|
761
|
-
|
|
762
|
-
function visitFunctionExpression(node) {
|
|
763
|
-
// We only want to lint nodes which are reactive hook callbacks.
|
|
764
|
-
if (node.type !== 'FunctionExpression' && node.type !== 'ArrowFunctionExpression' || node.parent.type !== 'CallExpression') {
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
793
|
|
|
768
|
-
|
|
794
|
+
function visitCallExpression(node) {
|
|
795
|
+
var callbackIndex = getReactiveHookCallbackIndex(node.callee, options);
|
|
769
796
|
|
|
770
|
-
if (
|
|
797
|
+
if (callbackIndex === -1) {
|
|
798
|
+
// Not a React Hook call that needs deps.
|
|
771
799
|
return;
|
|
772
|
-
}
|
|
773
|
-
|
|
800
|
+
}
|
|
774
801
|
|
|
775
|
-
var
|
|
802
|
+
var callback = node.arguments[callbackIndex];
|
|
803
|
+
var reactiveHook = node.callee;
|
|
776
804
|
var reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
|
|
777
|
-
var
|
|
805
|
+
var declaredDependenciesNode = node.arguments[callbackIndex + 1];
|
|
806
|
+
var isEffect = /Effect($|[^a-z])/g.test(reactiveHookName); // Check the declared dependencies for this reactive hook. If there is no
|
|
778
807
|
// second argument then the reactive callback will re-run on every render.
|
|
779
808
|
// So no need to check for dependency inclusion.
|
|
780
809
|
|
|
781
|
-
var depsIndex = callbackIndex + 1;
|
|
782
|
-
var declaredDependenciesNode = node.parent.arguments[depsIndex];
|
|
783
|
-
|
|
784
810
|
if (!declaredDependenciesNode && !isEffect) {
|
|
785
811
|
// These are only used for optimization.
|
|
786
812
|
if (reactiveHookName === 'useMemo' || reactiveHookName === 'useCallback') {
|
|
787
813
|
// TODO: Can this have a suggestion?
|
|
788
|
-
|
|
789
|
-
node:
|
|
814
|
+
reportProblem({
|
|
815
|
+
node: reactiveHook,
|
|
790
816
|
message: "React Hook " + reactiveHookName + " does nothing when called with " + "only one argument. Did you forget to pass an array of " + "dependencies?"
|
|
791
817
|
});
|
|
792
818
|
}
|
|
@@ -794,15 +820,120 @@ var ExhaustiveDeps = {
|
|
|
794
820
|
return;
|
|
795
821
|
}
|
|
796
822
|
|
|
823
|
+
switch (callback.type) {
|
|
824
|
+
case 'FunctionExpression':
|
|
825
|
+
case 'ArrowFunctionExpression':
|
|
826
|
+
visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
|
827
|
+
return;
|
|
828
|
+
// Handled
|
|
829
|
+
|
|
830
|
+
case 'Identifier':
|
|
831
|
+
if (!declaredDependenciesNode) {
|
|
832
|
+
// No deps, no problems.
|
|
833
|
+
return; // Handled
|
|
834
|
+
} // The function passed as a callback is not written inline.
|
|
835
|
+
// But perhaps it's in the dependencies array?
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
if (declaredDependenciesNode.elements && declaredDependenciesNode.elements.some(function (el) {
|
|
839
|
+
return el.type === 'Identifier' && el.name === callback.name;
|
|
840
|
+
})) {
|
|
841
|
+
// If it's already in the list of deps, we don't care because
|
|
842
|
+
// this is valid regardless.
|
|
843
|
+
return; // Handled
|
|
844
|
+
} // We'll do our best effort to find it, complain otherwise.
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
var variable = context.getScope().set.get(callback.name);
|
|
848
|
+
|
|
849
|
+
if (variable == null || variable.defs == null) {
|
|
850
|
+
// If it's not in scope, we don't care.
|
|
851
|
+
return; // Handled
|
|
852
|
+
} // The function passed as a callback is not written inline.
|
|
853
|
+
// But it's defined somewhere in the render scope.
|
|
854
|
+
// We'll do our best effort to find and check it, complain otherwise.
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
var def = variable.defs[0];
|
|
858
|
+
|
|
859
|
+
if (!def || !def.node) {
|
|
860
|
+
break; // Unhandled
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
|
|
864
|
+
// Parameter or an unusual pattern. Bail out.
|
|
865
|
+
break; // Unhandled
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
switch (def.node.type) {
|
|
869
|
+
case 'FunctionDeclaration':
|
|
870
|
+
// useEffect(() => { ... }, []);
|
|
871
|
+
visitFunctionWithDependencies(def.node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
|
872
|
+
return;
|
|
873
|
+
// Handled
|
|
874
|
+
|
|
875
|
+
case 'VariableDeclarator':
|
|
876
|
+
var init = def.node.init;
|
|
877
|
+
|
|
878
|
+
if (!init) {
|
|
879
|
+
break; // Unhandled
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
switch (init.type) {
|
|
883
|
+
// const effectBody = () => {...};
|
|
884
|
+
// useEffect(effectBody, []);
|
|
885
|
+
case 'ArrowFunctionExpression':
|
|
886
|
+
case 'FunctionExpression':
|
|
887
|
+
// We can inspect this function as if it were inline.
|
|
888
|
+
visitFunctionWithDependencies(init, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
|
889
|
+
return;
|
|
890
|
+
// Handled
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
break;
|
|
894
|
+
// Unhandled
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
break;
|
|
898
|
+
// Unhandled
|
|
899
|
+
|
|
900
|
+
default:
|
|
901
|
+
// useEffect(generateEffectBody(), []);
|
|
902
|
+
reportProblem({
|
|
903
|
+
node: reactiveHook,
|
|
904
|
+
message: "React Hook " + reactiveHookName + " received a function whose dependencies " + "are unknown. Pass an inline function instead."
|
|
905
|
+
});
|
|
906
|
+
return;
|
|
907
|
+
// Handled
|
|
908
|
+
} // Something unusual. Fall back to suggesting to add the body itself as a dep.
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
reportProblem({
|
|
912
|
+
node: reactiveHook,
|
|
913
|
+
message: "React Hook " + reactiveHookName + " has a missing dependency: '" + callback.name + "'. " + "Either include it or remove the dependency array.",
|
|
914
|
+
suggest: [{
|
|
915
|
+
desc: "Update the dependencies array to be: [" + callback.name + "]",
|
|
916
|
+
fix: function (fixer) {
|
|
917
|
+
return fixer.replaceText(declaredDependenciesNode, "[" + callback.name + "]");
|
|
918
|
+
}
|
|
919
|
+
}]
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Visitor for both function expressions and arrow function expressions.
|
|
924
|
+
*/
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
function visitFunctionWithDependencies(node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect) {
|
|
797
928
|
if (isEffect && node.async) {
|
|
798
|
-
|
|
929
|
+
reportProblem({
|
|
799
930
|
node: node,
|
|
800
931
|
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'
|
|
801
932
|
});
|
|
802
933
|
} // Get the current scope.
|
|
803
934
|
|
|
804
935
|
|
|
805
|
-
var scope =
|
|
936
|
+
var scope = scopeManager.acquire(node); // Find all our "pure scopes". On every re-render of a component these
|
|
806
937
|
// pure scopes may have changes to the variables declared within. So all
|
|
807
938
|
// variables used in our reactive hook callback but declared in a pure
|
|
808
939
|
// scope need to be listed as dependencies of our reactive hook callback.
|
|
@@ -865,6 +996,10 @@ var ExhaustiveDeps = {
|
|
|
865
996
|
|
|
866
997
|
if (init == null) {
|
|
867
998
|
return false;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
while (init.type === 'TSAsExpression') {
|
|
1002
|
+
init = init.expression;
|
|
868
1003
|
} // Detect primitive constants
|
|
869
1004
|
// const foo = 42
|
|
870
1005
|
|
|
@@ -1165,7 +1300,7 @@ var ExhaustiveDeps = {
|
|
|
1165
1300
|
return;
|
|
1166
1301
|
}
|
|
1167
1302
|
|
|
1168
|
-
|
|
1303
|
+
reportProblem({
|
|
1169
1304
|
node: dependencyNode.parent.property,
|
|
1170
1305
|
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."
|
|
1171
1306
|
});
|
|
@@ -1180,7 +1315,7 @@ var ExhaustiveDeps = {
|
|
|
1180
1315
|
}
|
|
1181
1316
|
|
|
1182
1317
|
staleAssignments.add(key);
|
|
1183
|
-
|
|
1318
|
+
reportProblem({
|
|
1184
1319
|
node: writeExpr,
|
|
1185
1320
|
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) + ".")
|
|
1186
1321
|
});
|
|
@@ -1257,8 +1392,8 @@ var ExhaustiveDeps = {
|
|
|
1257
1392
|
}),
|
|
1258
1393
|
_suggestedDependencies = _collectRecommendatio.suggestedDependencies;
|
|
1259
1394
|
|
|
1260
|
-
|
|
1261
|
-
node:
|
|
1395
|
+
reportProblem({
|
|
1396
|
+
node: reactiveHook,
|
|
1262
1397
|
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."),
|
|
1263
1398
|
suggest: [{
|
|
1264
1399
|
desc: "Add dependencies array: [" + _suggestedDependencies.join(', ') + "]",
|
|
@@ -1279,7 +1414,7 @@ var ExhaustiveDeps = {
|
|
|
1279
1414
|
// If the declared dependencies are not an array expression then we
|
|
1280
1415
|
// can't verify that the user provided the correct dependencies. Tell
|
|
1281
1416
|
// the user this in an error.
|
|
1282
|
-
|
|
1417
|
+
reportProblem({
|
|
1283
1418
|
node: declaredDependenciesNode,
|
|
1284
1419
|
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.'
|
|
1285
1420
|
});
|
|
@@ -1292,7 +1427,7 @@ var ExhaustiveDeps = {
|
|
|
1292
1427
|
|
|
1293
1428
|
|
|
1294
1429
|
if (declaredDependencyNode.type === 'SpreadElement') {
|
|
1295
|
-
|
|
1430
|
+
reportProblem({
|
|
1296
1431
|
node: declaredDependencyNode,
|
|
1297
1432
|
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.'
|
|
1298
1433
|
});
|
|
@@ -1309,18 +1444,18 @@ var ExhaustiveDeps = {
|
|
|
1309
1444
|
if (/Unsupported node type/.test(error.message)) {
|
|
1310
1445
|
if (declaredDependencyNode.type === 'Literal') {
|
|
1311
1446
|
if (dependencies.has(declaredDependencyNode.value)) {
|
|
1312
|
-
|
|
1447
|
+
reportProblem({
|
|
1313
1448
|
node: declaredDependencyNode,
|
|
1314
1449
|
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?")
|
|
1315
1450
|
});
|
|
1316
1451
|
} else {
|
|
1317
|
-
|
|
1452
|
+
reportProblem({
|
|
1318
1453
|
node: declaredDependencyNode,
|
|
1319
1454
|
message: "The " + declaredDependencyNode.raw + " literal is not a valid dependency " + 'because it never changes. You can safely remove it.'
|
|
1320
1455
|
});
|
|
1321
1456
|
}
|
|
1322
1457
|
} else {
|
|
1323
|
-
|
|
1458
|
+
reportProblem({
|
|
1324
1459
|
node: declaredDependencyNode,
|
|
1325
1460
|
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.'
|
|
1326
1461
|
});
|
|
@@ -1365,6 +1500,7 @@ var ExhaustiveDeps = {
|
|
|
1365
1500
|
missingDependencies = _collectRecommendatio2.missingDependencies,
|
|
1366
1501
|
duplicateDependencies = _collectRecommendatio2.duplicateDependencies;
|
|
1367
1502
|
|
|
1503
|
+
var suggestedDeps = suggestedDependencies;
|
|
1368
1504
|
var problemCount = duplicateDependencies.size + missingDependencies.size + unnecessaryDependencies.size;
|
|
1369
1505
|
|
|
1370
1506
|
if (problemCount === 0) {
|
|
@@ -1406,7 +1542,7 @@ var ExhaustiveDeps = {
|
|
|
1406
1542
|
// Should we suggest removing effect deps as an appropriate fix too?
|
|
1407
1543
|
|
|
1408
1544
|
|
|
1409
|
-
|
|
1545
|
+
reportProblem({
|
|
1410
1546
|
// TODO: Why not report this at the dependency site?
|
|
1411
1547
|
node: fn.node,
|
|
1412
1548
|
message: message,
|
|
@@ -1423,7 +1559,7 @@ var ExhaustiveDeps = {
|
|
|
1423
1559
|
|
|
1424
1560
|
|
|
1425
1561
|
if (!isEffect && missingDependencies.size > 0) {
|
|
1426
|
-
|
|
1562
|
+
suggestedDeps = collectRecommendations({
|
|
1427
1563
|
dependencies: dependencies,
|
|
1428
1564
|
declaredDependencies: [],
|
|
1429
1565
|
// Pretend we don't know
|
|
@@ -1447,7 +1583,7 @@ var ExhaustiveDeps = {
|
|
|
1447
1583
|
}
|
|
1448
1584
|
|
|
1449
1585
|
if (areDeclaredDepsAlphabetized()) {
|
|
1450
|
-
|
|
1586
|
+
suggestedDeps.sort();
|
|
1451
1587
|
}
|
|
1452
1588
|
|
|
1453
1589
|
function getWarningMessage(deps, singlePrefix, label, fixVerb) {
|
|
@@ -1670,15 +1806,15 @@ var ExhaustiveDeps = {
|
|
|
1670
1806
|
}
|
|
1671
1807
|
}
|
|
1672
1808
|
|
|
1673
|
-
|
|
1809
|
+
reportProblem({
|
|
1674
1810
|
node: declaredDependenciesNode,
|
|
1675
1811
|
message: "React Hook " + context.getSource(reactiveHook) + " has " + ( // To avoid a long message, show the next actionable item.
|
|
1676
1812
|
getWarningMessage(missingDependencies, 'a', 'missing', 'include') || getWarningMessage(unnecessaryDependencies, 'an', 'unnecessary', 'exclude') || getWarningMessage(duplicateDependencies, 'a', 'duplicate', 'omit')) + extraWarning,
|
|
1677
1813
|
suggest: [{
|
|
1678
|
-
desc: "Update the dependencies array to be: [" +
|
|
1814
|
+
desc: "Update the dependencies array to be: [" + suggestedDeps.join(', ') + "]",
|
|
1679
1815
|
fix: function (fixer) {
|
|
1680
1816
|
// TODO: consider preserving the comments or formatting?
|
|
1681
|
-
return fixer.replaceText(declaredDependenciesNode, "[" +
|
|
1817
|
+
return fixer.replaceText(declaredDependenciesNode, "[" + suggestedDeps.join(', ') + "]");
|
|
1682
1818
|
}
|
|
1683
1819
|
}]
|
|
1684
1820
|
});
|
|
@@ -1978,6 +2114,8 @@ function scanForDeclaredBareFunctions(_ref8) {
|
|
|
1978
2114
|
function getDependency(node) {
|
|
1979
2115
|
if (node.parent.type === 'MemberExpression' && node.parent.object === node && node.parent.property.name !== 'current' && !node.parent.computed && !(node.parent.parent != null && node.parent.parent.type === 'CallExpression' && node.parent.parent.callee === node.parent)) {
|
|
1980
2116
|
return getDependency(node.parent);
|
|
2117
|
+
} else if (node.type === 'MemberExpression' && node.parent && node.parent.type === 'AssignmentExpression') {
|
|
2118
|
+
return node.object;
|
|
1981
2119
|
} else {
|
|
1982
2120
|
return node;
|
|
1983
2121
|
}
|
|
@@ -2020,7 +2158,7 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
|
|
|
2020
2158
|
var node = getNodeWithoutReactNamespace(calleeNode);
|
|
2021
2159
|
|
|
2022
2160
|
if (node.type !== 'Identifier') {
|
|
2023
|
-
return
|
|
2161
|
+
return -1;
|
|
2024
2162
|
}
|
|
2025
2163
|
|
|
2026
2164
|
switch (node.name) {
|
|
@@ -2036,7 +2174,9 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
|
|
|
2036
2174
|
return 1;
|
|
2037
2175
|
|
|
2038
2176
|
default:
|
|
2039
|
-
if (node === calleeNode &&
|
|
2177
|
+
if (node === calleeNode && node.name.match(/use.+Effect/)) {
|
|
2178
|
+
return 0;
|
|
2179
|
+
} else if (node === calleeNode && options && options.additionalHooks) {
|
|
2040
2180
|
// Allow the user to provide a regular expression which enables the lint to
|
|
2041
2181
|
// target custom reactive hooks.
|
|
2042
2182
|
var name;
|
|
@@ -2151,7 +2291,7 @@ var configs = {
|
|
|
2151
2291
|
}
|
|
2152
2292
|
};
|
|
2153
2293
|
var rules = {
|
|
2154
|
-
'rules-of-hooks':
|
|
2294
|
+
'rules-of-hooks': RulesOfHooks,
|
|
2155
2295
|
'exhaustive-deps': ExhaustiveDeps
|
|
2156
2296
|
};
|
|
2157
2297
|
|
|
@@ -7,47 +7,51 @@
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
'use strict';function U(a){
|
|
11
|
-
function
|
|
12
|
-
function
|
|
10
|
+
'use strict';function U(a){if("Identifier"===a.type)return/^use[A-Z0-9].*$/.test(a.name);if("MemberExpression"===a.type&&!a.computed&&U(a.property)){a=a.object;var c=/^[A-Z].*/;return"Identifier"===a.type&&c.test(a.name)}return!1}function aa(a){return"Identifier"===a.type?!/^[a-z]/.test(a.name):!1}function ca(a,c){return a.name===c||"MemberExpression"===a.type&&"React"===a.object.name&&a.property.name===c}function fa(a){return!!(a.parent&&a.parent.callee&&ca(a.parent.callee,"forwardRef"))}
|
|
11
|
+
function ha(a){return!!(a.parent&&a.parent.callee&&ca(a.parent.callee,"memo"))}function ia(a){for(;a;){var c=ja(a);if(c&&(aa(c)||U(c))||fa(a)||ha(a))return!0;a=a.parent}return!1}
|
|
12
|
+
function ja(a){if("FunctionDeclaration"===a.type||"FunctionExpression"===a.type&&a.id)return a.id;if("FunctionExpression"===a.type||"ArrowFunctionExpression"===a.type)return"VariableDeclarator"===a.parent.type&&a.parent.init===a?a.parent.id:"AssignmentExpression"===a.parent.type&&a.parent.right===a&&"="===a.parent.operator?a.parent.left:"Property"!==a.parent.type||a.parent.value!==a||a.parent.computed?"AssignmentPattern"!==a.parent.type||a.parent.right!==a||a.parent.computed?void 0:a.parent.left:
|
|
13
13
|
a.parent.key}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
!0})});p.forEach(function(a){e(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function na(a
|
|
21
|
-
function
|
|
22
|
-
function pa(a){
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
A+"
|
|
14
|
+
function ka(a){function c(){return{isRequired:!1,isSatisfiedRecursively:!1,hasRequiredNodesBelow:!1,children:new Map}}function e(a,b){b=b.split(".");var k=!0,f=!1,d=void 0;try{for(var e=b[Symbol.iterator](),h;!(k=(h=e.next()).done);k=!0){var n=h.value,z=a.children.get(n);z||(z=c(),a.children.set(n,z));a=z}}catch(G){f=!0,d=G}finally{try{k||null==e.return||e.return()}finally{if(f)throw d;}}return a}function h(a,c,b){c=c.split(".");var k=!0,f=!1,d=void 0;try{for(var e=c[Symbol.iterator](),h;!(k=
|
|
15
|
+
(h=e.next()).done);k=!0){var z=a.children.get(h.value);if(!z)break;b(z);a=z}}catch(G){f=!0,d=G}finally{try{k||null==e.return||e.return()}finally{if(f)throw d;}}}function f(a,c,b,d){a.children.forEach(function(a,k){var e=d(k);a.isSatisfiedRecursively?a.hasRequiredNodesBelow&&b.add(e):a.isRequired?c.add(e):f(a,c,b,function(a){return e+"."+a})})}var H=a.dependencies,p=a.declaredDependencies,u=a.optionalDependencies,x=a.externalDependencies,y=a.isEffect,I=c();H.forEach(function(a,c){e(I,c).isRequired=
|
|
16
|
+
!0;h(I,c,function(a){a.hasRequiredNodesBelow=!0})});p.forEach(function(a){e(I,a.key).isSatisfiedRecursively=!0});u.forEach(function(a){e(I,a).isSatisfiedRecursively=!0});a=new Set;var d=new Set;f(I,a,d,function(a){return a});var b=[],z=new Set,A=new Set;p.forEach(function(a){a=a.key;d.has(a)?-1===b.indexOf(a)?b.push(a):A.add(a):!y||a.endsWith(".current")||x.has(a)?z.add(a):-1===b.indexOf(a)&&b.push(a)});a.forEach(function(a){b.push(a)});return{suggestedDependencies:b,unnecessaryDependencies:z,duplicateDependencies:A,
|
|
17
|
+
missingDependencies:a}}
|
|
18
|
+
function la(a){var c=a.declaredDependenciesNode,e=a.componentScope,h=a.scope;return a.declaredDependencies.map(function(a){a=e.set.get(a.key);if(null==a)return null;var c=a.defs[0];return null==c?null:"Variable"===c.type&&"VariableDeclarator"===c.node.type&&null!=c.node.init&&("ArrowFunctionExpression"===c.node.init.type||"FunctionExpression"===c.node.init.type)||"FunctionName"===c.type&&"FunctionDeclaration"===c.node.type?a:null}).filter(Boolean).map(function(a){var e=a.defs[0];a:{for(var f=!1,u=
|
|
19
|
+
0;u<a.references.length;u++){var x=a.references[u];if(x.writeExpr)if(f){a=!0;break a}else{f=!0;continue}for(var y=x.from;y!==h&&null!=y;)y=y.upper;if(y!==h&&!ma(c,x.identifier)){a=!0;break a}}a=!1}return{fn:e,suggestUseCallback:a}})}
|
|
20
|
+
function na(a){return"MemberExpression"!==a.parent.type||a.parent.object!==a||"current"===a.parent.property.name||a.parent.computed||null!=a.parent.parent&&"CallExpression"===a.parent.parent.type&&a.parent.parent.callee===a.parent?"MemberExpression"===a.type&&a.parent&&"AssignmentExpression"===a.parent.type?a.object:a:na(a.parent)}
|
|
21
|
+
function Y(a){if("Identifier"===a.type)return a.name;if("MemberExpression"!==a.type||a.computed)throw Error("Unsupported node type: "+a.type);var c=Y(a.object);a=Y(a.property);return c+"."+a}function oa(a){return"MemberExpression"!==a.type||"Identifier"!==a.object.type||"React"!==a.object.name||"Identifier"!==a.property.type||a.computed?a:a.property}
|
|
22
|
+
function pa(a,c){var e=oa(a);if("Identifier"!==e.type)return-1;switch(e.name){case "useEffect":case "useLayoutEffect":case "useCallback":case "useMemo":return 0;case "useImperativeHandle":return 1;default:if(e===a&&e.name.match(/use.+Effect/))return 0;if(e===a&&c&&c.additionalHooks){try{var h=Y(e)}catch(f){if(/Unsupported node type/.test(f.message))return 0;throw f;}return c.additionalHooks.test(h)?0:-1}return-1}}
|
|
23
|
+
function qa(a,c){for(var e=[a],h=null;e.length;){h=e.shift();if("Identifier"===h.type&&h.name===c.name&&h.range[0]===c.range[0]&&h.range[1]===c.range[1])return h;if(ma(h,c)){a=0;for(var f=Object.entries(h);a<f.length;a++){var H=f[a],p=H[1];"parent"!==H[0]&&(ra(p)?(p.parent=h,e.push(p)):Array.isArray(p)&&p.forEach(function(a){ra(a)&&(a.parent=h,e.push(a))}))}}}return null}
|
|
24
|
+
function sa(a){for(var c="",e=0;e<a.length;e++)c+=a[e],0===e&&2===a.length?c+=" and ":e===a.length-2&&2<a.length?c+=", and ":e<a.length-1&&(c+=", ");return c}function ra(a){return"object"===typeof a&&null!==a&&!Array.isArray(a)&&"string"===typeof a.type}function ma(a,c){return a.range[0]<=c.range[0]&&a.range[1]>=c.range[1]}exports.configs={recommended:{plugins:["react-hooks"],rules:{"react-hooks/rules-of-hooks":"error","react-hooks/exhaustive-deps":"warn"}}};
|
|
25
|
+
exports.rules={"rules-of-hooks":{meta:{type:"problem",docs:{description:"enforces the Rules of Hooks",category:"Possible Errors",recommended:!0,url:"https://reactjs.org/docs/hooks-rules.html"}},create:function(a){var c=[],e=[];return{onCodePathSegmentStart:function(a){return e.push(a)},onCodePathSegmentEnd:function(){return e.pop()},onCodePathStart:function(){return c.push(new Map)},onCodePathEnd:function(e,f){function h(a,c){var b=h.cache,d=b.get(a.id);c=new Set(c);if(c.has(a.id)){b=[].concat(c);
|
|
26
|
+
a=b.slice(b.indexOf(a.id)+1);b=!0;d=!1;var g=void 0;try{for(var m=a[Symbol.iterator](),B;!(b=(B=m.next()).done);b=!0)y.add(B.value)}catch(J){d=!0,g=J}finally{try{b||null==m.return||m.return()}finally{if(d)throw g;}}return 0}c.add(a.id);if(void 0!==d)return d;if(e.thrownSegments.includes(a))d=0;else if(0===a.prevSegments.length)d=1;else{d=0;m=!0;B=!1;var L=void 0;try{g=a.prevSegments[Symbol.iterator]();for(var M;!(m=(M=g.next()).done);m=!0)d+=h(M.value,c)}catch(J){B=!0,L=J}finally{try{m||null==
|
|
27
|
+
g.return||g.return()}finally{if(B)throw L;}}}a.reachable&&0===d?b.delete(a.id):b.set(a.id,d);return d}function p(a,c){var b=p.cache,d=b.get(a.id);c=new Set(c);if(c.has(a.id)){b=Array.from(c);a=b.slice(b.indexOf(a.id)+1);b=!0;d=!1;var g=void 0;try{for(var m=a[Symbol.iterator](),B;!(b=(B=m.next()).done);b=!0)y.add(B.value)}catch(J){d=!0,g=J}finally{try{b||null==m.return||m.return()}finally{if(d)throw g;}}return 0}c.add(a.id);if(void 0!==d)return d;if(e.thrownSegments.includes(a))d=0;else if(0===
|
|
28
|
+
a.nextSegments.length)d=1;else{d=0;m=!0;B=!1;var L=void 0;try{g=a.nextSegments[Symbol.iterator]();for(var M;!(m=(M=g.next()).done);m=!0)d+=p(M.value,c)}catch(J){B=!0,L=J}finally{try{m||null==g.return||g.return()}finally{if(B)throw L;}}}b.set(a.id,d);return d}function u(a){var c=u.cache,b=c.get(a.id);if(null===b)return Infinity;if(void 0!==b)return b;c.set(a.id,null);if(0===a.prevSegments.length)b=1;else{b=Infinity;var d=!0,g=!1,m=void 0;try{for(var B=a.prevSegments[Symbol.iterator](),L;!(d=
|
|
29
|
+
(L=B.next()).done);d=!0){var e=u(L.value);e<b&&(b=e)}}catch(J){g=!0,m=J}finally{try{d||null==B.return||B.return()}finally{if(g)throw m;}}b+=1}c.set(a.id,b);return b}var x=c.pop();if(0!==x.size){var y=new Set;h.cache=new Map;p.cache=new Map;u.cache=new Map;var I=p(e.initialSegment),d=ja(f),b=ia(f),z=d?aa(d)||U(d):fa(f)||ha(f),A=Infinity,k=!0,n=!1,T=void 0;try{for(var q=e.finalSegments[Symbol.iterator](),F;!(k=(F=q.next()).done);k=!0){var C=F.value;if(C.reachable){var w=u(C);w<A&&(A=w)}}}catch(E){n=
|
|
30
|
+
!0,T=E}finally{try{k||null==q.return||q.return()}finally{if(n)throw T;}}k=!0;n=!1;T=void 0;try{for(var ba=x[Symbol.iterator](),Z;!(k=(Z=ba.next()).done);k=!0){var G=Z.value,t=G[0],W=G[1];if(t.reachable){var Q=0===t.nextSegments.length?A<=u(t):A<u(t),O=h(t)*p(t),N=y.has(t.id);x=!0;q=!1;F=void 0;try{for(var l=W[Symbol.iterator](),D;!(x=(D=l.next()).done);x=!0){var v=D.value;N&&a.report({node:v,message:'React Hook "'+a.getSource(v)+'" 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.'});
|
|
31
|
+
if(z){if(!N&&O!==I){var R='React Hook "'+a.getSource(v)+'" is called conditionally. React Hooks must be called in the exact same order in every component render.'+(Q?" Did you accidentally call a React Hook after an early return?":"");a.report({node:v,message:R})}}else if(f.parent&&("MethodDefinition"===f.parent.type||"ClassProperty"===f.parent.type)&&f.parent.value===f){var S='React Hook "'+a.getSource(v)+'" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.';
|
|
32
|
+
a.report({node:v,message:S})}else if(d){var K='React Hook "'+a.getSource(v)+'" is called in function "'+(a.getSource(d)+'" that is neither a React function component nor a custom React Hook function.');a.report({node:v,message:K})}else if("Program"===f.type){var X='React Hook "'+a.getSource(v)+'" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.';a.report({node:v,message:X})}else if(b){var P='React Hook "'+a.getSource(v)+'" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.';
|
|
33
|
+
a.report({node:v,message:P})}}}catch(E){q=!0,F=E}finally{try{x||null==l.return||l.return()}finally{if(q)throw F;}}}}}catch(E){n=!0,T=E}finally{try{k||null==ba.return||ba.return()}finally{if(n)throw T;}}}},CallExpression:function(a){if(U(a.callee)){var f=c[c.length-1],h=e[e.length-1],p=f.get(h);p||(p=[],f.set(h,p));p.push(a.callee)}}}}},"exhaustive-deps":{meta:{type:"suggestion",docs:{description:"verifies the list of dependencies for Hooks like useEffect and similar",category:"Best Practices",recommended:!0,
|
|
34
|
+
url:"https://github.com/facebook/react/issues/14920"},fixable:"code",schema:[{type:"object",additionalProperties:!1,enableDangerousAutofixThisMayCauseInfiniteLoops:!1,properties:{additionalHooks:{type:"string"},enableDangerousAutofixThisMayCauseInfiniteLoops:{type:"boolean"}}}]},create:function(a){function c(c){f&&Array.isArray(c.suggest)&&0<c.suggest.length&&(c.fix=c.suggest[0].fix);a.report(c)}function e(a,c){return function(b){if(c.has(b))return c.get(b);var d=a(b);c.set(b,d);return d}}function h(d,
|
|
35
|
+
b,h,f,k){function z(a){var g=!0,c=!1,b=void 0;try{for(var e=a.references[Symbol.iterator](),h;!(g=(h=e.next()).done);g=!0){var f=h.value;if(f.resolved&&F.has(f.resolved.scope)){var p=qa(d,f.identifier),l=na(p),A=Y(l),q;if(q=k&&"Identifier"===l.type&&"MemberExpression"===l.parent.type&&!l.parent.computed&&"Identifier"===l.parent.property.type&&"current"===l.parent.property.name){for(var n=f.from,u=!1;n.block!==d;)"function"===n.type&&(u=null!=n.block.parent&&"ReturnStatement"===n.block.parent.type),
|
|
36
|
+
n=n.upper;q=u}q&&G.set(A,{reference:f,dependencyNode:l});var r=f.resolved.defs[0];if(null!=r&&(null==r.node||r.node.init!==d.parent)&&"TypeParameter"!==r.type)if(t.has(A))t.get(A).references.push(f);else{var w=f.resolved,x=H(w)||Z(w);t.set(A,{isStatic:x,references:[f]})}}}}catch(ea){c=!0,b=ea}finally{try{g||null==e.return||e.return()}finally{if(c)throw b;}}g=!0;c=!1;b=void 0;try{for(var v=a.childScopes[Symbol.iterator](),y;!(g=(y=v.next()).done);g=!0)z(y.value)}catch(ea){c=!0,b=ea}finally{try{g||
|
|
37
|
+
null==v.return||v.return()}finally{if(c)throw b;}}}function A(a,c,b,d){return 0===a.size?null:(1<a.size?"":c+" ")+b+" "+(1<a.size?"dependencies":"dependency")+": "+sa(Array.from(a).sort().map(function(a){return"'"+a+"'"}))+(". Either "+d+" "+(1<a.size?"them":"it")+" or remove the dependency array.")}k&&d.async&&c({node:d,message:"Effect callbacks are synchronous to prevent race conditions. Put the async function inside:\n\nuseEffect(() => {\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\nLearn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching"});
|
|
38
|
+
for(var q=p.acquire(d),F=new Set,C=null,w=q.upper;w;){F.add(w);if("function"===w.type)break;w=w.upper}if(w){C=w;var H=e(function(a){if(!Array.isArray(a.defs))return!1;var c=a.defs[0];if(null==c||"VariableDeclarator"!==c.node.type)return!1;var b=c.node.init;if(null==b)return!1;for(;"TSAsExpression"===b.type;)b=b.expression;var g=c.node.parent;if(null==g&&(qa(C.block,c.node.id),g=c.node.parent,null==g))return!1;if("const"===g.kind&&"Literal"===b.type&&("string"===typeof b.value||"number"===typeof b.value||
|
|
39
|
+
null===b.value))return!0;if("CallExpression"!==b.type)return!1;b=b.callee;"MemberExpression"!==b.type||"React"!==b.object.name||null==b.property||b.computed||(b=b.property);if("Identifier"!==b.type)return!1;c=c.node.id;b=b.name;if("useRef"===b&&"Identifier"===c.type)return!0;if(("useState"===b||"useReducer"===b)&&"ArrayPattern"===c.type&&2===c.elements.length&&Array.isArray(a.identifiers)){if(c.elements[1]===a.identifiers[0]){if("useState"===b)for(a=a.references,b=0;b<a.length;b++)u.set(a[b].identifier,
|
|
40
|
+
c.elements[0]);return!0}if(c.elements[0]===a.identifiers[0]&&"useState"===b)for(a=a.references,c=0;c<a.length;c++)x.add(a[c].identifier)}return!1},y),Z=e(function(a){if(!Array.isArray(a.defs))return!1;a=a.defs[0];if(null==a||null==a.node||null==a.node.id)return!1;var c=a.node,b=C.childScopes;a=null;var g;for(g=0;g<b.length;g++){var d=b[g],e=d.block;if("FunctionDeclaration"===c.type&&e===c||"VariableDeclarator"===c.type&&e.parent===c){a=d;break}}if(null==a)return!1;for(g=0;g<a.through.length;g++)if(c=
|
|
41
|
+
a.through[g],null!=c.resolved&&F.has(c.resolved.scope)&&!H(c.resolved))return!1;return!0},I),G=new Map,t=new Map;z(q);G.forEach(function(a,b){var g=a.dependencyNode;a=a.reference.resolved.references;for(var d=!1,e=0;e<a.length;e++){var m=a[e].identifier.parent;if(null!=m&&"MemberExpression"===m.type&&!m.computed&&"Identifier"===m.property.type&&"current"===m.property.name&&"AssignmentExpression"===m.parent.type&&m.parent.left===m){d=!0;break}}d||c({node:g.parent.property,message:"The ref value '"+
|
|
42
|
+
b+".current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy '"+(b+".current' to a variable inside the effect, and use that variable in the cleanup function.")})});var W=new Set,Q=new Set;t.forEach(function(b,d){var g=b.references;b.isStatic&&Q.add(d);g.forEach(function(b){b.writeExpr&&(b=b.writeExpr,W.has(d)||(W.add(d),c({node:b,message:"Assignments to the '"+d+"' variable from inside React Hook "+(a.getSource(h)+" 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 ")+
|
|
43
|
+
(a.getSource(h)+".")})))})});if(!(0<W.size))if(b){var O=[],N=new Set;"ArrayExpression"!==b.type?c({node:b,message:"React Hook "+a.getSource(h)+" 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."}):b.elements.forEach(function(b){if(null!==b)if("SpreadElement"===b.type)c({node:b,message:"React Hook "+a.getSource(h)+" has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies."});
|
|
44
|
+
else{try{var d=Y(b)}catch(M){if(/Unsupported node type/.test(M.message)){"Literal"===b.type?t.has(b.value)?c({node:b,message:"The "+b.raw+" literal is not a valid dependency because it never changes. Did you mean to include "+(b.value+" in the array instead?")}):c({node:b,message:"The "+b.raw+" literal is not a valid dependency because it never changes. You can safely remove it."}):c({node:b,message:"React Hook "+a.getSource(h)+" has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked."});
|
|
45
|
+
return}throw M;}for(var g=b;"MemberExpression"===g.type;)g=g.object;var e=!C.through.some(function(a){return a.identifier===g});O.push({key:d,node:b});e||N.add(d)}});var l=ka({dependencies:t,declaredDependencies:O,optionalDependencies:Q,externalDependencies:N,isEffect:k});w=l.unnecessaryDependencies;var D=l.missingDependencies,v=l.duplicateDependencies,R=l.suggestedDependencies;if(0===v.size+D.size+w.size)la({declaredDependencies:O,declaredDependenciesNode:b,componentScope:C,scope:q}).forEach(function(a){var d=
|
|
46
|
+
a.fn;a=a.suggestUseCallback;var g="The '"+d.name.name+"' function makes the dependencies of "+(f+" Hook (at line "+b.loc.start.line+") change on every render.");g=a?g+(" To fix this, wrap the '"+(d.name.name+"' definition into its own useCallback() Hook.")):g+(" Move it inside the "+f+" callback. Alternatively, wrap the '"+(d.name.name+"' definition into its own useCallback() Hook."));var e;a&&"Variable"===d.type&&(e=[{desc:"Wrap the '"+d.name.name+"' definition into its own useCallback() Hook.",
|
|
47
|
+
fix:function(a){return[a.insertTextBefore(d.node.init,"useCallback("),a.insertTextAfter(d.node.init,")")]}}]);c({node:d.node,message:g,suggest:e})});else{!k&&0<D.size&&(R=ka({dependencies:t,declaredDependencies:[],optionalDependencies:Q,externalDependencies:N,isEffect:k}).suggestedDependencies);(function(){if(0===O.length)return!0;var a=O.map(function(a){return a.key}),b=a.slice().sort();return a.join(",")===b.join(",")})()&&R.sort();l="";if(0<w.size){var S=null;Array.from(w.keys()).forEach(function(a){null===
|
|
48
|
+
S&&a.endsWith(".current")&&(S=a)});if(null!==S)l=" Mutable values like '"+S+"' aren't valid dependencies because mutating them doesn't re-render the component.";else if(0<N.size){var K=Array.from(N)[0];q.set.has(K)||(l=" Outer scope values like '"+K+"' aren't valid dependencies because mutating them doesn't re-render the component.")}}if(!l&&D.has("props")){q=t.get("props");if(null==q)return;q=q.references;if(!Array.isArray(q))return;K=!0;for(var X=0;X<q.length;X++){var P=qa(C.block,q[X].identifier);
|
|
49
|
+
if(!P){K=!1;break}P=P.parent;if(null==P){K=!1;break}if("MemberExpression"!==P.type){K=!1;break}}K&&(l=" However, 'props' will change when *any* prop changes, so the preferred fix is to destructure the 'props' object outside of the "+(f+" call and refer to those specific props inside ")+(a.getSource(h)+"."))}if(!l&&0<D.size){var E=null;D.forEach(function(a){if(!E){var b=C.set.get(a),c=t.get(a);if(c.references[0].resolved===b&&(b=b.defs[0],null!=b&&null!=b.name&&"Parameter"===b.type)){b=!1;for(var d,
|
|
50
|
+
e=0;e<c.references.length;e++)if(d=c.references[e].identifier,null!=d&&null!=d.parent&&"CallExpression"===d.parent.type&&d.parent.callee===d){b=!0;break}b&&(E=a)}}});null!==E&&(l=" If '"+E+"' changes too often, find the parent component that defines it and wrap that definition in useCallback.")}if(!l&&0<D.size){var r=null;D.forEach(function(a){if(null===r)for(var b=t.get(a).references,c,d,e=0;e<b.length;e++){c=b[e].identifier;for(d=c.parent;null!=d&&d!==C.block;){if("CallExpression"===d.type){var g=
|
|
51
|
+
u.get(d.callee);if(null!=g){g.name===a?r={missingDep:a,setter:d.callee.name,form:"updater"}:x.has(c)?r={missingDep:a,setter:d.callee.name,form:"reducer"}:(c=b[e].resolved,null!=c&&(c=c.defs[0],null!=c&&"Parameter"===c.type&&(r={missingDep:a,setter:d.callee.name,form:"inlineReducer"})));break}}d=d.parent}if(null!==r)break}});if(null!==r)switch(r.form){case "reducer":l=" You can also replace multiple useState variables with useReducer if '"+(r.setter+"' needs the current value of '")+(r.missingDep+
|
|
52
|
+
"'.");break;case "inlineReducer":l=" If '"+r.setter+"' needs the current value of '"+(r.missingDep+"', you can also switch to useReducer instead of useState and read '")+(r.missingDep+"' in the reducer.");break;case "updater":l=" You can also do a functional update '"+r.setter+"("+r.missingDep.substring(0,1)+" => ...)' if you only need '"+r.missingDep+"' in the '"+(r.setter+"' call.");break;default:throw Error("Unknown case.");}}c({node:b,message:"React Hook "+a.getSource(h)+" has "+(A(D,"a","missing",
|
|
53
|
+
"include")||A(w,"an","unnecessary","exclude")||A(v,"a","duplicate","omit"))+l,suggest:[{desc:"Update the dependencies array to be: ["+R.join(", ")+"]",fix:function(a){return a.replaceText(b,"["+R.join(", ")+"]")}}]})}}else{var V=null;t.forEach(function(a,b){V||a.references.forEach(function(a){if(!V&&u.has(a.identifier)){for(a=a.from;"function"!==a.type;)a=a.upper;a.block===d&&(V=b)}})});if(V){var da=ka({dependencies:t,declaredDependencies:[],optionalDependencies:Q,externalDependencies:new Set,isEffect:!0}).suggestedDependencies;
|
|
54
|
+
c({node:h,message:"React Hook "+f+" contains a call to '"+V+"'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass ["+da.join(", ")+("] as a second argument to the "+f+" Hook."),suggest:[{desc:"Add dependencies array: ["+da.join(", ")+"]",fix:function(a){return a.insertTextAfter(d,", ["+da.join(", ")+"]")}}]})}}}}var f=a.options&&a.options[0]&&a.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops||!1,H={additionalHooks:a.options&&a.options[0]&&
|
|
55
|
+
a.options[0].additionalHooks?new RegExp(a.options[0].additionalHooks):void 0,enableDangerousAutofixThisMayCauseInfiniteLoops:f},p=a.getSourceCode().scopeManager,u=new WeakMap,x=new WeakSet,y=new WeakMap,I=new WeakMap;return{CallExpression:function(d){var b=pa(d.callee,H);if(-1!==b){var e=d.arguments[b],f=d.callee,k=oa(f).name,n=d.arguments[b+1];d=/Effect($|[^a-z])/g.test(k);if(n||d){switch(e.type){case "FunctionExpression":case "ArrowFunctionExpression":h(e,n,f,k,d);return;case "Identifier":if(!n||
|
|
56
|
+
n.elements&&n.elements.some(function(a){return"Identifier"===a.type&&a.name===e.name}))return;b=a.getScope().set.get(e.name);if(null==b||null==b.defs)return;b=b.defs[0];if(!b||!b.node)break;if("Variable"!==b.type&&"FunctionName"!==b.type)break;switch(b.node.type){case "FunctionDeclaration":h(b.node,n,f,k,d);return;case "VariableDeclarator":if(b=b.node.init)switch(b.type){case "ArrowFunctionExpression":case "FunctionExpression":h(b,n,f,k,d);return}}break;default:c({node:f,message:"React Hook "+k+" received a function whose dependencies are unknown. Pass an inline function instead."});
|
|
57
|
+
return}c({node:f,message:"React Hook "+k+" has a missing dependency: '"+e.name+"'. Either include it or remove the dependency array.",suggest:[{desc:"Update the dependencies array to be: ["+e.name+"]",fix:function(a){return a.replaceText(n,"["+e.name+"]")}}]})}else"useMemo"!==k&&"useCallback"!==k||c({node:f,message:"React Hook "+k+" does nothing when called with only one argument. Did you forget to pass an array of dependencies?"})}}}}}};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-hooks",
|
|
3
3
|
"description": "ESLint rules for React Hooks",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/facebook/react.git",
|
|
@@ -25,10 +25,13 @@
|
|
|
25
25
|
"url": "https://github.com/facebook/react/issues"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=
|
|
28
|
+
"node": ">=10"
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://reactjs.org/",
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@typescript-eslint/parser": "^2.26.0"
|
|
33
36
|
}
|
|
34
37
|
}
|