eslint-plugin-react-x 5.18.1 → 5.18.3

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 +103 -18
  2. package/package.json +16 -16
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ const rules$6 = {
145
145
  //#endregion
146
146
  //#region package.json
147
147
  var name$6 = "eslint-plugin-react-x";
148
- var version = "5.18.1";
148
+ var version = "5.18.3";
149
149
 
150
150
  //#endregion
151
151
  //#region src/utils/create-rule.ts
@@ -3396,25 +3396,90 @@ function create$23(context) {
3396
3396
 
3397
3397
  //#endregion
3398
3398
  //#region src/rules/no-nested-component-definitions/lib.ts
3399
+ /** Check if the node is a call expression to react-redux's `connect`. */
3400
+ const isConnectCall = core.isAPICall("connect");
3401
+ /** Check if the node is a call expression to Relay's `createFragmentContainer`. */
3402
+ const isCreateFragmentContainerCall = core.isAPICall("createFragmentContainer");
3403
+ /** Check if the node is a call expression to Relay's `createPaginationContainer`. */
3404
+ const isCreatePaginationContainerCall = core.isAPICall("createPaginationContainer");
3405
+ /** Check if the node is a call expression to Relay's `createRefetchContainer`. */
3406
+ const isCreateRefetchContainerCall = core.isAPICall("createRefetchContainer");
3407
+ /** Check if the node is a call expression to React's `forwardRef`. */
3408
+ const isForwardRefCall = core.isAPICall("forwardRef");
3409
+ /** Check if the node is a call expression to a `graphql` tag function (e.g. from Relay or Apollo). */
3410
+ const isGraphqlCall = core.isAPICall("graphql");
3411
+ /** Check if the node is a call expression to React's `memo`. */
3412
+ const isMemoCall = core.isAPICall("memo");
3413
+ /** Check if the node is a call expression to MobX's `observer`. */
3414
+ const isObserverCall = core.isAPICall("observer");
3415
+ /** Check if the node is a call expression to React's `useCallback`. */
3416
+ const isUseCallbackCall = core.isAPICall("useCallback");
3417
+ /** Check if the node is a call expression to Formik's `withFormik`. */
3418
+ const isWithFormikCall = core.isAPICall("withFormik");
3419
+ /** Check if the node is a call expression to recompose's `withHandlers`. */
3420
+ const isWithHandlersCall = core.isAPICall("withHandlers");
3421
+ /** Check if the node is a call expression to recompose's `withLifecycle`. */
3422
+ const isWithLifecycleCall = core.isAPICall("withLifecycle");
3423
+ /** Check if the node is a call expression to recompose's `withProps`. */
3424
+ const isWithPropsCall = core.isAPICall("withProps");
3425
+ /** Check if the node is a call expression to react-router v5's `withRouter`. */
3426
+ const isWithRouterCall = core.isAPICall("withRouter");
3427
+ /** Check if the node is a call expression to recompose's `withState`. */
3428
+ const isWithStateCall = core.isAPICall("withState");
3399
3429
  /**
3400
- * Determine whether the node is inside JSX attribute value
3401
- * @param node The AST node to check
3402
- * @returns `true` if the node is inside JSX attribute value
3430
+ * Check if a call is a well-known component wrapper call (e.g. `memo`, `connect`, `withFormik`).
3431
+ * Curried forms like `connect(...)(Component)` are matched via the innermost callee call.
3432
+ * @param context The rule context
3433
+ * @param call The call expression to check
3434
+ * @param arg The function node passed to the call
3435
+ * @returns `true` if the call is a component wrapper call
3403
3436
  */
3404
- function isInsideJSXAttributeValue(node) {
3405
- return node.parent.type === AST_NODE_TYPES.JSXAttribute || findParentAttribute(node, (n) => n.value?.type === AST_NODE_TYPES.JSXExpressionContainer) != null;
3437
+ function isComponentWrapperCall(context, call, arg) {
3438
+ if (Extract.unwrap(call.callee) === arg) return false;
3439
+ call = Extract.getInnermostCall(call);
3440
+ if (isConnectCall(context, call)) return true;
3441
+ if (isCreateFragmentContainerCall(context, call)) return true;
3442
+ if (isCreatePaginationContainerCall(context, call)) return true;
3443
+ if (isCreateRefetchContainerCall(context, call)) return true;
3444
+ if (isForwardRefCall(context, call)) return true;
3445
+ if (isGraphqlCall(context, call)) return true;
3446
+ if (isMemoCall(context, call)) return true;
3447
+ if (isObserverCall(context, call)) return true;
3448
+ if (isUseCallbackCall(context, call)) return true;
3449
+ if (isWithFormikCall(context, call)) return true;
3450
+ if (isWithHandlersCall(context, call)) return true;
3451
+ if (isWithLifecycleCall(context, call)) return true;
3452
+ if (isWithPropsCall(context, call)) return true;
3453
+ if (isWithRouterCall(context, call)) return true;
3454
+ if (isWithStateCall(context, call)) return true;
3455
+ return false;
3406
3456
  }
3407
3457
  /**
3408
- * Check whether a given node is declared inside a class component's render block
3409
- * Ex: class C extends React.Component { render() { const Nested = () => <div />; } }
3410
- * @param node The AST node being checked
3411
- * @returns `true` if the node is inside a class component's render block
3458
+ * Resolve the name a function is bound to through wrapping calls (e.g. `const C = useCallback(...)` → `C`).
3459
+ * @param context The rule context
3460
+ * @param node The function node to resolve the bound name for
3461
+ * @returns The bound name, or `null` if the call chain does not end at an identifier declarator
3412
3462
  */
3413
- function isInsideRenderMethod(node) {
3414
- return Traverse.findParent(node, (n) => core.isRenderMethodLike(n) && core.isClassComponent(n.parent.parent)) != null;
3463
+ function getWrapperCallBoundName(context, node) {
3464
+ let current = node;
3465
+ let parent = current.parent;
3466
+ while (Check.isTypeExpression(parent)) {
3467
+ current = parent;
3468
+ parent = parent.parent;
3469
+ }
3470
+ while (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCall(context, parent, current)) {
3471
+ current = parent;
3472
+ parent = parent.parent;
3473
+ while (Check.isTypeExpression(parent)) {
3474
+ current = parent;
3475
+ parent = parent.parent;
3476
+ }
3477
+ }
3478
+ if (parent.type !== AST_NODE_TYPES.VariableDeclarator || parent.id.type !== AST_NODE_TYPES.Identifier) return null;
3479
+ return parent.id.name;
3415
3480
  }
3416
3481
  /**
3417
- * Determine whether the node is inside `createElement`'s props argument
3482
+ * Check if the node is inside `createElement`'s props argument
3418
3483
  * @param context The rule context
3419
3484
  * @param node The AST node to check
3420
3485
  * @returns `true` if the node is inside `createElement`'s props
@@ -3426,6 +3491,23 @@ function isInsideCreateElementProps(context, node) {
3426
3491
  if (prop == null) return false;
3427
3492
  return prop === call.arguments[1];
3428
3493
  }
3494
+ /**
3495
+ * Check if the node is inside a JSX attribute value
3496
+ * @param node The AST node to check
3497
+ * @returns `true` if the node is inside a JSX attribute value
3498
+ */
3499
+ function isInsideJSXAttributeValue(node) {
3500
+ return node.parent.type === AST_NODE_TYPES.JSXAttribute || findParentAttribute(node, (n) => n.value?.type === AST_NODE_TYPES.JSXExpressionContainer) != null;
3501
+ }
3502
+ /**
3503
+ * Check if the node is declared inside a class component's render block
3504
+ * Ex: class C extends React.Component { render() { const Nested = () => <div />; } }
3505
+ * @param node The AST node to check
3506
+ * @returns `true` if the node is inside a class component's render block
3507
+ */
3508
+ function isInsideRenderMethod(node) {
3509
+ return Traverse.findParent(node, (n) => core.isRenderMethodLike(n) && core.isClassComponent(n.parent.parent)) != null;
3510
+ }
3429
3511
 
3430
3512
  //#endregion
3431
3513
  //#region src/rules/no-nested-component-definitions/no-nested-component-definitions.ts
@@ -3442,21 +3524,24 @@ var no_nested_component_definitions_default = createRule({
3442
3524
  defaultOptions: []
3443
3525
  });
3444
3526
  function create$22(context) {
3445
- const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
3527
+ const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback;
3446
3528
  const fc = core.getFunctionComponentCollector(context, { hint });
3447
3529
  const cc = core.getClassComponentCollector(context);
3448
3530
  return merge(fc.visitor, cc.visitor, { "Program:exit"(program) {
3449
3531
  const fComponents = [...fc.api.getAllComponents(program)];
3450
3532
  const cComponents = [...cc.api.getAllComponents(program)];
3533
+ const fComponentNodes = new Set(fComponents.map((c) => c.node));
3534
+ const cComponentNodes = new Set(cComponents.map((c) => c.node));
3451
3535
  function findEnclosingComponent(node) {
3452
3536
  return Traverse.findParent(node, (n) => {
3453
- if (Check.isFunction(n)) return fComponents.some((c) => c.node === n);
3454
- if (Check.isClass(n)) return cComponents.some((c) => c.node === n);
3537
+ if (Check.isFunction(n)) return fComponentNodes.has(n);
3538
+ if (Check.isClass(n)) return cComponentNodes.has(n);
3455
3539
  return false;
3456
3540
  });
3457
3541
  }
3458
- for (const { name, node: component } of fComponents) {
3459
- if (name == null) continue;
3542
+ for (let { name, node: component } of fComponents) {
3543
+ name ??= getWrapperCallBoundName(context, component);
3544
+ if (name == null || !core.isFunctionComponentNameLoose(name)) continue;
3460
3545
  if (isInsideJSXAttributeValue(component)) {
3461
3546
  context.report({
3462
3547
  data: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.18.1",
3
+ "version": "5.18.3",
4
4
  "description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
5
5
  "keywords": [
6
6
  "react",
@@ -36,25 +36,25 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@typescript-eslint/scope-manager": "^8.65.0",
40
- "@typescript-eslint/type-utils": "^8.65.0",
41
- "@typescript-eslint/types": "^8.65.0",
42
- "@typescript-eslint/typescript-estree": "^8.65.0",
43
- "@typescript-eslint/utils": "^8.65.0",
39
+ "@typescript-eslint/scope-manager": "^8.66.0",
40
+ "@typescript-eslint/type-utils": "^8.66.0",
41
+ "@typescript-eslint/types": "^8.66.0",
42
+ "@typescript-eslint/typescript-estree": "^8.66.0",
43
+ "@typescript-eslint/utils": "^8.66.0",
44
44
  "compare-versions": "^6.1.1",
45
45
  "string-ts": "^2.3.1",
46
46
  "ts-api-utils": "^2.5.0",
47
47
  "ts-pattern": "^5.9.0",
48
- "@eslint-react/ast": "5.18.1",
49
- "@eslint-react/core": "5.18.1",
50
- "@eslint-react/jsx": "5.18.1",
51
- "@eslint-react/shared": "5.18.1",
52
- "@eslint-react/eslint": "5.18.1",
53
- "@eslint-react/var": "5.18.1"
48
+ "@eslint-react/ast": "5.18.3",
49
+ "@eslint-react/core": "5.18.3",
50
+ "@eslint-react/eslint": "5.18.3",
51
+ "@eslint-react/shared": "5.18.3",
52
+ "@eslint-react/var": "5.18.3",
53
+ "@eslint-react/jsx": "5.18.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@types/react": "^19.2.17",
57
- "@types/react-dom": "^19.2.3",
56
+ "@types/react": "^19.2.18",
57
+ "@types/react-dom": "^19.2.4",
58
58
  "dedent": "^1.7.2",
59
59
  "eslint": "^10.8.0",
60
60
  "react": "^19.2.8",
@@ -63,8 +63,8 @@
63
63
  "tsl": "^1.0.30",
64
64
  "tsl-dx": "^0.13.3",
65
65
  "typescript": "6.0.3",
66
- "@local/configs": "0.0.0",
67
- "@local/eff": "0.0.0"
66
+ "@local/eff": "0.0.0",
67
+ "@local/configs": "0.0.0"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "eslint": "*",