eslint-plugin-react-x 5.17.3 → 5.18.1

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 +64 -68
  2. package/package.json +16 -16
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.3";
148
+ var version = "5.18.1";
148
149
 
149
150
  //#endregion
150
151
  //#region src/utils/create-rule.ts
@@ -170,27 +171,6 @@ var error_boundaries_default = createRule({
170
171
  create: create$50,
171
172
  defaultOptions: []
172
173
  });
173
- /**
174
- * Finds the nearest TryStatement whose `try` block (not catch/finally)
175
- * encloses the given node.
176
- * @param node The node to check.
177
- * @returns The enclosing TryStatement, or null if none is found.
178
- */
179
- function getEnclosingTryBlock(node) {
180
- let current = node.parent;
181
- while (current != null) {
182
- if (current.type === AST_NODE_TYPES.TryStatement) {
183
- let n = node;
184
- while (n != null && n !== current) {
185
- if (n === current.block) return current;
186
- n = n.parent;
187
- }
188
- }
189
- if (current.type === AST_NODE_TYPES.Program) return null;
190
- current = current.parent;
191
- }
192
- return null;
193
- }
194
174
  function create$50(context) {
195
175
  if (!context.sourceCode.text.includes("try")) return {};
196
176
  const hint = core.JsxDetectionHint.DoNotIncludeJsxWithNullValue | core.JsxDetectionHint.DoNotIncludeJsxWithNumberValue | core.JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | core.JsxDetectionHint.DoNotIncludeJsxWithStringValue | core.JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | core.JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue;
@@ -208,7 +188,7 @@ function create$50(context) {
208
188
  const hooks = hc.api.getAllHooks(node);
209
189
  const funcs = [...comps, ...hooks];
210
190
  for (const call of useCalls) {
211
- const stmt = getEnclosingTryBlock(call);
191
+ const stmt = Traverse.findEnclosingTryBlock(call);
212
192
  const func = Traverse.findParent(stmt, (n) => funcs.some((f) => f.node === n));
213
193
  if (stmt != null && func != null && !reported.has(stmt)) {
214
194
  context.report({
@@ -221,7 +201,7 @@ function create$50(context) {
221
201
  for (const { rets } of funcs) for (const ret of rets) {
222
202
  if (ret == null) continue;
223
203
  if (!core.isJsxLike(context, ret, hint)) continue;
224
- const stmt = getEnclosingTryBlock(ret);
204
+ const stmt = Traverse.findEnclosingTryBlock(ret);
225
205
  if (stmt != null && !reported.has(stmt)) {
226
206
  context.report({
227
207
  messageId: "tryCatchWithJsx",
@@ -1391,7 +1371,8 @@ function isInitializedFromCall(context, node, isCall) {
1391
1371
  return init.type === AST_NODE_TYPES.CallExpression && isCall(init);
1392
1372
  }
1393
1373
  function isInitializedFromUseRef(context, node) {
1394
- return isInitializedFromCall(context, node, (init) => core.isUseRefCall(context, init));
1374
+ const { additionalRefHooks } = getSettingsFromContext(context);
1375
+ return isInitializedFromCall(context, node, (init) => core.isUseRefLikeCall(init, additionalRefHooks));
1395
1376
  }
1396
1377
  function isKnownNonMutatingMethodCall(context, node) {
1397
1378
  const callee = Extract.unwrap(node.callee);
@@ -2377,6 +2358,42 @@ function buildFixForComponentProps(context, fixer, node, typeArguments) {
2377
2358
  ].join(" ")), ...arg1 == null ? [] : [fixer.remove(arg1), fixer.removeRange([arg0.range[1], arg1.range[0]])]];
2378
2359
  }
2379
2360
 
2361
+ //#endregion
2362
+ //#region src/utils/create-implicit-prop-listener.ts
2363
+ /**
2364
+ * Creates a listener that detects JSX spread attributes implicitly passing a given prop
2365
+ * (ex: 'children', 'key', 'ref') to a component.
2366
+ *
2367
+ * For each union constituent of the spread argument's type, the prop is reported unless:
2368
+ * 1. The prop symbol's fully qualified name is allowed (pass-through of React internally defined props)
2369
+ * 2. The fully qualified name of the prop's type symbol (or its alias) is allowed (React type aliases)
2370
+ *
2371
+ * @param context The ESLint rule context
2372
+ * @param options The prop name, the allow-list predicates (receive lowercased fully qualified names), and the report callback
2373
+ * @param options.name The name of the prop to detect
2374
+ * @param options.isAllowedProp A predicate that receives the prop symbol's lowercased fully qualified name and returns `true` if it is allowed
2375
+ * @param options.isAllowedType A predicate that receives the prop's type symbol's lowercased fully qualified name and returns `true` if it is allowed
2376
+ * @param options.onImplicitProp A callback invoked with the spread attribute node when an implicit prop is detected
2377
+ * @returns A rule listener reporting on JSX spread attributes
2378
+ */
2379
+ function createImplicitPropListener(context, options) {
2380
+ const { name, isAllowedProp, isAllowedType, onImplicitProp } = options;
2381
+ const services = ESLintUtils.getParserServices(context, false);
2382
+ const checker = services.program.getTypeChecker();
2383
+ const getFqn = (symbol) => core.getFullyQualifiedNameEx(checker, symbol).toLowerCase();
2384
+ return { JSXSpreadAttribute(node) {
2385
+ for (const type of unionConstituents(getConstrainedTypeAtLocation(services, node.argument))) {
2386
+ const prop = type.getProperty(name);
2387
+ if (prop == null) continue;
2388
+ if (isAllowedProp(getFqn(prop))) continue;
2389
+ const propType = checker.getTypeOfSymbol(prop);
2390
+ const propTypeSymbol = propType.aliasSymbol ?? propType.symbol;
2391
+ if (propTypeSymbol != null && isAllowedType(getFqn(propTypeSymbol))) continue;
2392
+ onImplicitProp(node);
2393
+ }
2394
+ } };
2395
+ }
2396
+
2380
2397
  //#endregion
2381
2398
  //#region src/rules/no-implicit-children/no-implicit-children.ts
2382
2399
  const RULE_NAME$30 = "no-implicit-children";
@@ -2393,26 +2410,17 @@ var no_implicit_children_default = createRule({
2393
2410
  defaultOptions: []
2394
2411
  });
2395
2412
  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
- }
2413
+ return createImplicitPropListener(context, {
2414
+ name: "children",
2415
+ isAllowedProp: (fqn) => fqn.endsWith("attributes.children") || fqn.endsWith("propswithchildren.children"),
2416
+ isAllowedType: (fqn) => RE_REACT_CHILDREN_TYPE.test(fqn) || fqn === "jsx.element",
2417
+ onImplicitProp(node) {
2410
2418
  context.report({
2411
2419
  messageId: "default",
2412
2420
  node
2413
2421
  });
2414
2422
  }
2415
- } };
2423
+ });
2416
2424
  }
2417
2425
 
2418
2426
  //#endregion
@@ -2430,23 +2438,17 @@ var no_implicit_key_default = createRule({
2430
2438
  defaultOptions: []
2431
2439
  });
2432
2440
  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
- }
2441
+ return createImplicitPropListener(context, {
2442
+ name: "key",
2443
+ isAllowedProp: (fqn) => fqn.endsWith("react.attributes.key"),
2444
+ isAllowedType: (fqn) => fqn.endsWith("react.key"),
2445
+ onImplicitProp(node) {
2444
2446
  context.report({
2445
2447
  messageId: "default",
2446
2448
  node
2447
2449
  });
2448
2450
  }
2449
- } };
2451
+ });
2450
2452
  }
2451
2453
 
2452
2454
  //#endregion
@@ -2465,25 +2467,17 @@ var no_implicit_ref_default = createRule({
2465
2467
  defaultOptions: []
2466
2468
  });
2467
2469
  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
- }
2470
+ return createImplicitPropListener(context, {
2471
+ name: "ref",
2472
+ isAllowedProp: (fqn) => fqn.endsWith("attributes.ref"),
2473
+ isAllowedType: (fqn) => RE_REACT_REF_TYPE.test(fqn),
2474
+ onImplicitProp(node) {
2481
2475
  context.report({
2482
2476
  messageId: "default",
2483
2477
  node
2484
2478
  });
2485
2479
  }
2486
- } };
2480
+ });
2487
2481
  }
2488
2482
 
2489
2483
  //#endregion
@@ -4682,6 +4676,7 @@ const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
4682
4676
  "sort"
4683
4677
  ]);
4684
4678
  function createBindingResolver(context) {
4679
+ const { additionalRefHooks } = getSettingsFromContext(context);
4685
4680
  const bindings = /* @__PURE__ */ new Map();
4686
4681
  const memberBindings = /* @__PURE__ */ new Map();
4687
4682
  const jsxRefs = /* @__PURE__ */ new Set();
@@ -4701,7 +4696,7 @@ function createBindingResolver(context) {
4701
4696
  kind: "function",
4702
4697
  node: value
4703
4698
  };
4704
- if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4699
+ if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefLikeCall(value, additionalRefHooks) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4705
4700
  if (value.type === AST_NODE_TYPES.MemberExpression && value.property.type === AST_NODE_TYPES.Identifier) {
4706
4701
  if (isRefLikeName(value.property.name)) return { kind: "ref" };
4707
4702
  }
@@ -7344,6 +7339,7 @@ function isHookDecl(node) {
7344
7339
  return name != null && core.isHookName(name);
7345
7340
  }
7346
7341
  function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ */ new Set()) {
7342
+ const { additionalRefHooks } = getSettingsFromContext(context);
7347
7343
  if (seen.has(name)) return false;
7348
7344
  seen.add(name);
7349
7345
  for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
@@ -7352,7 +7348,7 @@ function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ *
7352
7348
  if (init == null) continue;
7353
7349
  switch (true) {
7354
7350
  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;
7351
+ case init.type === AST_NODE_TYPES.CallExpression && core.isUseRefLikeCall(init, additionalRefHooks): return true;
7356
7352
  case init.type === AST_NODE_TYPES.CallExpression: return getNestedIdentifiers(init).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id), seen));
7357
7353
  }
7358
7354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.17.3",
3
+ "version": "5.18.1",
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.3",
49
- "@eslint-react/eslint": "5.17.3",
50
- "@eslint-react/jsx": "5.17.3",
51
- "@eslint-react/core": "5.17.3",
52
- "@eslint-react/shared": "5.17.3",
53
- "@eslint-react/var": "5.17.3"
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"
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
- "eslint": "^10.7.0",
60
- "react": "^19.2.7",
61
- "react-dom": "^19.2.7",
62
- "tsdown": "^0.22.9",
59
+ "eslint": "^10.8.0",
60
+ "react": "^19.2.8",
61
+ "react-dom": "^19.2.8",
62
+ "tsdown": "^0.22.14",
63
63
  "tsl": "^1.0.30",
64
64
  "tsl-dx": "^0.13.3",
65
65
  "typescript": "6.0.3",