eslint-plugin-react-x 5.14.7 → 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 +177 -156
  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.7";
147
+ var version = "5.14.8";
148
148
 
149
149
  //#endregion
150
150
  //#region src/utils/create-rule.ts
@@ -4212,21 +4212,6 @@ const IMPURE_CTORS = /* @__PURE__ */ new Set([
4212
4212
  "Worker",
4213
4213
  "XMLHttpRequest"
4214
4214
  ]);
4215
-
4216
- //#endregion
4217
- //#region src/rules/purity/purity.ts
4218
- const RULE_NAME$7 = "purity";
4219
- var purity_default = createRule({
4220
- meta: {
4221
- type: "problem",
4222
- docs: { description: "Validates that components and hooks are pure by checking that they do not call known-impure functions during render." },
4223
- 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." },
4224
- schema: []
4225
- },
4226
- name: RULE_NAME$7,
4227
- create: create$7,
4228
- defaultOptions: []
4229
- });
4230
4215
  /**
4231
4216
  * Recursively resolve an identifier to the root builtin global object name.
4232
4217
  * Follows simple assignment chains like `const M = Math` or `const w = window`.
@@ -4254,6 +4239,21 @@ function resolveBuiltinObjectName(context, node, seen = /* @__PURE__ */ new Set(
4254
4239
  }
4255
4240
  return null;
4256
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
+ });
4257
4257
  function create$7(context) {
4258
4258
  const hc = core.getHookCollector(context);
4259
4259
  const fc = core.getFunctionComponentCollector(context);
@@ -4341,6 +4341,150 @@ const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
4341
4341
  "some",
4342
4342
  "sort"
4343
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
+ }
4344
4488
  function getLatestValue(events, position) {
4345
4489
  let latest = null;
4346
4490
  for (const event of events ?? []) {
@@ -4381,6 +4525,16 @@ function isReachedThroughFunctions(node, boundary, reached) {
4381
4525
  }
4382
4526
  return current === boundary;
4383
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
+ }
4384
4538
  /**
4385
4539
  * Check whether `node` (a `ref.current` MemberExpression) is being written to indirectly through
4386
4540
  * a nested property write, e.g. `ref.current.inner = value` or `ref.current.inner++`.
@@ -4402,16 +4556,6 @@ function isNestedRefCurrentWrite(node) {
4402
4556
  return false;
4403
4557
  }
4404
4558
  }
4405
- function getRefAccess(node) {
4406
- let parent = node.parent;
4407
- while (Check.isTypeExpression(parent)) parent = parent.parent;
4408
- 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;
4409
- return {
4410
- isInitializationWrite: parent.type === AST_NODE_TYPES.AssignmentExpression && parent.operator === "=" && (parent.left === node || Extract.unwrap(parent.left) === node),
4411
- isWrite: isDirectWrite || isNestedRefCurrentWrite(node),
4412
- node
4413
- };
4414
- }
4415
4559
  /**
4416
4560
  * Parse an exact nullish check and return the branch where the checked value is null/undefined.
4417
4561
  * Truthiness checks such as `!ref.current` are deliberately excluded: falsy ref values are not
@@ -4475,7 +4619,7 @@ function isAfterTerminatingNonNullGuard(node, getNullBranch) {
4475
4619
  statement = statement.parent;
4476
4620
  }
4477
4621
  const block = statement.parent;
4478
- const index = block.body.indexOf(statement);
4622
+ const index = block.body.findIndex((sibling) => sibling === statement);
4479
4623
  for (let i = index - 1; i >= 0; i--) {
4480
4624
  const sibling = block.body[i];
4481
4625
  if (sibling?.type !== AST_NODE_TYPES.IfStatement) continue;
@@ -4509,156 +4653,33 @@ var refs_default = createRule({
4509
4653
  function create$6(context) {
4510
4654
  const hc = core.getHookCollector(context);
4511
4655
  const fc = core.getFunctionComponentCollector(context);
4512
- const bindings = /* @__PURE__ */ new Map();
4513
- const memberBindings = /* @__PURE__ */ new Map();
4514
- const jsxRefs = /* @__PURE__ */ new Set();
4656
+ const { addFunctionBinding, addIdentifierBinding, addJsxRef, addMemberBinding, getNullBranch, getRefTarget, getVariable, resolveCallable, resolveRef } = createBindingResolver(context);
4515
4657
  const calls = [];
4516
4658
  const refAccesses = [];
4517
- function getVariable(node) {
4518
- return findVariable(context.sourceCode.getScope(node), node) ?? null;
4519
- }
4520
- function addBinding(variable, value, position) {
4521
- const events = bindings.get(variable) ?? [];
4522
- bindings.set(variable, events);
4523
- events.push({
4524
- position,
4525
- value
4526
- });
4527
- }
4528
- function addIdentifierBinding(node, value, position = node.range[0]) {
4529
- const variable = getVariable(node);
4530
- if (variable != null) addBinding(variable, value, position);
4531
- }
4532
- function addMemberBinding(object, property, value, position) {
4533
- const variable = getVariable(object);
4534
- if (variable == null) return;
4535
- const properties = memberBindings.get(variable) ?? /* @__PURE__ */ new Map();
4536
- memberBindings.set(variable, properties);
4537
- const events = properties.get(property) ?? [];
4538
- properties.set(property, events);
4539
- events.push({
4540
- position,
4541
- value
4542
- });
4543
- }
4544
- function bindingValueFrom(node) {
4545
- const value = Extract.unwrap(node);
4546
- if (value.type === AST_NODE_TYPES.Identifier) {
4547
- const variable = getVariable(value);
4548
- return variable == null ? { kind: "unknown" } : {
4549
- kind: "variable",
4550
- variable
4551
- };
4552
- }
4553
- if (isFunctionExpressionLike(value)) return {
4554
- kind: "function",
4555
- node: value
4556
- };
4557
- if (value.type === AST_NODE_TYPES.CallExpression && (core.isUseRefCall(context, value) || core.isCreateRefCall(context, value))) return { kind: "ref" };
4558
- if (value.type === AST_NODE_TYPES.MemberExpression) {
4559
- const property = Extract.getPropertyName(value.property);
4560
- if (property != null && isRefLikeName(property)) return { kind: "ref" };
4561
- }
4562
- return { kind: "unknown" };
4563
- }
4564
- function resolveRef(variable, position, seen = /* @__PURE__ */ new Set()) {
4565
- if (seen.has(variable)) return null;
4566
- seen.add(variable);
4567
- if (jsxRefs.has(variable) || isRefLikeName(variable.name)) return variable;
4568
- const event = getLatestValue(bindings.get(variable), position);
4569
- if (event != null) switch (event.value.kind) {
4570
- case "ref": return variable;
4571
- case "variable": return resolveRef(event.value.variable, event.position, seen);
4572
- case "function":
4573
- case "unknown": return null;
4574
- }
4575
- return null;
4576
- }
4577
- function resolveFunction(variable, position, seen = /* @__PURE__ */ new Set()) {
4578
- if (seen.has(variable)) return null;
4579
- seen.add(variable);
4580
- const event = getLatestValue(bindings.get(variable), position);
4581
- if (event == null) return null;
4582
- switch (event.value.kind) {
4583
- case "function": return event.value.node;
4584
- case "variable": return resolveFunction(event.value.variable, event.position, seen);
4585
- case "ref":
4586
- case "unknown": return null;
4587
- }
4588
- }
4589
- function resolveCallable(node, position) {
4590
- const callee = Extract.unwrap(node);
4591
- if (isFunctionExpressionLike(callee)) return callee;
4592
- if (callee.type === AST_NODE_TYPES.Identifier) {
4593
- const variable = getVariable(callee);
4594
- return variable == null ? null : resolveFunction(variable, position);
4595
- }
4596
- if (callee.type !== AST_NODE_TYPES.MemberExpression) return null;
4597
- const object = Extract.unwrap(callee.object);
4598
- const property = Extract.getPropertyName(callee.property);
4599
- if (object.type !== AST_NODE_TYPES.Identifier || property == null) return null;
4600
- const variable = getVariable(object);
4601
- if (variable == null) return null;
4602
- const event = getLatestValue(memberBindings.get(variable)?.get(property), position);
4603
- if (event == null) return null;
4604
- if (event.value.kind === "function") return event.value.node;
4605
- if (event.value.kind === "variable") return resolveFunction(event.value.variable, event.position);
4606
- return null;
4607
- }
4608
- function getRefTarget(node) {
4609
- const object = Extract.unwrap(node.object);
4610
- if (object.type === AST_NODE_TYPES.Identifier) {
4611
- const variable = getVariable(object);
4612
- if (variable == null) return null;
4613
- const identity = resolveRef(variable, node.range[0]);
4614
- return identity == null ? null : { identity };
4615
- }
4616
- if (object.type === AST_NODE_TYPES.MemberExpression) {
4617
- const property = Extract.getPropertyName(object.property);
4618
- if (property != null && isRefLikeName(property)) return { identity: null };
4619
- }
4620
- return null;
4621
- }
4622
- function getNullBranch(test, identity) {
4623
- return getNullCheckBranch(test, (candidate) => {
4624
- if (candidate.type !== AST_NODE_TYPES.MemberExpression || Extract.getPropertyName(candidate.property) !== "current") return false;
4625
- return getRefTarget(candidate)?.identity === identity;
4626
- }, (candidate) => {
4627
- if (candidate.type === AST_NODE_TYPES.Literal) return candidate.value == null;
4628
- if (candidate.type === AST_NODE_TYPES.UnaryExpression && candidate.operator === "void") return true;
4629
- if (candidate.type !== AST_NODE_TYPES.Identifier || candidate.name !== "undefined") return false;
4630
- const variable = getVariable(candidate);
4631
- return variable == null || variable.defs.length === 0;
4632
- });
4633
- }
4634
4659
  return merge(hc.visitor, fc.visitor, {
4635
4660
  AssignmentExpression(node) {
4636
4661
  if (node.operator !== "=") return;
4637
4662
  const left = Extract.unwrap(node.left);
4638
4663
  if (left.type === AST_NODE_TYPES.Identifier) {
4639
- addIdentifierBinding(left, bindingValueFrom(node.right), node.range[0]);
4664
+ addIdentifierBinding(left, node.right, node.range[0]);
4640
4665
  return;
4641
4666
  }
4642
4667
  if (left.type !== AST_NODE_TYPES.MemberExpression) return;
4643
4668
  const object = Extract.unwrap(left.object);
4644
4669
  const property = Extract.getPropertyName(left.property);
4645
- 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]);
4646
4671
  },
4647
4672
  CallExpression(node) {
4648
4673
  calls.push(node);
4649
4674
  },
4650
4675
  FunctionDeclaration(node) {
4651
- if (node.id != null) addIdentifierBinding(node.id, {
4652
- kind: "function",
4653
- node
4654
- }, -1);
4676
+ if (node.id != null) addFunctionBinding(node.id, node, -1);
4655
4677
  },
4656
4678
  JSXAttribute(node) {
4657
4679
  if (node.name.type !== AST_NODE_TYPES.JSXIdentifier || node.name.name !== "ref" || node.value?.type !== AST_NODE_TYPES.JSXExpressionContainer) return;
4658
4680
  const expression = Extract.unwrap(node.value.expression);
4659
4681
  if (expression.type !== AST_NODE_TYPES.Identifier) return;
4660
- const variable = getVariable(expression);
4661
- if (variable != null) jsxRefs.add(variable);
4682
+ addJsxRef(expression);
4662
4683
  },
4663
4684
  MemberExpression(node) {
4664
4685
  if (Extract.getPropertyName(node.property) !== "current") return;
@@ -4752,7 +4773,7 @@ function create$6(context) {
4752
4773
  },
4753
4774
  VariableDeclarator(node) {
4754
4775
  if (node.id.type !== AST_NODE_TYPES.Identifier || node.init == null) return;
4755
- addIdentifierBinding(node.id, bindingValueFrom(node.init), node.range[0]);
4776
+ addIdentifierBinding(node.id, node.init, node.range[0]);
4756
4777
  }
4757
4778
  });
4758
4779
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.14.7",
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.7",
49
- "@eslint-react/core": "5.14.7",
50
- "@eslint-react/eslint": "5.14.7",
51
- "@eslint-react/jsx": "5.14.7",
52
- "@eslint-react/var": "5.14.7",
53
- "@eslint-react/shared": "5.14.7"
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",