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.
- package/dist/index.js +62 -9
- 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.
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/shared": "3.0.0-next.
|
|
53
|
-
"@eslint-react/var": "3.0.0-next.
|
|
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": "
|
|
74
|
+
"lint:ts": "tsl"
|
|
75
75
|
}
|
|
76
76
|
}
|