oxlint-plugin-react-doctor 0.7.9-dev.44d3ad8 → 0.7.9-dev.4f3848b
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 +336 -504
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -836,131 +836,22 @@ const getImportedName = (importSpecifier) => {
|
|
|
836
836
|
if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return imported.value;
|
|
837
837
|
};
|
|
838
838
|
//#endregion
|
|
839
|
-
//#region src/plugin/utils/get-
|
|
840
|
-
const
|
|
841
|
-
if (!isNodeOfType(node, "
|
|
842
|
-
|
|
843
|
-
if (node.
|
|
844
|
-
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
845
|
-
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
846
|
-
return null;
|
|
847
|
-
}
|
|
848
|
-
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
849
|
-
if (isNodeOfType(key, "Literal")) {
|
|
850
|
-
if (typeof key.value === "string") return key.value;
|
|
851
|
-
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
852
|
-
}
|
|
853
|
-
return null;
|
|
854
|
-
};
|
|
855
|
-
//#endregion
|
|
856
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
857
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
858
|
-
const property = memberExpression.property;
|
|
859
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
860
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
861
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
839
|
+
//#region src/plugin/utils/get-callee-name.ts
|
|
840
|
+
const getCalleeName$2 = (node) => {
|
|
841
|
+
if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
|
|
842
|
+
if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
|
|
843
|
+
if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
|
|
862
844
|
return null;
|
|
863
845
|
};
|
|
864
846
|
//#endregion
|
|
865
|
-
//#region src/plugin/utils/
|
|
866
|
-
const
|
|
867
|
-
"ParenthesizedExpression",
|
|
868
|
-
"TSAsExpression",
|
|
869
|
-
"TSSatisfiesExpression",
|
|
870
|
-
"TSTypeAssertion",
|
|
871
|
-
"TSNonNullExpression",
|
|
872
|
-
"TSInstantiationExpression",
|
|
873
|
-
"ChainExpression"
|
|
874
|
-
]);
|
|
875
|
-
const stripParenExpression = (node) => {
|
|
876
|
-
let current = node;
|
|
877
|
-
while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
|
|
878
|
-
return current;
|
|
879
|
-
};
|
|
880
|
-
//#endregion
|
|
881
|
-
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
882
|
-
const resolveConstIdentifierAlias = (identifier, scopes, allowPatternBinding = false) => {
|
|
883
|
-
if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
|
|
884
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
885
|
-
let symbol = scopes.symbolFor(identifier);
|
|
886
|
-
while (symbol?.kind === "const") {
|
|
887
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
|
|
888
|
-
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
|
|
889
|
-
visitedSymbolIds.add(symbol.id);
|
|
890
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
891
|
-
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
892
|
-
symbol = scopes.symbolFor(initializer);
|
|
893
|
-
}
|
|
894
|
-
return symbol;
|
|
895
|
-
};
|
|
896
|
-
//#endregion
|
|
897
|
-
//#region src/plugin/utils/is-react-api-call.ts
|
|
898
|
-
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
899
|
-
const isImportedFromReact = (symbol) => {
|
|
900
|
-
if (symbol.kind !== "import") return false;
|
|
901
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
902
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
903
|
-
};
|
|
904
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
|
|
905
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
906
|
-
const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
|
|
907
|
-
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
908
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
909
|
-
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
910
|
-
};
|
|
911
|
-
const isReactNamespaceImport = (identifier, scopes) => {
|
|
912
|
-
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
913
|
-
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
914
|
-
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
915
|
-
};
|
|
916
|
-
const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
|
|
917
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
918
|
-
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
919
|
-
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
920
|
-
};
|
|
921
|
-
const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
|
|
922
|
-
const symbol = scopes.symbolFor(identifier);
|
|
923
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
924
|
-
const pattern = symbol.declarationNode.id;
|
|
925
|
-
if (!isNodeOfType(pattern, "ObjectPattern")) return false;
|
|
926
|
-
for (const property of pattern.properties) {
|
|
927
|
-
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
928
|
-
const propertyName = getStaticPropertyKeyName(property);
|
|
929
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
|
|
930
|
-
}
|
|
931
|
-
return false;
|
|
932
|
-
};
|
|
933
|
-
const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
847
|
+
//#region src/plugin/utils/is-hook-call.ts
|
|
848
|
+
const isHookCall$2 = (node, hookName) => {
|
|
934
849
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const callee = stripParenExpression(rawCallee);
|
|
939
|
-
if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
|
|
940
|
-
if (isNodeOfType(callee, "Identifier")) {
|
|
941
|
-
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
942
|
-
if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
|
|
943
|
-
if (options.resolveConditionalAliases) {
|
|
944
|
-
const symbol = scopes.symbolFor(callee);
|
|
945
|
-
if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
|
|
946
|
-
visitedSymbolIds.add(symbol.id);
|
|
947
|
-
return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
951
|
-
}
|
|
952
|
-
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
953
|
-
return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
|
|
850
|
+
const calleeName = getCalleeName$2(node);
|
|
851
|
+
if (!calleeName) return false;
|
|
852
|
+
return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
|
|
954
853
|
};
|
|
955
854
|
//#endregion
|
|
956
|
-
//#region src/plugin/utils/is-react-hook-call.ts
|
|
957
|
-
const isReactHookCall = (node, hookNames, scopes) => isReactApiCall(node, hookNames, scopes, {
|
|
958
|
-
allowGlobalReactNamespace: true,
|
|
959
|
-
allowUnboundBareCalls: true,
|
|
960
|
-
resolveConditionalAliases: true,
|
|
961
|
-
resolveNamedAliases: true
|
|
962
|
-
});
|
|
963
|
-
//#endregion
|
|
964
855
|
//#region src/plugin/utils/is-ast-node.ts
|
|
965
856
|
const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
|
|
966
857
|
//#endregion
|
|
@@ -1030,12 +921,12 @@ const collectChildComponentNames = (element, into) => {
|
|
|
1030
921
|
into.add(name);
|
|
1031
922
|
});
|
|
1032
923
|
};
|
|
1033
|
-
const countEffectHookCalls = (body
|
|
924
|
+
const countEffectHookCalls = (body) => {
|
|
1034
925
|
if (!body) return 0;
|
|
1035
926
|
let count = 0;
|
|
1036
927
|
walkAst(body, (child) => {
|
|
1037
928
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
1038
|
-
if (
|
|
929
|
+
if (isHookCall$2(child, EFFECT_HOOK_NAMES$1)) count++;
|
|
1039
930
|
});
|
|
1040
931
|
return count;
|
|
1041
932
|
};
|
|
@@ -1061,11 +952,11 @@ const getComponentEffectIndex = (programRoot) => {
|
|
|
1061
952
|
componentEffectIndexCache.set(programRoot, index);
|
|
1062
953
|
return index;
|
|
1063
954
|
};
|
|
1064
|
-
const getSameFileComponentEffectCount = (programRoot, componentName
|
|
955
|
+
const getSameFileComponentEffectCount = (programRoot, componentName) => {
|
|
1065
956
|
const index = getComponentEffectIndex(programRoot);
|
|
1066
957
|
const cachedCount = index.effectCountByName.get(componentName);
|
|
1067
958
|
if (cachedCount !== void 0) return cachedCount;
|
|
1068
|
-
const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null
|
|
959
|
+
const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
|
|
1069
960
|
index.effectCountByName.set(componentName, count);
|
|
1070
961
|
return count;
|
|
1071
962
|
};
|
|
@@ -1125,7 +1016,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
|
|
|
1125
1016
|
let totalEffects = 0;
|
|
1126
1017
|
const effectfulChildren = [];
|
|
1127
1018
|
for (const componentName of childComponentNames) {
|
|
1128
|
-
const effectCount = getSameFileComponentEffectCount(programRoot, componentName
|
|
1019
|
+
const effectCount = getSameFileComponentEffectCount(programRoot, componentName);
|
|
1129
1020
|
if (effectCount === 0) continue;
|
|
1130
1021
|
totalEffects += effectCount;
|
|
1131
1022
|
effectfulChildren.push(`<${componentName}>`);
|
|
@@ -1316,14 +1207,6 @@ const findVariableInitializer = (referenceNode, bindingName) => {
|
|
|
1316
1207
|
return best;
|
|
1317
1208
|
};
|
|
1318
1209
|
//#endregion
|
|
1319
|
-
//#region src/plugin/utils/get-callee-name.ts
|
|
1320
|
-
const getCalleeName$1 = (node) => {
|
|
1321
|
-
if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
|
|
1322
|
-
if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
|
|
1323
|
-
if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
|
|
1324
|
-
return null;
|
|
1325
|
-
};
|
|
1326
|
-
//#endregion
|
|
1327
1210
|
//#region src/plugin/utils/is-function-like.ts
|
|
1328
1211
|
/**
|
|
1329
1212
|
* Type-guard for the three "function-like" ESTree node shapes:
|
|
@@ -1337,6 +1220,38 @@ const getCalleeName$1 = (node) => {
|
|
|
1337
1220
|
*/
|
|
1338
1221
|
const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
|
|
1339
1222
|
//#endregion
|
|
1223
|
+
//#region src/plugin/utils/strip-paren-expression.ts
|
|
1224
|
+
const TRANSPARENT_EXPRESSION_WRAPPER_TYPES = new Set([
|
|
1225
|
+
"ParenthesizedExpression",
|
|
1226
|
+
"TSAsExpression",
|
|
1227
|
+
"TSSatisfiesExpression",
|
|
1228
|
+
"TSTypeAssertion",
|
|
1229
|
+
"TSNonNullExpression",
|
|
1230
|
+
"TSInstantiationExpression",
|
|
1231
|
+
"ChainExpression"
|
|
1232
|
+
]);
|
|
1233
|
+
const stripParenExpression = (node) => {
|
|
1234
|
+
let current = node;
|
|
1235
|
+
while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
|
|
1236
|
+
return current;
|
|
1237
|
+
};
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
1240
|
+
const resolveConstIdentifierAlias = (identifier, scopes, allowPatternBinding = false) => {
|
|
1241
|
+
if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
|
|
1242
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
1243
|
+
let symbol = scopes.symbolFor(identifier);
|
|
1244
|
+
while (symbol?.kind === "const") {
|
|
1245
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
|
|
1246
|
+
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
|
|
1247
|
+
visitedSymbolIds.add(symbol.id);
|
|
1248
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
1249
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
1250
|
+
symbol = scopes.symbolFor(initializer);
|
|
1251
|
+
}
|
|
1252
|
+
return symbol;
|
|
1253
|
+
};
|
|
1254
|
+
//#endregion
|
|
1340
1255
|
//#region src/plugin/utils/resolve-exact-local-function.ts
|
|
1341
1256
|
const resolveExactLocalFunction = (expression, scopes) => {
|
|
1342
1257
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -1382,17 +1297,9 @@ const getRootIdentifier$1 = (node, options) => {
|
|
|
1382
1297
|
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1383
1298
|
const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
|
|
1384
1299
|
//#endregion
|
|
1385
|
-
//#region src/plugin/utils/is-hook-call.ts
|
|
1386
|
-
const isHookCall$2 = (node, hookName) => {
|
|
1387
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
1388
|
-
const calleeName = getCalleeName$1(node);
|
|
1389
|
-
if (!calleeName) return false;
|
|
1390
|
-
return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
|
|
1391
|
-
};
|
|
1392
|
-
//#endregion
|
|
1393
1300
|
//#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1301
|
+
const STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
1302
|
+
"useCallback",
|
|
1396
1303
|
"useEffectEvent",
|
|
1397
1304
|
"useEvent",
|
|
1398
1305
|
"useEventCallback",
|
|
@@ -1401,21 +1308,21 @@ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
|
1401
1308
|
]);
|
|
1402
1309
|
const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
|
|
1403
1310
|
const isThrottledHandlerHookCall = (callNode) => {
|
|
1404
|
-
const calleeName = getCalleeName$
|
|
1311
|
+
const calleeName = getCalleeName$2(callNode);
|
|
1405
1312
|
return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
|
|
1406
1313
|
};
|
|
1407
|
-
const isEmptyDepsUseMemoCall = (callNode
|
|
1408
|
-
if (!
|
|
1314
|
+
const isEmptyDepsUseMemoCall = (callNode) => {
|
|
1315
|
+
if (!isHookCall$2(callNode, "useMemo")) return false;
|
|
1409
1316
|
const memoDepsNode = callNode.arguments?.[1];
|
|
1410
1317
|
return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
|
|
1411
1318
|
};
|
|
1412
|
-
const isStableHandlerInitializer = (initializer
|
|
1413
|
-
if (isNodeOfType(initializer, "CallExpression")) return
|
|
1319
|
+
const isStableHandlerInitializer = (initializer) => {
|
|
1320
|
+
if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
|
|
1414
1321
|
return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
|
|
1415
1322
|
};
|
|
1416
|
-
const isStableRefReceiverDep = (referenceNode, receiverDepName
|
|
1323
|
+
const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
|
|
1417
1324
|
const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
|
|
1418
|
-
return Boolean(receiverBinding?.initializer &&
|
|
1325
|
+
return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
|
|
1419
1326
|
};
|
|
1420
1327
|
const advancedEventHandlerRefs = defineRule({
|
|
1421
1328
|
id: "advanced-event-handler-refs",
|
|
@@ -1425,7 +1332,7 @@ const advancedEventHandlerRefs = defineRule({
|
|
|
1425
1332
|
category: "Performance",
|
|
1426
1333
|
recommendation: "Store the handler in a ref and have the listener read `handlerRef.current()`. The subscription stays put while the latest handler still runs.",
|
|
1427
1334
|
create: (context) => ({ CallExpression(node) {
|
|
1428
|
-
if (!
|
|
1335
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
1429
1336
|
if ((node.arguments?.length ?? 0) < 2) return;
|
|
1430
1337
|
const callback = getEffectCallback(node);
|
|
1431
1338
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -1449,8 +1356,8 @@ const advancedEventHandlerRefs = defineRule({
|
|
|
1449
1356
|
});
|
|
1450
1357
|
if (!registeredHandlerName) return;
|
|
1451
1358
|
const handlerBinding = findVariableInitializer(node, registeredHandlerName);
|
|
1452
|
-
if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer
|
|
1453
|
-
if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName
|
|
1359
|
+
if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
|
|
1360
|
+
if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
|
|
1454
1361
|
context.report({
|
|
1455
1362
|
node,
|
|
1456
1363
|
message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
|
|
@@ -1937,6 +1844,15 @@ const flattenJsxName$1 = (node) => {
|
|
|
1937
1844
|
return null;
|
|
1938
1845
|
};
|
|
1939
1846
|
//#endregion
|
|
1847
|
+
//#region src/plugin/utils/get-static-property-name.ts
|
|
1848
|
+
const getStaticPropertyName = (memberExpression) => {
|
|
1849
|
+
const property = memberExpression.property;
|
|
1850
|
+
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
1851
|
+
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
1852
|
+
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
1853
|
+
return null;
|
|
1854
|
+
};
|
|
1855
|
+
//#endregion
|
|
1940
1856
|
//#region src/plugin/utils/is-generated-image-renderer-call.ts
|
|
1941
1857
|
const GENERATED_IMAGE_RENDERER_MODULES = [
|
|
1942
1858
|
"next/og",
|
|
@@ -4944,6 +4860,23 @@ const containsDirectAwait = (node) => {
|
|
|
4944
4860
|
return foundAwait;
|
|
4945
4861
|
};
|
|
4946
4862
|
//#endregion
|
|
4863
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
4864
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
4865
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
4866
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
4867
|
+
if (node.computed) {
|
|
4868
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
4869
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
4870
|
+
return null;
|
|
4871
|
+
}
|
|
4872
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
4873
|
+
if (isNodeOfType(key, "Literal")) {
|
|
4874
|
+
if (typeof key.value === "string") return key.value;
|
|
4875
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
4876
|
+
}
|
|
4877
|
+
return null;
|
|
4878
|
+
};
|
|
4879
|
+
//#endregion
|
|
4947
4880
|
//#region src/plugin/utils/get-destructured-binding-property-name.ts
|
|
4948
4881
|
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
4949
4882
|
let bindingNode = bindingIdentifier;
|
|
@@ -9008,6 +8941,65 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
|
9008
8941
|
}
|
|
9009
8942
|
};
|
|
9010
8943
|
//#endregion
|
|
8944
|
+
//#region src/plugin/utils/is-react-api-call.ts
|
|
8945
|
+
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
8946
|
+
const isImportedFromReact = (symbol) => {
|
|
8947
|
+
if (symbol.kind !== "import") return false;
|
|
8948
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
8949
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
8950
|
+
};
|
|
8951
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
|
|
8952
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
8953
|
+
const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
|
|
8954
|
+
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8955
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
8956
|
+
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
8957
|
+
};
|
|
8958
|
+
const isReactNamespaceImport = (identifier, scopes) => {
|
|
8959
|
+
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
8960
|
+
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8961
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8962
|
+
};
|
|
8963
|
+
const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
|
|
8964
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8965
|
+
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8966
|
+
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
8967
|
+
};
|
|
8968
|
+
const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
|
|
8969
|
+
const symbol = scopes.symbolFor(identifier);
|
|
8970
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
8971
|
+
const pattern = symbol.declarationNode.id;
|
|
8972
|
+
if (!isNodeOfType(pattern, "ObjectPattern")) return false;
|
|
8973
|
+
for (const property of pattern.properties) {
|
|
8974
|
+
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8975
|
+
const propertyName = getStaticPropertyKeyName(property);
|
|
8976
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
|
|
8977
|
+
}
|
|
8978
|
+
return false;
|
|
8979
|
+
};
|
|
8980
|
+
const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
8981
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
8982
|
+
return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
|
|
8983
|
+
};
|
|
8984
|
+
const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
|
|
8985
|
+
const callee = stripParenExpression(rawCallee);
|
|
8986
|
+
if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
|
|
8987
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
8988
|
+
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
8989
|
+
if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
|
|
8990
|
+
if (options.resolveConditionalAliases) {
|
|
8991
|
+
const symbol = scopes.symbolFor(callee);
|
|
8992
|
+
if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
|
|
8993
|
+
visitedSymbolIds.add(symbol.id);
|
|
8994
|
+
return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
|
|
8995
|
+
}
|
|
8996
|
+
}
|
|
8997
|
+
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8998
|
+
}
|
|
8999
|
+
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
9000
|
+
return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
|
|
9001
|
+
};
|
|
9002
|
+
//#endregion
|
|
9011
9003
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
9012
9004
|
const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
|
|
9013
9005
|
"AbortSignal",
|
|
@@ -13891,7 +13883,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
|
|
|
13891
13883
|
};
|
|
13892
13884
|
//#endregion
|
|
13893
13885
|
//#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
|
|
13894
|
-
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13895
13886
|
const getSubscribeLikeMethodName = (node) => {
|
|
13896
13887
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
13897
13888
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
@@ -13902,11 +13893,6 @@ const isSubscribeLikeCallExpression = (node) => {
|
|
|
13902
13893
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13903
13894
|
return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
|
|
13904
13895
|
};
|
|
13905
|
-
const getSubscribeOrObserveMethodName = (node) => {
|
|
13906
|
-
const methodName = getSubscribeLikeMethodName(node);
|
|
13907
|
-
return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
|
|
13908
|
-
};
|
|
13909
|
-
const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
|
|
13910
13896
|
const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
13911
13897
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13912
13898
|
if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
|
|
@@ -13959,6 +13945,7 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
13959
13945
|
};
|
|
13960
13946
|
//#endregion
|
|
13961
13947
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
13948
|
+
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13962
13949
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
13963
13950
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
13964
13951
|
const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -13968,6 +13955,10 @@ const RESOURCE_NOUN_BY_KIND = {
|
|
|
13968
13955
|
socket: "connection"
|
|
13969
13956
|
};
|
|
13970
13957
|
const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
|
|
13958
|
+
const isSubscribeOrObserveCall = (node) => {
|
|
13959
|
+
if (isSubscribeLikeCallExpression(node)) return true;
|
|
13960
|
+
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
|
|
13961
|
+
};
|
|
13971
13962
|
const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13972
13963
|
if (!expression) return null;
|
|
13973
13964
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14069,13 +14060,12 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
14069
14060
|
});
|
|
14070
14061
|
return;
|
|
14071
14062
|
}
|
|
14072
|
-
|
|
14073
|
-
if (subscribeOrObserveMethodName !== null) {
|
|
14063
|
+
if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) {
|
|
14074
14064
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
14075
14065
|
usages.push({
|
|
14076
14066
|
kind: "subscribe",
|
|
14077
14067
|
node: child,
|
|
14078
|
-
resourceName:
|
|
14068
|
+
resourceName: child.callee.property.name,
|
|
14079
14069
|
handleKey: findAssignedResourceKey(child, context),
|
|
14080
14070
|
...registrationDetails
|
|
14081
14071
|
});
|
|
@@ -14606,7 +14596,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
14606
14596
|
walkAst(componentFunction.body, (child) => {
|
|
14607
14597
|
if (didFindUnmountCleanup) return false;
|
|
14608
14598
|
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
14609
|
-
if (!
|
|
14599
|
+
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
14610
14600
|
const dependencyList = child.arguments?.[1];
|
|
14611
14601
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
14612
14602
|
const cleanupCallback = getEffectCallback(child);
|
|
@@ -15037,7 +15027,7 @@ const getReleaseVerbName = (node) => {
|
|
|
15037
15027
|
const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
|
|
15038
15028
|
const releaseFunction = findEnclosingFunction$1(releaseReceiver);
|
|
15039
15029
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15040
|
-
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction
|
|
15030
|
+
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
|
|
15041
15031
|
const controllerKey = getListenerAbortControllerKey(usage, context);
|
|
15042
15032
|
const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
|
|
15043
15033
|
if (controllerKey === null || refCurrentKey === null) return false;
|
|
@@ -15184,7 +15174,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15184
15174
|
return true;
|
|
15185
15175
|
};
|
|
15186
15176
|
const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
|
|
15187
|
-
const isReturnedEffectCleanupFunction = (functionNode
|
|
15177
|
+
const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
15188
15178
|
let currentNode = functionNode;
|
|
15189
15179
|
let parentNode = currentNode.parent;
|
|
15190
15180
|
while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
|
|
@@ -15193,10 +15183,10 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
|
|
|
15193
15183
|
}
|
|
15194
15184
|
const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
|
|
15195
15185
|
const effectCall = effectCallback?.parent;
|
|
15196
|
-
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") &&
|
|
15186
|
+
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
15197
15187
|
};
|
|
15198
15188
|
const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
15199
|
-
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode
|
|
15189
|
+
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
|
|
15200
15190
|
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15201
15191
|
if (!bindingIdentifier) return false;
|
|
15202
15192
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
@@ -15693,7 +15683,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
15693
15683
|
return false;
|
|
15694
15684
|
}
|
|
15695
15685
|
}
|
|
15696
|
-
if (
|
|
15686
|
+
if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
|
|
15697
15687
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15698
15688
|
const subscriptionUsage = {
|
|
15699
15689
|
kind: "subscribe",
|
|
@@ -15901,7 +15891,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
15901
15891
|
if (!isFunctionLike$1(functionNode)) return false;
|
|
15902
15892
|
const functionRoot = findTransparentExpressionRoot(functionNode);
|
|
15903
15893
|
const callbackCall = functionRoot.parent;
|
|
15904
|
-
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot &&
|
|
15894
|
+
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
|
|
15905
15895
|
const parentNode = functionNode.parent;
|
|
15906
15896
|
if (isDirectJsxEventHandlerValue(functionNode)) return true;
|
|
15907
15897
|
if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
|
|
@@ -15937,12 +15927,12 @@ const effectNeedsCleanup = defineRule({
|
|
|
15937
15927
|
};
|
|
15938
15928
|
return {
|
|
15939
15929
|
CallExpression(node) {
|
|
15940
|
-
if (
|
|
15930
|
+
if (isHookCall$2(node, "useCallback")) {
|
|
15941
15931
|
const retainedCallback = getEffectCallback(node);
|
|
15942
15932
|
if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
|
|
15943
15933
|
return;
|
|
15944
15934
|
}
|
|
15945
|
-
if (!
|
|
15935
|
+
if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
15946
15936
|
const callback = getEffectCallback(node);
|
|
15947
15937
|
if (!callback) return;
|
|
15948
15938
|
const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
|
|
@@ -15950,7 +15940,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
15950
15940
|
const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
|
|
15951
15941
|
if (!firstUsage) return;
|
|
15952
15942
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
|
|
15953
|
-
const hookName = getCalleeName$
|
|
15943
|
+
const hookName = getCalleeName$2(node) ?? "effect";
|
|
15954
15944
|
context.report({
|
|
15955
15945
|
node,
|
|
15956
15946
|
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
|
|
@@ -17293,38 +17283,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
|
|
|
17293
17283
|
const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
|
|
17294
17284
|
return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
|
|
17295
17285
|
};
|
|
17296
|
-
const
|
|
17297
|
-
let currentNode = findTransparentExpressionRoot(node);
|
|
17298
|
-
while (currentNode.parent) {
|
|
17299
|
-
const parentNode = currentNode.parent;
|
|
17300
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
17301
|
-
if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
|
|
17302
|
-
if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
|
|
17303
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
17304
|
-
if (isNodeOfType(parentNode, "ArrayPattern") && parentNode.elements.some((element) => element === currentNode) || isNodeOfType(parentNode, "ObjectPattern") && parentNode.properties.some((property) => property === currentNode) || isNodeOfType(parentNode, "RestElement") && parentNode.argument === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.left === currentNode || isNodeOfType(parentNode, "Property") && parentNode.value === currentNode && isNodeOfType(parentNode.parent, "ObjectPattern")) {
|
|
17305
|
-
currentNode = parentNode;
|
|
17306
|
-
continue;
|
|
17307
|
-
}
|
|
17308
|
-
return false;
|
|
17309
|
-
}
|
|
17310
|
-
return false;
|
|
17311
|
-
};
|
|
17312
|
-
const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
|
|
17313
|
-
if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
17314
|
-
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
17315
|
-
if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
|
|
17316
|
-
const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
|
|
17317
|
-
if (!refSymbol) return false;
|
|
17318
|
-
return refSymbol.references.every((reference) => {
|
|
17319
|
-
const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
|
|
17320
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
|
|
17321
|
-
const referenceRoot = findTransparentExpressionRoot(memberExpression);
|
|
17322
|
-
const parentNode = referenceRoot.parent;
|
|
17323
|
-
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
|
|
17324
|
-
return !isAssignmentTarget(referenceRoot);
|
|
17325
|
-
});
|
|
17326
|
-
};
|
|
17327
|
-
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableRefLazyInitialization(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
17286
|
+
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
17328
17287
|
//#endregion
|
|
17329
17288
|
//#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
|
|
17330
17289
|
const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
|
|
@@ -18507,7 +18466,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
18507
18466
|
if (!isUsed) continue;
|
|
18508
18467
|
const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
|
|
18509
18468
|
const rootSymbol = getRootSymbol(reportNode, context.scopes);
|
|
18510
|
-
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) ||
|
|
18469
|
+
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
|
|
18511
18470
|
context.report({
|
|
18512
18471
|
node: reportNode,
|
|
18513
18472
|
message: buildUnstableDepMessage(hookName, declaredKey)
|
|
@@ -18823,7 +18782,7 @@ const flattenCalleeName = (callee) => {
|
|
|
18823
18782
|
const PRAGMA = "React";
|
|
18824
18783
|
const isReactFunctionCall = (node, expectedCall) => {
|
|
18825
18784
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
18826
|
-
if (getCalleeName$
|
|
18785
|
+
if (getCalleeName$2(node) !== expectedCall) return false;
|
|
18827
18786
|
if (isNodeOfType(node.callee, "MemberExpression")) {
|
|
18828
18787
|
const receiver = stripParenExpression(node.callee.object);
|
|
18829
18788
|
return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
|
|
@@ -19132,8 +19091,8 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19132
19091
|
severity: "warn",
|
|
19133
19092
|
recommendation: "Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",
|
|
19134
19093
|
create: (context) => ({ CallExpression(node) {
|
|
19135
|
-
if (!
|
|
19136
|
-
const depsIndex =
|
|
19094
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
|
|
19095
|
+
const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
|
|
19137
19096
|
const depsArgument = node.arguments[depsIndex];
|
|
19138
19097
|
if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
|
|
19139
19098
|
for (const element of depsArgument.elements) {
|
|
@@ -20304,7 +20263,7 @@ const isImportedSelectAtom = (callExpression) => {
|
|
|
20304
20263
|
const isDeferredCallbackPosition$1 = (functionNode) => {
|
|
20305
20264
|
const parent = functionNode.parent;
|
|
20306
20265
|
if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
|
|
20307
|
-
const hookName = getCalleeName$
|
|
20266
|
+
const hookName = getCalleeName$2(parent);
|
|
20308
20267
|
if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
|
|
20309
20268
|
if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
|
|
20310
20269
|
}
|
|
@@ -30375,7 +30334,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
|
|
|
30375
30334
|
"on",
|
|
30376
30335
|
"once"
|
|
30377
30336
|
]);
|
|
30378
|
-
const getCalleeName = (callee) => {
|
|
30337
|
+
const getCalleeName$1 = (callee) => {
|
|
30379
30338
|
if (!callee) return null;
|
|
30380
30339
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
30381
30340
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
@@ -30387,11 +30346,11 @@ const isDeferredCallbackPosition = (expression) => {
|
|
|
30387
30346
|
const parent = parentOf(expression);
|
|
30388
30347
|
if (!parent) return false;
|
|
30389
30348
|
if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30390
|
-
const name = getCalleeName(parent.callee);
|
|
30349
|
+
const name = getCalleeName$1(parent.callee);
|
|
30391
30350
|
if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
|
|
30392
30351
|
}
|
|
30393
30352
|
if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30394
|
-
const name = getCalleeName(parent.callee);
|
|
30353
|
+
const name = getCalleeName$1(parent.callee);
|
|
30395
30354
|
if (name && (name.endsWith("Observer") || name === "Promise")) return true;
|
|
30396
30355
|
}
|
|
30397
30356
|
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
|
|
@@ -30811,6 +30770,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
|
|
|
30811
30770
|
if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30812
30771
|
return false;
|
|
30813
30772
|
};
|
|
30773
|
+
const isUseEffect = (node) => {
|
|
30774
|
+
if (!node || !isNodeOfType(node, "CallExpression")) return false;
|
|
30775
|
+
const callee = node.callee;
|
|
30776
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
|
|
30777
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
30778
|
+
const receiver = stripParenExpression(callee.object);
|
|
30779
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
|
|
30780
|
+
};
|
|
30814
30781
|
const getEffectFn = (analysis, node) => {
|
|
30815
30782
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
30816
30783
|
const fn = node.arguments?.[0];
|
|
@@ -32272,7 +32239,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32272
32239
|
sourceReferences,
|
|
32273
32240
|
isDeferred: frame.isDeferred,
|
|
32274
32241
|
isRenderKnownCopy,
|
|
32275
|
-
isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
|
|
32276
32242
|
matchesStateInitializer: doesMatchStateInitializer,
|
|
32277
32243
|
resetsSourceState: false
|
|
32278
32244
|
});
|
|
@@ -32288,71 +32254,27 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32288
32254
|
});
|
|
32289
32255
|
};
|
|
32290
32256
|
//#endregion
|
|
32291
|
-
//#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
|
|
32292
|
-
const DEFERRED_MEMBER_NAMES = new Set([
|
|
32293
|
-
"catch",
|
|
32294
|
-
"finally",
|
|
32295
|
-
"then"
|
|
32296
|
-
]);
|
|
32297
|
-
const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
|
|
32298
|
-
const effectFunction = getEffectFn(analysis, effectNode);
|
|
32299
|
-
if (!effectFunction) return false;
|
|
32300
|
-
if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
|
|
32301
|
-
const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
|
|
32302
|
-
let didFindDeferredOrExternalWork = false;
|
|
32303
|
-
walkAst(effectFunction, (child) => {
|
|
32304
|
-
if (didFindDeferredOrExternalWork) return false;
|
|
32305
|
-
if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
|
|
32306
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
32307
|
-
const assignmentTarget = child.left;
|
|
32308
|
-
if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
|
|
32309
|
-
didFindDeferredOrExternalWork = true;
|
|
32310
|
-
return false;
|
|
32311
|
-
}
|
|
32312
|
-
}
|
|
32313
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32314
|
-
if (isSubscribeOrObserveCallExpression(child)) {
|
|
32315
|
-
didFindDeferredOrExternalWork = true;
|
|
32316
|
-
return false;
|
|
32317
|
-
}
|
|
32318
|
-
const localFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
32319
|
-
if (isFunctionLike$1(localFunction) && localFunction.async) {
|
|
32320
|
-
didFindDeferredOrExternalWork = true;
|
|
32321
|
-
return false;
|
|
32322
|
-
}
|
|
32323
|
-
const callee = child.callee;
|
|
32324
|
-
if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
|
|
32325
|
-
didFindDeferredOrExternalWork = true;
|
|
32326
|
-
return false;
|
|
32327
|
-
}
|
|
32328
|
-
const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
32329
|
-
if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
|
|
32330
|
-
didFindDeferredOrExternalWork = true;
|
|
32331
|
-
return false;
|
|
32332
|
-
}
|
|
32333
|
-
});
|
|
32334
|
-
return didFindDeferredOrExternalWork;
|
|
32335
|
-
};
|
|
32336
|
-
//#endregion
|
|
32337
32257
|
//#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
|
|
32338
32258
|
const noAdjustStateOnPropChange = defineRule({
|
|
32339
32259
|
id: "no-adjust-state-on-prop-change",
|
|
32340
|
-
title: "State
|
|
32260
|
+
title: "State synced to a prop inside an effect",
|
|
32341
32261
|
severity: "warn",
|
|
32342
32262
|
tags: ["test-noise"],
|
|
32343
|
-
recommendation: "
|
|
32263
|
+
recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
|
|
32344
32264
|
create: (context) => ({ CallExpression(node) {
|
|
32345
|
-
if (!
|
|
32265
|
+
if (!isReactApiCall(node, "useEffect", context.scopes, {
|
|
32266
|
+
allowGlobalReactNamespace: true,
|
|
32267
|
+
allowUnboundBareCalls: true,
|
|
32268
|
+
resolveConditionalAliases: true,
|
|
32269
|
+
resolveNamedAliases: true
|
|
32270
|
+
})) return;
|
|
32346
32271
|
const analysis = getProgramAnalysis(node);
|
|
32347
32272
|
if (!analysis) return;
|
|
32348
32273
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32349
32274
|
if (!dependencyReferences) return;
|
|
32350
32275
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
32351
|
-
const
|
|
32352
|
-
|
|
32353
|
-
for (const fact of facts) {
|
|
32354
|
-
if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
|
|
32355
|
-
if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
|
|
32276
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
32277
|
+
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32356
32278
|
context.report({
|
|
32357
32279
|
node: fact.callExpression,
|
|
32358
32280
|
message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
|
|
@@ -34365,7 +34287,7 @@ const declarationBodyContainsHookCall = (symbol) => {
|
|
|
34365
34287
|
walkAst(componentFunction, (descendant) => {
|
|
34366
34288
|
if (didFindHookCall) return false;
|
|
34367
34289
|
if (!isNodeOfType(descendant, "CallExpression")) return;
|
|
34368
|
-
const calleeName = getCalleeName$
|
|
34290
|
+
const calleeName = getCalleeName$2(descendant);
|
|
34369
34291
|
if (calleeName && isReactHookName(calleeName)) {
|
|
34370
34292
|
didFindHookCall = true;
|
|
34371
34293
|
return false;
|
|
@@ -34390,7 +34312,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
|
|
|
34390
34312
|
if (isNodeOfType(parent, "ArrowFunctionExpression")) {
|
|
34391
34313
|
if (parent.body !== current) return false;
|
|
34392
34314
|
const grandparent = parent.parent;
|
|
34393
|
-
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$
|
|
34315
|
+
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
|
|
34394
34316
|
}
|
|
34395
34317
|
if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
|
|
34396
34318
|
current = parent;
|
|
@@ -36922,7 +36844,12 @@ const noDerivedState = defineRule({
|
|
|
36922
36844
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36923
36845
|
} }).visitors,
|
|
36924
36846
|
CallExpression(node) {
|
|
36925
|
-
if (!
|
|
36847
|
+
if (!isReactApiCall(node, "useEffect", context.scopes, {
|
|
36848
|
+
allowGlobalReactNamespace: true,
|
|
36849
|
+
allowUnboundBareCalls: true,
|
|
36850
|
+
resolveConditionalAliases: true,
|
|
36851
|
+
resolveNamedAliases: true
|
|
36852
|
+
})) return;
|
|
36926
36853
|
const analysis = getProgramAnalysis(node);
|
|
36927
36854
|
if (!analysis) return;
|
|
36928
36855
|
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
@@ -36942,7 +36869,12 @@ const noDerivedStateEffect = defineRule({
|
|
|
36942
36869
|
tags: ["test-noise"],
|
|
36943
36870
|
recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
|
|
36944
36871
|
create: (context) => ({ CallExpression(node) {
|
|
36945
|
-
if (!
|
|
36872
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
36873
|
+
allowGlobalReactNamespace: true,
|
|
36874
|
+
allowUnboundBareCalls: true,
|
|
36875
|
+
resolveConditionalAliases: true,
|
|
36876
|
+
resolveNamedAliases: true
|
|
36877
|
+
})) return;
|
|
36946
36878
|
const analysis = getProgramAnalysis(node);
|
|
36947
36879
|
if (!analysis) return;
|
|
36948
36880
|
if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
@@ -37065,7 +36997,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
37065
36997
|
if (isFunctionLike$1(cursor)) {
|
|
37066
36998
|
const parent = cursor.parent ?? null;
|
|
37067
36999
|
if (parent && isNodeOfType(parent, "CallExpression")) {
|
|
37068
|
-
const calleeName = getCalleeName$
|
|
37000
|
+
const calleeName = getCalleeName$2(parent);
|
|
37069
37001
|
if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
|
|
37070
37002
|
}
|
|
37071
37003
|
}
|
|
@@ -37136,7 +37068,7 @@ const isNonHandlerHookCallback = (functionNode) => {
|
|
|
37136
37068
|
const parent = functionNode.parent ?? null;
|
|
37137
37069
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
37138
37070
|
if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
|
|
37139
|
-
const calleeName = getCalleeName$
|
|
37071
|
+
const calleeName = getCalleeName$2(parent);
|
|
37140
37072
|
return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
|
|
37141
37073
|
};
|
|
37142
37074
|
const isHandlerShapedReseed = (setterCall, componentFunction) => {
|
|
@@ -37218,7 +37150,7 @@ const noDerivedUseState = defineRule({
|
|
|
37218
37150
|
return {
|
|
37219
37151
|
...propStackTracker.visitors,
|
|
37220
37152
|
CallExpression(node) {
|
|
37221
|
-
if (!
|
|
37153
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
37222
37154
|
const seed = unwrapInitializerSeed(node.arguments[0]);
|
|
37223
37155
|
const reportStalePropCopy = (propName) => {
|
|
37224
37156
|
if (isIntentionalSnapshotState(node)) return;
|
|
@@ -37907,7 +37839,12 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37907
37839
|
const setterElement = elements[1];
|
|
37908
37840
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37909
37841
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37910
|
-
if (!
|
|
37842
|
+
if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
|
|
37843
|
+
allowGlobalReactNamespace: true,
|
|
37844
|
+
allowUnboundBareCalls: true,
|
|
37845
|
+
resolveConditionalAliases: true,
|
|
37846
|
+
resolveNamedAliases: true
|
|
37847
|
+
}) : isHookCall$2(declarator.init, "useState"))) continue;
|
|
37911
37848
|
bindings.push({
|
|
37912
37849
|
valueName: valueElement.name,
|
|
37913
37850
|
setterName: setterElement.name,
|
|
@@ -38074,7 +38011,7 @@ const noDirectStateMutation = defineRule({
|
|
|
38074
38011
|
create: (context) => {
|
|
38075
38012
|
const checkComponent = (componentBody) => {
|
|
38076
38013
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
38077
|
-
const bindings = collectUseStateBindings(componentBody
|
|
38014
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
38078
38015
|
if (bindings.length === 0) return;
|
|
38079
38016
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
38080
38017
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
@@ -38641,7 +38578,12 @@ const findTopLevelEffectCalls = (componentBody, scopes) => {
|
|
|
38641
38578
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38642
38579
|
const expression = unwrapDiscardedExpression(statement);
|
|
38643
38580
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38644
|
-
if (!
|
|
38581
|
+
if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
|
|
38582
|
+
allowGlobalReactNamespace: true,
|
|
38583
|
+
allowUnboundBareCalls: true,
|
|
38584
|
+
resolveConditionalAliases: true,
|
|
38585
|
+
resolveNamedAliases: true
|
|
38586
|
+
})) continue;
|
|
38645
38587
|
effectCalls.push(expression);
|
|
38646
38588
|
}
|
|
38647
38589
|
return effectCalls;
|
|
@@ -38885,7 +38827,7 @@ const collectStorageHookSetterNames = (componentBody) => {
|
|
|
38885
38827
|
for (const declarator of statement.declarations ?? []) {
|
|
38886
38828
|
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
38887
38829
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
38888
|
-
const calleeName = getCalleeName$
|
|
38830
|
+
const calleeName = getCalleeName$2(declarator.init);
|
|
38889
38831
|
if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
|
|
38890
38832
|
for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
|
|
38891
38833
|
}
|
|
@@ -39373,7 +39315,7 @@ const noEffectEventHandler = defineRule({
|
|
|
39373
39315
|
return {
|
|
39374
39316
|
...propStackTracker.visitors,
|
|
39375
39317
|
CallExpression(node) {
|
|
39376
|
-
if (!
|
|
39318
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
39377
39319
|
const callback = getEffectCallback(node);
|
|
39378
39320
|
if (!callback) return;
|
|
39379
39321
|
const analysis = getProgramAnalysis(node);
|
|
@@ -39493,14 +39435,14 @@ const noEffectEventInDeps = defineRule({
|
|
|
39493
39435
|
if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
|
|
39494
39436
|
const initializer = declaratorNode.init;
|
|
39495
39437
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
39496
|
-
if (!
|
|
39438
|
+
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
39497
39439
|
if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
|
|
39498
39440
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
39499
39441
|
} });
|
|
39500
39442
|
return {
|
|
39501
39443
|
...componentBindings.visitors,
|
|
39502
39444
|
CallExpression(node) {
|
|
39503
|
-
if (!
|
|
39445
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
39504
39446
|
if (!componentBindings.isInsideComponent()) return;
|
|
39505
39447
|
const depsNode = node.arguments[1];
|
|
39506
39448
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -39557,7 +39499,7 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
39557
39499
|
node: finding.reportNode,
|
|
39558
39500
|
message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
|
|
39559
39501
|
});
|
|
39560
|
-
if (!
|
|
39502
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
|
|
39561
39503
|
const args = node.arguments ?? [];
|
|
39562
39504
|
if (args.length < 2) return;
|
|
39563
39505
|
const depsNode = args[1];
|
|
@@ -39818,7 +39760,7 @@ const noEventHandler = defineRule({
|
|
|
39818
39760
|
severity: "warn",
|
|
39819
39761
|
recommendation: "Run the side effect in the event handler that triggers it, instead of watching its state from a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers",
|
|
39820
39762
|
create: (context) => ({ CallExpression(node) {
|
|
39821
|
-
if (!
|
|
39763
|
+
if (!isUseEffect(node)) return;
|
|
39822
39764
|
const analysis = getProgramAnalysis(node);
|
|
39823
39765
|
if (!analysis || hasCleanup(analysis, node)) return;
|
|
39824
39766
|
const frames = collectBoundedEffectExecutionFrames(analysis, node);
|
|
@@ -40280,25 +40222,25 @@ const addDeclarationBindings = (statement, scope) => {
|
|
|
40280
40222
|
}
|
|
40281
40223
|
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
|
|
40282
40224
|
};
|
|
40283
|
-
const collectRenderReachableNamesFromStatements = (statements, names, scope,
|
|
40225
|
+
const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40284
40226
|
let hasReturn = false;
|
|
40285
|
-
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope,
|
|
40227
|
+
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
|
|
40286
40228
|
else addDeclarationBindings(statement, scope);
|
|
40287
40229
|
return hasReturn;
|
|
40288
40230
|
};
|
|
40289
|
-
const collectRenderReachableNamesFromStatement = (statement, names, scope,
|
|
40231
|
+
const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
|
|
40290
40232
|
if (isNodeOfType(statement, "ReturnStatement")) {
|
|
40291
40233
|
if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
|
|
40292
40234
|
return true;
|
|
40293
40235
|
}
|
|
40294
|
-
if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") &&
|
|
40236
|
+
if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) && !EFFECT_HOOK_NAMES$1.has(statement.expression.callee.name)) {
|
|
40295
40237
|
for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
|
|
40296
40238
|
return false;
|
|
40297
40239
|
}
|
|
40298
|
-
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope),
|
|
40240
|
+
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
|
|
40299
40241
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40300
|
-
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope,
|
|
40301
|
-
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope,
|
|
40242
|
+
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
|
|
40243
|
+
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
|
|
40302
40244
|
if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40303
40245
|
return consequentHasReturn || alternateHasReturn;
|
|
40304
40246
|
}
|
|
@@ -40306,7 +40248,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40306
40248
|
let hasReturn = false;
|
|
40307
40249
|
for (const switchCase of statement.cases ?? []) {
|
|
40308
40250
|
const caseScope = createBlockBindingScope(scope);
|
|
40309
|
-
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope,
|
|
40251
|
+
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
|
|
40310
40252
|
hasReturn = true;
|
|
40311
40253
|
if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
|
|
40312
40254
|
}
|
|
@@ -40314,25 +40256,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40314
40256
|
return hasReturn;
|
|
40315
40257
|
}
|
|
40316
40258
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40317
|
-
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope,
|
|
40318
|
-
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope,
|
|
40319
|
-
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope,
|
|
40259
|
+
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
|
|
40260
|
+
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
|
|
40261
|
+
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
|
|
40320
40262
|
return blockHasReturn || handlerHasReturn || finalizerHasReturn;
|
|
40321
40263
|
}
|
|
40322
40264
|
if (isNodeOfType(statement, "CatchClause")) {
|
|
40323
40265
|
const catchScope = createBlockBindingScope(scope);
|
|
40324
40266
|
addPatternBindings(statement.param, catchScope);
|
|
40325
|
-
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope,
|
|
40267
|
+
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
|
|
40326
40268
|
}
|
|
40327
40269
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40328
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40270
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40329
40271
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40330
40272
|
return bodyHasReturn;
|
|
40331
40273
|
}
|
|
40332
40274
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40333
40275
|
const loopScope = createBlockBindingScope(scope);
|
|
40334
40276
|
if (statement.init) addDeclarationBindings(statement.init, loopScope);
|
|
40335
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
40277
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40336
40278
|
if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
|
|
40337
40279
|
if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
|
|
40338
40280
|
if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
|
|
@@ -40342,22 +40284,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40342
40284
|
const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
|
|
40343
40285
|
const loopScope = createBlockBindingScope(scope);
|
|
40344
40286
|
if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
|
|
40345
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
40287
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40346
40288
|
addNames(names, rightNames);
|
|
40347
40289
|
return true;
|
|
40348
40290
|
}
|
|
40349
|
-
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40291
|
+
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40350
40292
|
if (isNodeOfType(statement, "WithStatement")) {
|
|
40351
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40293
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40352
40294
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
|
|
40353
40295
|
return bodyHasReturn;
|
|
40354
40296
|
}
|
|
40355
40297
|
return false;
|
|
40356
40298
|
};
|
|
40357
|
-
const collectRenderReachableNames = (componentBody,
|
|
40299
|
+
const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40358
40300
|
const names = /* @__PURE__ */ new Set();
|
|
40359
40301
|
if (!isNodeOfType(componentBody, "BlockStatement")) return names;
|
|
40360
|
-
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(),
|
|
40302
|
+
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
|
|
40361
40303
|
return names;
|
|
40362
40304
|
};
|
|
40363
40305
|
//#endregion
|
|
@@ -40380,36 +40322,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
|
|
|
40380
40322
|
};
|
|
40381
40323
|
//#endregion
|
|
40382
40324
|
//#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
|
|
40383
|
-
const
|
|
40384
|
-
|
|
40325
|
+
const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
|
|
40326
|
+
const getCalleeName = (node) => {
|
|
40327
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
40328
|
+
if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
|
|
40329
|
+
return null;
|
|
40330
|
+
};
|
|
40331
|
+
const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
|
|
40332
|
+
if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
|
|
40385
40333
|
if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
|
|
40386
40334
|
const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
|
|
40387
40335
|
return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
|
|
40388
40336
|
};
|
|
40389
|
-
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope
|
|
40337
|
+
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
|
|
40390
40338
|
if (!isNodeOfType(node, "ObjectExpression")) return;
|
|
40391
40339
|
for (const property of node.properties ?? []) {
|
|
40392
40340
|
if (!isNodeOfType(property, "Property")) continue;
|
|
40393
40341
|
const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
|
|
40394
40342
|
if (!propertyName) continue;
|
|
40395
|
-
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope
|
|
40343
|
+
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
|
|
40396
40344
|
functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
|
|
40397
40345
|
}
|
|
40398
40346
|
};
|
|
40399
|
-
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope
|
|
40347
|
+
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40400
40348
|
if (!isNodeOfType(statement, "VariableDeclaration")) return;
|
|
40401
40349
|
const declarationScope = getVariableDeclarationScope(statement, scope);
|
|
40402
40350
|
for (const declarator of statement.declarations ?? []) {
|
|
40403
40351
|
const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
|
|
40404
40352
|
if (!declarator.init) continue;
|
|
40405
|
-
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope
|
|
40353
|
+
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
|
|
40406
40354
|
for (const declaredBindingName of declaredBindingNames) {
|
|
40407
40355
|
if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
|
|
40408
|
-
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope
|
|
40356
|
+
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
|
|
40409
40357
|
}
|
|
40410
40358
|
}
|
|
40411
40359
|
};
|
|
40412
|
-
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope
|
|
40360
|
+
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40413
40361
|
if (isNodeOfType(statement, "FunctionDeclaration")) {
|
|
40414
40362
|
if (statement.id) {
|
|
40415
40363
|
const declaredBindingNames = addPatternBindings(statement.id, scope);
|
|
@@ -40418,61 +40366,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
|
|
|
40418
40366
|
return;
|
|
40419
40367
|
}
|
|
40420
40368
|
if (isNodeOfType(statement, "VariableDeclaration")) {
|
|
40421
|
-
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope
|
|
40369
|
+
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40422
40370
|
return;
|
|
40423
40371
|
}
|
|
40424
40372
|
if (isNodeOfType(statement, "BlockStatement")) {
|
|
40425
|
-
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
40373
|
+
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40426
40374
|
return;
|
|
40427
40375
|
}
|
|
40428
40376
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40429
|
-
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope
|
|
40430
|
-
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope
|
|
40377
|
+
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
|
|
40378
|
+
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
|
|
40431
40379
|
return;
|
|
40432
40380
|
}
|
|
40433
40381
|
if (isNodeOfType(statement, "SwitchStatement")) {
|
|
40434
|
-
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
40382
|
+
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40435
40383
|
return;
|
|
40436
40384
|
}
|
|
40437
40385
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40438
|
-
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope
|
|
40386
|
+
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
|
|
40439
40387
|
if (statement.handler) {
|
|
40440
40388
|
const catchScope = createBlockBindingScope(scope);
|
|
40441
40389
|
addPatternBindings(statement.handler.param, catchScope);
|
|
40442
|
-
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope
|
|
40390
|
+
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
|
|
40443
40391
|
}
|
|
40444
|
-
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope
|
|
40392
|
+
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
|
|
40445
40393
|
return;
|
|
40446
40394
|
}
|
|
40447
40395
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40448
40396
|
const loopScope = createBlockBindingScope(scope);
|
|
40449
|
-
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope
|
|
40450
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
40397
|
+
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
|
|
40398
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40451
40399
|
return;
|
|
40452
40400
|
}
|
|
40453
40401
|
if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
|
|
40454
40402
|
const loopScope = createBlockBindingScope(scope);
|
|
40455
|
-
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope
|
|
40403
|
+
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
|
|
40456
40404
|
else addPatternBindings(statement.left, loopScope);
|
|
40457
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
40405
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40458
40406
|
return;
|
|
40459
40407
|
}
|
|
40460
40408
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40461
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
40409
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40462
40410
|
return;
|
|
40463
40411
|
}
|
|
40464
|
-
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
40412
|
+
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40465
40413
|
};
|
|
40466
|
-
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope
|
|
40467
|
-
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope
|
|
40414
|
+
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
|
|
40415
|
+
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40468
40416
|
};
|
|
40469
|
-
const collectFunctionLikeLocalNames = (componentBody
|
|
40417
|
+
const collectFunctionLikeLocalNames = (componentBody) => {
|
|
40470
40418
|
const functionLikeLocalNames = /* @__PURE__ */ new Set();
|
|
40471
40419
|
if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
|
|
40472
40420
|
let previousSize = -1;
|
|
40473
40421
|
while (previousSize !== functionLikeLocalNames.size) {
|
|
40474
40422
|
previousSize = functionLikeLocalNames.size;
|
|
40475
|
-
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope()
|
|
40423
|
+
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
|
|
40476
40424
|
}
|
|
40477
40425
|
return functionLikeLocalNames;
|
|
40478
40426
|
};
|
|
@@ -40512,17 +40460,17 @@ const noEventTriggerState = defineRule({
|
|
|
40512
40460
|
create: (context) => {
|
|
40513
40461
|
const checkComponent = (componentBody) => {
|
|
40514
40462
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
40515
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
40463
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
40516
40464
|
if (useStateBindings.length === 0) return;
|
|
40517
40465
|
const analysis = getProgramAnalysis(componentBody);
|
|
40518
40466
|
if (!analysis) return;
|
|
40519
40467
|
const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
|
|
40520
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
40468
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
40521
40469
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
40522
|
-
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody,
|
|
40470
|
+
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
|
|
40523
40471
|
walkAst(componentBody, (effectCall) => {
|
|
40524
40472
|
if (!isNodeOfType(effectCall, "CallExpression")) return;
|
|
40525
|
-
if (!
|
|
40473
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
|
|
40526
40474
|
if ((effectCall.arguments?.length ?? 0) < 2) return;
|
|
40527
40475
|
const depsNode = effectCall.arguments[1];
|
|
40528
40476
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -42351,7 +42299,7 @@ const noInitializeState = defineRule({
|
|
|
42351
42299
|
tags: ["test-noise"],
|
|
42352
42300
|
recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
|
|
42353
42301
|
create: (context) => ({ CallExpression(node) {
|
|
42354
|
-
if (!
|
|
42302
|
+
if (!isUseEffect(node)) return;
|
|
42355
42303
|
const dependencies = node.arguments?.[1];
|
|
42356
42304
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42357
42305
|
const analysis = getProgramAnalysis(node);
|
|
@@ -43963,7 +43911,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43963
43911
|
const setterElement = elements[1];
|
|
43964
43912
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
43965
43913
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
43966
|
-
if (!
|
|
43914
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
43967
43915
|
const initializer = declarator.init.arguments?.[0];
|
|
43968
43916
|
if (!initializer) continue;
|
|
43969
43917
|
const propRootName = getPropRootName(initializer, propNames);
|
|
@@ -43981,7 +43929,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43981
43929
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
43982
43930
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
43983
43931
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
43984
|
-
if (!
|
|
43932
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
43985
43933
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
43986
43934
|
const depsNode = effectCall.arguments[1];
|
|
43987
43935
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -44390,7 +44338,7 @@ const noMultiComp = defineRule({
|
|
|
44390
44338
|
});
|
|
44391
44339
|
//#endregion
|
|
44392
44340
|
//#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
|
|
44393
|
-
const collectUseRefBindingNames = (componentBody
|
|
44341
|
+
const collectUseRefBindingNames = (componentBody) => {
|
|
44394
44342
|
const useRefBindings = /* @__PURE__ */ new Set();
|
|
44395
44343
|
if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
|
|
44396
44344
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -44398,7 +44346,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
|
|
|
44398
44346
|
for (const declarator of statement.declarations ?? []) {
|
|
44399
44347
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
44400
44348
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
44401
|
-
if (!
|
|
44349
|
+
if (!isHookCall$2(declarator.init, "useRef")) continue;
|
|
44402
44350
|
useRefBindings.add(declarator.id.name);
|
|
44403
44351
|
}
|
|
44404
44352
|
}
|
|
@@ -44433,12 +44381,12 @@ const noMutableInDeps = defineRule({
|
|
|
44433
44381
|
create: (context) => {
|
|
44434
44382
|
const checkComponent = (componentBody, componentParams = []) => {
|
|
44435
44383
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
44436
|
-
const useRefBindingNames = collectUseRefBindingNames(componentBody
|
|
44384
|
+
const useRefBindingNames = collectUseRefBindingNames(componentBody);
|
|
44437
44385
|
const localBindingNames = collectLocalBindingNames(componentBody);
|
|
44438
44386
|
for (const param of componentParams) collectPatternNames(param, localBindingNames);
|
|
44439
44387
|
walkAst(componentBody, (child) => {
|
|
44440
44388
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
44441
|
-
if (!
|
|
44389
|
+
if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
|
|
44442
44390
|
if ((child.arguments?.length ?? 0) < 2) return;
|
|
44443
44391
|
const depsNode = child.arguments[1];
|
|
44444
44392
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -46351,10 +46299,16 @@ const noPassDataToParent = defineRule({
|
|
|
46351
46299
|
tags: ["test-noise"],
|
|
46352
46300
|
recommendation: "Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent",
|
|
46353
46301
|
create: (context) => {
|
|
46354
|
-
const isReactUseRefCall = (node) =>
|
|
46355
|
-
|
|
46302
|
+
const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
|
|
46303
|
+
allowGlobalReactNamespace: true,
|
|
46304
|
+
allowUnboundBareCalls: true
|
|
46305
|
+
});
|
|
46306
|
+
const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
|
|
46307
|
+
allowGlobalReactNamespace: true,
|
|
46308
|
+
allowUnboundBareCalls: true
|
|
46309
|
+
});
|
|
46356
46310
|
return { CallExpression(node) {
|
|
46357
|
-
if (!
|
|
46311
|
+
if (!isUseEffect(node)) return;
|
|
46358
46312
|
const analysis = getProgramAnalysis(node);
|
|
46359
46313
|
if (!analysis) return;
|
|
46360
46314
|
if (hasCleanup(analysis, node)) return;
|
|
@@ -46641,7 +46595,7 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46641
46595
|
tags: ["test-noise"],
|
|
46642
46596
|
recommendation: "Move the state up to the parent (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes",
|
|
46643
46597
|
create: (context) => ({ CallExpression(node) {
|
|
46644
|
-
if (!
|
|
46598
|
+
if (!isUseEffect(node)) return;
|
|
46645
46599
|
const analysis = getProgramAnalysis(node);
|
|
46646
46600
|
if (!analysis) return;
|
|
46647
46601
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -47056,7 +47010,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
47056
47010
|
if (!isNodeOfType(element, "Identifier")) continue;
|
|
47057
47011
|
const binding = findVariableInitializer(effectNode, element.name);
|
|
47058
47012
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
|
|
47059
|
-
const calleeName = getCalleeName$
|
|
47013
|
+
const calleeName = getCalleeName$2(binding.initializer);
|
|
47060
47014
|
if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
|
|
47061
47015
|
}
|
|
47062
47016
|
return false;
|
|
@@ -47078,7 +47032,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47078
47032
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
47079
47033
|
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
47080
47034
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
47081
|
-
if (getCalleeName$
|
|
47035
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
47082
47036
|
const callbackArgument = binding.initializer.arguments?.[0];
|
|
47083
47037
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47084
47038
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
@@ -47110,7 +47064,7 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47110
47064
|
return {
|
|
47111
47065
|
...propStackTracker.visitors,
|
|
47112
47066
|
CallExpression(node) {
|
|
47113
|
-
if (!
|
|
47067
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
47114
47068
|
const callback = getEffectCallback(node);
|
|
47115
47069
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
47116
47070
|
const depsNode = node.arguments[1];
|
|
@@ -49003,7 +48957,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
49003
48957
|
tags: ["test-noise"],
|
|
49004
48958
|
recommendation: "Pass the prop as `key` so React resets the component for you when the prop changes, instead of clearing every state value by hand in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes",
|
|
49005
48959
|
create: (context) => ({ CallExpression(node) {
|
|
49006
|
-
if (!
|
|
48960
|
+
if (!isUseEffect(node)) return;
|
|
49007
48961
|
const analysis = getProgramAnalysis(node);
|
|
49008
48962
|
if (!analysis) return;
|
|
49009
48963
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -49272,7 +49226,7 @@ const isTanStackServerFnHandlerCall = (node) => {
|
|
|
49272
49226
|
if (node.callee.property.name !== "handler") return false;
|
|
49273
49227
|
let currentNode = node.callee.object;
|
|
49274
49228
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
49275
|
-
const calleeName = getCalleeName$
|
|
49229
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
49276
49230
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
|
|
49277
49231
|
if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
|
|
49278
49232
|
currentNode = currentNode.callee.object;
|
|
@@ -49773,7 +49727,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49773
49727
|
create: (context) => {
|
|
49774
49728
|
const checkFunctionScope = (functionBody) => {
|
|
49775
49729
|
if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
|
|
49776
|
-
const useStateBindings = collectUseStateBindings(functionBody
|
|
49730
|
+
const useStateBindings = collectUseStateBindings(functionBody);
|
|
49777
49731
|
if (useStateBindings.length === 0) return;
|
|
49778
49732
|
const setterNameToStateName = /* @__PURE__ */ new Map();
|
|
49779
49733
|
for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
|
|
@@ -49782,7 +49736,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49782
49736
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
49783
49737
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
49784
49738
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
49785
|
-
if (!
|
|
49739
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
49786
49740
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
49787
49741
|
const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
|
|
49788
49742
|
if (dependencyStateNames.size === 0) continue;
|
|
@@ -49868,7 +49822,7 @@ const noSetStateInRender = defineRule({
|
|
|
49868
49822
|
create: (context) => {
|
|
49869
49823
|
const checkComponent = (componentBody) => {
|
|
49870
49824
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
49871
|
-
const setterNames = new Set(collectUseStateBindings(componentBody
|
|
49825
|
+
const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
|
|
49872
49826
|
if (setterNames.size === 0) return;
|
|
49873
49827
|
for (const statement of componentBody.body ?? []) {
|
|
49874
49828
|
const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
|
|
@@ -50117,10 +50071,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
|
|
|
50117
50071
|
});
|
|
50118
50072
|
return facts;
|
|
50119
50073
|
};
|
|
50120
|
-
const isEffectCallbackFunction = (functionNode
|
|
50074
|
+
const isEffectCallbackFunction = (functionNode) => {
|
|
50121
50075
|
const parent = functionNode.parent;
|
|
50122
50076
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
50123
|
-
return
|
|
50077
|
+
return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
|
|
50124
50078
|
};
|
|
50125
50079
|
const doesEffectCallbackReturnName = (effectCallback, name) => {
|
|
50126
50080
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -50139,24 +50093,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
|
|
|
50139
50093
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50140
50094
|
return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
|
|
50141
50095
|
};
|
|
50142
|
-
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope
|
|
50096
|
+
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
50143
50097
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50144
50098
|
if (cleanupBindingName === null) return false;
|
|
50145
50099
|
let isReturnedFromEffect = false;
|
|
50146
50100
|
walkAst(ownerScope, (child) => {
|
|
50147
50101
|
if (isReturnedFromEffect) return false;
|
|
50148
|
-
if (!isNodeOfType(child, "CallExpression") || !
|
|
50102
|
+
if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
50149
50103
|
const effectCallback = getEffectCallback(child);
|
|
50150
50104
|
if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
|
|
50151
50105
|
});
|
|
50152
50106
|
return isReturnedFromEffect;
|
|
50153
50107
|
};
|
|
50154
|
-
const isInsideEffectCleanupReturn = (node, ownerScope
|
|
50108
|
+
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
50155
50109
|
let functionNode = findEnclosingFunction$1(node);
|
|
50156
50110
|
while (functionNode) {
|
|
50157
50111
|
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
50158
|
-
if (outerFunction && isEffectCallbackFunction(outerFunction
|
|
50159
|
-
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope
|
|
50112
|
+
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
50113
|
+
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
50160
50114
|
functionNode = outerFunction;
|
|
50161
50115
|
}
|
|
50162
50116
|
return false;
|
|
@@ -50192,10 +50146,10 @@ const noStaleTimerRef = defineRule({
|
|
|
50192
50146
|
if (isShadowedTimerGlobal(node)) return;
|
|
50193
50147
|
const { clearCalleeName, refName } = clearCall;
|
|
50194
50148
|
const refBinding = findVariableInitializer(node, refName);
|
|
50195
|
-
if (!refBinding?.initializer || !
|
|
50149
|
+
if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
|
|
50196
50150
|
const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
|
|
50197
50151
|
if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
|
|
50198
|
-
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner
|
|
50152
|
+
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
|
|
50199
50153
|
if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
|
|
50200
50154
|
context.report({
|
|
50201
50155
|
node,
|
|
@@ -55999,7 +55953,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
|
|
|
55999
55953
|
allReadsAreInSubHandlers = false;
|
|
56000
55954
|
return;
|
|
56001
55955
|
}
|
|
56002
|
-
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$
|
|
55956
|
+
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
|
|
56003
55957
|
});
|
|
56004
55958
|
return {
|
|
56005
55959
|
hasAnyRead,
|
|
@@ -56022,7 +55976,7 @@ const preferUseEffectEvent = defineRule({
|
|
|
56022
55976
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
56023
55977
|
const effectCall = statement.expression;
|
|
56024
55978
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56025
|
-
if (!
|
|
55979
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
56026
55980
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56027
55981
|
const depsNode = effectCall.arguments[1];
|
|
56028
55982
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -56054,11 +56008,11 @@ const preferUseEffectEvent = defineRule({
|
|
|
56054
56008
|
});
|
|
56055
56009
|
//#endregion
|
|
56056
56010
|
//#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
|
|
56057
|
-
const findUseEffectsInComponent = (componentBody
|
|
56011
|
+
const findUseEffectsInComponent = (componentBody) => {
|
|
56058
56012
|
const effectCalls = [];
|
|
56059
56013
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
56060
56014
|
for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
|
|
56061
|
-
if (isNodeOfType(child, "CallExpression") &&
|
|
56015
|
+
if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
|
|
56062
56016
|
});
|
|
56063
56017
|
return effectCalls;
|
|
56064
56018
|
};
|
|
@@ -56261,7 +56215,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56261
56215
|
};
|
|
56262
56216
|
const checkComponent = (componentBody) => {
|
|
56263
56217
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
56264
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
56218
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
56265
56219
|
if (useStateBindings.length === 0) return;
|
|
56266
56220
|
const useStateInitializerByValueName = /* @__PURE__ */ new Map();
|
|
56267
56221
|
for (const binding of useStateBindings) {
|
|
@@ -56274,7 +56228,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56274
56228
|
}
|
|
56275
56229
|
const setterNameToValueName = /* @__PURE__ */ new Map();
|
|
56276
56230
|
for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
|
|
56277
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
56231
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56278
56232
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56279
56233
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56280
56234
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56316,7 +56270,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56316
56270
|
})).filter((candidate) => candidate.storeName !== null);
|
|
56317
56271
|
if (snapshotBindings.length === 0) return;
|
|
56318
56272
|
const reportedDeclarators = /* @__PURE__ */ new Set();
|
|
56319
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
56273
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56320
56274
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56321
56275
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56322
56276
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56413,7 +56367,7 @@ const preferUseReducer = defineRule({
|
|
|
56413
56367
|
create: (context) => {
|
|
56414
56368
|
const reportCoUpdatedUseState = (body, componentName) => {
|
|
56415
56369
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
56416
|
-
const bindings = collectUseStateBindings(body
|
|
56370
|
+
const bindings = collectUseStateBindings(body);
|
|
56417
56371
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
56418
56372
|
if (setterNames.size < 5) return;
|
|
56419
56373
|
const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
|
|
@@ -56629,7 +56583,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
|
|
|
56629
56583
|
]);
|
|
56630
56584
|
const isQueryCacheSourceCall = (initializer) => {
|
|
56631
56585
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
56632
|
-
const hookName = getCalleeName$
|
|
56586
|
+
const hookName = getCalleeName$2(initializer);
|
|
56633
56587
|
if (!hookName) return false;
|
|
56634
56588
|
return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
|
|
56635
56589
|
};
|
|
@@ -56761,7 +56715,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
56761
56715
|
},
|
|
56762
56716
|
CallExpression(node) {
|
|
56763
56717
|
if (!hasQueryReadUsage) {
|
|
56764
|
-
const callName = getCalleeName$
|
|
56718
|
+
const callName = getCalleeName$2(node);
|
|
56765
56719
|
if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
|
|
56766
56720
|
}
|
|
56767
56721
|
const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
|
|
@@ -58424,125 +58378,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
58424
58378
|
const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
|
|
58425
58379
|
const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
|
|
58426
58380
|
const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
|
|
58427
|
-
const FILE_READER_READ_METHOD_NAMES = new Set([
|
|
58428
|
-
"readAsArrayBuffer",
|
|
58429
|
-
"readAsBinaryString",
|
|
58430
|
-
"readAsDataURL",
|
|
58431
|
-
"readAsText"
|
|
58432
|
-
]);
|
|
58433
|
-
const isGlobalFileReaderConstruction = (expression, context) => {
|
|
58434
|
-
if (!expression) return false;
|
|
58435
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58436
|
-
if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
58437
|
-
return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
|
|
58438
|
-
};
|
|
58439
|
-
const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
|
|
58440
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58441
|
-
let latestValue = null;
|
|
58442
|
-
let latestStart = null;
|
|
58443
|
-
if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
|
|
58444
|
-
latestValue = readerSymbol.initializer;
|
|
58445
|
-
latestStart = readerSymbol.declarationNode.range[0];
|
|
58446
|
-
}
|
|
58447
|
-
for (const reference of readerSymbol.references) {
|
|
58448
|
-
if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
|
|
58449
|
-
const assignment = reference.identifier.parent;
|
|
58450
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
|
|
58451
|
-
latestValue = assignment.right;
|
|
58452
|
-
latestStart = reference.identifier.range[0];
|
|
58453
|
-
}
|
|
58454
|
-
return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
|
|
58455
|
-
};
|
|
58456
|
-
const resolveLoadingCompletionFunction = (expression, context) => {
|
|
58457
|
-
const directFunction = resolveExactLocalFunction(expression, context.scopes);
|
|
58458
|
-
if (directFunction) return directFunction;
|
|
58459
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58460
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
58461
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
58462
|
-
const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
|
|
58463
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
|
|
58464
|
-
const callback = initializer.arguments?.[0];
|
|
58465
|
-
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
58466
|
-
};
|
|
58467
|
-
const isSetterBooleanCall = (node, setterSymbol, value, context) => {
|
|
58468
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
58469
|
-
const callee = stripParenExpression(node.callee);
|
|
58470
|
-
const argument = node.arguments?.[0];
|
|
58471
|
-
const unwrappedArgument = argument ? stripParenExpression(argument) : null;
|
|
58472
|
-
return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
|
|
58473
|
-
};
|
|
58474
|
-
const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
|
|
58475
|
-
if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
|
|
58476
|
-
visitedFunctions.add(functionNode);
|
|
58477
|
-
let didClearLoadingState = false;
|
|
58478
|
-
walkAst(functionNode.body, (child) => {
|
|
58479
|
-
if (didClearLoadingState) return false;
|
|
58480
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
58481
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
58482
|
-
if (isSetterBooleanCall(child, setterSymbol, false, context)) {
|
|
58483
|
-
didClearLoadingState = true;
|
|
58484
|
-
return false;
|
|
58485
|
-
}
|
|
58486
|
-
const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
|
|
58487
|
-
if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
|
|
58488
|
-
didClearLoadingState = true;
|
|
58489
|
-
return false;
|
|
58490
|
-
}
|
|
58491
|
-
});
|
|
58492
|
-
return didClearLoadingState;
|
|
58493
|
-
};
|
|
58494
|
-
const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
|
|
58495
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58496
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return null;
|
|
58497
|
-
let callback = null;
|
|
58498
|
-
let callbackStart = originStart;
|
|
58499
|
-
walkAst(readFunction.body, (child) => {
|
|
58500
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58501
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
|
|
58502
|
-
const receiver = stripParenExpression(child.left.object);
|
|
58503
|
-
if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
|
|
58504
|
-
callback = child.right;
|
|
58505
|
-
callbackStart = child.range[0];
|
|
58506
|
-
});
|
|
58507
|
-
return callback;
|
|
58508
|
-
};
|
|
58509
|
-
const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
|
|
58510
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58511
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return false;
|
|
58512
|
-
let didStartLoading = false;
|
|
58513
|
-
walkAst(readFunction.body, (child) => {
|
|
58514
|
-
if (didStartLoading) return false;
|
|
58515
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58516
|
-
if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
|
|
58517
|
-
if (isSetterBooleanCall(child, setterSymbol, true, context)) {
|
|
58518
|
-
didStartLoading = true;
|
|
58519
|
-
return false;
|
|
58520
|
-
}
|
|
58521
|
-
});
|
|
58522
|
-
return didStartLoading;
|
|
58523
|
-
};
|
|
58524
|
-
const setterTracksFileReader = (functionBody, setterSymbol, context) => {
|
|
58525
|
-
let didFindFileReaderLifecycle = false;
|
|
58526
|
-
walkAst(functionBody, (child) => {
|
|
58527
|
-
if (didFindFileReaderLifecycle) return false;
|
|
58528
|
-
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
|
|
58529
|
-
const receiver = stripParenExpression(child.callee.object);
|
|
58530
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
58531
|
-
const readerSymbol = context.scopes.symbolFor(receiver);
|
|
58532
|
-
if (!readerSymbol) return;
|
|
58533
|
-
const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
|
|
58534
|
-
if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
|
|
58535
|
-
const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
|
|
58536
|
-
const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
|
|
58537
|
-
const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
|
|
58538
|
-
const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
|
|
58539
|
-
if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
|
|
58540
|
-
didFindFileReaderLifecycle = true;
|
|
58541
|
-
return false;
|
|
58542
|
-
}
|
|
58543
|
-
});
|
|
58544
|
-
return didFindFileReaderLifecycle;
|
|
58545
|
-
};
|
|
58546
58381
|
const hasAsyncLoadingWork = (fnBody, setterName) => {
|
|
58547
58382
|
let found = false;
|
|
58548
58383
|
walkAst(fnBody, (child) => {
|
|
@@ -58716,8 +58551,6 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
58716
58551
|
const fnBody = enclosingFunctionBody(node);
|
|
58717
58552
|
if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
|
|
58718
58553
|
if (fnBody && setterName) {
|
|
58719
|
-
const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
|
|
58720
|
-
if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
|
|
58721
58554
|
if (setterEscapes(fnBody, setterName, node)) return;
|
|
58722
58555
|
if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
|
|
58723
58556
|
if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
|
|
@@ -59049,7 +58882,7 @@ const rerenderDependencies = defineRule({
|
|
|
59049
58882
|
severity: "error",
|
|
59050
58883
|
recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
|
|
59051
58884
|
create: (context) => ({ CallExpression(node) {
|
|
59052
|
-
if (!
|
|
58885
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
59053
58886
|
const depsNode = node.arguments[1];
|
|
59054
58887
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
59055
58888
|
for (const element of depsNode.elements ?? []) {
|
|
@@ -59304,7 +59137,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
59304
59137
|
category: "Performance",
|
|
59305
59138
|
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
59306
59139
|
create: (context) => ({ CallExpression(node) {
|
|
59307
|
-
if (!
|
|
59140
|
+
if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
|
|
59308
59141
|
const initializer = stripParenExpression(node.arguments[0]);
|
|
59309
59142
|
const isPlainCall = isNodeOfType(initializer, "CallExpression");
|
|
59310
59143
|
const isNewCall = isNodeOfType(initializer, "NewExpression");
|
|
@@ -59368,7 +59201,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
59368
59201
|
category: "Performance",
|
|
59369
59202
|
recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
|
|
59370
59203
|
create: (context) => ({ CallExpression(node) {
|
|
59371
|
-
if (!
|
|
59204
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
59372
59205
|
const initializer = findEagerInitializerCall(node.arguments[0]);
|
|
59373
59206
|
if (!initializer) return;
|
|
59374
59207
|
const isConstructor = isNodeOfType(initializer, "NewExpression");
|
|
@@ -60131,11 +59964,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
|
|
|
60131
59964
|
}
|
|
60132
59965
|
return false;
|
|
60133
59966
|
};
|
|
60134
|
-
const collectEffectDependencyInfos = (componentBody, setterNames
|
|
59967
|
+
const collectEffectDependencyInfos = (componentBody, setterNames) => {
|
|
60135
59968
|
const effectInfos = [];
|
|
60136
59969
|
walkAst(componentBody, (child) => {
|
|
60137
59970
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60138
|
-
if (!
|
|
59971
|
+
if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
60139
59972
|
const dependencyNames = /* @__PURE__ */ new Set();
|
|
60140
59973
|
for (const argument of child.arguments ?? []) {
|
|
60141
59974
|
if (!isNodeOfType(argument, "ArrayExpression")) continue;
|
|
@@ -60191,14 +60024,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
|
|
|
60191
60024
|
});
|
|
60192
60025
|
return effectInfos;
|
|
60193
60026
|
};
|
|
60194
|
-
const collectCustomHookArgumentNames = (componentBody
|
|
60027
|
+
const collectCustomHookArgumentNames = (componentBody) => {
|
|
60195
60028
|
const argumentNames = /* @__PURE__ */ new Set();
|
|
60196
60029
|
walkAst(componentBody, (child) => {
|
|
60197
60030
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60198
60031
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
60199
60032
|
const calleeName = child.callee.name;
|
|
60200
60033
|
if (!isReactHookName(calleeName)) return;
|
|
60201
|
-
if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
|
|
60202
60034
|
if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
|
|
60203
60035
|
if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
|
|
60204
60036
|
for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
|
|
@@ -60239,21 +60071,21 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60239
60071
|
create: (context) => {
|
|
60240
60072
|
const checkComponent = (componentBody) => {
|
|
60241
60073
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60242
|
-
const bindings = collectUseStateBindings(componentBody
|
|
60074
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
60243
60075
|
if (bindings.length === 0) return;
|
|
60244
60076
|
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
60245
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
60077
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
60246
60078
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60247
|
-
const directRenderNames = collectRenderReachableNames(componentBody,
|
|
60079
|
+
const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
|
|
60248
60080
|
if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
|
|
60249
60081
|
const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
|
|
60250
60082
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
60251
|
-
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames
|
|
60083
|
+
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
|
|
60252
60084
|
const selfEchoValueNames = /* @__PURE__ */ new Set();
|
|
60253
60085
|
for (const binding of bindings) if (effectInfos.some((effectInfo) => effectInfo.dependencyNames.has(binding.valueName) && effectInfo.synchronouslyCalledFunctionNames.has(binding.setterName) && !effectInfo.payloadReadNames.has(binding.valueName) && !effectInfo.nestedCallbackCalledFunctionNames.has(binding.setterName))) selfEchoValueNames.add(binding.valueName);
|
|
60254
60086
|
const effectConsumedNames = /* @__PURE__ */ new Set();
|
|
60255
60087
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
60256
|
-
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody
|
|
60088
|
+
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
|
|
60257
60089
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
60258
60090
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60259
60091
|
walkAst(componentBody, (child) => {
|
|
@@ -68154,7 +67986,7 @@ const declarationAwaitsGate = (declaration, context) => {
|
|
|
68154
67986
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
68155
67987
|
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
68156
67988
|
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
68157
|
-
const calleeName = getCalleeName$
|
|
67989
|
+
const calleeName = getCalleeName$2(argument);
|
|
68158
67990
|
if (!calleeName) continue;
|
|
68159
67991
|
if (isAuthGuardName(calleeName)) return true;
|
|
68160
67992
|
const [leadingToken] = tokenizeIdentifierWords(calleeName);
|
|
@@ -68794,7 +68626,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
68794
68626
|
if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
|
|
68795
68627
|
let currentNode = stripParenExpression(outerNode.callee.object);
|
|
68796
68628
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
68797
|
-
const calleeName = getCalleeName$
|
|
68629
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
68798
68630
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
|
|
68799
68631
|
result.isServerFnChain = true;
|
|
68800
68632
|
const optionsArgument = currentNode.arguments?.[0];
|