oxlint-plugin-react-doctor 0.7.9-dev.d8a20e0 → 0.7.9-dev.e86bc57
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 +249 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28469,7 +28469,11 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
|
|
|
28469
28469
|
}));
|
|
28470
28470
|
//#endregion
|
|
28471
28471
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
28472
|
-
const
|
|
28472
|
+
const KNOWN_COMPONENT_WRAPPER_NAMES = new Set([
|
|
28473
|
+
"memo",
|
|
28474
|
+
"forwardRef",
|
|
28475
|
+
"observer"
|
|
28476
|
+
]);
|
|
28473
28477
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
28474
28478
|
const isReactFunctionalComponent = (node) => {
|
|
28475
28479
|
if (!node) return false;
|
|
@@ -28491,7 +28495,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
28491
28495
|
const isWrappedInline = () => {
|
|
28492
28496
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
28493
28497
|
if (!isNodeOfType(init.callee, "Identifier")) return false;
|
|
28494
|
-
if (
|
|
28498
|
+
if (KNOWN_COMPONENT_WRAPPER_NAMES.has(init.callee.name)) return false;
|
|
28495
28499
|
const firstArg = init.arguments?.[0];
|
|
28496
28500
|
if (!firstArg) return false;
|
|
28497
28501
|
return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
|
|
@@ -28511,7 +28515,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
28511
28515
|
if (!args.includes(refId)) continue;
|
|
28512
28516
|
const callee = parent.callee;
|
|
28513
28517
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
|
|
28514
|
-
if (calleeName != null && !
|
|
28518
|
+
if (calleeName != null && !KNOWN_COMPONENT_WRAPPER_NAMES.has(calleeName)) return true;
|
|
28515
28519
|
}
|
|
28516
28520
|
return false;
|
|
28517
28521
|
};
|
|
@@ -35902,15 +35906,167 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
35902
35906
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
35903
35907
|
}
|
|
35904
35908
|
};
|
|
35905
|
-
const
|
|
35906
|
-
const
|
|
35909
|
+
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
35910
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
35911
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
35912
|
+
const literalValue = unwrappedExpression.value;
|
|
35913
|
+
if (literalValue === null || typeof literalValue === "boolean" || typeof literalValue === "number" || typeof literalValue === "string") return { value: literalValue };
|
|
35914
|
+
return null;
|
|
35915
|
+
}
|
|
35916
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
35917
|
+
if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
|
|
35918
|
+
if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
|
|
35919
|
+
const immutableSymbol = scopes.symbolFor(unwrappedExpression);
|
|
35920
|
+
if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
|
|
35921
|
+
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
|
|
35922
|
+
}
|
|
35923
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
|
|
35924
|
+
if (unwrappedExpression.operator === "void") return { value: void 0 };
|
|
35925
|
+
if (unwrappedExpression.operator !== "!") return null;
|
|
35926
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35927
|
+
return argumentValue ? { value: !argumentValue.value } : null;
|
|
35928
|
+
}
|
|
35929
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
35930
|
+
if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
|
|
35931
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35932
|
+
return argumentValue ? { value: Boolean(argumentValue.value) } : null;
|
|
35933
|
+
}
|
|
35934
|
+
return null;
|
|
35935
|
+
}
|
|
35936
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
35937
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35938
|
+
if (!leftValue) return null;
|
|
35939
|
+
if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
|
|
35940
|
+
if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
|
|
35941
|
+
if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
|
|
35942
|
+
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35943
|
+
}
|
|
35944
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
35945
|
+
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35946
|
+
if (!testValue) return null;
|
|
35947
|
+
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35948
|
+
}
|
|
35949
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
|
|
35950
|
+
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35951
|
+
if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
|
|
35952
|
+
return null;
|
|
35953
|
+
}
|
|
35954
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
35955
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35956
|
+
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
35957
|
+
if (!leftValue || !rightValue) return null;
|
|
35958
|
+
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
35959
|
+
const areEqual = leftValue.value === rightValue.value;
|
|
35960
|
+
return { value: unwrappedExpression.operator === "===" ? areEqual : !areEqual };
|
|
35961
|
+
}
|
|
35962
|
+
if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
|
|
35963
|
+
const isLeftNullish = leftValue.value === null || leftValue.value === void 0;
|
|
35964
|
+
const isRightNullish = rightValue.value === null || rightValue.value === void 0;
|
|
35965
|
+
if (!isLeftNullish && !isRightNullish && typeof leftValue.value !== typeof rightValue.value) return null;
|
|
35966
|
+
const areEqual = isLeftNullish || isRightNullish ? isLeftNullish && isRightNullish : leftValue.value === rightValue.value;
|
|
35967
|
+
return { value: unwrappedExpression.operator === "==" ? areEqual : !areEqual };
|
|
35968
|
+
}
|
|
35969
|
+
}
|
|
35970
|
+
return null;
|
|
35971
|
+
};
|
|
35972
|
+
const readStaticUpdaterReturnValue = (updater, scopes) => {
|
|
35973
|
+
if (!isFunctionLike$1(updater) || updater.async || updater.generator) return null;
|
|
35974
|
+
if (!isNodeOfType(updater.body, "BlockStatement")) return readStaticEffectValue(updater.body, scopes, null, null);
|
|
35975
|
+
if (updater.body.body.length === 0) return { value: void 0 };
|
|
35976
|
+
if (updater.body.body.length !== 1) return null;
|
|
35977
|
+
const returnStatement = updater.body.body[0];
|
|
35978
|
+
if (!isNodeOfType(returnStatement, "ReturnStatement")) return null;
|
|
35979
|
+
if (!returnStatement.argument) return { value: void 0 };
|
|
35980
|
+
return readStaticEffectValue(returnStatement.argument, scopes, null, null);
|
|
35981
|
+
};
|
|
35982
|
+
const readStaticSetterValue = (setterCall, scopes) => {
|
|
35983
|
+
const argument = setterCall.arguments[0];
|
|
35984
|
+
if (!argument) return { value: void 0 };
|
|
35985
|
+
if (isNodeOfType(argument, "SpreadElement")) return null;
|
|
35986
|
+
const updater = resolveExactLocalFunction(argument, scopes);
|
|
35987
|
+
if (updater) return readStaticUpdaterReturnValue(updater, scopes);
|
|
35988
|
+
return readStaticEffectValue(argument, scopes, null, null);
|
|
35989
|
+
};
|
|
35990
|
+
const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
|
|
35991
|
+
const stateWrites = /* @__PURE__ */ new Map();
|
|
35907
35992
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
35908
35993
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35909
35994
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35910
35995
|
const stateName = setterToStateName.get(child.callee.name);
|
|
35911
|
-
if (stateName)
|
|
35996
|
+
if (!stateName) return;
|
|
35997
|
+
const writeInfo = stateWrites.get(stateName) ?? {
|
|
35998
|
+
values: /* @__PURE__ */ new Set(),
|
|
35999
|
+
hasUnknownValue: false
|
|
36000
|
+
};
|
|
36001
|
+
const staticValue = readStaticSetterValue(child, scopes);
|
|
36002
|
+
if (staticValue) writeInfo.values.add(staticValue.value);
|
|
36003
|
+
else writeInfo.hasUnknownValue = true;
|
|
36004
|
+
stateWrites.set(stateName, writeInfo);
|
|
35912
36005
|
});
|
|
35913
|
-
return
|
|
36006
|
+
return stateWrites;
|
|
36007
|
+
};
|
|
36008
|
+
const isGlobalBooleanCall = (node, scopes) => {
|
|
36009
|
+
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
|
|
36010
|
+
};
|
|
36011
|
+
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
|
|
36012
|
+
let currentNode = workNode;
|
|
36013
|
+
while (currentNode.parent) {
|
|
36014
|
+
const parentNode = currentNode.parent;
|
|
36015
|
+
if (isFunctionLike$1(parentNode)) break;
|
|
36016
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
36017
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
36018
|
+
if (testValue) {
|
|
36019
|
+
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
36020
|
+
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
36021
|
+
}
|
|
36022
|
+
}
|
|
36023
|
+
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
36024
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
36025
|
+
if (testValue) {
|
|
36026
|
+
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
36027
|
+
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
36028
|
+
}
|
|
36029
|
+
}
|
|
36030
|
+
if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
|
|
36031
|
+
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
|
|
36032
|
+
if (leftValue) {
|
|
36033
|
+
if (parentNode.operator === "&&" && !leftValue.value) return false;
|
|
36034
|
+
if (parentNode.operator === "||" && leftValue.value) return false;
|
|
36035
|
+
if (parentNode.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return false;
|
|
36036
|
+
}
|
|
36037
|
+
}
|
|
36038
|
+
if (isNodeOfType(parentNode, "BlockStatement")) {
|
|
36039
|
+
const statementIndex = parentNode.body.findIndex((statement) => statement === currentNode);
|
|
36040
|
+
if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
|
|
36041
|
+
const earlierStatement = parentNode.body[index];
|
|
36042
|
+
if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
|
|
36043
|
+
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
|
|
36044
|
+
}
|
|
36045
|
+
}
|
|
36046
|
+
currentNode = parentNode;
|
|
36047
|
+
}
|
|
36048
|
+
return true;
|
|
36049
|
+
};
|
|
36050
|
+
const isReaderWorkNode = (node, analysisFunctions, scopes) => {
|
|
36051
|
+
if (isNodeOfType(node, "CallExpression")) {
|
|
36052
|
+
if (isGlobalBooleanCall(node, scopes)) return false;
|
|
36053
|
+
const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
|
|
36054
|
+
return !invokedFunction || !analysisFunctions.has(invokedFunction);
|
|
36055
|
+
}
|
|
36056
|
+
return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
|
|
36057
|
+
};
|
|
36058
|
+
const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
|
|
36059
|
+
if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
|
|
36060
|
+
for (const writtenValue of writeInfo.values) {
|
|
36061
|
+
const stateValue = { value: writtenValue };
|
|
36062
|
+
let didFindReachableWork = false;
|
|
36063
|
+
visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
|
|
36064
|
+
if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
|
|
36065
|
+
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
|
|
36066
|
+
});
|
|
36067
|
+
if (didFindReachableWork) return true;
|
|
36068
|
+
}
|
|
36069
|
+
return false;
|
|
35914
36070
|
};
|
|
35915
36071
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
35916
36072
|
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
@@ -35999,18 +36155,29 @@ const noEffectChain = defineRule({
|
|
|
35999
36155
|
const useStateBindings = collectUseStateBindings(componentBody);
|
|
36000
36156
|
if (useStateBindings.length === 0) return;
|
|
36001
36157
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
36002
|
-
|
|
36158
|
+
const stateSymbolIds = /* @__PURE__ */ new Map();
|
|
36159
|
+
for (const binding of useStateBindings) {
|
|
36160
|
+
setterToStateName.set(binding.setterName, binding.valueName);
|
|
36161
|
+
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
36162
|
+
const stateIdentifier = binding.declarator.id.elements[0];
|
|
36163
|
+
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
36164
|
+
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
36165
|
+
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
36166
|
+
}
|
|
36167
|
+
}
|
|
36003
36168
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
36004
36169
|
const effectInfos = [];
|
|
36005
36170
|
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
36006
36171
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
36007
36172
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
36008
36173
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
36009
|
-
const
|
|
36174
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
36175
|
+
const writtenStateNames = new Set(stateWrites.keys());
|
|
36010
36176
|
effectInfos.push({
|
|
36011
36177
|
node: effectCall,
|
|
36012
36178
|
depNames: collectDepIdentifierNames(effectCall),
|
|
36013
|
-
|
|
36179
|
+
stateWrites,
|
|
36180
|
+
analysisFunctions,
|
|
36014
36181
|
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
36015
36182
|
});
|
|
36016
36183
|
}
|
|
@@ -36018,13 +36185,15 @@ const noEffectChain = defineRule({
|
|
|
36018
36185
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
36019
36186
|
for (const writerEffect of effectInfos) {
|
|
36020
36187
|
if (writerEffect.isExternalSync) continue;
|
|
36021
|
-
if (writerEffect.
|
|
36188
|
+
if (writerEffect.stateWrites.size === 0) continue;
|
|
36022
36189
|
for (const readerEffect of effectInfos) {
|
|
36023
36190
|
if (readerEffect === writerEffect) continue;
|
|
36024
36191
|
if (readerEffect.isExternalSync) continue;
|
|
36025
36192
|
if (readerEffect.depNames.size === 0) continue;
|
|
36026
36193
|
let chainedStateName = null;
|
|
36027
|
-
for (const writtenName of writerEffect.
|
|
36194
|
+
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
36195
|
+
if (!readerEffect.depNames.has(writtenName)) continue;
|
|
36196
|
+
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
36028
36197
|
chainedStateName = writtenName;
|
|
36029
36198
|
break;
|
|
36030
36199
|
}
|
|
@@ -39691,6 +39860,18 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
|
39691
39860
|
return containsReactHookCall;
|
|
39692
39861
|
};
|
|
39693
39862
|
//#endregion
|
|
39863
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39864
|
+
const isNullExpression = (expression) => {
|
|
39865
|
+
const candidate = stripParenExpression(expression);
|
|
39866
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39867
|
+
};
|
|
39868
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
39869
|
+
if (!isFunctionLike$1(functionNode)) return false;
|
|
39870
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39871
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39872
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39873
|
+
};
|
|
39874
|
+
//#endregion
|
|
39694
39875
|
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39695
39876
|
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39696
39877
|
if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
|
|
@@ -39723,17 +39904,8 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
|
39723
39904
|
}, controlFlow);
|
|
39724
39905
|
};
|
|
39725
39906
|
//#endregion
|
|
39726
|
-
//#region src/plugin/utils/function-
|
|
39727
|
-
const
|
|
39728
|
-
const candidate = stripParenExpression(expression);
|
|
39729
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39730
|
-
};
|
|
39731
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
39732
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
39733
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39734
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39735
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39736
|
-
};
|
|
39907
|
+
//#region src/plugin/utils/function-has-react-component-evidence.ts
|
|
39908
|
+
const functionHasReactComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39737
39909
|
//#endregion
|
|
39738
39910
|
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39739
39911
|
const findFactoryRoot = (node) => {
|
|
@@ -39766,17 +39938,16 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
|
39766
39938
|
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39767
39939
|
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39768
39940
|
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
39769
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39770
39941
|
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39771
39942
|
const candidate = stripParenExpression(expression);
|
|
39772
|
-
if (isInlineFunctionExpression(candidate)) return
|
|
39943
|
+
if (isInlineFunctionExpression(candidate)) return functionHasReactComponentEvidence(candidate, scopes, controlFlow);
|
|
39773
39944
|
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39774
39945
|
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39775
39946
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
39776
39947
|
const symbol = scopes.symbolFor(candidate);
|
|
39777
39948
|
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39778
39949
|
visitedSymbolIds.add(symbol.id);
|
|
39779
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return
|
|
39950
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasReactComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39780
39951
|
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39781
39952
|
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39782
39953
|
}
|
|
@@ -39803,7 +39974,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
|
|
|
39803
39974
|
for (const candidateSymbol of candidateSymbols) {
|
|
39804
39975
|
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39805
39976
|
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39806
|
-
if (
|
|
39977
|
+
if (functionHasReactComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39807
39978
|
continue;
|
|
39808
39979
|
}
|
|
39809
39980
|
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
@@ -43589,6 +43760,53 @@ const noPropCallbackInEffect = defineRule({
|
|
|
43589
43760
|
});
|
|
43590
43761
|
//#endregion
|
|
43591
43762
|
//#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
|
|
43763
|
+
const functionBindingSymbols = (functionNode, scopes) => {
|
|
43764
|
+
let bindingIdentifier = null;
|
|
43765
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) bindingIdentifier = functionNode.id;
|
|
43766
|
+
else {
|
|
43767
|
+
let bindingExpression = findTransparentExpressionRoot(functionNode);
|
|
43768
|
+
let parent = bindingExpression.parent;
|
|
43769
|
+
while (isNodeOfType(parent, "CallExpression") && parent.arguments[0] === bindingExpression) {
|
|
43770
|
+
const callee = parent.callee;
|
|
43771
|
+
const wrapperName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
43772
|
+
if (!isReactApiCall(parent, REACT_HOC_NAMES, scopes, {
|
|
43773
|
+
allowGlobalReactNamespace: true,
|
|
43774
|
+
resolveNamedAliases: true
|
|
43775
|
+
}) && (!wrapperName || REACT_HOC_NAMES.has(wrapperName) || !COMPONENT_HOC_WRAPPER_NAMES.has(wrapperName))) break;
|
|
43776
|
+
bindingExpression = findTransparentExpressionRoot(parent);
|
|
43777
|
+
parent = bindingExpression.parent;
|
|
43778
|
+
}
|
|
43779
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === bindingExpression && isNodeOfType(parent.id, "Identifier")) bindingIdentifier = parent.id;
|
|
43780
|
+
}
|
|
43781
|
+
if (!bindingIdentifier) return [];
|
|
43782
|
+
let scope = scopes.scopeFor(functionNode);
|
|
43783
|
+
while (scope) {
|
|
43784
|
+
const symbols = scope.symbols.filter((symbol) => symbol.bindingIdentifier === bindingIdentifier);
|
|
43785
|
+
if (symbols.length > 0) return symbols;
|
|
43786
|
+
scope = scope.parent;
|
|
43787
|
+
}
|
|
43788
|
+
return [];
|
|
43789
|
+
};
|
|
43790
|
+
const symbolHasReactComponentUse = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
43791
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
43792
|
+
visitedSymbolIds.add(symbol.id);
|
|
43793
|
+
for (const reference of symbol.references) {
|
|
43794
|
+
const identifier = reference.identifier;
|
|
43795
|
+
if (hasSymbolWriteBefore(symbol, identifier, scopes)) continue;
|
|
43796
|
+
const parent = identifier.parent;
|
|
43797
|
+
if (isNodeOfType(parent, "JSXOpeningElement") && isNodeOfType(parent.name, "JSXIdentifier") && parent.name === identifier) return true;
|
|
43798
|
+
const expression = findTransparentExpressionRoot(identifier);
|
|
43799
|
+
const expressionParent = expression.parent;
|
|
43800
|
+
if (isNodeOfType(expressionParent, "CallExpression") && expressionParent.arguments[0] === expression && isReactApiCall(expressionParent, "createElement", scopes, { resolveNamedAliases: true })) return true;
|
|
43801
|
+
if (!isNodeOfType(expressionParent, "VariableDeclarator") || expressionParent.init !== expression || !isNodeOfType(expressionParent.id, "Identifier") || !isNodeOfType(expressionParent.parent, "VariableDeclaration") || expressionParent.parent.kind !== "const") continue;
|
|
43802
|
+
const aliasSymbol = scopes.symbolFor(expressionParent.id);
|
|
43803
|
+
if (aliasSymbol && symbolHasReactComponentUse(aliasSymbol, scopes, visitedSymbolIds)) return true;
|
|
43804
|
+
}
|
|
43805
|
+
return false;
|
|
43806
|
+
};
|
|
43807
|
+
const functionHasReactComponentUse = (functionNode, scopes) => {
|
|
43808
|
+
return functionBindingSymbols(functionNode, scopes).some((symbol) => symbolHasReactComponentUse(symbol, scopes));
|
|
43809
|
+
};
|
|
43592
43810
|
const isPreservedThroughConciseArrow = (callExpression, scopes) => {
|
|
43593
43811
|
let node = callExpression;
|
|
43594
43812
|
let parent = node.parent;
|
|
@@ -43635,7 +43853,10 @@ const noPropCallbackInRender = defineRule({
|
|
|
43635
43853
|
create: (context) => ({ CallExpression(node) {
|
|
43636
43854
|
if (!isResultDiscardedCall(node)) return;
|
|
43637
43855
|
if (isPreservedThroughConciseArrow(node, context.scopes)) return;
|
|
43638
|
-
|
|
43856
|
+
const renderPhaseOwner = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
43857
|
+
if (!renderPhaseOwner) return;
|
|
43858
|
+
const renderPhaseOwnerName = componentOrHookDisplayNameForFunction(renderPhaseOwner);
|
|
43859
|
+
if (!renderPhaseOwnerName || !isReactHookName(renderPhaseOwnerName) && !functionHasReactComponentEvidence(renderPhaseOwner, context.scopes, context.cfg) && !functionHasReactComponentUse(renderPhaseOwner, context.scopes)) return;
|
|
43639
43860
|
const analysis = getProgramAnalysis(node);
|
|
43640
43861
|
if (!analysis) return;
|
|
43641
43862
|
const callee = stripParenExpression(node.callee);
|