oxlint-plugin-react-doctor 0.7.2-dev.e872767 → 0.7.2-dev.f10f9ca
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 +111 -44
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8987,8 +8987,8 @@ const isAstDescendant = (inner, outer) => {
|
|
|
8987
8987
|
* One cohesive concept: "given a captured symbol, is its value
|
|
8988
8988
|
* structurally stable across re-renders (and therefore unnecessary
|
|
8989
8989
|
* in a deps array)?". The rule reads `symbolHasStableValue` /
|
|
8990
|
-
* `symbolHasStableHookOrigin` / `
|
|
8991
|
-
*
|
|
8990
|
+
* `symbolHasStableHookOrigin` / `isRecursiveInitializerCapture` at
|
|
8991
|
+
* multiple sites — extracting
|
|
8992
8992
|
* them lets the rule body stay focused on the diff-the-captured-vs-
|
|
8993
8993
|
* declared logic.
|
|
8994
8994
|
*
|
|
@@ -9044,11 +9044,6 @@ const symbolHasStableHookOrigin = (symbol) => {
|
|
|
9044
9044
|
}
|
|
9045
9045
|
return false;
|
|
9046
9046
|
};
|
|
9047
|
-
const symbolHasUseEffectEventOrigin = (symbol) => {
|
|
9048
|
-
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
9049
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
9050
|
-
return getHookName(initializer.callee) === "useEffectEvent";
|
|
9051
|
-
};
|
|
9052
9047
|
const getFunctionValueNode = (symbol) => {
|
|
9053
9048
|
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
|
|
9054
9049
|
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
@@ -9134,6 +9129,37 @@ const symbolHasStableFunctionOrigin = (symbol, scopes, visitedSymbolIds) => {
|
|
|
9134
9129
|
};
|
|
9135
9130
|
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
9136
9131
|
//#endregion
|
|
9132
|
+
//#region src/plugin/utils/is-imported-from-non-react-module.ts
|
|
9133
|
+
const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
|
|
9134
|
+
const importSource = getImportSourceForName(contextNode, localIdentifierName);
|
|
9135
|
+
if (importSource === null) return false;
|
|
9136
|
+
return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
|
|
9137
|
+
};
|
|
9138
|
+
//#endregion
|
|
9139
|
+
//#region src/plugin/utils/is-non-react-effect-event-callee.ts
|
|
9140
|
+
const resolvesToLocalNonImportBinding = (identifier, scopes) => {
|
|
9141
|
+
const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
|
|
9142
|
+
return Boolean(symbol && symbol.kind !== "import");
|
|
9143
|
+
};
|
|
9144
|
+
const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
|
|
9145
|
+
if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
|
|
9146
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
|
|
9147
|
+
return false;
|
|
9148
|
+
};
|
|
9149
|
+
//#endregion
|
|
9150
|
+
//#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
|
|
9151
|
+
const getUseEffectEventCalleeName = (callee) => {
|
|
9152
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
9153
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
9154
|
+
return null;
|
|
9155
|
+
};
|
|
9156
|
+
const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
|
|
9157
|
+
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
9158
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
9159
|
+
if (getUseEffectEventCalleeName(initializer.callee) !== "useEffectEvent") return false;
|
|
9160
|
+
return !isNonReactEffectEventCallee(initializer.callee, initializer, scopes);
|
|
9161
|
+
};
|
|
9162
|
+
//#endregion
|
|
9137
9163
|
//#region src/plugin/rules/react-builtins/exhaustive-deps.ts
|
|
9138
9164
|
const HOOKS_REQUIRING_DEPS_MATCH = new Set([
|
|
9139
9165
|
"useEffect",
|
|
@@ -9784,7 +9810,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
9784
9810
|
if (isLiteralOrEmptyTemplate(stripped)) continue;
|
|
9785
9811
|
if (isNodeOfType(stripped, "Identifier")) {
|
|
9786
9812
|
const depSymbol = context.scopes.symbolFor(stripped);
|
|
9787
|
-
if (depSymbol &&
|
|
9813
|
+
if (depSymbol && symbolHasReactUseEffectEventOrigin(depSymbol, context.scopes)) {
|
|
9788
9814
|
context.report({
|
|
9789
9815
|
node: elementNode,
|
|
9790
9816
|
message: buildEffectEventDepMessage()
|
|
@@ -25734,18 +25760,45 @@ const collectUseStateBindings = (componentBody) => {
|
|
|
25734
25760
|
//#region src/plugin/rules/state-and-effects/no-direct-state-mutation.ts
|
|
25735
25761
|
const PLAIN_DATA_PRODUCER_GLOBAL_NAMES = new Set(["Array", "structuredClone"]);
|
|
25736
25762
|
const PLAIN_DATA_ARRAY_STATIC_METHODS = new Set(["from", "of"]);
|
|
25763
|
+
const PLAIN_DATA_JSON_STATIC_METHODS = new Set(["parse"]);
|
|
25764
|
+
const PLAIN_DATA_OBJECT_STATIC_METHODS = new Set([
|
|
25765
|
+
"assign",
|
|
25766
|
+
"entries",
|
|
25767
|
+
"fromEntries",
|
|
25768
|
+
"keys",
|
|
25769
|
+
"values"
|
|
25770
|
+
]);
|
|
25771
|
+
const ARRAY_COPY_METHOD_NAMES = new Set([
|
|
25772
|
+
"map",
|
|
25773
|
+
"filter",
|
|
25774
|
+
"slice",
|
|
25775
|
+
"concat",
|
|
25776
|
+
"flat",
|
|
25777
|
+
"flatMap",
|
|
25778
|
+
"toSorted",
|
|
25779
|
+
"toReversed",
|
|
25780
|
+
"toSpliced",
|
|
25781
|
+
"with"
|
|
25782
|
+
]);
|
|
25737
25783
|
const isNullOrUndefinedExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
25738
25784
|
const isPlainDataProducerCall = (expression) => {
|
|
25739
25785
|
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
25740
25786
|
const callee = expression.callee;
|
|
25741
25787
|
if (isNodeOfType(callee, "Identifier")) return PLAIN_DATA_PRODUCER_GLOBAL_NAMES.has(callee.name);
|
|
25742
25788
|
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
25743
|
-
if (isNodeOfType(callee.object, "Identifier")
|
|
25789
|
+
if (isNodeOfType(callee.object, "Identifier")) {
|
|
25790
|
+
if (callee.object.name === "Array") return PLAIN_DATA_ARRAY_STATIC_METHODS.has(callee.property.name);
|
|
25791
|
+
if (callee.object.name === "JSON") return PLAIN_DATA_JSON_STATIC_METHODS.has(callee.property.name);
|
|
25792
|
+
if (callee.object.name === "Object") return PLAIN_DATA_OBJECT_STATIC_METHODS.has(callee.property.name);
|
|
25793
|
+
}
|
|
25744
25794
|
return producesPlainStateValue(callee.object);
|
|
25745
25795
|
};
|
|
25796
|
+
const PLAIN_DATA_CONSTRUCTOR_NAMES = new Set(["Array", "Object"]);
|
|
25797
|
+
const isPlainDataNewExpression = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && PLAIN_DATA_CONSTRUCTOR_NAMES.has(expression.callee.name);
|
|
25746
25798
|
const producesPlainStateValue = (expression) => {
|
|
25747
25799
|
const unwrapped = stripParenExpression(expression);
|
|
25748
25800
|
if (isNodeOfType(unwrapped, "ObjectExpression") || isNodeOfType(unwrapped, "ArrayExpression")) return true;
|
|
25801
|
+
if (isPlainDataNewExpression(unwrapped)) return true;
|
|
25749
25802
|
if (isNullOrUndefinedExpression(unwrapped)) return true;
|
|
25750
25803
|
if (isNodeOfType(unwrapped, "MemberExpression") && getRootIdentifierName(unwrapped) === "props") return true;
|
|
25751
25804
|
return isPlainDataProducerCall(unwrapped);
|
|
@@ -25760,6 +25813,38 @@ const initializerMarksPlainState = (initializerArgument) => {
|
|
|
25760
25813
|
}
|
|
25761
25814
|
return producesPlainStateValue(unwrapped);
|
|
25762
25815
|
};
|
|
25816
|
+
const producesOpaqueInstanceValue = (expression) => {
|
|
25817
|
+
if (isNodeOfType(expression, "NewExpression")) return !isPlainDataNewExpression(expression);
|
|
25818
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
25819
|
+
const callee = expression.callee;
|
|
25820
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
25821
|
+
if (isPlainDataProducerCall(expression)) return false;
|
|
25822
|
+
if (!callee.computed && isNodeOfType(callee.property, "Identifier") && ARRAY_COPY_METHOD_NAMES.has(callee.property.name)) return false;
|
|
25823
|
+
return true;
|
|
25824
|
+
};
|
|
25825
|
+
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
25826
|
+
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
25827
|
+
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
25828
|
+
walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
|
|
25829
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
25830
|
+
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
25831
|
+
const setterName = node.callee.name;
|
|
25832
|
+
if (!setterNames.has(setterName) || currentlyShadowed.has(setterName)) return;
|
|
25833
|
+
const argument = node.arguments?.[0];
|
|
25834
|
+
if (!argument) return;
|
|
25835
|
+
const unwrapped = stripParenExpression(argument);
|
|
25836
|
+
if (isNullOrUndefinedExpression(unwrapped)) return;
|
|
25837
|
+
if (producesPlainStateValue(unwrapped)) {
|
|
25838
|
+
plainFedSetterNames.add(setterName);
|
|
25839
|
+
return;
|
|
25840
|
+
}
|
|
25841
|
+
if (producesOpaqueInstanceValue(unwrapped)) opaqueFedSetterNames.add(setterName);
|
|
25842
|
+
}, true);
|
|
25843
|
+
return {
|
|
25844
|
+
plainFedSetterNames,
|
|
25845
|
+
opaqueFedSetterNames
|
|
25846
|
+
};
|
|
25847
|
+
};
|
|
25763
25848
|
const collectCallbackRefSetterNames = (componentBody) => {
|
|
25764
25849
|
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
25765
25850
|
walkAst(componentBody, (node) => {
|
|
@@ -25829,11 +25914,15 @@ const noDirectStateMutation = defineRule({
|
|
|
25829
25914
|
if (bindings.length === 0) return;
|
|
25830
25915
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
25831
25916
|
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
25917
|
+
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
25832
25918
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
25833
25919
|
for (const binding of bindings) {
|
|
25834
25920
|
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
25835
25921
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
25836
|
-
|
|
25922
|
+
const initializerArgument = binding.declarator.init.arguments?.[0];
|
|
25923
|
+
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
25924
|
+
if ((!initializerArgument || isNullOrUndefinedExpression(stripParenExpression(initializerArgument))) && setterValueObservations.opaqueFedSetterNames.has(binding.setterName) && !setterValueObservations.plainFedSetterNames.has(binding.setterName)) continue;
|
|
25925
|
+
plainObjectStateValueNames.add(binding.valueName);
|
|
25837
25926
|
}
|
|
25838
25927
|
const visitMutationCandidate = (child, currentlyShadowed) => {
|
|
25839
25928
|
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
@@ -26614,13 +26703,6 @@ const noEffectEventHandler = defineRule({
|
|
|
26614
26703
|
}
|
|
26615
26704
|
});
|
|
26616
26705
|
//#endregion
|
|
26617
|
-
//#region src/plugin/utils/is-imported-from-non-react-module.ts
|
|
26618
|
-
const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
|
|
26619
|
-
const importSource = getImportSourceForName(contextNode, localIdentifierName);
|
|
26620
|
-
if (importSource === null) return false;
|
|
26621
|
-
return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
|
|
26622
|
-
};
|
|
26623
|
-
//#endregion
|
|
26624
26706
|
//#region src/plugin/rules/state-and-effects/no-effect-event-in-deps.ts
|
|
26625
26707
|
const createComponentBindingStackTracker = (callbacks) => {
|
|
26626
26708
|
const componentBindingStack = [];
|
|
@@ -26674,12 +26756,7 @@ const noEffectEventInDeps = defineRule({
|
|
|
26674
26756
|
const initializer = declaratorNode.init;
|
|
26675
26757
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
26676
26758
|
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
26677
|
-
if (
|
|
26678
|
-
if (isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
|
|
26679
|
-
const calleeSymbol = context.scopes.referenceFor(initializer.callee)?.resolvedSymbol;
|
|
26680
|
-
if (calleeSymbol && calleeSymbol.kind !== "import") return;
|
|
26681
|
-
}
|
|
26682
|
-
if (isNodeOfType(initializer.callee, "MemberExpression") && !initializer.callee.computed && isNodeOfType(initializer.callee.object, "Identifier") && isImportedFromNonReactModule(declaratorNode, initializer.callee.object.name)) return;
|
|
26759
|
+
if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
|
|
26683
26760
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
26684
26761
|
} });
|
|
26685
26762
|
return {
|
|
@@ -37350,6 +37427,10 @@ const isRouteFactoryCall = (expression) => {
|
|
|
37350
37427
|
}
|
|
37351
37428
|
return false;
|
|
37352
37429
|
};
|
|
37430
|
+
const isConfigOnlyFactoryCall = (call) => call.arguments.length > 0 && call.arguments.every((argument) => {
|
|
37431
|
+
const expression = skipTsExpression(argument);
|
|
37432
|
+
return isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "Literal") || isNodeOfType(expression, "TemplateLiteral");
|
|
37433
|
+
});
|
|
37353
37434
|
const isReactHocName = (name, state) => state.customHocs.has(name);
|
|
37354
37435
|
const isHocCallee = (callee, state) => {
|
|
37355
37436
|
if (isNodeOfType(callee, "Identifier")) return isReactHocName(callee.name, state);
|
|
@@ -37582,6 +37663,10 @@ const onlyExportComponents = defineRule({
|
|
|
37582
37663
|
return false;
|
|
37583
37664
|
})();
|
|
37584
37665
|
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
37666
|
+
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
37667
|
+
kind: "non-component",
|
|
37668
|
+
reportNode: stripped
|
|
37669
|
+
});
|
|
37585
37670
|
else context.report({
|
|
37586
37671
|
node: stripped,
|
|
37587
37672
|
message: ANONYMOUS_MESSAGE
|
|
@@ -48549,25 +48634,6 @@ const isInsideLoop = (descendant, ancestor) => {
|
|
|
48549
48634
|
}
|
|
48550
48635
|
return false;
|
|
48551
48636
|
};
|
|
48552
|
-
const isUseEffectEventSymbol = (symbol) => {
|
|
48553
|
-
const initializer = symbol.initializer;
|
|
48554
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
48555
|
-
return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
|
|
48556
|
-
};
|
|
48557
|
-
const resolvesToLocalNonImportBinding = (identifier, scopes) => {
|
|
48558
|
-
const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
|
|
48559
|
-
return Boolean(symbol && symbol.kind !== "import");
|
|
48560
|
-
};
|
|
48561
|
-
const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
|
|
48562
|
-
if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
|
|
48563
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
|
|
48564
|
-
return false;
|
|
48565
|
-
};
|
|
48566
|
-
const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
|
|
48567
|
-
const initializer = symbol.initializer;
|
|
48568
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
48569
|
-
return isNonReactEffectEventCallee(initializer.callee, contextNode, scopes);
|
|
48570
|
-
};
|
|
48571
48637
|
const findEnclosingComponentOrHookFunction = (node) => {
|
|
48572
48638
|
let current = node.parent;
|
|
48573
48639
|
while (current) {
|
|
@@ -48757,8 +48823,7 @@ const rulesOfHooks = defineRule({
|
|
|
48757
48823
|
},
|
|
48758
48824
|
Identifier(node) {
|
|
48759
48825
|
const symbol = context.scopes.referenceFor(node)?.resolvedSymbol;
|
|
48760
|
-
if (!symbol || !
|
|
48761
|
-
if (isNonReactEffectEventSymbol(symbol, node, context.scopes)) return;
|
|
48826
|
+
if (!symbol || !symbolHasReactUseEffectEventOrigin(symbol, context.scopes)) return;
|
|
48762
48827
|
if (!isSameComponentOrHookScope(symbol, node)) return;
|
|
48763
48828
|
if (isInsideAllowedEffectEventCallback(node, additionalEffectHooksRegex)) return;
|
|
48764
48829
|
context.report({
|
|
@@ -56983,6 +57048,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
56983
57048
|
"nextjs-no-use-search-params-without-suspense",
|
|
56984
57049
|
"no-dynamic-import-path",
|
|
56985
57050
|
"no-full-lodash-import",
|
|
57051
|
+
"no-locale-format-in-render",
|
|
56986
57052
|
"no-mutating-reducer-state",
|
|
56987
57053
|
"prefer-dynamic-import",
|
|
56988
57054
|
"rendering-hydration-mismatch-time",
|
|
@@ -57117,6 +57183,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
57117
57183
|
["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
|
|
57118
57184
|
["no-dynamic-import-path", collectNearestManifestDependencies],
|
|
57119
57185
|
["no-full-lodash-import", collectNearestManifestDependencies],
|
|
57186
|
+
["no-locale-format-in-render", collectNearestManifestDependencies],
|
|
57120
57187
|
["no-mutating-reducer-state", collectMutatingReducerDependencies],
|
|
57121
57188
|
["prefer-dynamic-import", collectNearestManifestDependencies],
|
|
57122
57189
|
["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
|