@agilebot/eslint-plugin 0.5.2 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.js +79 -12
- package/package.json +2 -2
package/dist/index.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license @agilebot/eslint-plugin v0.5.
|
2
|
+
* @license @agilebot/eslint-plugin v0.5.4
|
3
3
|
*
|
4
4
|
* Copyright (c) Agilebot, Inc. and its affiliates.
|
5
5
|
*
|
@@ -792,6 +792,14 @@ var reactBetterExhaustiveDeps = {
|
|
792
792
|
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
793
793
|
type: 'boolean'
|
794
794
|
},
|
795
|
+
customHooks: {
|
796
|
+
type: 'object',
|
797
|
+
additionalProperties: {
|
798
|
+
callbackIndex: {
|
799
|
+
type: 'number'
|
800
|
+
}
|
801
|
+
}
|
802
|
+
},
|
795
803
|
staticHooks: {
|
796
804
|
type: 'object',
|
797
805
|
additionalProperties: {
|
@@ -819,11 +827,13 @@ var reactBetterExhaustiveDeps = {
|
|
819
827
|
create(context) {
|
820
828
|
const additionalHooks = context.options && context.options[0] && context.options[0].additionalHooks ? new RegExp(context.options[0].additionalHooks) : undefined;
|
821
829
|
const enableDangerousAutofixThisMayCauseInfiniteLoops = context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops || false;
|
830
|
+
const customHooks = context.options && context.options[0] && context.options[0].customHooks || {};
|
822
831
|
const staticHooks = context.options && context.options[0] && context.options[0].staticHooks || {};
|
823
832
|
const checkMemoizedVariableIsStatic = context.options && context.options[0] && context.options[0].checkMemoizedVariableIsStatic || false;
|
824
833
|
const options = {
|
825
834
|
additionalHooks,
|
826
835
|
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
836
|
+
customHooks,
|
827
837
|
staticHooks,
|
828
838
|
checkMemoizedVariableIsStatic
|
829
839
|
};
|
@@ -849,6 +859,7 @@ var reactBetterExhaustiveDeps = {
|
|
849
859
|
const stateVariables = new WeakSet();
|
850
860
|
const stableKnownValueCache = new WeakMap();
|
851
861
|
const functionWithoutCapturedValueCache = new WeakMap();
|
862
|
+
const useEffectEventVariables = new WeakSet();
|
852
863
|
function memoizeWithWeakMap(fn, map) {
|
853
864
|
return function (arg) {
|
854
865
|
if (map.has(arg)) {
|
@@ -861,10 +872,20 @@ var reactBetterExhaustiveDeps = {
|
|
861
872
|
}
|
862
873
|
function visitFunctionWithDependencies(node, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect) {
|
863
874
|
if (isEffect && node.async) {
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
875
|
+
let isCustomHook = false;
|
876
|
+
if (options.customHooks) {
|
877
|
+
Object.entries(options.customHooks).forEach(([key, customParts]) => {
|
878
|
+
if (typeof customParts === 'object' && new RegExp(key).test(reactiveHookName)) {
|
879
|
+
isCustomHook = true;
|
880
|
+
}
|
881
|
+
});
|
882
|
+
}
|
883
|
+
if (!isCustomHook) {
|
884
|
+
reportProblem({
|
885
|
+
node: node,
|
886
|
+
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://react.dev/link/hooks-data-fetching'
|
887
|
+
});
|
888
|
+
}
|
868
889
|
}
|
869
890
|
const scope = scopeManager.acquire(node);
|
870
891
|
const pureScopes = new Set();
|
@@ -901,7 +922,7 @@ var reactBetterExhaustiveDeps = {
|
|
901
922
|
if (init == null) {
|
902
923
|
return false;
|
903
924
|
}
|
904
|
-
while (init.type === 'TSAsExpression') {
|
925
|
+
while (init.type === 'TSAsExpression' || init.type === 'AsExpression') {
|
905
926
|
init = init.expression;
|
906
927
|
}
|
907
928
|
let declaration = def.node.parent;
|
@@ -931,7 +952,14 @@ var reactBetterExhaustiveDeps = {
|
|
931
952
|
} = callee;
|
932
953
|
if (name === 'useRef' && id.type === 'Identifier') {
|
933
954
|
return true;
|
934
|
-
} else if (
|
955
|
+
} else if (isUseEffectEventIdentifier() && id.type === 'Identifier') {
|
956
|
+
for (const ref of resolved.references) {
|
957
|
+
if (ref !== id) {
|
958
|
+
useEffectEventVariables.add(ref.identifier);
|
959
|
+
}
|
960
|
+
}
|
961
|
+
return true;
|
962
|
+
} else if (name === 'useState' || name === 'useReducer' || name === 'useActionState') {
|
935
963
|
if (id.type === 'ArrayPattern' && id.elements.length === 2 && isArray(resolved.identifiers)) {
|
936
964
|
if (id.elements[1] === resolved.identifiers[0]) {
|
937
965
|
if (name === 'useState') {
|
@@ -1218,13 +1246,16 @@ var reactBetterExhaustiveDeps = {
|
|
1218
1246
|
}
|
1219
1247
|
const declaredDependencies = [];
|
1220
1248
|
const externalDependencies = new Set();
|
1221
|
-
|
1249
|
+
const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression';
|
1250
|
+
const isTSAsArrayExpression = declaredDependenciesNode.type === 'TSAsExpression' && declaredDependenciesNode.expression.type === 'ArrayExpression';
|
1251
|
+
if (!isArrayExpression && !isTSAsArrayExpression) {
|
1222
1252
|
reportProblem({
|
1223
1253
|
node: declaredDependenciesNode,
|
1224
1254
|
message: "React Hook ".concat(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.'
|
1225
1255
|
});
|
1226
1256
|
} else {
|
1227
|
-
declaredDependenciesNode.
|
1257
|
+
const arrayExpression = isTSAsArrayExpression ? declaredDependenciesNode.expression : declaredDependenciesNode;
|
1258
|
+
arrayExpression.elements.forEach(declaredDependencyNode => {
|
1228
1259
|
if (declaredDependencyNode == null) {
|
1229
1260
|
return;
|
1230
1261
|
}
|
@@ -1235,6 +1266,18 @@ var reactBetterExhaustiveDeps = {
|
|
1235
1266
|
});
|
1236
1267
|
return;
|
1237
1268
|
}
|
1269
|
+
if (useEffectEventVariables.has(declaredDependencyNode)) {
|
1270
|
+
reportProblem({
|
1271
|
+
node: declaredDependencyNode,
|
1272
|
+
message: 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + "Remove `".concat(getSource(declaredDependencyNode), "` from the list."),
|
1273
|
+
suggest: [{
|
1274
|
+
desc: "Remove the dependency `".concat(getSource(declaredDependencyNode), "`"),
|
1275
|
+
fix(fixer) {
|
1276
|
+
return fixer.removeRange(declaredDependencyNode.range);
|
1277
|
+
}
|
1278
|
+
}]
|
1279
|
+
});
|
1280
|
+
}
|
1238
1281
|
let declaredDependency;
|
1239
1282
|
try {
|
1240
1283
|
declaredDependency = analyzePropertyChain(declaredDependencyNode, null);
|
@@ -1512,7 +1555,8 @@ var reactBetterExhaustiveDeps = {
|
|
1512
1555
|
extraWarning = " If '".concat(setStateRecommendation.setter, "' needs the ") + "current value of '".concat(setStateRecommendation.missingDep, "', ") + "you can also switch to useReducer instead of useState and " + "read '".concat(setStateRecommendation.missingDep, "' in the reducer.");
|
1513
1556
|
break;
|
1514
1557
|
case 'updater':
|
1515
|
-
extraWarning = " You can also do a functional update '".concat(setStateRecommendation.setter, "(").concat(setStateRecommendation.missingDep.slice(0, 1), " => ...)' if you only need '").concat(setStateRecommendation.missingDep
|
1558
|
+
extraWarning = " You can also do a functional update '".concat(setStateRecommendation.setter, "(").concat(setStateRecommendation.missingDep.slice(0, 1), " => ...)' if you only need '").concat(setStateRecommendation.missingDep
|
1559
|
+
, "'") + " in the '".concat(setStateRecommendation.setter, "' call.");
|
1516
1560
|
break;
|
1517
1561
|
default:
|
1518
1562
|
throw new Error('Unknown case.');
|
@@ -1539,7 +1583,8 @@ var reactBetterExhaustiveDeps = {
|
|
1539
1583
|
const callback = node.arguments[callbackIndex];
|
1540
1584
|
const reactiveHook = node.callee;
|
1541
1585
|
const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name;
|
1542
|
-
const
|
1586
|
+
const maybeNode = node.arguments[callbackIndex + 1];
|
1587
|
+
const declaredDependenciesNode = maybeNode && !(maybeNode.type === 'Identifier' && maybeNode.name === 'undefined') ? maybeNode : undefined;
|
1543
1588
|
const isEffect = /Effect($|[^a-z])/g.test(reactiveHookName);
|
1544
1589
|
if (!callback) {
|
1545
1590
|
reportProblem({
|
@@ -1562,6 +1607,9 @@ var reactBetterExhaustiveDeps = {
|
|
1562
1607
|
case 'ArrowFunctionExpression':
|
1563
1608
|
visitFunctionWithDependencies(callback, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1564
1609
|
return;
|
1610
|
+
case 'TSAsExpression':
|
1611
|
+
visitFunctionWithDependencies(callback.expression, declaredDependenciesNode, reactiveHook, reactiveHookName, isEffect);
|
1612
|
+
return;
|
1565
1613
|
case 'Identifier':
|
1566
1614
|
if (!declaredDependenciesNode) {
|
1567
1615
|
return;
|
@@ -1766,7 +1814,7 @@ function getConstructionExpressionType(node) {
|
|
1766
1814
|
}
|
1767
1815
|
return null;
|
1768
1816
|
case 'TypeCastExpression':
|
1769
|
-
|
1817
|
+
case 'AsExpression':
|
1770
1818
|
case 'TSAsExpression':
|
1771
1819
|
return getConstructionExpressionType(node.expression);
|
1772
1820
|
}
|
@@ -1904,6 +1952,22 @@ function getReactiveHookCallbackIndex(calleeNode, options) {
|
|
1904
1952
|
case 'useImperativeHandle':
|
1905
1953
|
return 1;
|
1906
1954
|
default:
|
1955
|
+
if (node === calleeNode && options && options.customHooks) {
|
1956
|
+
let name;
|
1957
|
+
try {
|
1958
|
+
name = analyzePropertyChain(node, null);
|
1959
|
+
} catch (err) {
|
1960
|
+
if (/Unsupported node type/.test(err.message)) {
|
1961
|
+
return 0;
|
1962
|
+
}
|
1963
|
+
throw err;
|
1964
|
+
}
|
1965
|
+
for (const [key, customParts] of Object.entries(options.customHooks)) {
|
1966
|
+
if (typeof customParts === 'object' && typeof customParts.callbackIndex === 'number' && new RegExp(key).test(name)) {
|
1967
|
+
return customParts.callbackIndex;
|
1968
|
+
}
|
1969
|
+
}
|
1970
|
+
}
|
1907
1971
|
if (node === calleeNode && options && options.additionalHooks) {
|
1908
1972
|
let name;
|
1909
1973
|
try {
|
@@ -1972,6 +2036,9 @@ function isSameIdentifier(a, b) {
|
|
1972
2036
|
function isAncestorNodeOf(a, b) {
|
1973
2037
|
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
|
1974
2038
|
}
|
2039
|
+
function isUseEffectEventIdentifier(node) {
|
2040
|
+
return false;
|
2041
|
+
}
|
1975
2042
|
|
1976
2043
|
const Components = require('eslint-plugin-react/lib/util/Components');
|
1977
2044
|
const RULE_NAME$7 = 'react-hook-use-ref';
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@agilebot/eslint-plugin",
|
3
|
-
"version": "0.5.
|
3
|
+
"version": "0.5.4",
|
4
4
|
"description": "Agilebot's ESLint plugin",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"types": "dist/index.d.ts",
|
@@ -23,7 +23,7 @@
|
|
23
23
|
"eslint-plugin-deprecation": "^3.0.0",
|
24
24
|
"eslint-plugin-react": "^7.35.0",
|
25
25
|
"eslint-plugin-react-hooks": "^4.6.2",
|
26
|
-
"@agilebot/eslint-utils": "0.5.
|
26
|
+
"@agilebot/eslint-utils": "0.5.4"
|
27
27
|
},
|
28
28
|
"peerDependencies": {
|
29
29
|
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
|