eslint-plugin-react-x 5.17.2 → 5.18.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 +69 -52
  2. package/package.json +15 -15
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { compare } from "compare-versions";
12
12
  import { P, isMatching, match } from "ts-pattern";
13
13
  import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
14
14
  import { unionConstituents } from "ts-api-utils";
15
+ import "typescript";
15
16
  import { simpleTraverse } from "@typescript-eslint/typescript-estree";
16
17
  import { delimiterCase, snakeCase, toLowerCase } from "string-ts";
17
18
 
@@ -144,7 +145,7 @@ const rules$6 = {
144
145
  //#endregion
145
146
  //#region package.json
146
147
  var name$6 = "eslint-plugin-react-x";
147
- var version = "5.17.2";
148
+ var version = "5.18.0";
148
149
 
149
150
  //#endregion
150
151
  //#region src/utils/create-rule.ts
@@ -1380,7 +1381,7 @@ function hasRefLikeNameInChain(node) {
1380
1381
  return node.property.type === AST_NODE_TYPES.Identifier ? isRefLikeName$1(node.property.name) || hasRefLikeNameInChain(node.object) : hasRefLikeNameInChain(node.object);
1381
1382
  }
1382
1383
  function isInitializedFromCall(context, node, isCall) {
1383
- const root = node.type === AST_NODE_TYPES.Identifier ? node : Extract.getRootIdentifier(node);
1384
+ const root = node.type === AST_NODE_TYPES.Identifier ? node : Extract.getIdentifierAt(node, 0);
1384
1385
  if (root == null) return false;
1385
1386
  const variable = findVariable(context.sourceCode.getScope(root), root);
1386
1387
  if (variable == null) return false;
@@ -1391,7 +1392,8 @@ function isInitializedFromCall(context, node, isCall) {
1391
1392
  return init.type === AST_NODE_TYPES.CallExpression && isCall(init);
1392
1393
  }
1393
1394
  function isInitializedFromUseRef(context, node) {
1394
- return isInitializedFromCall(context, node, (init) => core.isUseRefCall(context, init));
1395
+ const { additionalRefHooks } = getSettingsFromContext(context);
1396
+ return isInitializedFromCall(context, node, (init) => core.isUseRefLikeCall(init, additionalRefHooks));
1395
1397
  }
1396
1398
  function isKnownNonMutatingMethodCall(context, node) {
1397
1399
  const callee = Extract.unwrap(node.callee);
@@ -1431,7 +1433,7 @@ function createImmutabilityCollector() {
1431
1433
  pushMutation("binding", node, target, target);
1432
1434
  return;
1433
1435
  case AST_NODE_TYPES.MemberExpression: {
1434
- const root = Extract.getRootIdentifier(target);
1436
+ const root = Extract.getIdentifierAt(target, 0);
1435
1437
  if (root != null) pushMutation("value", node, target, root);
1436
1438
  return;
1437
1439
  }
@@ -1442,7 +1444,7 @@ function createImmutabilityCollector() {
1442
1444
  if (callee.type === AST_NODE_TYPES.MemberExpression) {
1443
1445
  const method = Extract.getCalleeName(node);
1444
1446
  if (method != null && MUTATING_METHODS.has(method)) {
1445
- const root = Extract.getRootIdentifier(callee.object);
1447
+ const root = Extract.getIdentifierAt(callee.object, 0);
1446
1448
  if (root != null) pushMutation("value", node, callee.object, root);
1447
1449
  }
1448
1450
  }
@@ -1468,7 +1470,7 @@ function createImmutabilityCollector() {
1468
1470
  if (node.operator !== "delete") return;
1469
1471
  const target = Extract.unwrap(node.argument);
1470
1472
  if (target.type !== AST_NODE_TYPES.MemberExpression) return;
1471
- const root = Extract.getRootIdentifier(target);
1473
+ const root = Extract.getIdentifierAt(target, 0);
1472
1474
  if (root != null) pushMutation("value", node, target, root);
1473
1475
  },
1474
1476
  UpdateExpression(node) {
@@ -1478,7 +1480,7 @@ function createImmutabilityCollector() {
1478
1480
  pushMutation("binding", node, target, target);
1479
1481
  return;
1480
1482
  case AST_NODE_TYPES.MemberExpression: {
1481
- const root = Extract.getRootIdentifier(target);
1483
+ const root = Extract.getIdentifierAt(target, 0);
1482
1484
  if (root != null) pushMutation("value", node, target, root);
1483
1485
  return;
1484
1486
  }
@@ -2377,6 +2379,42 @@ function buildFixForComponentProps(context, fixer, node, typeArguments) {
2377
2379
  ].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
2378
2380
  }
2379
2381
 
2382
+ //#endregion
2383
+ //#region src/utils/create-implicit-prop-listener.ts
2384
+ /**
2385
+ * Creates a listener that detects JSX spread attributes implicitly passing a given prop
2386
+ * (ex: 'children', 'key', 'ref') to a component.
2387
+ *
2388
+ * For each union constituent of the spread argument's type, the prop is reported unless:
2389
+ * 1. The prop symbol's fully qualified name is allowed (pass-through of React internally defined props)
2390
+ * 2. The fully qualified name of the prop's type symbol (or its alias) is allowed (React type aliases)
2391
+ *
2392
+ * @param context The ESLint rule context
2393
+ * @param options The prop name, the allow-list predicates (receive lowercased fully qualified names), and the report callback
2394
+ * @param options.name The name of the prop to detect
2395
+ * @param options.isAllowedProp A predicate that receives the prop symbol's lowercased fully qualified name and returns `true` if it is allowed
2396
+ * @param options.isAllowedType A predicate that receives the prop's type symbol's lowercased fully qualified name and returns `true` if it is allowed
2397
+ * @param options.onImplicitProp A callback invoked with the spread attribute node when an implicit prop is detected
2398
+ * @returns A rule listener reporting on JSX spread attributes
2399
+ */
2400
+ function createImplicitPropListener(context, options) {
2401
+ const { name, isAllowedProp, isAllowedType, onImplicitProp } = options;
2402
+ const services = ESLintUtils.getParserServices(context, false);
2403
+ const checker = services.program.getTypeChecker();
2404
+ const getFqn = (symbol) => core.getFullyQualifiedNameEx(checker, symbol).toLowerCase();
2405
+ return { JSXSpreadAttribute(node) {
2406
+ for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
2407
+ const prop = type.getProperty(name);
2408
+ if (prop == null) continue;
2409
+ if (isAllowedProp(getFqn(prop))) continue;
2410
+ const propType = checker.getTypeOfSymbol(prop);
2411
+ const propTypeSymbol = propType.aliasSymbol ?? propType.symbol;
2412
+ if (propTypeSymbol != null && isAllowedType(getFqn(propTypeSymbol))) continue;
2413
+ onImplicitProp(node);
2414
+ }
2415
+ } };
2416
+ }
2417
+
2380
2418
  //#endregion
2381
2419
  //#region src/rules/no-implicit-children/no-implicit-children.ts
2382
2420
  const RULE_NAME$30 = "no-implicit-children";
@@ -2393,26 +2431,17 @@ var no_implicit_children_default = createRule({
2393
2431
  defaultOptions: []
2394
2432
  });
2395
2433
  function create$30(context) {
2396
- const services = ESLintUtils.getParserServices(context, false);
2397
- const checker = services.program.getTypeChecker();
2398
- return { JSXSpreadAttribute(node) {
2399
- for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
2400
- const children = type.getProperty("children");
2401
- if (children == null) continue;
2402
- const fqn = core.getFullyQualifiedNameEx(checker, children).toLowerCase();
2403
- if (fqn.endsWith("attributes.children") || fqn.endsWith("propswithchildren.children")) continue;
2404
- const childrenType = checker.getTypeOfSymbol(children);
2405
- const typeSymbol = childrenType.aliasSymbol ?? childrenType.symbol;
2406
- if (typeSymbol != null) {
2407
- const typeFqn = checker.getFullyQualifiedName(typeSymbol);
2408
- if (RE_REACT_CHILDREN_TYPE.test(typeFqn) || /^JSX\.Element$/i.test(typeFqn)) continue;
2409
- }
2434
+ return createImplicitPropListener(context, {
2435
+ name: "children",
2436
+ isAllowedProp: (fqn) => fqn.endsWith("attributes.children") || fqn.endsWith("propswithchildren.children"),
2437
+ isAllowedType: (fqn) => RE_REACT_CHILDREN_TYPE.test(fqn) || fqn === "jsx.element",
2438
+ onImplicitProp(node) {
2410
2439
  context.report({
2411
2440
  messageId: "default",
2412
2441
  node
2413
2442
  });
2414
2443
  }
2415
- } };
2444
+ });
2416
2445
  }
2417
2446
 
2418
2447
  //#endregion
@@ -2430,23 +2459,17 @@ var no_implicit_key_default = createRule({
2430
2459
  defaultOptions: []
2431
2460
  });
2432
2461
  function create$29(context) {
2433
- const services = ESLintUtils.getParserServices(context, false);
2434
- const checker = services.program.getTypeChecker();
2435
- return { JSXSpreadAttribute(node) {
2436
- for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
2437
- const key = type.getProperty("key");
2438
- if (key == null) continue;
2439
- if (core.getFullyQualifiedNameEx(checker, key).toLowerCase().endsWith("react.attributes.key")) continue;
2440
- const keyType = checker.getTypeOfSymbol(key);
2441
- if (keyType.aliasSymbol != null) {
2442
- if (checker.getFullyQualifiedName(keyType.aliasSymbol).toLowerCase().endsWith("react.key")) continue;
2443
- }
2462
+ return createImplicitPropListener(context, {
2463
+ name: "key",
2464
+ isAllowedProp: (fqn) => fqn.endsWith("react.attributes.key"),
2465
+ isAllowedType: (fqn) => fqn.endsWith("react.key"),
2466
+ onImplicitProp(node) {
2444
2467
  context.report({
2445
2468
  messageId: "default",
2446
2469
  node
2447
2470
  });
2448
2471
  }
2449
- } };
2472
+ });
2450
2473
  }
2451
2474
 
2452
2475
  //#endregion
@@ -2465,25 +2488,17 @@ var no_implicit_ref_default = createRule({
2465
2488
  defaultOptions: []
2466
2489
  });
2467
2490
  function create$28(context) {
2468
- const services = ESLintUtils.getParserServices(context, false);
2469
- const checker = services.program.getTypeChecker();
2470
- return { JSXSpreadAttribute(node) {
2471
- for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
2472
- const ref = type.getProperty("ref");
2473
- if (ref == null) continue;
2474
- if (core.getFullyQualifiedNameEx(checker, ref).toLowerCase().endsWith("attributes.ref")) continue;
2475
- const refType = checker.getTypeOfSymbol(ref);
2476
- const typeSymbol = refType.aliasSymbol ?? refType.symbol;
2477
- if (typeSymbol != null) {
2478
- const typeFqn = checker.getFullyQualifiedName(typeSymbol);
2479
- if (RE_REACT_REF_TYPE.test(typeFqn)) continue;
2480
- }
2491
+ return createImplicitPropListener(context, {
2492
+ name: "ref",
2493
+ isAllowedProp: (fqn) => fqn.endsWith("attributes.ref"),
2494
+ isAllowedType: (fqn) => RE_REACT_REF_TYPE.test(fqn),
2495
+ onImplicitProp(node) {
2481
2496
  context.report({
2482
2497
  messageId: "default",
2483
2498
  node
2484
2499
  });
2485
2500
  }
2486
- } };
2501
+ });
2487
2502
  }
2488
2503
 
2489
2504
  //#endregion
@@ -4573,7 +4588,7 @@ function resolveBuiltinObjectName(context, node, seen = /* @__PURE__ */ new Set(
4573
4588
  const init = Extract.unwrap(def.node.init);
4574
4589
  if (init.type === AST_NODE_TYPES.Identifier) return resolveBuiltinObjectName(context, init, seen);
4575
4590
  if (init.type === AST_NODE_TYPES.MemberExpression) {
4576
- const rootId = Extract.getRootIdentifier(init);
4591
+ const rootId = Extract.getIdentifierAt(init, 0);
4577
4592
  if (rootId != null) return resolveBuiltinObjectName(context, rootId, seen);
4578
4593
  }
4579
4594
  }
@@ -4617,7 +4632,7 @@ function create$7(context) {
4617
4632
  break;
4618
4633
  }
4619
4634
  case expr.type === AST_NODE_TYPES.MemberExpression && expr.property.type === AST_NODE_TYPES.Identifier: {
4620
- const rootId = Extract.getRootIdentifier(expr.object);
4635
+ const rootId = Extract.getIdentifierAt(expr.object, 0);
4621
4636
  if (rootId == null) return;
4622
4637
  const objectName = resolveBuiltinObjectName(context, rootId);
4623
4638
  if (objectName == null) return;
@@ -4682,6 +4697,7 @@ const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
4682
4697
  "sort"
4683
4698
  ]);
4684
4699
  function createBindingResolver(context) {
4700
+ const { additionalRefHooks } = getSettingsFromContext(context);
4685
4701
  const bindings = /* @__PURE__ */ new Map();
4686
4702
  const memberBindings = /* @__PURE__ */ new Map();
4687
4703
  const jsxRefs = /* @__PURE__ */ new Set();
@@ -4701,7 +4717,7 @@ function createBindingResolver(context) {
4701
4717
  kind: "function",
4702
4718
  node: value
4703
4719
  };
4704
- if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4720
+ if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefLikeCall(value, additionalRefHooks) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4705
4721
  if (value.type === AST_NODE_TYPES.MemberExpression && value.property.type === AST_NODE_TYPES.Identifier) {
4706
4722
  if (isRefLikeName(value.property.name)) return { kind: "ref" };
4707
4723
  }
@@ -7344,6 +7360,7 @@ function isHookDecl(node) {
7344
7360
  return name != null && core.isHookName(name);
7345
7361
  }
7346
7362
  function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ */ new Set()) {
7363
+ const { additionalRefHooks } = getSettingsFromContext(context);
7347
7364
  if (seen.has(name)) return false;
7348
7365
  seen.add(name);
7349
7366
  for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
@@ -7352,7 +7369,7 @@ function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ *
7352
7369
  if (init == null) continue;
7353
7370
  switch (true) {
7354
7371
  case init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier && (init.object.name === "ref" || init.object.name.endsWith("Ref")): return true;
7355
- case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefCall(context, init): return true;
7372
+ case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefLikeCall(init, additionalRefHooks): return true;
7356
7373
  case init.type === AST_NODE_TYPES.CallExpression: return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id), seen));
7357
7374
  }
7358
7375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.17.2",
3
+ "version": "5.18.0",
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,30 +36,30 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@typescript-eslint/scope-manager": "^8.64.0",
40
- "@typescript-eslint/type-utils": "^8.64.0",
41
- "@typescript-eslint/types": "^8.64.0",
42
- "@typescript-eslint/typescript-estree": "^8.64.0",
43
- "@typescript-eslint/utils": "^8.64.0",
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",
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.17.2",
49
- "@eslint-react/core": "5.17.2",
50
- "@eslint-react/jsx": "5.17.2",
51
- "@eslint-react/shared": "5.17.2",
52
- "@eslint-react/var": "5.17.2",
53
- "@eslint-react/eslint": "5.17.2"
48
+ "@eslint-react/ast": "5.18.0",
49
+ "@eslint-react/eslint": "5.18.0",
50
+ "@eslint-react/core": "5.18.0",
51
+ "@eslint-react/jsx": "5.18.0",
52
+ "@eslint-react/shared": "5.18.0",
53
+ "@eslint-react/var": "5.18.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/react": "^19.2.17",
57
57
  "@types/react-dom": "^19.2.3",
58
58
  "dedent": "^1.7.2",
59
59
  "eslint": "^10.7.0",
60
- "react": "^19.2.7",
61
- "react-dom": "^19.2.7",
62
- "tsdown": "^0.22.9",
60
+ "react": "^19.2.8",
61
+ "react-dom": "^19.2.8",
62
+ "tsdown": "^0.22.13",
63
63
  "tsl": "^1.0.30",
64
64
  "tsl-dx": "^0.13.3",
65
65
  "typescript": "6.0.3",