eslint-plugin-react-x 5.14.6 → 5.14.8

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 +188 -161
  2. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -144,7 +144,7 @@ const rules$6 = {
144
144
  //#endregion
145
145
  //#region package.json
146
146
  var name$6 = "eslint-plugin-react-x";
147
- var version = "5.14.6";
147
+ var version = "5.14.8";
148
148
 
149
149
  //#endregion
150
150
  //#region src/utils/create-rule.ts
@@ -1344,7 +1344,14 @@ const MUTATING_METHODS = /* @__PURE__ */ new Set([
1344
1344
  "splice",
1345
1345
  "unshift"
1346
1346
  ]);
1347
- const isUseRouterCall = core.isAPICall("useRouter");
1347
+ /**
1348
+ * Known navigation hooks.
1349
+ */
1350
+ const NAVIGATION_HOOKS = /* @__PURE__ */ new Set([
1351
+ "useNavigate",
1352
+ "useNavigation",
1353
+ "useRouter"
1354
+ ]);
1348
1355
  function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set()) {
1349
1356
  const expr = Extract.unwrap(node);
1350
1357
  if (Check.isFunction(expr)) return expr;
@@ -1386,12 +1393,11 @@ function isInitializedFromCall(context, node, isCall) {
1386
1393
  function isInitializedFromUseRef(context, node) {
1387
1394
  return isInitializedFromCall(context, node, (init) => core.isUseRefCall(context, init));
1388
1395
  }
1389
- function isInitializedFromUseRouter(context, node) {
1390
- return isInitializedFromCall(context, node, (init) => isUseRouterCall(context, init));
1391
- }
1392
1396
  function isKnownNonMutatingMethodCall(context, node) {
1393
1397
  const callee = Extract.unwrap(node.callee);
1394
- return Check.isExpression(callee) && isInitializedFromUseRouter(context, callee);
1398
+ return Check.isExpression(callee) && isInitializedFromCall(context, callee, (init) => {
1399
+ return NAVIGATION_HOOKS.values().some((hook) => core.isAPICall(hook)(context, init));
1400
+ });
1395
1401
  }
1396
1402
  function isRefLikeChain(context, node) {
1397
1403
  return hasRefLikeNameInChain(node) || isInitializedFromUseRef(context, node);
@@ -4206,21 +4212,6 @@ const IMPURE_CTORS = /* @__PURE__ */ new Set([
4206
4212
  "Worker",
4207
4213
  "XMLHttpRequest"
4208
4214
  ]);
4209
-
4210
- //#endregion
4211
- //#region src/rules/purity/purity.ts
4212
- const RULE_NAME$7 = "purity";
4213
- var purity_default = createRule({
4214
- meta: {
4215
- type: "problem",
4216
- docs: { description: "Validates that components and hooks are pure by checking that they do not call known-impure functions during render." },
4217
- messages: { default: "Do not call '{{name}}' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer." },
4218
- schema: []
4219
- },
4220
- name: RULE_NAME$7,
4221
- create: create$7,
4222
- defaultOptions: []
4223
- });
4224
4215
  /**
4225
4216
  * Recursively resolve an identifier to the root builtin global object name.
4226
4217
  * Follows simple assignment chains like `const M = Math` or `const w = window`.
@@ -4248,6 +4239,21 @@ function resolveBuiltinObjectName(context, node, seen = /* @__PURE__ */ new Set(
4248
4239
  }
4249
4240
  return null;
4250
4241
  }
4242
+
4243
+ //#endregion
4244
+ //#region src/rules/purity/purity.ts
4245
+ const RULE_NAME$7 = "purity";
4246
+ var purity_default = createRule({
4247
+ meta: {
4248
+ type: "problem",
4249
+ docs: { description: "Validates that components and hooks are pure by checking that they do not call known-impure functions during render." },
4250
+ messages: { default: "Do not call '{{name}}' during render. Components and hooks must be pure. Move this call into an event handler, effect, or state initializer." },
4251
+ schema: []
4252
+ },
4253
+ name: RULE_NAME$7,
4254
+ create: create$7,
4255
+ defaultOptions: []
4256
+ });
4251
4257
  function create$7(context) {
4252
4258
  const hc = core.getHookCollector(context);
4253
4259
  const fc = core.getFunctionComponentCollector(context);
@@ -4335,6 +4341,150 @@ const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
4335
4341
  "some",
4336
4342
  "sort"
4337
4343
  ]);
4344
+ function createBindingResolver(context) {
4345
+ const bindings = /* @__PURE__ */ new Map();
4346
+ const memberBindings = /* @__PURE__ */ new Map();
4347
+ const jsxRefs = /* @__PURE__ */ new Set();
4348
+ function getVariable(node) {
4349
+ return findVariable(context.sourceCode.getScope(node), node) ?? null;
4350
+ }
4351
+ function getBindingValue(node) {
4352
+ const value = Extract.unwrap(node);
4353
+ if (value.type === AST_NODE_TYPES.Identifier) {
4354
+ const variable = getVariable(value);
4355
+ return variable == null ? { kind: "unknown" } : {
4356
+ kind: "variable",
4357
+ variable
4358
+ };
4359
+ }
4360
+ if (isFunctionExpressionLike(value)) return {
4361
+ kind: "function",
4362
+ node: value
4363
+ };
4364
+ if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4365
+ if (value.type === AST_NODE_TYPES.MemberExpression) {
4366
+ const property = Extract.getPropertyName(value.property);
4367
+ if (property != null && isRefLikeName(property)) return { kind: "ref" };
4368
+ }
4369
+ return { kind: "unknown" };
4370
+ }
4371
+ function addBinding(variable, value, position) {
4372
+ const events = bindings.get(variable) ?? [];
4373
+ bindings.set(variable, events);
4374
+ events.push({
4375
+ position,
4376
+ value
4377
+ });
4378
+ }
4379
+ function addIdentifierBinding(node, value, position = node.range[0]) {
4380
+ const variable = getVariable(node);
4381
+ if (variable != null) addBinding(variable, getBindingValue(value), position);
4382
+ }
4383
+ function addFunctionBinding(node, value, position) {
4384
+ const variable = getVariable(node);
4385
+ if (variable != null) addBinding(variable, {
4386
+ kind: "function",
4387
+ node: value
4388
+ }, position);
4389
+ }
4390
+ function addMemberBinding(object, property, value, position) {
4391
+ const variable = getVariable(object);
4392
+ if (variable == null) return;
4393
+ const properties = memberBindings.get(variable) ?? /* @__PURE__ */ new Map();
4394
+ memberBindings.set(variable, properties);
4395
+ const events = properties.get(property) ?? [];
4396
+ properties.set(property, events);
4397
+ events.push({
4398
+ position,
4399
+ value: getBindingValue(value)
4400
+ });
4401
+ }
4402
+ function addJsxRef(node) {
4403
+ const variable = getVariable(node);
4404
+ if (variable != null) jsxRefs.add(variable);
4405
+ }
4406
+ function resolveRef(variable, position, seen = /* @__PURE__ */ new Set()) {
4407
+ if (seen.has(variable)) return null;
4408
+ seen.add(variable);
4409
+ if (jsxRefs.has(variable) || isRefLikeName(variable.name)) return variable;
4410
+ const event = getLatestValue(bindings.get(variable), position);
4411
+ if (event != null) switch (event.value.kind) {
4412
+ case "ref": return variable;
4413
+ case "variable": return resolveRef(event.value.variable, event.position, seen);
4414
+ case "function":
4415
+ case "unknown": return null;
4416
+ }
4417
+ return null;
4418
+ }
4419
+ function resolveFunction(variable, position, seen = /* @__PURE__ */ new Set()) {
4420
+ if (seen.has(variable)) return null;
4421
+ seen.add(variable);
4422
+ const event = getLatestValue(bindings.get(variable), position);
4423
+ if (event == null) return null;
4424
+ switch (event.value.kind) {
4425
+ case "function": return event.value.node;
4426
+ case "variable": return resolveFunction(event.value.variable, event.position, seen);
4427
+ case "ref":
4428
+ case "unknown": return null;
4429
+ }
4430
+ }
4431
+ function resolveCallable(node, position) {
4432
+ const callee = Extract.unwrap(node);
4433
+ if (isFunctionExpressionLike(callee)) return callee;
4434
+ if (callee.type === AST_NODE_TYPES.Identifier) {
4435
+ const variable = getVariable(callee);
4436
+ return variable == null ? null : resolveFunction(variable, position);
4437
+ }
4438
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return null;
4439
+ const object = Extract.unwrap(callee.object);
4440
+ const property = Extract.getPropertyName(callee.property);
4441
+ if (object.type !== AST_NODE_TYPES.Identifier || property == null) return null;
4442
+ const variable = getVariable(object);
4443
+ if (variable == null) return null;
4444
+ const event = getLatestValue(memberBindings.get(variable)?.get(property), position);
4445
+ if (event == null) return null;
4446
+ if (event.value.kind === "function") return event.value.node;
4447
+ if (event.value.kind === "variable") return resolveFunction(event.value.variable, event.position);
4448
+ return null;
4449
+ }
4450
+ function getRefTarget(node) {
4451
+ const object = Extract.unwrap(node.object);
4452
+ if (object.type === AST_NODE_TYPES.Identifier) {
4453
+ const variable = getVariable(object);
4454
+ if (variable == null) return null;
4455
+ const identity = resolveRef(variable, node.range[0]);
4456
+ return identity == null ? null : { identity };
4457
+ }
4458
+ if (object.type === AST_NODE_TYPES.MemberExpression) {
4459
+ const property = Extract.getPropertyName(object.property);
4460
+ if (property != null && isRefLikeName(property)) return { identity: null };
4461
+ }
4462
+ return null;
4463
+ }
4464
+ function getNullBranch(test, identity) {
4465
+ return getNullCheckBranch(test, (candidate) => {
4466
+ if (candidate.type !== AST_NODE_TYPES.MemberExpression || Extract.getPropertyName(candidate.property) !== "current") return false;
4467
+ return getRefTarget(candidate)?.identity === identity;
4468
+ }, (candidate) => {
4469
+ if (candidate.type === AST_NODE_TYPES.Literal) return candidate.value == null;
4470
+ if (candidate.type === AST_NODE_TYPES.UnaryExpression && candidate.operator === "void") return true;
4471
+ if (candidate.type !== AST_NODE_TYPES.Identifier || candidate.name !== "undefined") return false;
4472
+ const variable = getVariable(candidate);
4473
+ return variable == null || variable.defs.length === 0;
4474
+ });
4475
+ }
4476
+ return {
4477
+ addFunctionBinding,
4478
+ addIdentifierBinding,
4479
+ addJsxRef,
4480
+ addMemberBinding,
4481
+ getNullBranch,
4482
+ getRefTarget,
4483
+ getVariable,
4484
+ resolveCallable,
4485
+ resolveRef
4486
+ };
4487
+ }
4338
4488
  function getLatestValue(events, position) {
4339
4489
  let latest = null;
4340
4490
  for (const event of events ?? []) {
@@ -4375,6 +4525,16 @@ function isReachedThroughFunctions(node, boundary, reached) {
4375
4525
  }
4376
4526
  return current === boundary;
4377
4527
  }
4528
+ function getRefAccess(node) {
4529
+ let parent = node.parent;
4530
+ while (Check.isTypeExpression(parent)) parent = parent.parent;
4531
+ const isDirectWrite = parent.type === AST_NODE_TYPES.AssignmentExpression ? parent.left === node || Extract.unwrap(parent.left) === node : parent.type === AST_NODE_TYPES.UpdateExpression ? parent.argument === node || Extract.unwrap(parent.argument) === node : false;
4532
+ return {
4533
+ isInitializationWrite: parent.type === AST_NODE_TYPES.AssignmentExpression && parent.operator === "=" && (parent.left === node || Extract.unwrap(parent.left) === node),
4534
+ isWrite: isDirectWrite || isNestedRefCurrentWrite(node),
4535
+ node
4536
+ };
4537
+ }
4378
4538
  /**
4379
4539
  * Check whether `node` (a `ref.current` MemberExpression) is being written to indirectly through
4380
4540
  * a nested property write, e.g. `ref.current.inner = value` or `ref.current.inner++`.
@@ -4396,16 +4556,6 @@ function isNestedRefCurrentWrite(node) {
4396
4556
  return false;
4397
4557
  }
4398
4558
  }
4399
- function getRefAccess(node) {
4400
- let parent = node.parent;
4401
- while (Check.isTypeExpression(parent)) parent = parent.parent;
4402
- const isDirectWrite = parent.type === AST_NODE_TYPES.AssignmentExpression ? parent.left === node || Extract.unwrap(parent.left) === node : parent.type === AST_NODE_TYPES.UpdateExpression ? parent.argument === node || Extract.unwrap(parent.argument) === node : false;
4403
- return {
4404
- isInitializationWrite: parent.type === AST_NODE_TYPES.AssignmentExpression && parent.operator === "=" && (parent.left === node || Extract.unwrap(parent.left) === node),
4405
- isWrite: isDirectWrite || isNestedRefCurrentWrite(node),
4406
- node
4407
- };
4408
- }
4409
4559
  /**
4410
4560
  * Parse an exact nullish check and return the branch where the checked value is null/undefined.
4411
4561
  * Truthiness checks such as `!ref.current` are deliberately excluded: falsy ref values are not
@@ -4469,7 +4619,7 @@ function isAfterTerminatingNonNullGuard(node, getNullBranch) {
4469
4619
  statement = statement.parent;
4470
4620
  }
4471
4621
  const block = statement.parent;
4472
- const index = block.body.indexOf(statement);
4622
+ const index = block.body.findIndex((sibling) => sibling === statement);
4473
4623
  for (let i = index - 1; i >= 0; i--) {
4474
4624
  const sibling = block.body[i];
4475
4625
  if (sibling?.type !== AST_NODE_TYPES.IfStatement) continue;
@@ -4503,156 +4653,33 @@ var refs_default = createRule({
4503
4653
  function create$6(context) {
4504
4654
  const hc = core.getHookCollector(context);
4505
4655
  const fc = core.getFunctionComponentCollector(context);
4506
- const bindings = /* @__PURE__ */ new Map();
4507
- const memberBindings = /* @__PURE__ */ new Map();
4508
- const jsxRefs = /* @__PURE__ */ new Set();
4656
+ const { addFunctionBinding, addIdentifierBinding, addJsxRef, addMemberBinding, getNullBranch, getRefTarget, getVariable, resolveCallable, resolveRef } = createBindingResolver(context);
4509
4657
  const calls = [];
4510
4658
  const refAccesses = [];
4511
- function getVariable(node) {
4512
- return findVariable(context.sourceCode.getScope(node), node) ?? null;
4513
- }
4514
- function addBinding(variable, value, position) {
4515
- const events = bindings.get(variable) ?? [];
4516
- bindings.set(variable, events);
4517
- events.push({
4518
- position,
4519
- value
4520
- });
4521
- }
4522
- function addIdentifierBinding(node, value, position = node.range[0]) {
4523
- const variable = getVariable(node);
4524
- if (variable != null) addBinding(variable, value, position);
4525
- }
4526
- function addMemberBinding(object, property, value, position) {
4527
- const variable = getVariable(object);
4528
- if (variable == null) return;
4529
- const properties = memberBindings.get(variable) ?? /* @__PURE__ */ new Map();
4530
- memberBindings.set(variable, properties);
4531
- const events = properties.get(property) ?? [];
4532
- properties.set(property, events);
4533
- events.push({
4534
- position,
4535
- value
4536
- });
4537
- }
4538
- function bindingValueFrom(node) {
4539
- const value = Extract.unwrap(node);
4540
- if (value.type === AST_NODE_TYPES.Identifier) {
4541
- const variable = getVariable(value);
4542
- return variable == null ? { kind: "unknown" } : {
4543
- kind: "variable",
4544
- variable
4545
- };
4546
- }
4547
- if (isFunctionExpressionLike(value)) return {
4548
- kind: "function",
4549
- node: value
4550
- };
4551
- if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4552
- if (value.type === AST_NODE_TYPES.MemberExpression) {
4553
- const property = Extract.getPropertyName(value.property);
4554
- if (property != null && isRefLikeName(property)) return { kind: "ref" };
4555
- }
4556
- return { kind: "unknown" };
4557
- }
4558
- function resolveRef(variable, position, seen = /* @__PURE__ */ new Set()) {
4559
- if (seen.has(variable)) return null;
4560
- seen.add(variable);
4561
- if (jsxRefs.has(variable) || isRefLikeName(variable.name)) return variable;
4562
- const event = getLatestValue(bindings.get(variable), position);
4563
- if (event != null) switch (event.value.kind) {
4564
- case "ref": return variable;
4565
- case "variable": return resolveRef(event.value.variable, event.position, seen);
4566
- case "function":
4567
- case "unknown": return null;
4568
- }
4569
- return null;
4570
- }
4571
- function resolveFunction(variable, position, seen = /* @__PURE__ */ new Set()) {
4572
- if (seen.has(variable)) return null;
4573
- seen.add(variable);
4574
- const event = getLatestValue(bindings.get(variable), position);
4575
- if (event == null) return null;
4576
- switch (event.value.kind) {
4577
- case "function": return event.value.node;
4578
- case "variable": return resolveFunction(event.value.variable, event.position, seen);
4579
- case "ref":
4580
- case "unknown": return null;
4581
- }
4582
- }
4583
- function resolveCallable(node, position) {
4584
- const callee = Extract.unwrap(node);
4585
- if (isFunctionExpressionLike(callee)) return callee;
4586
- if (callee.type === AST_NODE_TYPES.Identifier) {
4587
- const variable = getVariable(callee);
4588
- return variable == null ? null : resolveFunction(variable, position);
4589
- }
4590
- if (callee.type !== AST_NODE_TYPES.MemberExpression) return null;
4591
- const object = Extract.unwrap(callee.object);
4592
- const property = Extract.getPropertyName(callee.property);
4593
- if (object.type !== AST_NODE_TYPES.Identifier || property == null) return null;
4594
- const variable = getVariable(object);
4595
- if (variable == null) return null;
4596
- const event = getLatestValue(memberBindings.get(variable)?.get(property), position);
4597
- if (event == null) return null;
4598
- if (event.value.kind === "function") return event.value.node;
4599
- if (event.value.kind === "variable") return resolveFunction(event.value.variable, event.position);
4600
- return null;
4601
- }
4602
- function getRefTarget(node) {
4603
- const object = Extract.unwrap(node.object);
4604
- if (object.type === AST_NODE_TYPES.Identifier) {
4605
- const variable = getVariable(object);
4606
- if (variable == null) return null;
4607
- const identity = resolveRef(variable, node.range[0]);
4608
- return identity == null ? null : { identity };
4609
- }
4610
- if (object.type === AST_NODE_TYPES.MemberExpression) {
4611
- const property = Extract.getPropertyName(object.property);
4612
- if (property != null && isRefLikeName(property)) return { identity: null };
4613
- }
4614
- return null;
4615
- }
4616
- function getNullBranch(test, identity) {
4617
- return getNullCheckBranch(test, (candidate) => {
4618
- if (candidate.type !== AST_NODE_TYPES.MemberExpression || Extract.getPropertyName(candidate.property) !== "current") return false;
4619
- return getRefTarget(candidate)?.identity === identity;
4620
- }, (candidate) => {
4621
- if (candidate.type === AST_NODE_TYPES.Literal) return candidate.value == null;
4622
- if (candidate.type === AST_NODE_TYPES.UnaryExpression && candidate.operator === "void") return true;
4623
- if (candidate.type !== AST_NODE_TYPES.Identifier || candidate.name !== "undefined") return false;
4624
- const variable = getVariable(candidate);
4625
- return variable == null || variable.defs.length === 0;
4626
- });
4627
- }
4628
4659
  return merge(hc.visitor, fc.visitor, {
4629
4660
  AssignmentExpression(node) {
4630
4661
  if (node.operator !== "=") return;
4631
4662
  const left = Extract.unwrap(node.left);
4632
4663
  if (left.type === AST_NODE_TYPES.Identifier) {
4633
- addIdentifierBinding(left, bindingValueFrom(node.right), node.range[0]);
4664
+ addIdentifierBinding(left, node.right, node.range[0]);
4634
4665
  return;
4635
4666
  }
4636
4667
  if (left.type !== AST_NODE_TYPES.MemberExpression) return;
4637
4668
  const object = Extract.unwrap(left.object);
4638
4669
  const property = Extract.getPropertyName(left.property);
4639
- if (object.type === AST_NODE_TYPES.Identifier && property != null) addMemberBinding(object, property, bindingValueFrom(node.right), node.range[0]);
4670
+ if (object.type === AST_NODE_TYPES.Identifier && property != null) addMemberBinding(object, property, node.right, node.range[0]);
4640
4671
  },
4641
4672
  CallExpression(node) {
4642
4673
  calls.push(node);
4643
4674
  },
4644
4675
  FunctionDeclaration(node) {
4645
- if (node.id != null) addIdentifierBinding(node.id, {
4646
- kind: "function",
4647
- node
4648
- }, -1);
4676
+ if (node.id != null) addFunctionBinding(node.id, node, -1);
4649
4677
  },
4650
4678
  JSXAttribute(node) {
4651
4679
  if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== "ref" || node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) return;
4652
4680
  const expression = Extract.unwrap(node.value.expression);
4653
4681
  if (expression.type !== AST_NODE_TYPES.Identifier) return;
4654
- const variable = getVariable(expression);
4655
- if (variable != null) jsxRefs.add(variable);
4682
+ addJsxRef(expression);
4656
4683
  },
4657
4684
  MemberExpression(node) {
4658
4685
  if (Extract.getPropertyName(node.property) !== "current") return;
@@ -4746,7 +4773,7 @@ function create$6(context) {
4746
4773
  },
4747
4774
  VariableDeclarator(node) {
4748
4775
  if (node.id.type !== AST_NODE_TYPES.Identifier || node.init == null) return;
4749
- addIdentifierBinding(node.id, bindingValueFrom(node.init), node.range[0]);
4776
+ addIdentifierBinding(node.id, node.init, node.range[0]);
4750
4777
  }
4751
4778
  });
4752
4779
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.14.6",
3
+ "version": "5.14.8",
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,12 +45,12 @@
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.14.6",
49
- "@eslint-react/core": "5.14.6",
50
- "@eslint-react/jsx": "5.14.6",
51
- "@eslint-react/eslint": "5.14.6",
52
- "@eslint-react/shared": "5.14.6",
53
- "@eslint-react/var": "5.14.6"
48
+ "@eslint-react/core": "5.14.8",
49
+ "@eslint-react/eslint": "5.14.8",
50
+ "@eslint-react/jsx": "5.14.8",
51
+ "@eslint-react/var": "5.14.8",
52
+ "@eslint-react/shared": "5.14.8",
53
+ "@eslint-react/ast": "5.14.8"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/react": "^19.2.17",
@@ -59,7 +59,7 @@
59
59
  "eslint": "^10.7.0",
60
60
  "react": "^19.2.7",
61
61
  "react-dom": "^19.2.7",
62
- "tsdown": "^0.22.4",
62
+ "tsdown": "^0.22.5",
63
63
  "tsl": "^1.0.30",
64
64
  "tsl-dx": "^0.13.2",
65
65
  "typescript": "6.0.3",