eslint-plugin-react-x 5.18.1 → 5.18.2

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 +95 -17
  2. package/package.json +9 -9
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.2";
149
149
 
150
150
  //#endregion
151
151
  //#region src/utils/create-rule.ts
@@ -3397,21 +3397,79 @@ function create$23(context) {
3397
3397
  //#endregion
3398
3398
  //#region src/rules/no-nested-component-definitions/lib.ts
3399
3399
  /**
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
3400
+ * Well-known component wrapper function names, matched by exact or `.`-suffixed fully
3401
+ * qualified name (e.g. `memo`, `React.memo`, `mobx.observer`, `connect` from react-redux,
3402
+ * or Relay's `create*Container` helpers). Only wrappers whose argument is a render function
3403
+ * are listed; wrappers taking a component identifier (`styled`, `motion`) or a loader
3404
+ * (`lazy`, `dynamic`) are irrelevant for name resolution.
3403
3405
  */
3404
- function isInsideJSXAttributeValue(node) {
3405
- return node.parent.type === AST_NODE_TYPES.JSXAttribute || findParentAttribute(node, (n) => n.value?.type === AST_NODE_TYPES.JSXExpressionContainer) != null;
3406
+ const WELL_KNOWN_COMPONENT_WRAPPERS = [
3407
+ "connect",
3408
+ "createFragmentContainer",
3409
+ "createPaginationContainer",
3410
+ "createRefetchContainer",
3411
+ "forwardRef",
3412
+ "graphql",
3413
+ "memo",
3414
+ "observer",
3415
+ "useCallback"
3416
+ ];
3417
+ /** Matches the HOC naming convention shared by recompose, react-router v5, Formik and custom HOCs (e.g. `withProps`, `withRouter`, `withFormik`, `withAuth`). */
3418
+ const RE_HOC_WRAPPER_NAME = /^with[A-Z]/;
3419
+ /**
3420
+ * Check if a call expression is a well-known component wrapper call.
3421
+ * Only calls whose callee's fully qualified name is (or ends with) a well-known wrapper
3422
+ * name (e.g. `memo`, `React.memo`, `React.useCallback`, `mobx.observer`) are treated as
3423
+ * wrappers; anything else, including member calls on data objects (e.g. `items.map`,
3424
+ * `Array.from`), is not, so array method callbacks are never mistaken for wrapped components.
3425
+ * Curried wrappers like `connect(...)(Component)` or `withFormik(...)(Component)` are
3426
+ * recognized by unwrapping nested callee call expressions.
3427
+ * @param context The rule context
3428
+ * @param node The call expression to check
3429
+ * @returns `true` if the call is a well-known component wrapper call
3430
+ */
3431
+ function isWellKnownComponentWrapperCall(context, node) {
3432
+ let callee = Extract.unwrap(node.callee);
3433
+ while (callee.type === AST_NODE_TYPES.CallExpression) callee = Extract.unwrap(callee.callee);
3434
+ const name = Extract.getFullyQualifiedName(callee, (n) => context.sourceCode.getText(n));
3435
+ const baseName = name.slice(name.lastIndexOf(".") + 1);
3436
+ return RE_HOC_WRAPPER_NAME.test(baseName) || WELL_KNOWN_COMPONENT_WRAPPERS.some((wrapper) => name === wrapper || name.endsWith(`.${wrapper}`));
3406
3437
  }
3407
3438
  /**
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
3439
+ * Check if a call expression is a component wrapper call for name resolution purposes.
3440
+ * @param context The rule context
3441
+ * @param call The call expression to check
3442
+ * @param arg The function node passed to the call
3443
+ * @returns `true` if the call is a component wrapper call
3412
3444
  */
3413
- function isInsideRenderMethod(node) {
3414
- return Traverse.findParent(node, (n) => core.isRenderMethodLike(n) && core.isClassComponent(n.parent.parent)) != null;
3445
+ function isComponentWrapperCall(context, call, arg) {
3446
+ if (Extract.unwrap(call.callee) === arg) return false;
3447
+ return isWellKnownComponentWrapperCall(context, call);
3448
+ }
3449
+ /**
3450
+ * Resolve the name a function is bound to through a chain of wrapping call expressions,
3451
+ * e.g. `const Component = useCallback(() => <div />, [])` resolves to `Component`.
3452
+ * @param context The rule context
3453
+ * @param node The function node to resolve the bound name for
3454
+ * @returns The bound name if the call chain ends at a variable declarator with an identifier, `null` otherwise
3455
+ */
3456
+ function getWrapperCallBoundName(context, node) {
3457
+ let current = node;
3458
+ let parent = current.parent;
3459
+ while (Check.isTypeExpression(parent)) {
3460
+ current = parent;
3461
+ parent = parent.parent;
3462
+ }
3463
+ while (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCall(context, parent, current)) {
3464
+ current = parent;
3465
+ parent = parent.parent;
3466
+ while (Check.isTypeExpression(parent)) {
3467
+ current = parent;
3468
+ parent = parent.parent;
3469
+ }
3470
+ }
3471
+ if (parent.type !== AST_NODE_TYPES.VariableDeclarator || parent.id.type !== AST_NODE_TYPES.Identifier) return null;
3472
+ return parent.id.name;
3415
3473
  }
3416
3474
  /**
3417
3475
  * Determine whether the node is inside `createElement`'s props argument
@@ -3426,6 +3484,23 @@ function isInsideCreateElementProps(context, node) {
3426
3484
  if (prop == null) return false;
3427
3485
  return prop === call.arguments[1];
3428
3486
  }
3487
+ /**
3488
+ * Determine whether the node is inside JSX attribute value
3489
+ * @param node The AST node to check
3490
+ * @returns `true` if the node is inside JSX attribute value
3491
+ */
3492
+ function isInsideJSXAttributeValue(node) {
3493
+ return node.parent.type === AST_NODE_TYPES.JSXAttribute || findParentAttribute(node, (n) => n.value?.type === AST_NODE_TYPES.JSXExpressionContainer) != null;
3494
+ }
3495
+ /**
3496
+ * Check whether a given node is declared inside a class component's render block
3497
+ * Ex: class C extends React.Component { render() { const Nested = () => <div />; } }
3498
+ * @param node The AST node being checked
3499
+ * @returns `true` if the node is inside a class component's render block
3500
+ */
3501
+ function isInsideRenderMethod(node) {
3502
+ return Traverse.findParent(node, (n) => core.isRenderMethodLike(n) && core.isClassComponent(n.parent.parent)) != null;
3503
+ }
3429
3504
 
3430
3505
  //#endregion
3431
3506
  //#region src/rules/no-nested-component-definitions/no-nested-component-definitions.ts
@@ -3442,21 +3517,24 @@ var no_nested_component_definitions_default = createRule({
3442
3517
  defaultOptions: []
3443
3518
  });
3444
3519
  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;
3520
+ 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
3521
  const fc = core.getFunctionComponentCollector(context, { hint });
3447
3522
  const cc = core.getClassComponentCollector(context);
3448
3523
  return merge(fc.visitor, cc.visitor, { "Program:exit"(program) {
3449
3524
  const fComponents = [...fc.api.getAllComponents(program)];
3450
3525
  const cComponents = [...cc.api.getAllComponents(program)];
3526
+ const fComponentNodes = new Set(fComponents.map((c) => c.node));
3527
+ const cComponentNodes = new Set(cComponents.map((c) => c.node));
3451
3528
  function findEnclosingComponent(node) {
3452
3529
  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);
3530
+ if (Check.isFunction(n)) return fComponentNodes.has(n);
3531
+ if (Check.isClass(n)) return cComponentNodes.has(n);
3455
3532
  return false;
3456
3533
  });
3457
3534
  }
3458
- for (const { name, node: component } of fComponents) {
3459
- if (name == null) continue;
3535
+ for (let { name, node: component } of fComponents) {
3536
+ name ??= getWrapperCallBoundName(context, component);
3537
+ if (name == null || !core.isFunctionComponentNameLoose(name)) continue;
3460
3538
  if (isInsideJSXAttributeValue(component)) {
3461
3539
  context.report({
3462
3540
  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.2",
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",
@@ -45,16 +45,16 @@
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.2",
49
+ "@eslint-react/core": "5.18.2",
50
+ "@eslint-react/eslint": "5.18.2",
51
+ "@eslint-react/jsx": "5.18.2",
52
+ "@eslint-react/var": "5.18.2",
53
+ "@eslint-react/shared": "5.18.2"
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",