oxlint-plugin-react-doctor 0.7.9-dev.7e6cdf9 → 0.7.9-dev.8835214
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 +334 -313
- 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",
|
|
@@ -14606,7 +14598,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
14606
14598
|
walkAst(componentFunction.body, (child) => {
|
|
14607
14599
|
if (didFindUnmountCleanup) return false;
|
|
14608
14600
|
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
14609
|
-
if (!
|
|
14601
|
+
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
14610
14602
|
const dependencyList = child.arguments?.[1];
|
|
14611
14603
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
14612
14604
|
const cleanupCallback = getEffectCallback(child);
|
|
@@ -15037,7 +15029,7 @@ const getReleaseVerbName = (node) => {
|
|
|
15037
15029
|
const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
|
|
15038
15030
|
const releaseFunction = findEnclosingFunction$1(releaseReceiver);
|
|
15039
15031
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15040
|
-
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction
|
|
15032
|
+
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
|
|
15041
15033
|
const controllerKey = getListenerAbortControllerKey(usage, context);
|
|
15042
15034
|
const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
|
|
15043
15035
|
if (controllerKey === null || refCurrentKey === null) return false;
|
|
@@ -15184,7 +15176,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15184
15176
|
return true;
|
|
15185
15177
|
};
|
|
15186
15178
|
const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
|
|
15187
|
-
const isReturnedEffectCleanupFunction = (functionNode
|
|
15179
|
+
const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
15188
15180
|
let currentNode = functionNode;
|
|
15189
15181
|
let parentNode = currentNode.parent;
|
|
15190
15182
|
while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
|
|
@@ -15193,10 +15185,10 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
|
|
|
15193
15185
|
}
|
|
15194
15186
|
const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
|
|
15195
15187
|
const effectCall = effectCallback?.parent;
|
|
15196
|
-
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") &&
|
|
15188
|
+
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
15197
15189
|
};
|
|
15198
15190
|
const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
15199
|
-
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode
|
|
15191
|
+
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
|
|
15200
15192
|
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15201
15193
|
if (!bindingIdentifier) return false;
|
|
15202
15194
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
@@ -15901,7 +15893,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
15901
15893
|
if (!isFunctionLike$1(functionNode)) return false;
|
|
15902
15894
|
const functionRoot = findTransparentExpressionRoot(functionNode);
|
|
15903
15895
|
const callbackCall = functionRoot.parent;
|
|
15904
|
-
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot &&
|
|
15896
|
+
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
|
|
15905
15897
|
const parentNode = functionNode.parent;
|
|
15906
15898
|
if (isDirectJsxEventHandlerValue(functionNode)) return true;
|
|
15907
15899
|
if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
|
|
@@ -15937,12 +15929,12 @@ const effectNeedsCleanup = defineRule({
|
|
|
15937
15929
|
};
|
|
15938
15930
|
return {
|
|
15939
15931
|
CallExpression(node) {
|
|
15940
|
-
if (
|
|
15932
|
+
if (isHookCall$2(node, "useCallback")) {
|
|
15941
15933
|
const retainedCallback = getEffectCallback(node);
|
|
15942
15934
|
if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
|
|
15943
15935
|
return;
|
|
15944
15936
|
}
|
|
15945
|
-
if (!
|
|
15937
|
+
if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
15946
15938
|
const callback = getEffectCallback(node);
|
|
15947
15939
|
if (!callback) return;
|
|
15948
15940
|
const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
|
|
@@ -15950,7 +15942,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
15950
15942
|
const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
|
|
15951
15943
|
if (!firstUsage) return;
|
|
15952
15944
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
|
|
15953
|
-
const hookName = getCalleeName$
|
|
15945
|
+
const hookName = getCalleeName$2(node) ?? "effect";
|
|
15954
15946
|
context.report({
|
|
15955
15947
|
node,
|
|
15956
15948
|
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.`
|
|
@@ -18823,7 +18815,7 @@ const flattenCalleeName = (callee) => {
|
|
|
18823
18815
|
const PRAGMA = "React";
|
|
18824
18816
|
const isReactFunctionCall = (node, expectedCall) => {
|
|
18825
18817
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
18826
|
-
if (getCalleeName$
|
|
18818
|
+
if (getCalleeName$2(node) !== expectedCall) return false;
|
|
18827
18819
|
if (isNodeOfType(node.callee, "MemberExpression")) {
|
|
18828
18820
|
const receiver = stripParenExpression(node.callee.object);
|
|
18829
18821
|
return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
|
|
@@ -19021,14 +19013,10 @@ const hookUseState = defineRule({
|
|
|
19021
19013
|
create: (context) => {
|
|
19022
19014
|
const { allowDestructuredState } = resolveSettings$40(context.settings);
|
|
19023
19015
|
return { CallExpression(node) {
|
|
19024
|
-
|
|
19025
|
-
if (!isReactFunctionCall(callExpressionNode, "useState")) return;
|
|
19026
|
-
const expressionRoot = findTransparentExpressionRoot(callExpressionNode);
|
|
19027
|
-
const expressionParent = expressionRoot.parent;
|
|
19028
|
-
if (isNodeOfType(expressionParent, "ReturnStatement")) return;
|
|
19029
|
-
if (isNodeOfType(expressionParent, "ArrowFunctionExpression") && expressionParent.body === expressionRoot) return;
|
|
19016
|
+
if (!isReactFunctionCall(node, "useState")) return;
|
|
19030
19017
|
const parent = node.parent;
|
|
19031
19018
|
if (!parent) return;
|
|
19019
|
+
if (isNodeOfType(parent, "ReturnStatement")) return;
|
|
19032
19020
|
if (!isNodeOfType(parent, "VariableDeclarator")) {
|
|
19033
19021
|
context.report({
|
|
19034
19022
|
node,
|
|
@@ -19136,8 +19124,8 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19136
19124
|
severity: "warn",
|
|
19137
19125
|
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.",
|
|
19138
19126
|
create: (context) => ({ CallExpression(node) {
|
|
19139
|
-
if (!
|
|
19140
|
-
const depsIndex =
|
|
19127
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
|
|
19128
|
+
const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
|
|
19141
19129
|
const depsArgument = node.arguments[depsIndex];
|
|
19142
19130
|
if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
|
|
19143
19131
|
for (const element of depsArgument.elements) {
|
|
@@ -20308,7 +20296,7 @@ const isImportedSelectAtom = (callExpression) => {
|
|
|
20308
20296
|
const isDeferredCallbackPosition$1 = (functionNode) => {
|
|
20309
20297
|
const parent = functionNode.parent;
|
|
20310
20298
|
if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
|
|
20311
|
-
const hookName = getCalleeName$
|
|
20299
|
+
const hookName = getCalleeName$2(parent);
|
|
20312
20300
|
if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
|
|
20313
20301
|
if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
|
|
20314
20302
|
}
|
|
@@ -30379,7 +30367,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
|
|
|
30379
30367
|
"on",
|
|
30380
30368
|
"once"
|
|
30381
30369
|
]);
|
|
30382
|
-
const getCalleeName = (callee) => {
|
|
30370
|
+
const getCalleeName$1 = (callee) => {
|
|
30383
30371
|
if (!callee) return null;
|
|
30384
30372
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
30385
30373
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
@@ -30391,11 +30379,11 @@ const isDeferredCallbackPosition = (expression) => {
|
|
|
30391
30379
|
const parent = parentOf(expression);
|
|
30392
30380
|
if (!parent) return false;
|
|
30393
30381
|
if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30394
|
-
const name = getCalleeName(parent.callee);
|
|
30382
|
+
const name = getCalleeName$1(parent.callee);
|
|
30395
30383
|
if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
|
|
30396
30384
|
}
|
|
30397
30385
|
if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30398
|
-
const name = getCalleeName(parent.callee);
|
|
30386
|
+
const name = getCalleeName$1(parent.callee);
|
|
30399
30387
|
if (name && (name.endsWith("Observer") || name === "Promise")) return true;
|
|
30400
30388
|
}
|
|
30401
30389
|
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
|
|
@@ -30815,6 +30803,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
|
|
|
30815
30803
|
if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30816
30804
|
return false;
|
|
30817
30805
|
};
|
|
30806
|
+
const isUseEffect = (node) => {
|
|
30807
|
+
if (!node || !isNodeOfType(node, "CallExpression")) return false;
|
|
30808
|
+
const callee = node.callee;
|
|
30809
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
|
|
30810
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
30811
|
+
const receiver = stripParenExpression(callee.object);
|
|
30812
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
|
|
30813
|
+
};
|
|
30818
30814
|
const getEffectFn = (analysis, node) => {
|
|
30819
30815
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
30820
30816
|
const fn = node.arguments?.[0];
|
|
@@ -32346,7 +32342,12 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
32346
32342
|
tags: ["test-noise"],
|
|
32347
32343
|
recommendation: "Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
|
|
32348
32344
|
create: (context) => ({ CallExpression(node) {
|
|
32349
|
-
if (!
|
|
32345
|
+
if (!isReactApiCall(node, "useEffect", context.scopes, {
|
|
32346
|
+
allowGlobalReactNamespace: true,
|
|
32347
|
+
allowUnboundBareCalls: true,
|
|
32348
|
+
resolveConditionalAliases: true,
|
|
32349
|
+
resolveNamedAliases: true
|
|
32350
|
+
})) return;
|
|
32350
32351
|
const analysis = getProgramAnalysis(node);
|
|
32351
32352
|
if (!analysis) return;
|
|
32352
32353
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
@@ -34369,7 +34370,7 @@ const declarationBodyContainsHookCall = (symbol) => {
|
|
|
34369
34370
|
walkAst(componentFunction, (descendant) => {
|
|
34370
34371
|
if (didFindHookCall) return false;
|
|
34371
34372
|
if (!isNodeOfType(descendant, "CallExpression")) return;
|
|
34372
|
-
const calleeName = getCalleeName$
|
|
34373
|
+
const calleeName = getCalleeName$2(descendant);
|
|
34373
34374
|
if (calleeName && isReactHookName(calleeName)) {
|
|
34374
34375
|
didFindHookCall = true;
|
|
34375
34376
|
return false;
|
|
@@ -34394,7 +34395,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
|
|
|
34394
34395
|
if (isNodeOfType(parent, "ArrowFunctionExpression")) {
|
|
34395
34396
|
if (parent.body !== current) return false;
|
|
34396
34397
|
const grandparent = parent.parent;
|
|
34397
|
-
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$
|
|
34398
|
+
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
|
|
34398
34399
|
}
|
|
34399
34400
|
if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
|
|
34400
34401
|
current = parent;
|
|
@@ -36926,7 +36927,12 @@ const noDerivedState = defineRule({
|
|
|
36926
36927
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36927
36928
|
} }).visitors,
|
|
36928
36929
|
CallExpression(node) {
|
|
36929
|
-
if (!
|
|
36930
|
+
if (!isReactApiCall(node, "useEffect", context.scopes, {
|
|
36931
|
+
allowGlobalReactNamespace: true,
|
|
36932
|
+
allowUnboundBareCalls: true,
|
|
36933
|
+
resolveConditionalAliases: true,
|
|
36934
|
+
resolveNamedAliases: true
|
|
36935
|
+
})) return;
|
|
36930
36936
|
const analysis = getProgramAnalysis(node);
|
|
36931
36937
|
if (!analysis) return;
|
|
36932
36938
|
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
@@ -36946,7 +36952,12 @@ const noDerivedStateEffect = defineRule({
|
|
|
36946
36952
|
tags: ["test-noise"],
|
|
36947
36953
|
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",
|
|
36948
36954
|
create: (context) => ({ CallExpression(node) {
|
|
36949
|
-
if (!
|
|
36955
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
36956
|
+
allowGlobalReactNamespace: true,
|
|
36957
|
+
allowUnboundBareCalls: true,
|
|
36958
|
+
resolveConditionalAliases: true,
|
|
36959
|
+
resolveNamedAliases: true
|
|
36960
|
+
})) return;
|
|
36950
36961
|
const analysis = getProgramAnalysis(node);
|
|
36951
36962
|
if (!analysis) return;
|
|
36952
36963
|
if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
@@ -37069,7 +37080,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
37069
37080
|
if (isFunctionLike$1(cursor)) {
|
|
37070
37081
|
const parent = cursor.parent ?? null;
|
|
37071
37082
|
if (parent && isNodeOfType(parent, "CallExpression")) {
|
|
37072
|
-
const calleeName = getCalleeName$
|
|
37083
|
+
const calleeName = getCalleeName$2(parent);
|
|
37073
37084
|
if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
|
|
37074
37085
|
}
|
|
37075
37086
|
}
|
|
@@ -37140,7 +37151,7 @@ const isNonHandlerHookCallback = (functionNode) => {
|
|
|
37140
37151
|
const parent = functionNode.parent ?? null;
|
|
37141
37152
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
37142
37153
|
if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
|
|
37143
|
-
const calleeName = getCalleeName$
|
|
37154
|
+
const calleeName = getCalleeName$2(parent);
|
|
37144
37155
|
return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
|
|
37145
37156
|
};
|
|
37146
37157
|
const isHandlerShapedReseed = (setterCall, componentFunction) => {
|
|
@@ -37222,7 +37233,7 @@ const noDerivedUseState = defineRule({
|
|
|
37222
37233
|
return {
|
|
37223
37234
|
...propStackTracker.visitors,
|
|
37224
37235
|
CallExpression(node) {
|
|
37225
|
-
if (!
|
|
37236
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
37226
37237
|
const seed = unwrapInitializerSeed(node.arguments[0]);
|
|
37227
37238
|
const reportStalePropCopy = (propName) => {
|
|
37228
37239
|
if (isIntentionalSnapshotState(node)) return;
|
|
@@ -37911,7 +37922,12 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37911
37922
|
const setterElement = elements[1];
|
|
37912
37923
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37913
37924
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37914
|
-
if (!
|
|
37925
|
+
if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
|
|
37926
|
+
allowGlobalReactNamespace: true,
|
|
37927
|
+
allowUnboundBareCalls: true,
|
|
37928
|
+
resolveConditionalAliases: true,
|
|
37929
|
+
resolveNamedAliases: true
|
|
37930
|
+
}) : isHookCall$2(declarator.init, "useState"))) continue;
|
|
37915
37931
|
bindings.push({
|
|
37916
37932
|
valueName: valueElement.name,
|
|
37917
37933
|
setterName: setterElement.name,
|
|
@@ -38078,7 +38094,7 @@ const noDirectStateMutation = defineRule({
|
|
|
38078
38094
|
create: (context) => {
|
|
38079
38095
|
const checkComponent = (componentBody) => {
|
|
38080
38096
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
38081
|
-
const bindings = collectUseStateBindings(componentBody
|
|
38097
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
38082
38098
|
if (bindings.length === 0) return;
|
|
38083
38099
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
38084
38100
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
@@ -38645,7 +38661,12 @@ const findTopLevelEffectCalls = (componentBody, scopes) => {
|
|
|
38645
38661
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38646
38662
|
const expression = unwrapDiscardedExpression(statement);
|
|
38647
38663
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38648
|
-
if (!
|
|
38664
|
+
if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
|
|
38665
|
+
allowGlobalReactNamespace: true,
|
|
38666
|
+
allowUnboundBareCalls: true,
|
|
38667
|
+
resolveConditionalAliases: true,
|
|
38668
|
+
resolveNamedAliases: true
|
|
38669
|
+
})) continue;
|
|
38649
38670
|
effectCalls.push(expression);
|
|
38650
38671
|
}
|
|
38651
38672
|
return effectCalls;
|
|
@@ -38889,7 +38910,7 @@ const collectStorageHookSetterNames = (componentBody) => {
|
|
|
38889
38910
|
for (const declarator of statement.declarations ?? []) {
|
|
38890
38911
|
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
38891
38912
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
38892
|
-
const calleeName = getCalleeName$
|
|
38913
|
+
const calleeName = getCalleeName$2(declarator.init);
|
|
38893
38914
|
if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
|
|
38894
38915
|
for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
|
|
38895
38916
|
}
|
|
@@ -39377,7 +39398,7 @@ const noEffectEventHandler = defineRule({
|
|
|
39377
39398
|
return {
|
|
39378
39399
|
...propStackTracker.visitors,
|
|
39379
39400
|
CallExpression(node) {
|
|
39380
|
-
if (!
|
|
39401
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
39381
39402
|
const callback = getEffectCallback(node);
|
|
39382
39403
|
if (!callback) return;
|
|
39383
39404
|
const analysis = getProgramAnalysis(node);
|
|
@@ -39497,14 +39518,14 @@ const noEffectEventInDeps = defineRule({
|
|
|
39497
39518
|
if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
|
|
39498
39519
|
const initializer = declaratorNode.init;
|
|
39499
39520
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
39500
|
-
if (!
|
|
39521
|
+
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
39501
39522
|
if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
|
|
39502
39523
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
39503
39524
|
} });
|
|
39504
39525
|
return {
|
|
39505
39526
|
...componentBindings.visitors,
|
|
39506
39527
|
CallExpression(node) {
|
|
39507
|
-
if (!
|
|
39528
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
39508
39529
|
if (!componentBindings.isInsideComponent()) return;
|
|
39509
39530
|
const depsNode = node.arguments[1];
|
|
39510
39531
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -39561,7 +39582,7 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
39561
39582
|
node: finding.reportNode,
|
|
39562
39583
|
message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
|
|
39563
39584
|
});
|
|
39564
|
-
if (!
|
|
39585
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
|
|
39565
39586
|
const args = node.arguments ?? [];
|
|
39566
39587
|
if (args.length < 2) return;
|
|
39567
39588
|
const depsNode = args[1];
|
|
@@ -39822,7 +39843,7 @@ const noEventHandler = defineRule({
|
|
|
39822
39843
|
severity: "warn",
|
|
39823
39844
|
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",
|
|
39824
39845
|
create: (context) => ({ CallExpression(node) {
|
|
39825
|
-
if (!
|
|
39846
|
+
if (!isUseEffect(node)) return;
|
|
39826
39847
|
const analysis = getProgramAnalysis(node);
|
|
39827
39848
|
if (!analysis || hasCleanup(analysis, node)) return;
|
|
39828
39849
|
const frames = collectBoundedEffectExecutionFrames(analysis, node);
|
|
@@ -40284,25 +40305,25 @@ const addDeclarationBindings = (statement, scope) => {
|
|
|
40284
40305
|
}
|
|
40285
40306
|
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
|
|
40286
40307
|
};
|
|
40287
|
-
const collectRenderReachableNamesFromStatements = (statements, names, scope,
|
|
40308
|
+
const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40288
40309
|
let hasReturn = false;
|
|
40289
|
-
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope,
|
|
40310
|
+
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
|
|
40290
40311
|
else addDeclarationBindings(statement, scope);
|
|
40291
40312
|
return hasReturn;
|
|
40292
40313
|
};
|
|
40293
|
-
const collectRenderReachableNamesFromStatement = (statement, names, scope,
|
|
40314
|
+
const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
|
|
40294
40315
|
if (isNodeOfType(statement, "ReturnStatement")) {
|
|
40295
40316
|
if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
|
|
40296
40317
|
return true;
|
|
40297
40318
|
}
|
|
40298
|
-
if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") &&
|
|
40319
|
+
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)) {
|
|
40299
40320
|
for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
|
|
40300
40321
|
return false;
|
|
40301
40322
|
}
|
|
40302
|
-
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope),
|
|
40323
|
+
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
|
|
40303
40324
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40304
|
-
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope,
|
|
40305
|
-
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope,
|
|
40325
|
+
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
|
|
40326
|
+
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
|
|
40306
40327
|
if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40307
40328
|
return consequentHasReturn || alternateHasReturn;
|
|
40308
40329
|
}
|
|
@@ -40310,7 +40331,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40310
40331
|
let hasReturn = false;
|
|
40311
40332
|
for (const switchCase of statement.cases ?? []) {
|
|
40312
40333
|
const caseScope = createBlockBindingScope(scope);
|
|
40313
|
-
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope,
|
|
40334
|
+
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
|
|
40314
40335
|
hasReturn = true;
|
|
40315
40336
|
if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
|
|
40316
40337
|
}
|
|
@@ -40318,25 +40339,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40318
40339
|
return hasReturn;
|
|
40319
40340
|
}
|
|
40320
40341
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40321
|
-
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope,
|
|
40322
|
-
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope,
|
|
40323
|
-
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope,
|
|
40342
|
+
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
|
|
40343
|
+
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
|
|
40344
|
+
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
|
|
40324
40345
|
return blockHasReturn || handlerHasReturn || finalizerHasReturn;
|
|
40325
40346
|
}
|
|
40326
40347
|
if (isNodeOfType(statement, "CatchClause")) {
|
|
40327
40348
|
const catchScope = createBlockBindingScope(scope);
|
|
40328
40349
|
addPatternBindings(statement.param, catchScope);
|
|
40329
|
-
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope,
|
|
40350
|
+
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
|
|
40330
40351
|
}
|
|
40331
40352
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40332
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40353
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40333
40354
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40334
40355
|
return bodyHasReturn;
|
|
40335
40356
|
}
|
|
40336
40357
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40337
40358
|
const loopScope = createBlockBindingScope(scope);
|
|
40338
40359
|
if (statement.init) addDeclarationBindings(statement.init, loopScope);
|
|
40339
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
40360
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40340
40361
|
if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
|
|
40341
40362
|
if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
|
|
40342
40363
|
if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
|
|
@@ -40346,22 +40367,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40346
40367
|
const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
|
|
40347
40368
|
const loopScope = createBlockBindingScope(scope);
|
|
40348
40369
|
if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
|
|
40349
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
40370
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40350
40371
|
addNames(names, rightNames);
|
|
40351
40372
|
return true;
|
|
40352
40373
|
}
|
|
40353
|
-
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40374
|
+
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40354
40375
|
if (isNodeOfType(statement, "WithStatement")) {
|
|
40355
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
40376
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40356
40377
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
|
|
40357
40378
|
return bodyHasReturn;
|
|
40358
40379
|
}
|
|
40359
40380
|
return false;
|
|
40360
40381
|
};
|
|
40361
|
-
const collectRenderReachableNames = (componentBody,
|
|
40382
|
+
const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40362
40383
|
const names = /* @__PURE__ */ new Set();
|
|
40363
40384
|
if (!isNodeOfType(componentBody, "BlockStatement")) return names;
|
|
40364
|
-
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(),
|
|
40385
|
+
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
|
|
40365
40386
|
return names;
|
|
40366
40387
|
};
|
|
40367
40388
|
//#endregion
|
|
@@ -40384,36 +40405,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
|
|
|
40384
40405
|
};
|
|
40385
40406
|
//#endregion
|
|
40386
40407
|
//#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
|
|
40387
|
-
const
|
|
40388
|
-
|
|
40408
|
+
const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
|
|
40409
|
+
const getCalleeName = (node) => {
|
|
40410
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
40411
|
+
if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
|
|
40412
|
+
return null;
|
|
40413
|
+
};
|
|
40414
|
+
const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
|
|
40415
|
+
if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
|
|
40389
40416
|
if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
|
|
40390
40417
|
const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
|
|
40391
40418
|
return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
|
|
40392
40419
|
};
|
|
40393
|
-
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope
|
|
40420
|
+
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
|
|
40394
40421
|
if (!isNodeOfType(node, "ObjectExpression")) return;
|
|
40395
40422
|
for (const property of node.properties ?? []) {
|
|
40396
40423
|
if (!isNodeOfType(property, "Property")) continue;
|
|
40397
40424
|
const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
|
|
40398
40425
|
if (!propertyName) continue;
|
|
40399
|
-
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope
|
|
40426
|
+
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
|
|
40400
40427
|
functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
|
|
40401
40428
|
}
|
|
40402
40429
|
};
|
|
40403
|
-
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope
|
|
40430
|
+
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40404
40431
|
if (!isNodeOfType(statement, "VariableDeclaration")) return;
|
|
40405
40432
|
const declarationScope = getVariableDeclarationScope(statement, scope);
|
|
40406
40433
|
for (const declarator of statement.declarations ?? []) {
|
|
40407
40434
|
const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
|
|
40408
40435
|
if (!declarator.init) continue;
|
|
40409
|
-
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope
|
|
40436
|
+
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
|
|
40410
40437
|
for (const declaredBindingName of declaredBindingNames) {
|
|
40411
40438
|
if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
|
|
40412
|
-
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope
|
|
40439
|
+
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
|
|
40413
40440
|
}
|
|
40414
40441
|
}
|
|
40415
40442
|
};
|
|
40416
|
-
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope
|
|
40443
|
+
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40417
40444
|
if (isNodeOfType(statement, "FunctionDeclaration")) {
|
|
40418
40445
|
if (statement.id) {
|
|
40419
40446
|
const declaredBindingNames = addPatternBindings(statement.id, scope);
|
|
@@ -40422,61 +40449,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
|
|
|
40422
40449
|
return;
|
|
40423
40450
|
}
|
|
40424
40451
|
if (isNodeOfType(statement, "VariableDeclaration")) {
|
|
40425
|
-
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope
|
|
40452
|
+
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40426
40453
|
return;
|
|
40427
40454
|
}
|
|
40428
40455
|
if (isNodeOfType(statement, "BlockStatement")) {
|
|
40429
|
-
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
40456
|
+
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40430
40457
|
return;
|
|
40431
40458
|
}
|
|
40432
40459
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40433
|
-
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope
|
|
40434
|
-
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope
|
|
40460
|
+
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
|
|
40461
|
+
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
|
|
40435
40462
|
return;
|
|
40436
40463
|
}
|
|
40437
40464
|
if (isNodeOfType(statement, "SwitchStatement")) {
|
|
40438
|
-
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
40465
|
+
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40439
40466
|
return;
|
|
40440
40467
|
}
|
|
40441
40468
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40442
|
-
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope
|
|
40469
|
+
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
|
|
40443
40470
|
if (statement.handler) {
|
|
40444
40471
|
const catchScope = createBlockBindingScope(scope);
|
|
40445
40472
|
addPatternBindings(statement.handler.param, catchScope);
|
|
40446
|
-
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope
|
|
40473
|
+
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
|
|
40447
40474
|
}
|
|
40448
|
-
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope
|
|
40475
|
+
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
|
|
40449
40476
|
return;
|
|
40450
40477
|
}
|
|
40451
40478
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40452
40479
|
const loopScope = createBlockBindingScope(scope);
|
|
40453
|
-
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope
|
|
40454
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
40480
|
+
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
|
|
40481
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40455
40482
|
return;
|
|
40456
40483
|
}
|
|
40457
40484
|
if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
|
|
40458
40485
|
const loopScope = createBlockBindingScope(scope);
|
|
40459
|
-
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope
|
|
40486
|
+
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
|
|
40460
40487
|
else addPatternBindings(statement.left, loopScope);
|
|
40461
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
40488
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40462
40489
|
return;
|
|
40463
40490
|
}
|
|
40464
40491
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40465
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
40492
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40466
40493
|
return;
|
|
40467
40494
|
}
|
|
40468
|
-
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
40495
|
+
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40469
40496
|
};
|
|
40470
|
-
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope
|
|
40471
|
-
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope
|
|
40497
|
+
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
|
|
40498
|
+
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40472
40499
|
};
|
|
40473
|
-
const collectFunctionLikeLocalNames = (componentBody
|
|
40500
|
+
const collectFunctionLikeLocalNames = (componentBody) => {
|
|
40474
40501
|
const functionLikeLocalNames = /* @__PURE__ */ new Set();
|
|
40475
40502
|
if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
|
|
40476
40503
|
let previousSize = -1;
|
|
40477
40504
|
while (previousSize !== functionLikeLocalNames.size) {
|
|
40478
40505
|
previousSize = functionLikeLocalNames.size;
|
|
40479
|
-
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope()
|
|
40506
|
+
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
|
|
40480
40507
|
}
|
|
40481
40508
|
return functionLikeLocalNames;
|
|
40482
40509
|
};
|
|
@@ -40516,17 +40543,17 @@ const noEventTriggerState = defineRule({
|
|
|
40516
40543
|
create: (context) => {
|
|
40517
40544
|
const checkComponent = (componentBody) => {
|
|
40518
40545
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
40519
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
40546
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
40520
40547
|
if (useStateBindings.length === 0) return;
|
|
40521
40548
|
const analysis = getProgramAnalysis(componentBody);
|
|
40522
40549
|
if (!analysis) return;
|
|
40523
40550
|
const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
|
|
40524
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
40551
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
40525
40552
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
40526
|
-
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody,
|
|
40553
|
+
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
|
|
40527
40554
|
walkAst(componentBody, (effectCall) => {
|
|
40528
40555
|
if (!isNodeOfType(effectCall, "CallExpression")) return;
|
|
40529
|
-
if (!
|
|
40556
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
|
|
40530
40557
|
if ((effectCall.arguments?.length ?? 0) < 2) return;
|
|
40531
40558
|
const depsNode = effectCall.arguments[1];
|
|
40532
40559
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -40591,17 +40618,10 @@ const isShadowedByLocalBinding = (identifier) => {
|
|
|
40591
40618
|
};
|
|
40592
40619
|
const isRealFetchCall = (node) => {
|
|
40593
40620
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
40594
|
-
|
|
40595
|
-
|
|
40596
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
40597
|
-
const receiver = stripParenExpression(callee.object);
|
|
40598
|
-
return isNodeOfType(receiver, "Identifier") && FETCH_MEMBER_OBJECTS.has(receiver.name) && !isShadowedByLocalBinding(receiver);
|
|
40599
|
-
};
|
|
40600
|
-
const isXmlHttpRequestConstruction = (node) => {
|
|
40601
|
-
if (!isNodeOfType(node, "NewExpression")) return false;
|
|
40602
|
-
const callee = stripParenExpression(node.callee);
|
|
40603
|
-
return isNodeOfType(callee, "Identifier") && callee.name === "XMLHttpRequest";
|
|
40621
|
+
if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
|
|
40622
|
+
return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
|
|
40604
40623
|
};
|
|
40624
|
+
const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
|
|
40605
40625
|
const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
|
|
40606
40626
|
const resolveLocalFunction = (expression, context) => {
|
|
40607
40627
|
if (!expression) return null;
|
|
@@ -42362,7 +42382,7 @@ const noInitializeState = defineRule({
|
|
|
42362
42382
|
tags: ["test-noise"],
|
|
42363
42383
|
recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
|
|
42364
42384
|
create: (context) => ({ CallExpression(node) {
|
|
42365
|
-
if (!
|
|
42385
|
+
if (!isUseEffect(node)) return;
|
|
42366
42386
|
const dependencies = node.arguments?.[1];
|
|
42367
42387
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42368
42388
|
const analysis = getProgramAnalysis(node);
|
|
@@ -43974,7 +43994,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43974
43994
|
const setterElement = elements[1];
|
|
43975
43995
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
43976
43996
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
43977
|
-
if (!
|
|
43997
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
43978
43998
|
const initializer = declarator.init.arguments?.[0];
|
|
43979
43999
|
if (!initializer) continue;
|
|
43980
44000
|
const propRootName = getPropRootName(initializer, propNames);
|
|
@@ -43992,7 +44012,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43992
44012
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
43993
44013
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
43994
44014
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
43995
|
-
if (!
|
|
44015
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
43996
44016
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
43997
44017
|
const depsNode = effectCall.arguments[1];
|
|
43998
44018
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -44401,7 +44421,7 @@ const noMultiComp = defineRule({
|
|
|
44401
44421
|
});
|
|
44402
44422
|
//#endregion
|
|
44403
44423
|
//#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
|
|
44404
|
-
const collectUseRefBindingNames = (componentBody
|
|
44424
|
+
const collectUseRefBindingNames = (componentBody) => {
|
|
44405
44425
|
const useRefBindings = /* @__PURE__ */ new Set();
|
|
44406
44426
|
if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
|
|
44407
44427
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -44409,7 +44429,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
|
|
|
44409
44429
|
for (const declarator of statement.declarations ?? []) {
|
|
44410
44430
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
44411
44431
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
44412
|
-
if (!
|
|
44432
|
+
if (!isHookCall$2(declarator.init, "useRef")) continue;
|
|
44413
44433
|
useRefBindings.add(declarator.id.name);
|
|
44414
44434
|
}
|
|
44415
44435
|
}
|
|
@@ -44444,12 +44464,12 @@ const noMutableInDeps = defineRule({
|
|
|
44444
44464
|
create: (context) => {
|
|
44445
44465
|
const checkComponent = (componentBody, componentParams = []) => {
|
|
44446
44466
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
44447
|
-
const useRefBindingNames = collectUseRefBindingNames(componentBody
|
|
44467
|
+
const useRefBindingNames = collectUseRefBindingNames(componentBody);
|
|
44448
44468
|
const localBindingNames = collectLocalBindingNames(componentBody);
|
|
44449
44469
|
for (const param of componentParams) collectPatternNames(param, localBindingNames);
|
|
44450
44470
|
walkAst(componentBody, (child) => {
|
|
44451
44471
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
44452
|
-
if (!
|
|
44472
|
+
if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
|
|
44453
44473
|
if ((child.arguments?.length ?? 0) < 2) return;
|
|
44454
44474
|
const depsNode = child.arguments[1];
|
|
44455
44475
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -46362,10 +46382,16 @@ const noPassDataToParent = defineRule({
|
|
|
46362
46382
|
tags: ["test-noise"],
|
|
46363
46383
|
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",
|
|
46364
46384
|
create: (context) => {
|
|
46365
|
-
const isReactUseRefCall = (node) =>
|
|
46366
|
-
|
|
46385
|
+
const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
|
|
46386
|
+
allowGlobalReactNamespace: true,
|
|
46387
|
+
allowUnboundBareCalls: true
|
|
46388
|
+
});
|
|
46389
|
+
const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
|
|
46390
|
+
allowGlobalReactNamespace: true,
|
|
46391
|
+
allowUnboundBareCalls: true
|
|
46392
|
+
});
|
|
46367
46393
|
return { CallExpression(node) {
|
|
46368
|
-
if (!
|
|
46394
|
+
if (!isUseEffect(node)) return;
|
|
46369
46395
|
const analysis = getProgramAnalysis(node);
|
|
46370
46396
|
if (!analysis) return;
|
|
46371
46397
|
if (hasCleanup(analysis, node)) return;
|
|
@@ -46652,7 +46678,7 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46652
46678
|
tags: ["test-noise"],
|
|
46653
46679
|
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",
|
|
46654
46680
|
create: (context) => ({ CallExpression(node) {
|
|
46655
|
-
if (!
|
|
46681
|
+
if (!isUseEffect(node)) return;
|
|
46656
46682
|
const analysis = getProgramAnalysis(node);
|
|
46657
46683
|
if (!analysis) return;
|
|
46658
46684
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -47067,7 +47093,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
47067
47093
|
if (!isNodeOfType(element, "Identifier")) continue;
|
|
47068
47094
|
const binding = findVariableInitializer(effectNode, element.name);
|
|
47069
47095
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
|
|
47070
|
-
const calleeName = getCalleeName$
|
|
47096
|
+
const calleeName = getCalleeName$2(binding.initializer);
|
|
47071
47097
|
if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
|
|
47072
47098
|
}
|
|
47073
47099
|
return false;
|
|
@@ -47089,7 +47115,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47089
47115
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
47090
47116
|
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
47091
47117
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
47092
|
-
if (getCalleeName$
|
|
47118
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
47093
47119
|
const callbackArgument = binding.initializer.arguments?.[0];
|
|
47094
47120
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47095
47121
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
@@ -47121,7 +47147,7 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47121
47147
|
return {
|
|
47122
47148
|
...propStackTracker.visitors,
|
|
47123
47149
|
CallExpression(node) {
|
|
47124
|
-
if (!
|
|
47150
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
47125
47151
|
const callback = getEffectCallback(node);
|
|
47126
47152
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
47127
47153
|
const depsNode = node.arguments[1];
|
|
@@ -49014,7 +49040,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
49014
49040
|
tags: ["test-noise"],
|
|
49015
49041
|
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",
|
|
49016
49042
|
create: (context) => ({ CallExpression(node) {
|
|
49017
|
-
if (!
|
|
49043
|
+
if (!isUseEffect(node)) return;
|
|
49018
49044
|
const analysis = getProgramAnalysis(node);
|
|
49019
49045
|
if (!analysis) return;
|
|
49020
49046
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -49283,7 +49309,7 @@ const isTanStackServerFnHandlerCall = (node) => {
|
|
|
49283
49309
|
if (node.callee.property.name !== "handler") return false;
|
|
49284
49310
|
let currentNode = node.callee.object;
|
|
49285
49311
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
49286
|
-
const calleeName = getCalleeName$
|
|
49312
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
49287
49313
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
|
|
49288
49314
|
if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
|
|
49289
49315
|
currentNode = currentNode.callee.object;
|
|
@@ -49784,7 +49810,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49784
49810
|
create: (context) => {
|
|
49785
49811
|
const checkFunctionScope = (functionBody) => {
|
|
49786
49812
|
if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
|
|
49787
|
-
const useStateBindings = collectUseStateBindings(functionBody
|
|
49813
|
+
const useStateBindings = collectUseStateBindings(functionBody);
|
|
49788
49814
|
if (useStateBindings.length === 0) return;
|
|
49789
49815
|
const setterNameToStateName = /* @__PURE__ */ new Map();
|
|
49790
49816
|
for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
|
|
@@ -49793,7 +49819,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49793
49819
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
49794
49820
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
49795
49821
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
49796
|
-
if (!
|
|
49822
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
49797
49823
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
49798
49824
|
const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
|
|
49799
49825
|
if (dependencyStateNames.size === 0) continue;
|
|
@@ -49879,7 +49905,7 @@ const noSetStateInRender = defineRule({
|
|
|
49879
49905
|
create: (context) => {
|
|
49880
49906
|
const checkComponent = (componentBody) => {
|
|
49881
49907
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
49882
|
-
const setterNames = new Set(collectUseStateBindings(componentBody
|
|
49908
|
+
const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
|
|
49883
49909
|
if (setterNames.size === 0) return;
|
|
49884
49910
|
for (const statement of componentBody.body ?? []) {
|
|
49885
49911
|
const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
|
|
@@ -50128,10 +50154,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
|
|
|
50128
50154
|
});
|
|
50129
50155
|
return facts;
|
|
50130
50156
|
};
|
|
50131
|
-
const isEffectCallbackFunction = (functionNode
|
|
50157
|
+
const isEffectCallbackFunction = (functionNode) => {
|
|
50132
50158
|
const parent = functionNode.parent;
|
|
50133
50159
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
50134
|
-
return
|
|
50160
|
+
return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
|
|
50135
50161
|
};
|
|
50136
50162
|
const doesEffectCallbackReturnName = (effectCallback, name) => {
|
|
50137
50163
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -50150,24 +50176,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
|
|
|
50150
50176
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50151
50177
|
return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
|
|
50152
50178
|
};
|
|
50153
|
-
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope
|
|
50179
|
+
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
50154
50180
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50155
50181
|
if (cleanupBindingName === null) return false;
|
|
50156
50182
|
let isReturnedFromEffect = false;
|
|
50157
50183
|
walkAst(ownerScope, (child) => {
|
|
50158
50184
|
if (isReturnedFromEffect) return false;
|
|
50159
|
-
if (!isNodeOfType(child, "CallExpression") || !
|
|
50185
|
+
if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
50160
50186
|
const effectCallback = getEffectCallback(child);
|
|
50161
50187
|
if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
|
|
50162
50188
|
});
|
|
50163
50189
|
return isReturnedFromEffect;
|
|
50164
50190
|
};
|
|
50165
|
-
const isInsideEffectCleanupReturn = (node, ownerScope
|
|
50191
|
+
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
50166
50192
|
let functionNode = findEnclosingFunction$1(node);
|
|
50167
50193
|
while (functionNode) {
|
|
50168
50194
|
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
50169
|
-
if (outerFunction && isEffectCallbackFunction(outerFunction
|
|
50170
|
-
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope
|
|
50195
|
+
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
50196
|
+
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
50171
50197
|
functionNode = outerFunction;
|
|
50172
50198
|
}
|
|
50173
50199
|
return false;
|
|
@@ -50203,10 +50229,10 @@ const noStaleTimerRef = defineRule({
|
|
|
50203
50229
|
if (isShadowedTimerGlobal(node)) return;
|
|
50204
50230
|
const { clearCalleeName, refName } = clearCall;
|
|
50205
50231
|
const refBinding = findVariableInitializer(node, refName);
|
|
50206
|
-
if (!refBinding?.initializer || !
|
|
50232
|
+
if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
|
|
50207
50233
|
const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
|
|
50208
50234
|
if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
|
|
50209
|
-
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner
|
|
50235
|
+
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
|
|
50210
50236
|
if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
|
|
50211
50237
|
context.report({
|
|
50212
50238
|
node,
|
|
@@ -56010,7 +56036,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
|
|
|
56010
56036
|
allReadsAreInSubHandlers = false;
|
|
56011
56037
|
return;
|
|
56012
56038
|
}
|
|
56013
|
-
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$
|
|
56039
|
+
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
|
|
56014
56040
|
});
|
|
56015
56041
|
return {
|
|
56016
56042
|
hasAnyRead,
|
|
@@ -56033,7 +56059,7 @@ const preferUseEffectEvent = defineRule({
|
|
|
56033
56059
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
56034
56060
|
const effectCall = statement.expression;
|
|
56035
56061
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56036
|
-
if (!
|
|
56062
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
56037
56063
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56038
56064
|
const depsNode = effectCall.arguments[1];
|
|
56039
56065
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -56065,11 +56091,11 @@ const preferUseEffectEvent = defineRule({
|
|
|
56065
56091
|
});
|
|
56066
56092
|
//#endregion
|
|
56067
56093
|
//#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
|
|
56068
|
-
const findUseEffectsInComponent = (componentBody
|
|
56094
|
+
const findUseEffectsInComponent = (componentBody) => {
|
|
56069
56095
|
const effectCalls = [];
|
|
56070
56096
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
56071
56097
|
for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
|
|
56072
|
-
if (isNodeOfType(child, "CallExpression") &&
|
|
56098
|
+
if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
|
|
56073
56099
|
});
|
|
56074
56100
|
return effectCalls;
|
|
56075
56101
|
};
|
|
@@ -56272,7 +56298,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56272
56298
|
};
|
|
56273
56299
|
const checkComponent = (componentBody) => {
|
|
56274
56300
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
56275
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
56301
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
56276
56302
|
if (useStateBindings.length === 0) return;
|
|
56277
56303
|
const useStateInitializerByValueName = /* @__PURE__ */ new Map();
|
|
56278
56304
|
for (const binding of useStateBindings) {
|
|
@@ -56285,7 +56311,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56285
56311
|
}
|
|
56286
56312
|
const setterNameToValueName = /* @__PURE__ */ new Map();
|
|
56287
56313
|
for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
|
|
56288
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
56314
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56289
56315
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56290
56316
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56291
56317
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56327,7 +56353,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56327
56353
|
})).filter((candidate) => candidate.storeName !== null);
|
|
56328
56354
|
if (snapshotBindings.length === 0) return;
|
|
56329
56355
|
const reportedDeclarators = /* @__PURE__ */ new Set();
|
|
56330
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
56356
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56331
56357
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56332
56358
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56333
56359
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56424,7 +56450,7 @@ const preferUseReducer = defineRule({
|
|
|
56424
56450
|
create: (context) => {
|
|
56425
56451
|
const reportCoUpdatedUseState = (body, componentName) => {
|
|
56426
56452
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
56427
|
-
const bindings = collectUseStateBindings(body
|
|
56453
|
+
const bindings = collectUseStateBindings(body);
|
|
56428
56454
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
56429
56455
|
if (setterNames.size < 5) return;
|
|
56430
56456
|
const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
|
|
@@ -56640,7 +56666,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
|
|
|
56640
56666
|
]);
|
|
56641
56667
|
const isQueryCacheSourceCall = (initializer) => {
|
|
56642
56668
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
56643
|
-
const hookName = getCalleeName$
|
|
56669
|
+
const hookName = getCalleeName$2(initializer);
|
|
56644
56670
|
if (!hookName) return false;
|
|
56645
56671
|
return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
|
|
56646
56672
|
};
|
|
@@ -56772,7 +56798,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
56772
56798
|
},
|
|
56773
56799
|
CallExpression(node) {
|
|
56774
56800
|
if (!hasQueryReadUsage) {
|
|
56775
|
-
const callName = getCalleeName$
|
|
56801
|
+
const callName = getCalleeName$2(node);
|
|
56776
56802
|
if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
|
|
56777
56803
|
}
|
|
56778
56804
|
const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
|
|
@@ -57265,30 +57291,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
|
|
|
57265
57291
|
["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
|
|
57266
57292
|
["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
|
|
57267
57293
|
]);
|
|
57268
|
-
const resolveReactApiNameForIdentifier = (callee
|
|
57294
|
+
const resolveReactApiNameForIdentifier = (callee) => {
|
|
57269
57295
|
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
57270
|
-
if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
|
|
57271
57296
|
const importedName = getImportedNameFromModule(callee, callee.name, "react");
|
|
57272
57297
|
if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
|
|
57273
57298
|
return null;
|
|
57274
57299
|
};
|
|
57275
|
-
const resolveReactApiNameForMemberExpression = (callee
|
|
57300
|
+
const resolveReactApiNameForMemberExpression = (callee) => {
|
|
57276
57301
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
57277
57302
|
if (callee.computed) return null;
|
|
57278
|
-
const namespaceIdentifier =
|
|
57303
|
+
const namespaceIdentifier = callee.object;
|
|
57279
57304
|
const propertyIdentifier = callee.property;
|
|
57280
57305
|
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
57281
57306
|
if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
|
|
57282
57307
|
if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
|
|
57283
57308
|
const namespaceName = namespaceIdentifier.name;
|
|
57284
|
-
if (
|
|
57285
|
-
if (
|
|
57309
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
|
|
57310
|
+
if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
|
|
57286
57311
|
return null;
|
|
57287
57312
|
};
|
|
57288
|
-
const resolveReactApiNameForCallee = (callee
|
|
57289
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
57290
|
-
return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
|
|
57291
|
-
};
|
|
57313
|
+
const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
|
|
57292
57314
|
const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
|
|
57293
57315
|
const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
57294
57316
|
const calleeTrailingName = (callee) => {
|
|
@@ -57314,7 +57336,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
57314
57336
|
requires: ["react-compiler"],
|
|
57315
57337
|
recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
|
|
57316
57338
|
create: (context) => ({ CallExpression(node) {
|
|
57317
|
-
const apiName = resolveReactApiNameForCallee(node.callee
|
|
57339
|
+
const apiName = resolveReactApiNameForCallee(node.callee);
|
|
57318
57340
|
if (!apiName) return;
|
|
57319
57341
|
if (apiName === "memo") {
|
|
57320
57342
|
const comparatorArgument = node.arguments?.[1];
|
|
@@ -59064,7 +59086,7 @@ const rerenderDependencies = defineRule({
|
|
|
59064
59086
|
severity: "error",
|
|
59065
59087
|
recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
|
|
59066
59088
|
create: (context) => ({ CallExpression(node) {
|
|
59067
|
-
if (!
|
|
59089
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
59068
59090
|
const depsNode = node.arguments[1];
|
|
59069
59091
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
59070
59092
|
for (const element of depsNode.elements ?? []) {
|
|
@@ -59319,7 +59341,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
59319
59341
|
category: "Performance",
|
|
59320
59342
|
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
59321
59343
|
create: (context) => ({ CallExpression(node) {
|
|
59322
|
-
if (!
|
|
59344
|
+
if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
|
|
59323
59345
|
const initializer = stripParenExpression(node.arguments[0]);
|
|
59324
59346
|
const isPlainCall = isNodeOfType(initializer, "CallExpression");
|
|
59325
59347
|
const isNewCall = isNodeOfType(initializer, "NewExpression");
|
|
@@ -59383,7 +59405,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
59383
59405
|
category: "Performance",
|
|
59384
59406
|
recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
|
|
59385
59407
|
create: (context) => ({ CallExpression(node) {
|
|
59386
|
-
if (!
|
|
59408
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
59387
59409
|
const initializer = findEagerInitializerCall(node.arguments[0]);
|
|
59388
59410
|
if (!initializer) return;
|
|
59389
59411
|
const isConstructor = isNodeOfType(initializer, "NewExpression");
|
|
@@ -60146,11 +60168,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
|
|
|
60146
60168
|
}
|
|
60147
60169
|
return false;
|
|
60148
60170
|
};
|
|
60149
|
-
const collectEffectDependencyInfos = (componentBody, setterNames
|
|
60171
|
+
const collectEffectDependencyInfos = (componentBody, setterNames) => {
|
|
60150
60172
|
const effectInfos = [];
|
|
60151
60173
|
walkAst(componentBody, (child) => {
|
|
60152
60174
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60153
|
-
if (!
|
|
60175
|
+
if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
60154
60176
|
const dependencyNames = /* @__PURE__ */ new Set();
|
|
60155
60177
|
for (const argument of child.arguments ?? []) {
|
|
60156
60178
|
if (!isNodeOfType(argument, "ArrayExpression")) continue;
|
|
@@ -60206,14 +60228,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
|
|
|
60206
60228
|
});
|
|
60207
60229
|
return effectInfos;
|
|
60208
60230
|
};
|
|
60209
|
-
const collectCustomHookArgumentNames = (componentBody
|
|
60231
|
+
const collectCustomHookArgumentNames = (componentBody) => {
|
|
60210
60232
|
const argumentNames = /* @__PURE__ */ new Set();
|
|
60211
60233
|
walkAst(componentBody, (child) => {
|
|
60212
60234
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60213
60235
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
60214
60236
|
const calleeName = child.callee.name;
|
|
60215
60237
|
if (!isReactHookName(calleeName)) return;
|
|
60216
|
-
if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
|
|
60217
60238
|
if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
|
|
60218
60239
|
if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
|
|
60219
60240
|
for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
|
|
@@ -60254,21 +60275,21 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60254
60275
|
create: (context) => {
|
|
60255
60276
|
const checkComponent = (componentBody) => {
|
|
60256
60277
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60257
|
-
const bindings = collectUseStateBindings(componentBody
|
|
60278
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
60258
60279
|
if (bindings.length === 0) return;
|
|
60259
60280
|
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
60260
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
60281
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
60261
60282
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60262
|
-
const directRenderNames = collectRenderReachableNames(componentBody,
|
|
60283
|
+
const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
|
|
60263
60284
|
if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
|
|
60264
60285
|
const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
|
|
60265
60286
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
60266
|
-
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames
|
|
60287
|
+
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
|
|
60267
60288
|
const selfEchoValueNames = /* @__PURE__ */ new Set();
|
|
60268
60289
|
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);
|
|
60269
60290
|
const effectConsumedNames = /* @__PURE__ */ new Set();
|
|
60270
60291
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
60271
|
-
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody
|
|
60292
|
+
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
|
|
60272
60293
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
60273
60294
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60274
60295
|
walkAst(componentBody, (child) => {
|
|
@@ -68169,7 +68190,7 @@ const declarationAwaitsGate = (declaration, context) => {
|
|
|
68169
68190
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
68170
68191
|
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
68171
68192
|
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
68172
|
-
const calleeName = getCalleeName$
|
|
68193
|
+
const calleeName = getCalleeName$2(argument);
|
|
68173
68194
|
if (!calleeName) continue;
|
|
68174
68195
|
if (isAuthGuardName(calleeName)) return true;
|
|
68175
68196
|
const [leadingToken] = tokenizeIdentifierWords(calleeName);
|
|
@@ -68809,7 +68830,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
68809
68830
|
if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
|
|
68810
68831
|
let currentNode = stripParenExpression(outerNode.callee.object);
|
|
68811
68832
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
68812
|
-
const calleeName = getCalleeName$
|
|
68833
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
68813
68834
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
|
|
68814
68835
|
result.isServerFnChain = true;
|
|
68815
68836
|
const optionsArgument = currentNode.arguments?.[0];
|