eslint-plugin-react-x 5.22.0 → 5.23.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 +103 -64
  2. package/package.json +7 -7
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.22.0";
147
+ var version = "5.23.0";
148
148
 
149
149
  //#endregion
150
150
  //#region src/utils/create-rule.ts
@@ -1125,7 +1125,7 @@ function getUnknownDependenciesMessage(reactiveHookName) {
1125
1125
  }
1126
1126
 
1127
1127
  //#endregion
1128
- //#region src/rules/globals/lib.ts
1128
+ //#region src/rules/globals/helpers.ts
1129
1129
  /**
1130
1130
  * Array methods that mutate the array in place.
1131
1131
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
@@ -1154,7 +1154,7 @@ function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
1154
1154
 
1155
1155
  //#endregion
1156
1156
  //#region src/rules/globals/collect.ts
1157
- function createFactCollector$2() {
1157
+ function createFactCollector$3() {
1158
1158
  const facts = {
1159
1159
  callEdges: [],
1160
1160
  methodCalls: [],
@@ -1355,7 +1355,7 @@ var globals_default = createRule({
1355
1355
  function create$49(context) {
1356
1356
  const hooks = core.getHookCollector(context);
1357
1357
  const comps = core.getFunctionComponentCollector(context);
1358
- const facts = createFactCollector$2();
1358
+ const facts = createFactCollector$3();
1359
1359
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
1360
1360
  const renderFunctions = [...comps.api.getAllComponents(program), ...hooks.api.getAllHooks(program)].map(({ node }) => node);
1361
1361
  const directEffects = inferGlobalMutations(context, facts.facts);
@@ -1375,7 +1375,7 @@ function create$49(context) {
1375
1375
  }
1376
1376
 
1377
1377
  //#endregion
1378
- //#region src/rules/immutability/lib.ts
1378
+ //#region src/rules/immutability/helpers.ts
1379
1379
  /**
1380
1380
  * Methods that mutate their receiver in place.
1381
1381
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
@@ -1481,7 +1481,7 @@ function isRefLikeChain(context, node) {
1481
1481
 
1482
1482
  //#endregion
1483
1483
  //#region src/rules/immutability/collect.ts
1484
- function createFactCollector$1() {
1484
+ function createFactCollector$2() {
1485
1485
  const facts = {
1486
1486
  mutations: [],
1487
1487
  sinks: []
@@ -1742,7 +1742,7 @@ var immutability_default = createRule({
1742
1742
  function create$48(context) {
1743
1743
  const hooks = core.getHookCollector(context);
1744
1744
  const comps = core.getFunctionComponentCollector(context);
1745
- const facts = createFactCollector$1();
1745
+ const facts = createFactCollector$2();
1746
1746
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
1747
1747
  for (const hook of hooks.api.getAllHooks(program)) for (const expression of hook.rets) if (expression != null) facts.facts.sinks.push({
1748
1748
  kind: "hook-return",
@@ -5080,7 +5080,7 @@ function create$7(context) {
5080
5080
  }
5081
5081
 
5082
5082
  //#endregion
5083
- //#region src/rules/refs/lib.ts
5083
+ //#region src/rules/refs/helpers.ts
5084
5084
  const SYNC_ARRAY_CALLBACKS = /* @__PURE__ */ new Set([
5085
5085
  "every",
5086
5086
  "filter",
@@ -5272,7 +5272,7 @@ function isAfterTerminatingNonNullGuard(node, getNullBranch) {
5272
5272
  * reachable during render and are not collected. All inference is deferred to
5273
5273
  * `Program:exit`.
5274
5274
  */
5275
- function createFactCollector() {
5275
+ function createFactCollector$1() {
5276
5276
  const facts = {
5277
5277
  bindings: [],
5278
5278
  callEdges: [],
@@ -5719,7 +5719,7 @@ var refs_default = createRule({
5719
5719
  function create$6(context) {
5720
5720
  const hooks = core.getHookCollector(context);
5721
5721
  const comps = core.getFunctionComponentCollector(context);
5722
- const facts = createFactCollector();
5722
+ const facts = createFactCollector$1();
5723
5723
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
5724
5724
  const boundaries = /* @__PURE__ */ new Set([...comps.api.getAllComponents(program).map((component) => component.node), ...hooks.api.getAllHooks(program).map((hook) => hook.node)]);
5725
5725
  const resolver = createBindingResolver(context, facts.facts);
@@ -8389,36 +8389,67 @@ function create$4(context) {
8389
8389
  }
8390
8390
 
8391
8391
  //#endregion
8392
- //#region src/rules/static-components/lib.ts
8392
+ //#region src/rules/static-components/collect.ts
8393
+ function createFactCollector() {
8394
+ const facts = { componentUsages: [] };
8395
+ return {
8396
+ facts,
8397
+ visitor: { JSXOpeningElement(node) {
8398
+ if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return;
8399
+ const name = node.name.name;
8400
+ if (!core.isFunctionComponentName(name)) return;
8401
+ facts.componentUsages.push({
8402
+ name,
8403
+ node: node.name
8404
+ });
8405
+ } }
8406
+ };
8407
+ }
8408
+
8409
+ //#endregion
8410
+ //#region src/rules/static-components/helpers.ts
8393
8411
  /**
8394
8412
  * Expression kinds that always produce a brand-new value on every evaluation, and are
8395
8413
  * therefore considered "dynamically created" when used as a component's value.
8396
8414
  */
8397
- const DYNAMIC_EXPRESSION_TYPES = /* @__PURE__ */ new Set([
8415
+ const KNOWN_DYNAMIC_EXPRESSION_TYPES = /* @__PURE__ */ new Set([
8398
8416
  AST_NODE_TYPES.ArrowFunctionExpression,
8399
8417
  AST_NODE_TYPES.CallExpression,
8400
8418
  AST_NODE_TYPES.ClassExpression,
8401
8419
  AST_NODE_TYPES.FunctionExpression,
8402
8420
  AST_NODE_TYPES.NewExpression
8403
8421
  ]);
8404
- function findVariableForIdentifier(context, identifier) {
8405
- const scope = context.sourceCode.getScope(identifier);
8406
- return findVariable(scope, identifier.name);
8422
+ /**
8423
+ * Builds a predicate that tells whether a given node lies within the body of one of the
8424
+ * collected function or class components, i.e. whether it is "created during render".
8425
+ */
8426
+ function createRenderBoundaryChecker(componentNodes) {
8427
+ const components = new Set(componentNodes);
8428
+ return (node) => Traverse.findParent(node, (n) => (Check.isFunction(n) || Check.isClass(n)) && components.has(n)) != null;
8407
8429
  }
8430
+
8431
+ //#endregion
8432
+ //#region src/rules/static-components/origins.ts
8408
8433
  /**
8409
8434
  * Resolves an expression down to the node that dynamically created its value, if any.
8410
- * Recurses through identifier references and both branches of ternaries.
8435
+ * Recurses through identifier references, both branches of ternaries and logical
8436
+ * expressions, and the final element of sequence expressions.
8411
8437
  */
8412
8438
  function findDynamicCreationSite(context, node, isInsideRender, seen) {
8413
8439
  const expr = Extract.unwrap(node);
8414
- if (DYNAMIC_EXPRESSION_TYPES.has(expr.type)) return expr;
8440
+ if (KNOWN_DYNAMIC_EXPRESSION_TYPES.has(expr.type)) return expr;
8415
8441
  switch (expr.type) {
8416
8442
  case AST_NODE_TYPES.ConditionalExpression: return findDynamicCreationSite(context, expr.consequent, isInsideRender, seen) ?? findDynamicCreationSite(context, expr.alternate, isInsideRender, seen);
8443
+ case AST_NODE_TYPES.LogicalExpression: return findDynamicCreationSite(context, expr.left, isInsideRender, seen) ?? findDynamicCreationSite(context, expr.right, isInsideRender, seen);
8444
+ case AST_NODE_TYPES.SequenceExpression: {
8445
+ const last = expr.expressions.at(-1);
8446
+ return last == null ? null : findDynamicCreationSite(context, last, isInsideRender, seen);
8447
+ }
8417
8448
  case AST_NODE_TYPES.Identifier:
8418
8449
  case AST_NODE_TYPES.JSXIdentifier: {
8419
- const variable = findVariableForIdentifier(context, expr);
8450
+ const variable = findVariable(context.sourceCode.getScope(expr), expr.name);
8420
8451
  if (variable == null) return null;
8421
- return getDynamicComponentSource(context, variable, isInsideRender, seen).creationNode;
8452
+ return resolveDynamicComponentOrigin(context, variable, isInsideRender, seen).creationNode;
8422
8453
  }
8423
8454
  default: return null;
8424
8455
  }
@@ -8437,7 +8468,11 @@ function findReassignmentCreationSite(context, variable, isInsideRender, seen) {
8437
8468
  }
8438
8469
  return null;
8439
8470
  }
8440
- function getDynamicComponentSource(context, variable, isInsideRender, seen = /* @__PURE__ */ new Set()) {
8471
+ /**
8472
+ * Resolves a component binding to the node that dynamically created its value, if the
8473
+ * value is created during render (a fresh function/class/call result on every render).
8474
+ */
8475
+ function resolveDynamicComponentOrigin(context, variable, isInsideRender, seen = /* @__PURE__ */ new Set()) {
8441
8476
  if (seen.has(variable)) return {
8442
8477
  creationNode: null,
8443
8478
  isDynamic: false
@@ -8446,7 +8481,7 @@ function getDynamicComponentSource(context, variable, isInsideRender, seen = /*
8446
8481
  for (const def of variable.defs) {
8447
8482
  const defNode = def.node;
8448
8483
  if (!isInsideRender(defNode)) continue;
8449
- if (defNode.type === AST_NODE_TYPES.FunctionDeclaration || defNode.type === AST_NODE_TYPES.ClassDeclaration) return {
8484
+ if (def.type === DefinitionType.FunctionName || def.type === DefinitionType.ClassName) return {
8450
8485
  creationNode: defNode,
8451
8486
  isDynamic: true
8452
8487
  };
@@ -8468,6 +8503,32 @@ function getDynamicComponentSource(context, variable, isInsideRender, seen = /*
8468
8503
  };
8469
8504
  }
8470
8505
 
8506
+ //#endregion
8507
+ //#region src/rules/static-components/effects.ts
8508
+ /**
8509
+ * Turn collected JSX component usages into effects: a usage is an effect when the
8510
+ * binding it refers to is declared inside a component's render body and its value is
8511
+ * dynamically created, i.e. the component is recreated on every render.
8512
+ */
8513
+ function inferCreatedComponents(context, componentUsages, isInsideRender) {
8514
+ const effects = [];
8515
+ for (const usage of componentUsages) {
8516
+ const variable = findVariable(context.sourceCode.getScope(usage.node), usage.name);
8517
+ if (variable == null) continue;
8518
+ const def = variable.defs.at(0);
8519
+ if (def == null) continue;
8520
+ if (!isInsideRender(def.node)) continue;
8521
+ const { creationNode, isDynamic } = resolveDynamicComponentOrigin(context, variable, isInsideRender);
8522
+ if (!isDynamic) continue;
8523
+ effects.push({
8524
+ name: usage.name,
8525
+ creationNode,
8526
+ node: usage.node
8527
+ });
8528
+ }
8529
+ return effects;
8530
+ }
8531
+
8471
8532
  //#endregion
8472
8533
  //#region src/rules/static-components/static-components.ts
8473
8534
  const RULE_NAME$3 = "static-components";
@@ -8489,51 +8550,27 @@ function create$3(context) {
8489
8550
  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;
8490
8551
  const fc = core.getFunctionComponentCollector(context, { hint });
8491
8552
  const cc = core.getClassComponentCollector(context);
8492
- const candidates = [];
8493
- return merge(fc.visitor, cc.visitor, {
8494
- JSXOpeningElement(node) {
8495
- if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return;
8496
- const name = node.name.name;
8497
- if (!core.isFunctionComponentName(name)) return;
8498
- candidates.push({
8499
- name,
8500
- identifier: node.name
8553
+ const facts = createFactCollector();
8554
+ return merge(fc.visitor, cc.visitor, facts.visitor, { "Program:exit"(program) {
8555
+ const componentNodes = [...fc.api.getAllComponents(program), ...cc.api.getAllComponents(program)].map((component) => component.node);
8556
+ const isInsideRender = createRenderBoundaryChecker(componentNodes);
8557
+ const reportedCreations = /* @__PURE__ */ new Set();
8558
+ for (const effect of inferCreatedComponents(context, facts.facts.componentUsages, isInsideRender)) {
8559
+ context.report({
8560
+ data: { name: effect.name },
8561
+ messageId: "default",
8562
+ node: effect.node
8501
8563
  });
8502
- },
8503
- "Program:exit"(program) {
8504
- const isInsideRender = createRenderBoundaryChecker(fc, cc, program);
8505
- for (const candidate of candidates) reportIfCreatedDuringRender(context, candidate, isInsideRender);
8564
+ if (effect.creationNode != null && !reportedCreations.has(effect.creationNode)) {
8565
+ reportedCreations.add(effect.creationNode);
8566
+ context.report({
8567
+ data: { name: effect.name },
8568
+ messageId: "createdHere",
8569
+ node: effect.creationNode
8570
+ });
8571
+ }
8506
8572
  }
8507
- });
8508
- }
8509
- /**
8510
- * Builds a predicate that tells whether a given node lies within the body of a
8511
- * previously-collected function or class component, i.e. whether it is "created during render".
8512
- */
8513
- function createRenderBoundaryChecker(fc, cc, program) {
8514
- const componentNodes = new Set([...fc.api.getAllComponents(program), ...cc.api.getAllComponents(program)].map((component) => component.node));
8515
- const getEnclosingComponent = (node) => Traverse.findParent(node, (n) => (Check.isFunction(n) || Check.isClass(n)) && componentNodes.has(n));
8516
- return (node) => getEnclosingComponent(node) != null;
8517
- }
8518
- function reportIfCreatedDuringRender(context, candidate, isInsideRender) {
8519
- const { name, identifier } = candidate;
8520
- const variable = findVariableForIdentifier(context, identifier);
8521
- if (variable == null) return;
8522
- const def = variable.defs.at(0);
8523
- if (def == null) return;
8524
- if (!isInsideRender(def.node)) return;
8525
- const { creationNode, isDynamic } = getDynamicComponentSource(context, variable, isInsideRender);
8526
- if (!isDynamic) return;
8527
- context.report({
8528
- data: { name },
8529
- messageId: "default",
8530
- node: identifier
8531
- });
8532
- if (creationNode != null) context.report({
8533
- data: { name },
8534
- messageId: "createdHere",
8535
- node: creationNode
8536
- });
8573
+ } });
8537
8574
  }
8538
8575
 
8539
8576
  //#endregion
@@ -8904,6 +8941,8 @@ function create(context) {
8904
8941
  }
8905
8942
  let parent = node.parent;
8906
8943
  while (Check.isTypeExpression(parent)) parent = parent.parent;
8944
+ if (parent.type === AST_NODE_TYPES.ReturnStatement) return;
8945
+ if (parent.type === AST_NODE_TYPES.ArrowFunctionExpression) return;
8907
8946
  if (parent.type !== AST_NODE_TYPES.VariableDeclarator) {
8908
8947
  if (!enforceAssignment) return;
8909
8948
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.22.0",
3
+ "version": "5.23.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,12 +36,12 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@eslint-react/ast": "5.22.0",
40
- "@eslint-react/core": "5.22.0",
41
- "@eslint-react/eslint": "5.22.0",
42
- "@eslint-react/jsx": "5.22.0",
43
- "@eslint-react/shared": "5.22.0",
44
- "@eslint-react/var": "5.22.0",
39
+ "@eslint-react/ast": "5.23.0",
40
+ "@eslint-react/core": "5.23.0",
41
+ "@eslint-react/eslint": "5.23.0",
42
+ "@eslint-react/jsx": "5.23.0",
43
+ "@eslint-react/shared": "5.23.0",
44
+ "@eslint-react/var": "5.23.0",
45
45
  "@typescript-eslint/scope-manager": "^8.70.1",
46
46
  "@typescript-eslint/types": "^8.70.1",
47
47
  "@typescript-eslint/typescript-estree": "^8.70.1",