@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.
Files changed (2) hide show
  1. package/dist/index.js +79 -12
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license @agilebot/eslint-plugin v0.5.2
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
- reportProblem({
865
- node: node,
866
- 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'
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 (name === 'useState' || name === 'useReducer') {
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
- if (declaredDependenciesNode.type !== 'ArrayExpression') {
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.elements.forEach(declaredDependencyNode => {
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, "' in the '").concat(setStateRecommendation.setter, "' call.");
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 declaredDependenciesNode = node.arguments[callbackIndex + 1];
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
- return getConstructionExpressionType(node.expression);
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.2",
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.2"
26
+ "@agilebot/eslint-utils": "0.5.4"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"