eslint-plugin-react-x 2.3.8-next.1 → 2.3.9-next.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.
Files changed (2) hide show
  1. package/dist/index.js +78 -16
  2. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -6,11 +6,12 @@ import { P, isMatching, match } from "ts-pattern";
6
6
  import ts from "typescript";
7
7
  import * as AST from "@eslint-react/ast";
8
8
  import { findVariable, getChildScopes, getObjectType, getVariableDefinitionNode } from "@eslint-react/var";
9
- import { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, JsxEmit, findParentJsxAttribute, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, isAssignmentToThisState, isCaptureOwnerStackCall, isChildrenCount, isChildrenForEach, isChildrenMap, isChildrenOnly, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElementCall, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUpdate, isCreateContextCall, isCreateElementCall, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRefCall, isGetDerivedStateFromError, isGetDerivedStateFromProps, isInitializedFromReact, isInstanceIdEqual, isJsxFragmentElement, isJsxHostElement, isJsxText, isLazyCall, isReactHookCall, isReactHookName, isRenderMethodLike, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseCall, isUseCallbackCall, isUseContextCall, isUseMemoCall, isUseStateCall, useComponentCollector, useComponentCollectorLegacy, useHookCollector } from "@eslint-react/core";
9
+ import { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, JsxEmit, findParentJsxAttribute, getInstanceId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, isAssignmentToThisState, isCaptureOwnerStackCall, isChildrenCount, isChildrenForEach, isChildrenMap, isChildrenOnly, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElementCall, isComponentDidCatch, isComponentDidMount, isComponentDidUpdate, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUpdate, isCreateContextCall, isCreateElementCall, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRefCall, isGetDerivedStateFromError, isGetDerivedStateFromProps, isInitializedFromReact, isInstanceIdEqual, isJsxFragmentElement, isJsxHostElement, isJsxText, isLazyCall, isReactHookCall, isReactHookName, isRenderMethodLike, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseCall, isUseCallbackCall, isUseContextCall, isUseEffectLikeCall, isUseMemoCall, isUseStateCall, useComponentCollector, useComponentCollectorLegacy, useHookCollector } from "@eslint-react/core";
10
10
  import { constFalse, constTrue, flow, getOrElseUpdate, identity, unit } from "@eslint-react/eff";
11
11
  import { compare } from "compare-versions";
12
12
  import { getConstrainedTypeAtLocation, isTypeReadonly } from "@typescript-eslint/type-utils";
13
- import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
13
+ import { getStaticValue, isIdentifier, isVariableDeclarator } from "@typescript-eslint/utils/ast-utils";
14
+ import "@typescript-eslint/utils/ts-eslint";
14
15
  import { getTypeImmutability, isImmutable, isReadonlyDeep, isReadonlyShallow, isUnknown } from "is-immutable-type";
15
16
  import { camelCase } from "string-ts";
16
17
 
@@ -33,7 +34,7 @@ var __export = (all, symbols) => {
33
34
  //#endregion
34
35
  //#region package.json
35
36
  var name$6 = "eslint-plugin-react-x";
36
- var version = "2.3.8-next.1";
37
+ var version = "2.3.9-next.0";
37
38
 
38
39
  //#endregion
39
40
  //#region src/utils/create-rule.ts
@@ -2274,7 +2275,10 @@ var no_unnecessary_use_callback_default = createRule({
2274
2275
  description: "Disallow unnecessary usage of `useCallback`.",
2275
2276
  [Symbol.for("rule_features")]: RULE_FEATURES$16
2276
2277
  },
2277
- messages: { noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary." },
2278
+ messages: {
2279
+ noUnnecessaryUseCallback: "An 'useCallback' with empty deps and no references to the component scope may be unnecessary.",
2280
+ noUnnecessaryUseCallbackInsideUseEffect: "{{name}} is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
2281
+ },
2278
2282
  schema: []
2279
2283
  },
2280
2284
  name: RULE_NAME$18,
@@ -2285,6 +2289,7 @@ function create$18(context) {
2285
2289
  if (!context.sourceCode.text.includes("useCallback")) return {};
2286
2290
  return { CallExpression(node) {
2287
2291
  if (!isUseCallbackCall(node)) return;
2292
+ const checkForUsageInsideUseEffectReport = checkForUsageInsideUseEffect$1(context.sourceCode, node);
2288
2293
  const initialScope = context.sourceCode.getScope(node);
2289
2294
  const component = context.sourceCode.getScope(node).block;
2290
2295
  if (!AST.isFunction(component)) return;
@@ -2294,7 +2299,10 @@ function create$18(context) {
2294
2299
  const variableNode = getVariableDefinitionNode(findVariable(n.name, initialScope), 0);
2295
2300
  if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) return false;
2296
2301
  return variableNode.elements.length === 0;
2297
- }).otherwise(() => false)) return;
2302
+ }).otherwise(() => false)) {
2303
+ report(context)(checkForUsageInsideUseEffectReport);
2304
+ return;
2305
+ }
2298
2306
  const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
2299
2307
  if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) return n.body;
2300
2308
  return n;
@@ -2304,12 +2312,34 @@ function create$18(context) {
2304
2312
  return variableNode;
2305
2313
  }).otherwise(() => null);
2306
2314
  if (arg0Node == null) return;
2307
- if (!getChildScopes(context.sourceCode.getScope(arg0Node)).flatMap((x) => x.references).some((x) => x.resolved?.scope.block === component)) context.report({
2308
- messageId: "noUnnecessaryUseCallback",
2309
- node
2310
- });
2315
+ if (!getChildScopes(context.sourceCode.getScope(arg0Node)).flatMap((x) => x.references).some((x) => x.resolved?.scope.block === component)) {
2316
+ context.report({
2317
+ messageId: "noUnnecessaryUseCallback",
2318
+ node
2319
+ });
2320
+ return;
2321
+ }
2322
+ report(context)(checkForUsageInsideUseEffectReport);
2311
2323
  } };
2312
2324
  }
2325
+ function checkForUsageInsideUseEffect$1(sourceCode, node) {
2326
+ if (!/use\w*Effect/u.test(sourceCode.text)) return;
2327
+ if (!isVariableDeclarator(node.parent)) return;
2328
+ if (!isIdentifier(node.parent.id)) return;
2329
+ const usages = (sourceCode.getDeclaredVariables(node.parent)[0]?.references ?? []).filter((ref) => !(ref.init ?? false));
2330
+ const effectSet = /* @__PURE__ */ new Set();
2331
+ for (const usage of usages) {
2332
+ const effect = AST.findParentNode(usage.identifier, isUseEffectLikeCall);
2333
+ if (effect == null) return;
2334
+ effectSet.add(effect);
2335
+ if (effectSet.size > 1) return;
2336
+ }
2337
+ return {
2338
+ messageId: "noUnnecessaryUseCallbackInsideUseEffect",
2339
+ node,
2340
+ data: { name: node.parent.id.name }
2341
+ };
2342
+ }
2313
2343
 
2314
2344
  //#endregion
2315
2345
  //#region src/rules/no-unnecessary-use-memo.ts
@@ -2322,7 +2352,10 @@ var no_unnecessary_use_memo_default = createRule({
2322
2352
  description: "Disallow unnecessary usage of `useMemo`.",
2323
2353
  [Symbol.for("rule_features")]: RULE_FEATURES$15
2324
2354
  },
2325
- messages: { noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary." },
2355
+ messages: {
2356
+ noUnnecessaryUseMemo: "An 'useMemo' with empty deps and no references to the component scope may be unnecessary.",
2357
+ noUnnecessaryUseMemoInsideUseEffect: "{{name}} is only used inside 1 useEffect, which may be unnecessary. You can move the computation into useEffect directly and merge the dependency arrays."
2358
+ },
2326
2359
  schema: []
2327
2360
  },
2328
2361
  name: RULE_NAME$17,
@@ -2334,16 +2367,23 @@ function create$17(context) {
2334
2367
  return { CallExpression(node) {
2335
2368
  const initialScope = context.sourceCode.getScope(node);
2336
2369
  if (!isUseMemoCall(node)) return;
2370
+ const checkForUsageInsideUseEffectReport = checkForUsageInsideUseEffect(context.sourceCode, node);
2337
2371
  const component = context.sourceCode.getScope(node).block;
2338
2372
  if (!AST.isFunction(component)) return;
2339
2373
  const [arg0, arg1] = node.arguments;
2340
2374
  if (arg0 == null || arg1 == null) return;
2341
- if (AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0) return;
2375
+ if (AST.isFunction(arg0) && [...AST.getNestedCallExpressions(arg0.body), ...AST.getNestedNewExpressions(arg0.body)].length > 0) {
2376
+ report(context)(checkForUsageInsideUseEffectReport);
2377
+ return;
2378
+ }
2342
2379
  if (!match(arg1).with({ type: AST_NODE_TYPES.ArrayExpression }, (n) => n.elements.length === 0).with({ type: AST_NODE_TYPES.Identifier }, (n) => {
2343
2380
  const variableNode = getVariableDefinitionNode(findVariable(n.name, initialScope), 0);
2344
2381
  if (variableNode?.type !== AST_NODE_TYPES.ArrayExpression) return false;
2345
2382
  return variableNode.elements.length === 0;
2346
- }).otherwise(() => false)) return;
2383
+ }).otherwise(() => false)) {
2384
+ report(context)(checkForUsageInsideUseEffectReport);
2385
+ return;
2386
+ }
2347
2387
  const arg0Node = match(arg0).with({ type: AST_NODE_TYPES.ArrowFunctionExpression }, (n) => {
2348
2388
  if (n.body.type === AST_NODE_TYPES.ArrowFunctionExpression) return n.body;
2349
2389
  return n;
@@ -2353,12 +2393,34 @@ function create$17(context) {
2353
2393
  return variableNode;
2354
2394
  }).otherwise(() => null);
2355
2395
  if (arg0Node == null) return;
2356
- if (!getChildScopes(context.sourceCode.getScope(arg0Node)).flatMap((x) => x.references).some((x) => x.resolved?.scope.block === component)) context.report({
2357
- messageId: "noUnnecessaryUseMemo",
2358
- node
2359
- });
2396
+ if (!getChildScopes(context.sourceCode.getScope(arg0Node)).flatMap((x) => x.references).some((x) => x.resolved?.scope.block === component)) {
2397
+ context.report({
2398
+ messageId: "noUnnecessaryUseMemo",
2399
+ node
2400
+ });
2401
+ return;
2402
+ }
2403
+ report(context)(checkForUsageInsideUseEffectReport);
2360
2404
  } };
2361
2405
  }
2406
+ function checkForUsageInsideUseEffect(sourceCode, node) {
2407
+ if (!/use\w*Effect/u.test(sourceCode.text)) return;
2408
+ if (!isVariableDeclarator(node.parent)) return;
2409
+ if (!isIdentifier(node.parent.id)) return;
2410
+ const usages = (sourceCode.getDeclaredVariables(node.parent)[0]?.references ?? []).filter((ref) => !(ref.init ?? false));
2411
+ const effectSet = /* @__PURE__ */ new Set();
2412
+ for (const usage of usages) {
2413
+ const effect = AST.findParentNode(usage.identifier, isUseEffectLikeCall);
2414
+ if (effect == null) return;
2415
+ effectSet.add(effect);
2416
+ if (effectSet.size > 1) return;
2417
+ }
2418
+ return {
2419
+ messageId: "noUnnecessaryUseMemoInsideUseEffect",
2420
+ node,
2421
+ data: { name: node.parent.id.name }
2422
+ };
2423
+ }
2362
2424
 
2363
2425
  //#endregion
2364
2426
  //#region src/rules/no-unnecessary-use-prefix.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "2.3.8-next.1",
3
+ "version": "2.3.9-next.0",
4
4
  "description": "A set of composable ESLint rules for for libraries and frameworks that use React as a UI runtime.",
5
5
  "keywords": [
6
6
  "react",
@@ -37,30 +37,30 @@
37
37
  "./package.json"
38
38
  ],
39
39
  "dependencies": {
40
- "@typescript-eslint/scope-manager": "^8.47.0",
41
- "@typescript-eslint/type-utils": "^8.47.0",
42
- "@typescript-eslint/types": "^8.47.0",
43
- "@typescript-eslint/utils": "^8.47.0",
40
+ "@typescript-eslint/scope-manager": "^8.48.0",
41
+ "@typescript-eslint/type-utils": "^8.48.0",
42
+ "@typescript-eslint/types": "^8.48.0",
43
+ "@typescript-eslint/utils": "^8.48.0",
44
44
  "compare-versions": "^6.1.1",
45
45
  "is-immutable-type": "^5.0.1",
46
46
  "string-ts": "^2.2.1",
47
47
  "ts-api-utils": "^2.1.0",
48
48
  "ts-pattern": "^5.9.0",
49
- "@eslint-react/ast": "2.3.8-next.1",
50
- "@eslint-react/core": "2.3.8-next.1",
51
- "@eslint-react/eff": "2.3.8-next.1",
52
- "@eslint-react/var": "2.3.8-next.1",
53
- "@eslint-react/shared": "2.3.8-next.1"
49
+ "@eslint-react/ast": "2.3.9-next.0",
50
+ "@eslint-react/eff": "2.3.9-next.0",
51
+ "@eslint-react/core": "2.3.9-next.0",
52
+ "@eslint-react/shared": "2.3.9-next.0",
53
+ "@eslint-react/var": "2.3.9-next.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/react": "^19.2.6",
56
+ "@types/react": "^19.2.7",
57
57
  "@types/react-dom": "^19.2.3",
58
- "tsdown": "^0.16.6",
58
+ "tsdown": "^0.16.7",
59
59
  "@local/configs": "0.0.0"
60
60
  },
61
61
  "peerDependencies": {
62
- "eslint": "^9.39.1",
63
- "typescript": "^5.9.3"
62
+ "eslint": "^8.57.0 || ^9.0.0",
63
+ "typescript": ">=4.8.4 <6.0.0"
64
64
  },
65
65
  "engines": {
66
66
  "node": ">=20.19.0"