@agilebot/eslint-plugin 0.5.2 → 0.5.4
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.
- 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"
|