eslint-plugin-react-x 3.0.0-next.20 → 3.0.0-next.23

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 +62 -9
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ const rules$7 = {
70
70
  //#endregion
71
71
  //#region package.json
72
72
  var name$6 = "eslint-plugin-react-x";
73
- var version = "3.0.0-next.20";
73
+ var version = "3.0.0-next.23";
74
74
 
75
75
  //#endregion
76
76
  //#region src/utils/create-rule.ts
@@ -475,6 +475,23 @@ function findComponentOrHookScope(node) {
475
475
  return null;
476
476
  }
477
477
  /**
478
+ * Check if a variable's definition is contained within a given AST node.
479
+ * Used to exclude callback-local variables from the reactive deps list —
480
+ * a variable declared inside the effect/memo callback is a local, not a dep.
481
+ * @param variable - the variable to check
482
+ * @param node - the node that must NOT contain the variable's definition
483
+ */
484
+ function isDefinedInsideNode(variable, node) {
485
+ for (const def of variable.defs) {
486
+ let current = def.name;
487
+ while (current.parent != null) {
488
+ current = current.parent;
489
+ if (current === node) return true;
490
+ }
491
+ }
492
+ return false;
493
+ }
494
+ /**
478
495
  * Check if a variable is defined within the given function scope (reactive).
479
496
  * @param variable - the variable to check
480
497
  * @param scopeNode - the function scope to check against
@@ -586,6 +603,7 @@ function create$64(context) {
586
603
  if (variable == null) continue;
587
604
  if (isStableVariable(variable)) continue;
588
605
  if (!isDefinedInScope(variable, componentScope)) continue;
606
+ if (isDefinedInsideNode(variable, callbackNode)) continue;
589
607
  reactiveDeps.add(memberExprText);
590
608
  continue;
591
609
  }
@@ -593,6 +611,7 @@ function create$64(context) {
593
611
  if (variable == null) continue;
594
612
  if (isStableVariable(variable)) continue;
595
613
  if (!isDefinedInScope(variable, componentScope)) continue;
614
+ if (isDefinedInsideNode(variable, callbackNode)) continue;
596
615
  reactiveDeps.add(identifier.name);
597
616
  }
598
617
  return reactiveDeps;
@@ -642,6 +661,32 @@ function create$64(context) {
642
661
  return deps;
643
662
  }
644
663
  /**
664
+ * Check if a reactive dependency is covered by a declared dependency.
665
+ * A declared dep covers a reactive dep if they are equal, or if the
666
+ * declared dep is a prefix ancestor (e.g. `array` covers `array.map`,
667
+ * `obj` covers `obj.a?.toString`).
668
+ * @param reactiveDep
669
+ * @param declaredDeps
670
+ */
671
+ function isCoveredByDeclaredDep(reactiveDep, declaredDeps) {
672
+ if (declaredDeps.has(reactiveDep)) return true;
673
+ for (const declared of declaredDeps) if (reactiveDep.startsWith(`${declared}.`) || reactiveDep.startsWith(`${declared}?.`)) return true;
674
+ return false;
675
+ }
676
+ /**
677
+ * Check if a declared dependency covers at least one reactive dependency.
678
+ * A declared dep is considered necessary if it equals a reactive dep, or if
679
+ * it is a prefix ancestor of any reactive dep (e.g. `array` is necessary
680
+ * when `array.map` is reactive).
681
+ * @param declaredDep
682
+ * @param reactiveDeps
683
+ */
684
+ function declaredDepCoversAny(declaredDep, reactiveDeps) {
685
+ if (reactiveDeps.has(declaredDep)) return true;
686
+ for (const reactive of reactiveDeps) if (reactive.startsWith(`${declaredDep}.`) || reactive.startsWith(`${declaredDep}?.`)) return true;
687
+ return false;
688
+ }
689
+ /**
645
690
  * Generate a fix that produces a corrected dependency array.
646
691
  * Removes unnecessary deps, adds missing deps, and sorts all alphabetically.
647
692
  * @param depsNode - the dependency array node
@@ -666,9 +711,9 @@ function create$64(context) {
666
711
  const reactiveDeps = collectReactiveDeps(callback, componentScope);
667
712
  const declaredDeps = getDeclaredDeps(depsNode);
668
713
  const missingDeps = /* @__PURE__ */ new Set();
669
- for (const dep of reactiveDeps) if (!declaredDeps.has(dep)) missingDeps.add(dep);
714
+ for (const dep of reactiveDeps) if (!isCoveredByDeclaredDep(dep, declaredDeps)) missingDeps.add(dep);
670
715
  const unnecessaryDeps = /* @__PURE__ */ new Set();
671
- for (const dep of declaredDeps) if (!reactiveDeps.has(dep)) unnecessaryDeps.add(dep);
716
+ for (const dep of declaredDeps) if (!declaredDepCoversAny(dep, reactiveDeps)) unnecessaryDeps.add(dep);
672
717
  const hasMissing = missingDeps.size > 0;
673
718
  const hasUnnecessary = unnecessaryDeps.size > 0;
674
719
  if (!hasMissing && !hasUnnecessary) return;
@@ -953,7 +998,7 @@ function create$58(context) {
953
998
  fix: (fixer) => fixer.removeRange([node.name.range[1], value.range[1]])
954
999
  });
955
1000
  break;
956
- case policy === -1 && value === null:
1001
+ case policy === -1 && value == null:
957
1002
  context.report({
958
1003
  messageId: "default",
959
1004
  node: node.value ?? node,
@@ -2104,7 +2149,14 @@ function create$32(ctx) {
2104
2149
  }
2105
2150
  }
2106
2151
  function checkBlock(node) {
2107
- return ast.getNestedReturnStatements(node).filter((stmt) => stmt.argument != null).map((stmt) => check(stmt.argument)).filter((d) => d != null);
2152
+ const descriptors = [];
2153
+ for (const stmt of ast.getNestedReturnStatements(node)) {
2154
+ if (stmt.argument == null) continue;
2155
+ const desc = check(stmt.argument);
2156
+ if (desc == null) continue;
2157
+ descriptors.push(desc);
2158
+ }
2159
+ return descriptors;
2108
2160
  }
2109
2161
  return defineRuleListener({
2110
2162
  ArrayExpression(node) {
@@ -3940,6 +3992,7 @@ function create$5(context) {
3940
3992
  if (entry == null) continue;
3941
3993
  if (entry.kind === "component" || entry.kind === "hook") return entry;
3942
3994
  }
3995
+ return unit;
3943
3996
  }
3944
3997
  function checkHookCall(node) {
3945
3998
  const hookName = getHookName(node);
@@ -4734,7 +4787,6 @@ const plugin = {
4734
4787
  "no-missing-component-display-name": no_missing_component_display_name_default,
4735
4788
  "no-missing-context-display-name": no_missing_context_display_name_default,
4736
4789
  "no-missing-key": no_missing_key_default,
4737
- "use-memo": use_memo_default,
4738
4790
  "no-misused-capture-owner-stack": no_misused_capture_owner_stack_default,
4739
4791
  "no-nested-component-definitions": no_nested_component_definitions_default,
4740
4792
  "no-nested-lazy-component-declarations": no_nested_lazy_component_declarations_default,
@@ -4765,6 +4817,7 @@ const plugin = {
4765
4817
  "set-state-in-effect": set_state_in_effect_default,
4766
4818
  "set-state-in-render": set_state_in_render_default,
4767
4819
  "unsupported-syntax": unsupported_syntax_default,
4820
+ "use-memo": use_memo_default,
4768
4821
  "use-state": use_state_default
4769
4822
  }
4770
4823
  };
@@ -4803,8 +4856,6 @@ const rules$6 = {
4803
4856
  "react-x/no-direct-mutation-state": "error",
4804
4857
  "react-x/no-forward-ref": "warn",
4805
4858
  "react-x/no-missing-key": "error",
4806
- "react-x/use-memo": "error",
4807
- "react-x/use-state": "warn",
4808
4859
  "react-x/no-nested-component-definitions": "error",
4809
4860
  "react-x/no-nested-lazy-component-declarations": "error",
4810
4861
  "react-x/no-redundant-should-component-update": "error",
@@ -4821,7 +4872,9 @@ const rules$6 = {
4821
4872
  "react-x/rules-of-hooks": "error",
4822
4873
  "react-x/set-state-in-effect": "warn",
4823
4874
  "react-x/set-state-in-render": "error",
4824
- "react-x/unsupported-syntax": "error"
4875
+ "react-x/unsupported-syntax": "error",
4876
+ "react-x/use-memo": "error",
4877
+ "react-x/use-state": "warn"
4825
4878
  };
4826
4879
  const plugins$5 = { "react-x": plugin };
4827
4880
  const settings$5 = { "react-x": DEFAULT_ESLINT_REACT_SETTINGS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "3.0.0-next.20",
3
+ "version": "3.0.0-next.23",
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",
@@ -46,11 +46,11 @@
46
46
  "string-ts": "^2.3.1",
47
47
  "ts-api-utils": "^2.4.0",
48
48
  "ts-pattern": "^5.9.0",
49
- "@eslint-react/ast": "3.0.0-next.20",
50
- "@eslint-react/eff": "3.0.0-next.20",
51
- "@eslint-react/core": "3.0.0-next.20",
52
- "@eslint-react/shared": "3.0.0-next.20",
53
- "@eslint-react/var": "3.0.0-next.20"
49
+ "@eslint-react/ast": "3.0.0-next.23",
50
+ "@eslint-react/core": "3.0.0-next.23",
51
+ "@eslint-react/eff": "3.0.0-next.23",
52
+ "@eslint-react/shared": "3.0.0-next.23",
53
+ "@eslint-react/var": "3.0.0-next.23"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/react": "^19.2.14",
@@ -71,6 +71,6 @@
71
71
  "scripts": {
72
72
  "build": "tsdown",
73
73
  "lint:publish": "publint",
74
- "lint:ts": "tsc --noEmit"
74
+ "lint:ts": "tsl"
75
75
  }
76
76
  }