oxlint-plugin-react-doctor 0.7.9-dev.2450582 → 0.7.9-dev.2979a9b
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.d.ts +0 -46
- package/dist/index.js +413 -1512
- 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,37 @@ 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) => {
|
|
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") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
1246
|
+
visitedSymbolIds.add(symbol.id);
|
|
1247
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
1248
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
1249
|
+
symbol = scopes.symbolFor(initializer);
|
|
1250
|
+
}
|
|
1251
|
+
return symbol;
|
|
1252
|
+
};
|
|
1253
|
+
//#endregion
|
|
1340
1254
|
//#region src/plugin/utils/resolve-exact-local-function.ts
|
|
1341
1255
|
const resolveExactLocalFunction = (expression, scopes) => {
|
|
1342
1256
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -1382,17 +1296,9 @@ const getRootIdentifier$1 = (node, options) => {
|
|
|
1382
1296
|
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1383
1297
|
const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
|
|
1384
1298
|
//#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
1299
|
//#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1300
|
+
const STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
1301
|
+
"useCallback",
|
|
1396
1302
|
"useEffectEvent",
|
|
1397
1303
|
"useEvent",
|
|
1398
1304
|
"useEventCallback",
|
|
@@ -1401,21 +1307,21 @@ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
|
1401
1307
|
]);
|
|
1402
1308
|
const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
|
|
1403
1309
|
const isThrottledHandlerHookCall = (callNode) => {
|
|
1404
|
-
const calleeName = getCalleeName$
|
|
1310
|
+
const calleeName = getCalleeName$2(callNode);
|
|
1405
1311
|
return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
|
|
1406
1312
|
};
|
|
1407
|
-
const isEmptyDepsUseMemoCall = (callNode
|
|
1408
|
-
if (!
|
|
1313
|
+
const isEmptyDepsUseMemoCall = (callNode) => {
|
|
1314
|
+
if (!isHookCall$2(callNode, "useMemo")) return false;
|
|
1409
1315
|
const memoDepsNode = callNode.arguments?.[1];
|
|
1410
1316
|
return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
|
|
1411
1317
|
};
|
|
1412
|
-
const isStableHandlerInitializer = (initializer
|
|
1413
|
-
if (isNodeOfType(initializer, "CallExpression")) return
|
|
1318
|
+
const isStableHandlerInitializer = (initializer) => {
|
|
1319
|
+
if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
|
|
1414
1320
|
return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
|
|
1415
1321
|
};
|
|
1416
|
-
const isStableRefReceiverDep = (referenceNode, receiverDepName
|
|
1322
|
+
const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
|
|
1417
1323
|
const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
|
|
1418
|
-
return Boolean(receiverBinding?.initializer &&
|
|
1324
|
+
return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
|
|
1419
1325
|
};
|
|
1420
1326
|
const advancedEventHandlerRefs = defineRule({
|
|
1421
1327
|
id: "advanced-event-handler-refs",
|
|
@@ -1425,7 +1331,7 @@ const advancedEventHandlerRefs = defineRule({
|
|
|
1425
1331
|
category: "Performance",
|
|
1426
1332
|
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
1333
|
create: (context) => ({ CallExpression(node) {
|
|
1428
|
-
if (!
|
|
1334
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
1429
1335
|
if ((node.arguments?.length ?? 0) < 2) return;
|
|
1430
1336
|
const callback = getEffectCallback(node);
|
|
1431
1337
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -1449,8 +1355,8 @@ const advancedEventHandlerRefs = defineRule({
|
|
|
1449
1355
|
});
|
|
1450
1356
|
if (!registeredHandlerName) return;
|
|
1451
1357
|
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
|
|
1358
|
+
if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
|
|
1359
|
+
if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
|
|
1454
1360
|
context.report({
|
|
1455
1361
|
node,
|
|
1456
1362
|
message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
|
|
@@ -1937,6 +1843,15 @@ const flattenJsxName$1 = (node) => {
|
|
|
1937
1843
|
return null;
|
|
1938
1844
|
};
|
|
1939
1845
|
//#endregion
|
|
1846
|
+
//#region src/plugin/utils/get-static-property-name.ts
|
|
1847
|
+
const getStaticPropertyName = (memberExpression) => {
|
|
1848
|
+
const property = memberExpression.property;
|
|
1849
|
+
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
1850
|
+
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
1851
|
+
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
1852
|
+
return null;
|
|
1853
|
+
};
|
|
1854
|
+
//#endregion
|
|
1940
1855
|
//#region src/plugin/utils/is-generated-image-renderer-call.ts
|
|
1941
1856
|
const GENERATED_IMAGE_RENDERER_MODULES = [
|
|
1942
1857
|
"next/og",
|
|
@@ -4944,6 +4859,23 @@ const containsDirectAwait = (node) => {
|
|
|
4944
4859
|
return foundAwait;
|
|
4945
4860
|
};
|
|
4946
4861
|
//#endregion
|
|
4862
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
4863
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
4864
|
+
if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
|
|
4865
|
+
const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
|
|
4866
|
+
if (node.computed) {
|
|
4867
|
+
if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
|
|
4868
|
+
if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
|
|
4869
|
+
return null;
|
|
4870
|
+
}
|
|
4871
|
+
if (isNodeOfType(key, "Identifier")) return key.name;
|
|
4872
|
+
if (isNodeOfType(key, "Literal")) {
|
|
4873
|
+
if (typeof key.value === "string") return key.value;
|
|
4874
|
+
if (options.stringifyNonStringLiterals) return String(key.value);
|
|
4875
|
+
}
|
|
4876
|
+
return null;
|
|
4877
|
+
};
|
|
4878
|
+
//#endregion
|
|
4947
4879
|
//#region src/plugin/utils/get-destructured-binding-property-name.ts
|
|
4948
4880
|
const getDestructuredBindingPropertyName = (bindingIdentifier) => {
|
|
4949
4881
|
let bindingNode = bindingIdentifier;
|
|
@@ -9008,6 +8940,65 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
|
9008
8940
|
}
|
|
9009
8941
|
};
|
|
9010
8942
|
//#endregion
|
|
8943
|
+
//#region src/plugin/utils/is-react-api-call.ts
|
|
8944
|
+
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
8945
|
+
const isImportedFromReact = (symbol) => {
|
|
8946
|
+
if (symbol.kind !== "import") return false;
|
|
8947
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
8948
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
8949
|
+
};
|
|
8950
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
|
|
8951
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
8952
|
+
const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
|
|
8953
|
+
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8954
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
8955
|
+
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
8956
|
+
};
|
|
8957
|
+
const isReactNamespaceImport = (identifier, scopes) => {
|
|
8958
|
+
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
8959
|
+
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8960
|
+
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8961
|
+
};
|
|
8962
|
+
const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
|
|
8963
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8964
|
+
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8965
|
+
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
8966
|
+
};
|
|
8967
|
+
const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
|
|
8968
|
+
const symbol = scopes.symbolFor(identifier);
|
|
8969
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
8970
|
+
const pattern = symbol.declarationNode.id;
|
|
8971
|
+
if (!isNodeOfType(pattern, "ObjectPattern")) return false;
|
|
8972
|
+
for (const property of pattern.properties) {
|
|
8973
|
+
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8974
|
+
const propertyName = getStaticPropertyKeyName(property);
|
|
8975
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
|
|
8976
|
+
}
|
|
8977
|
+
return false;
|
|
8978
|
+
};
|
|
8979
|
+
const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
8980
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
8981
|
+
return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
|
|
8982
|
+
};
|
|
8983
|
+
const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
|
|
8984
|
+
const callee = stripParenExpression(rawCallee);
|
|
8985
|
+
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));
|
|
8986
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
8987
|
+
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
8988
|
+
if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
|
|
8989
|
+
if (options.resolveConditionalAliases) {
|
|
8990
|
+
const symbol = scopes.symbolFor(callee);
|
|
8991
|
+
if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
|
|
8992
|
+
visitedSymbolIds.add(symbol.id);
|
|
8993
|
+
return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8997
|
+
}
|
|
8998
|
+
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8999
|
+
return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
|
|
9000
|
+
};
|
|
9001
|
+
//#endregion
|
|
9011
9002
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
9012
9003
|
const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
|
|
9013
9004
|
"AbortSignal",
|
|
@@ -13661,7 +13652,7 @@ const getPromiseChainCallForCallback = (candidate) => {
|
|
|
13661
13652
|
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
13662
13653
|
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
13663
13654
|
};
|
|
13664
|
-
const
|
|
13655
|
+
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
13665
13656
|
const invokedFunctions = new Set([effectCallback]);
|
|
13666
13657
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
13667
13658
|
const calledBindingNames = /* @__PURE__ */ new Set();
|
|
@@ -13695,14 +13686,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
|
|
|
13695
13686
|
calledBindingNames.add(callee.name);
|
|
13696
13687
|
return;
|
|
13697
13688
|
}
|
|
13698
|
-
if (
|
|
13689
|
+
if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
|
|
13699
13690
|
});
|
|
13700
13691
|
for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
|
|
13701
13692
|
}
|
|
13702
13693
|
return invokedFunctions;
|
|
13703
13694
|
};
|
|
13704
|
-
const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
|
|
13705
|
-
const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
|
|
13706
13695
|
//#endregion
|
|
13707
13696
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
13708
13697
|
const isReactHookName = (name) => {
|
|
@@ -13861,19 +13850,15 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
|
13861
13850
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
13862
13851
|
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
13863
13852
|
};
|
|
13864
|
-
const
|
|
13853
|
+
const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13865
13854
|
const expression = stripParenExpression(node);
|
|
13866
|
-
|
|
13867
|
-
if (
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
13871
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
13872
|
-
if (!initializer) return null;
|
|
13855
|
+
if (resolveReactRefSymbol(expression, scopes)) return true;
|
|
13856
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
13857
|
+
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
13858
|
+
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
13873
13859
|
visitedSymbolIds.add(symbol.id);
|
|
13874
|
-
return
|
|
13860
|
+
return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
|
|
13875
13861
|
};
|
|
13876
|
-
const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
|
|
13877
13862
|
//#endregion
|
|
13878
13863
|
//#region src/plugin/utils/walk-inside-statement-blocks.ts
|
|
13879
13864
|
const walkInsideStatementBlocks = (node, visitor) => {
|
|
@@ -13891,7 +13876,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
|
|
|
13891
13876
|
};
|
|
13892
13877
|
//#endregion
|
|
13893
13878
|
//#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
|
|
13894
|
-
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13895
13879
|
const getSubscribeLikeMethodName = (node) => {
|
|
13896
13880
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
13897
13881
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
@@ -13902,11 +13886,6 @@ const isSubscribeLikeCallExpression = (node) => {
|
|
|
13902
13886
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13903
13887
|
return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
|
|
13904
13888
|
};
|
|
13905
|
-
const getSubscribeOrObserveMethodName = (node) => {
|
|
13906
|
-
const methodName = getSubscribeLikeMethodName(node);
|
|
13907
|
-
return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
|
|
13908
|
-
};
|
|
13909
|
-
const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
|
|
13910
13889
|
const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
13911
13890
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13912
13891
|
if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
|
|
@@ -13959,6 +13938,7 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
13959
13938
|
};
|
|
13960
13939
|
//#endregion
|
|
13961
13940
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
13941
|
+
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13962
13942
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
13963
13943
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
13964
13944
|
const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -13968,6 +13948,10 @@ const RESOURCE_NOUN_BY_KIND = {
|
|
|
13968
13948
|
socket: "connection"
|
|
13969
13949
|
};
|
|
13970
13950
|
const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
|
|
13951
|
+
const isSubscribeOrObserveCall = (node) => {
|
|
13952
|
+
if (isSubscribeLikeCallExpression(node)) return true;
|
|
13953
|
+
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
|
|
13954
|
+
};
|
|
13971
13955
|
const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13972
13956
|
if (!expression) return null;
|
|
13973
13957
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14069,13 +14053,12 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
14069
14053
|
});
|
|
14070
14054
|
return;
|
|
14071
14055
|
}
|
|
14072
|
-
|
|
14073
|
-
if (subscribeOrObserveMethodName !== null) {
|
|
14056
|
+
if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) {
|
|
14074
14057
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
14075
14058
|
usages.push({
|
|
14076
14059
|
kind: "subscribe",
|
|
14077
14060
|
node: child,
|
|
14078
|
-
resourceName:
|
|
14061
|
+
resourceName: child.callee.property.name,
|
|
14079
14062
|
handleKey: findAssignedResourceKey(child, context),
|
|
14080
14063
|
...registrationDetails
|
|
14081
14064
|
});
|
|
@@ -14357,39 +14340,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
|
|
|
14357
14340
|
}
|
|
14358
14341
|
return null;
|
|
14359
14342
|
};
|
|
14360
|
-
const findPushedResourceCollectionKey = (usage, context) => {
|
|
14361
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return null;
|
|
14362
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
14363
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
|
|
14364
|
-
const resourceIdentifier = stripParenExpression(registrationCallee.object);
|
|
14365
|
-
if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
|
|
14366
|
-
const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
|
|
14367
|
-
if (!resourceSymbol) return null;
|
|
14368
|
-
const pushCalls = resourceSymbol.references.flatMap((reference) => {
|
|
14369
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14370
|
-
const callNode = referenceRoot.parent;
|
|
14371
|
-
if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
|
|
14372
|
-
const pushCallee = stripParenExpression(callNode.callee);
|
|
14373
|
-
return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
|
|
14374
|
-
});
|
|
14375
|
-
if (pushCalls.length !== 1) return null;
|
|
14376
|
-
const pushCall = pushCalls[0];
|
|
14377
|
-
if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
|
|
14378
|
-
const pushCallee = stripParenExpression(pushCall.callee);
|
|
14379
|
-
if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
|
|
14380
|
-
const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
|
|
14381
|
-
const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
14382
|
-
if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
|
|
14383
|
-
return collectionSymbol.references.every((reference) => {
|
|
14384
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14385
|
-
const forOfStatement = referenceRoot.parent;
|
|
14386
|
-
if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
|
|
14387
|
-
const memberNode = referenceRoot.parent;
|
|
14388
|
-
const callNode = memberNode?.parent;
|
|
14389
|
-
if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
|
|
14390
|
-
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14391
|
-
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14392
|
-
};
|
|
14393
14343
|
const isWithinAssignmentTarget = (identifier) => {
|
|
14394
14344
|
let currentNode = identifier;
|
|
14395
14345
|
let parentNode = currentNode.parent;
|
|
@@ -14448,14 +14398,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
14448
14398
|
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
|
|
14449
14399
|
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
14450
14400
|
};
|
|
14451
|
-
const findEnclosingForEachCall = (node) => {
|
|
14452
|
-
const callbackNode = findEnclosingFunction$1(node);
|
|
14453
|
-
if (!callbackNode) return null;
|
|
14454
|
-
const callNode = callbackNode.parent;
|
|
14455
|
-
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
|
|
14456
|
-
const callee = stripParenExpression(callNode.callee);
|
|
14457
|
-
return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
|
|
14458
|
-
};
|
|
14459
14401
|
const findDirectCallForReference = (identifier) => {
|
|
14460
14402
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
14461
14403
|
const callNode = expressionRoot.parent;
|
|
@@ -14470,7 +14412,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
14470
14412
|
const callNode = findDirectCallForReference(reference.identifier);
|
|
14471
14413
|
return callNode ? [callNode] : [];
|
|
14472
14414
|
});
|
|
14473
|
-
if (invocationCalls.length !== 1
|
|
14415
|
+
if (invocationCalls.length !== 1) return null;
|
|
14474
14416
|
const invocationCall = invocationCalls[0];
|
|
14475
14417
|
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
14476
14418
|
};
|
|
@@ -14504,15 +14446,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14504
14446
|
if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
|
|
14505
14447
|
const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
|
|
14506
14448
|
if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
|
|
14507
|
-
const
|
|
14508
|
-
const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
|
|
14509
|
-
const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
|
|
14510
|
-
const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
|
|
14511
|
-
if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
|
|
14512
|
-
if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
|
|
14513
|
-
return;
|
|
14514
|
-
}
|
|
14515
|
-
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
|
|
14449
|
+
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
|
|
14516
14450
|
if (!cleanupForOfStatement) {
|
|
14517
14451
|
didCleanupFunctionMatch = true;
|
|
14518
14452
|
return false;
|
|
@@ -14556,28 +14490,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14556
14490
|
});
|
|
14557
14491
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14558
14492
|
};
|
|
14559
|
-
const
|
|
14560
|
-
if (
|
|
14561
|
-
const
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14493
|
+
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
|
|
14494
|
+
if (usage.handleKey === null) return null;
|
|
14495
|
+
const doesTestRequireLiveHandle = (test) => {
|
|
14496
|
+
if (resolveExpressionKey(test, context) === usage.handleKey) return true;
|
|
14497
|
+
const unwrappedTest = stripParenExpression(test);
|
|
14498
|
+
if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
|
|
14499
|
+
const isNullishOperand = (operand) => {
|
|
14500
|
+
const unwrappedOperand = stripParenExpression(operand);
|
|
14501
|
+
return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
|
|
14502
|
+
};
|
|
14503
|
+
return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
|
|
14566
14504
|
};
|
|
14567
|
-
return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
|
|
14568
|
-
};
|
|
14569
|
-
const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
|
|
14570
14505
|
let ancestor = releaseCall.parent;
|
|
14571
14506
|
while (ancestor && ancestor !== owner) {
|
|
14572
14507
|
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
14573
|
-
if (ancestor.alternate !== null || !
|
|
14508
|
+
if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
|
|
14574
14509
|
return ancestor;
|
|
14575
14510
|
}
|
|
14576
14511
|
ancestor = ancestor.parent;
|
|
14577
14512
|
}
|
|
14578
14513
|
return null;
|
|
14579
14514
|
};
|
|
14580
|
-
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
|
|
14581
14515
|
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
14582
14516
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
14583
14517
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
@@ -14606,7 +14540,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
14606
14540
|
walkAst(componentFunction.body, (child) => {
|
|
14607
14541
|
if (didFindUnmountCleanup) return false;
|
|
14608
14542
|
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
|
|
14609
|
-
if (!
|
|
14543
|
+
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
14610
14544
|
const dependencyList = child.arguments?.[1];
|
|
14611
14545
|
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
14612
14546
|
const cleanupCallback = getEffectCallback(child);
|
|
@@ -14765,108 +14699,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
14765
14699
|
});
|
|
14766
14700
|
return hasPotentialInterruption;
|
|
14767
14701
|
};
|
|
14768
|
-
const getNumericReactRefCurrentKey = (expression, context) => {
|
|
14769
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
|
|
14770
|
-
const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
14771
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
14772
|
-
const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
|
|
14773
|
-
if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
|
|
14774
|
-
return resolveExpressionKey(expression, context);
|
|
14775
|
-
};
|
|
14776
|
-
const getBlockingGenerationKey = (expression, context) => {
|
|
14777
|
-
const test = stripParenExpression(expression);
|
|
14778
|
-
if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
|
|
14779
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
|
|
14780
|
-
const leftKey = getNumericReactRefCurrentKey(test.left, context);
|
|
14781
|
-
const rightKey = getNumericReactRefCurrentKey(test.right, context);
|
|
14782
|
-
const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
|
|
14783
|
-
const key = leftKey ?? rightKey;
|
|
14784
|
-
return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
|
|
14785
|
-
};
|
|
14786
|
-
const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
|
|
14787
|
-
if (!isFunctionLike$1(usageFunction)) return null;
|
|
14788
|
-
let generationKey = null;
|
|
14789
|
-
walkAst(usageFunction.body, (child) => {
|
|
14790
|
-
if (generationKey) return false;
|
|
14791
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
14792
|
-
if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
|
|
14793
|
-
const key = getBlockingGenerationKey(child.test, context);
|
|
14794
|
-
if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
|
|
14795
|
-
generationKey = key;
|
|
14796
|
-
});
|
|
14797
|
-
return generationKey;
|
|
14798
|
-
};
|
|
14799
|
-
const isGenerationAdvance = (node, generationKey, context) => {
|
|
14800
|
-
if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
|
|
14801
|
-
if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
|
|
14802
|
-
const amount = stripParenExpression(node.right);
|
|
14803
|
-
return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
|
|
14804
|
-
};
|
|
14805
|
-
const functionAdvancesGeneration = (owner, generationKey, context) => {
|
|
14806
|
-
if (!isFunctionLike$1(owner)) return false;
|
|
14807
|
-
let didAdvanceGeneration = false;
|
|
14808
|
-
walkAst(owner.body, (child) => {
|
|
14809
|
-
if (didAdvanceGeneration) return false;
|
|
14810
|
-
if (child !== owner.body && isFunctionLike$1(child)) return false;
|
|
14811
|
-
if (isGenerationAdvance(child, generationKey, context)) {
|
|
14812
|
-
didAdvanceGeneration = true;
|
|
14813
|
-
return false;
|
|
14814
|
-
}
|
|
14815
|
-
});
|
|
14816
|
-
return didAdvanceGeneration;
|
|
14817
|
-
};
|
|
14818
|
-
const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
|
|
14819
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
14820
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14821
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
14822
|
-
});
|
|
14823
|
-
const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
|
|
14824
|
-
const directCall = findDirectCallForReference(reference);
|
|
14825
|
-
if (directCall) {
|
|
14826
|
-
const referenceOwner = findEnclosingFunction$1(directCall);
|
|
14827
|
-
if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
|
|
14828
|
-
const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
|
|
14829
|
-
return generationKey ? { generationKey } : null;
|
|
14830
|
-
}
|
|
14831
|
-
const referenceRoot = findTransparentExpressionRoot(reference);
|
|
14832
|
-
const schedulerCall = referenceRoot.parent;
|
|
14833
|
-
if (!isNodeOfType(schedulerCall, "CallExpression") || !schedulerCall.arguments.some((argument) => argument === referenceRoot) || !isNodeOfType(schedulerCall.callee, "Identifier") || schedulerCall.callee.name !== "setTimeout" || !context.scopes.isGlobalReference(schedulerCall.callee)) return null;
|
|
14834
|
-
const schedulerUsage = {
|
|
14835
|
-
kind: "timer",
|
|
14836
|
-
node: schedulerCall,
|
|
14837
|
-
resourceName: schedulerCall.callee.name,
|
|
14838
|
-
handleKey: findAssignedResourceKey(schedulerCall, context),
|
|
14839
|
-
receiverKey: null,
|
|
14840
|
-
registrationVerbName: schedulerCall.callee.name,
|
|
14841
|
-
eventKey: null,
|
|
14842
|
-
handlerKey: null
|
|
14843
|
-
};
|
|
14844
|
-
const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
|
|
14845
|
-
return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
|
|
14846
|
-
};
|
|
14847
|
-
const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14848
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14849
|
-
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14850
|
-
const usageAssignment = usageExpression.parent;
|
|
14851
|
-
if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
14852
|
-
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14853
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14854
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14855
|
-
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
14856
|
-
});
|
|
14857
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
|
|
14858
|
-
const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
14859
|
-
if (!functionSymbol || functionSymbol.references.length === 0) return false;
|
|
14860
|
-
const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
|
|
14861
|
-
if (ownedReferences.some((reference) => reference === null)) return false;
|
|
14862
|
-
const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
|
|
14863
|
-
if (generationKeys.size !== 1) return false;
|
|
14864
|
-
const generationKey = generationKeys.values().next().value;
|
|
14865
|
-
if (typeof generationKey !== "string") return false;
|
|
14866
|
-
return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
|
|
14867
|
-
};
|
|
14868
14702
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14869
|
-
if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
|
|
14870
14703
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14871
14704
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
14872
14705
|
if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
@@ -15037,7 +14870,7 @@ const getReleaseVerbName = (node) => {
|
|
|
15037
14870
|
const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
|
|
15038
14871
|
const releaseFunction = findEnclosingFunction$1(releaseReceiver);
|
|
15039
14872
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15040
|
-
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction
|
|
14873
|
+
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
|
|
15041
14874
|
const controllerKey = getListenerAbortControllerKey(usage, context);
|
|
15042
14875
|
const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
|
|
15043
14876
|
if (controllerKey === null || refCurrentKey === null) return false;
|
|
@@ -15057,62 +14890,6 @@ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) =>
|
|
|
15057
14890
|
const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
|
|
15058
14891
|
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15059
14892
|
};
|
|
15060
|
-
const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
|
|
15061
|
-
const isFunctionForwardedToReactRef = (functionNode, context) => {
|
|
15062
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15063
|
-
if (!bindingIdentifier) return false;
|
|
15064
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15065
|
-
if (!symbol) return false;
|
|
15066
|
-
return symbol.references.some((reference) => {
|
|
15067
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15068
|
-
const expressionContainer = referenceRoot.parent;
|
|
15069
|
-
return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
|
|
15070
|
-
});
|
|
15071
|
-
};
|
|
15072
|
-
const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
|
|
15073
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15074
|
-
if (!bindingIdentifier) return false;
|
|
15075
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15076
|
-
if (!symbol) return false;
|
|
15077
|
-
return symbol.references.some((reference) => {
|
|
15078
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15079
|
-
const property = referenceRoot.parent;
|
|
15080
|
-
const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
|
|
15081
|
-
if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
|
|
15082
|
-
const returnedObject = findTransparentExpressionRoot(property.parent);
|
|
15083
|
-
const returnStatement = returnedObject.parent;
|
|
15084
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
|
|
15085
|
-
const ownerFunction = findEnclosingFunction$1(returnStatement);
|
|
15086
|
-
return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
|
|
15087
|
-
});
|
|
15088
|
-
};
|
|
15089
|
-
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
15090
|
-
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
15091
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
15092
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15093
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
|
|
15094
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
15095
|
-
const releaseCallee = stripParenExpression(releaseCall.callee);
|
|
15096
|
-
const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
|
|
15097
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed || !isNodeOfType(registrationCallee.property, "Identifier") || registrationCallee.property.name !== "addEventListener" || !isNodeOfType(releaseCallee, "MemberExpression") || releaseCallee.computed || !isNodeOfType(releaseCallee.property, "Identifier") || releaseCallee.property.name !== "removeEventListener" || !releaseRefSymbol) return false;
|
|
15098
|
-
const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
|
|
15099
|
-
const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
|
|
15100
|
-
const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
|
|
15101
|
-
if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
|
|
15102
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15103
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15104
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15105
|
-
const releaseStart = getRangeStart(releaseCall);
|
|
15106
|
-
const matchingOwnershipAssignments = [];
|
|
15107
|
-
const usageFunctionBody = usageFunction.body;
|
|
15108
|
-
walkAst(usageFunctionBody, (child) => {
|
|
15109
|
-
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
15110
|
-
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
|
|
15111
|
-
});
|
|
15112
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15113
|
-
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15114
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15115
|
-
};
|
|
15116
14893
|
const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
15117
14894
|
const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
15118
14895
|
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
@@ -15129,21 +14906,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15129
14906
|
if (!releaseVerbName) return false;
|
|
15130
14907
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
15131
14908
|
const releaseReceiverKey = resolveExpressionKey(callee.object, context);
|
|
15132
|
-
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15133
|
-
const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15134
|
-
const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
|
|
15135
|
-
const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
|
|
15136
|
-
const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
|
|
15137
|
-
if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
|
|
15138
|
-
if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
|
|
15139
14909
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15140
14910
|
if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
|
|
15141
14911
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15142
14912
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15143
14913
|
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
15144
|
-
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
15145
14914
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15146
14915
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
14916
|
+
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15147
14917
|
const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
|
|
15148
14918
|
const releaseEventArgument = callNode.arguments?.[0];
|
|
15149
14919
|
if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
|
|
@@ -15177,14 +14947,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15177
14947
|
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
15178
14948
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15179
14949
|
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
15180
|
-
|
|
15181
|
-
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14950
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
|
|
15182
14951
|
}
|
|
15183
14952
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15184
14953
|
return true;
|
|
15185
14954
|
};
|
|
15186
14955
|
const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
|
|
15187
|
-
const isReturnedEffectCleanupFunction = (functionNode
|
|
14956
|
+
const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
15188
14957
|
let currentNode = functionNode;
|
|
15189
14958
|
let parentNode = currentNode.parent;
|
|
15190
14959
|
while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
|
|
@@ -15193,175 +14962,23 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
|
|
|
15193
14962
|
}
|
|
15194
14963
|
const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
|
|
15195
14964
|
const effectCall = effectCallback?.parent;
|
|
15196
|
-
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") &&
|
|
14965
|
+
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
15197
14966
|
};
|
|
15198
14967
|
const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
15199
|
-
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode
|
|
14968
|
+
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
|
|
15200
14969
|
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15201
14970
|
if (!bindingIdentifier) return false;
|
|
15202
14971
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15203
14972
|
if (!symbol) return false;
|
|
15204
14973
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
15205
14974
|
};
|
|
15206
|
-
const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
|
|
15207
|
-
if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
|
|
15208
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15209
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
|
|
15210
|
-
const assignments = /* @__PURE__ */ new Map();
|
|
15211
|
-
const collectAssignment = (expression) => {
|
|
15212
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
15213
|
-
const assignment = expressionRoot.parent;
|
|
15214
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
|
|
15215
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
|
|
15216
|
-
const refCurrentKey = resolveExpressionKey(assignment.left, context);
|
|
15217
|
-
const retainedFunction = findEnclosingFunction$1(assignment);
|
|
15218
|
-
const assignmentStart = getRangeStart(assignment);
|
|
15219
|
-
if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
|
|
15220
|
-
assignments.set(assignmentStart, {
|
|
15221
|
-
assignmentNode: assignment,
|
|
15222
|
-
refCurrentKey,
|
|
15223
|
-
retainedFunction
|
|
15224
|
-
});
|
|
15225
|
-
};
|
|
15226
|
-
collectAssignment(disposerFunction);
|
|
15227
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
|
|
15228
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
15229
|
-
for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
|
|
15230
|
-
walkAst(usageFunction.body, (child) => {
|
|
15231
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
15232
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
|
|
15233
|
-
});
|
|
15234
|
-
return [...assignments.values()];
|
|
15235
|
-
};
|
|
15236
|
-
const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
|
|
15237
|
-
const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
|
|
15238
|
-
let hasUnsafeOverwrite = false;
|
|
15239
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15240
|
-
if (hasUnsafeOverwrite) return false;
|
|
15241
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15242
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
|
|
15243
|
-
const storedValue = resolveStableValue(child.right, context);
|
|
15244
|
-
if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
|
|
15245
|
-
hasUnsafeOverwrite = true;
|
|
15246
|
-
return false;
|
|
15247
|
-
}
|
|
15248
|
-
});
|
|
15249
|
-
return hasUnsafeOverwrite;
|
|
15250
|
-
};
|
|
15251
|
-
const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
15252
|
-
const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
|
|
15253
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
|
|
15254
|
-
const cleanupFunctionInvokesRef = (cleanupFunction) => {
|
|
15255
|
-
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
15256
|
-
let didFindCleanupCall = false;
|
|
15257
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
15258
|
-
if (didFindCleanupCall) return false;
|
|
15259
|
-
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
15260
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
|
|
15261
|
-
const callRoot = findTransparentExpressionRoot(child);
|
|
15262
|
-
const callStatement = callRoot.parent;
|
|
15263
|
-
const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
|
|
15264
|
-
const isConciseBody = cleanupFunction.body === callRoot;
|
|
15265
|
-
if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
|
|
15266
|
-
didFindCleanupCall = true;
|
|
15267
|
-
return false;
|
|
15268
|
-
}
|
|
15269
|
-
}
|
|
15270
|
-
});
|
|
15271
|
-
return didFindCleanupCall;
|
|
15272
|
-
};
|
|
15273
|
-
const effectReturnsCleanup = (effectCallback) => {
|
|
15274
|
-
if (!isFunctionLike$1(effectCallback)) return false;
|
|
15275
|
-
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
15276
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
|
|
15277
|
-
return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
|
|
15278
|
-
}
|
|
15279
|
-
const matchingReturns = [];
|
|
15280
|
-
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15281
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
15282
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
|
|
15283
|
-
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15284
|
-
matchingReturns.push(child);
|
|
15285
|
-
});
|
|
15286
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15287
|
-
};
|
|
15288
|
-
let didFindInvocation = false;
|
|
15289
|
-
walkAst(componentFunction.body, (child) => {
|
|
15290
|
-
if (didFindInvocation) return false;
|
|
15291
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
15292
|
-
const effectCallback = getEffectCallback(child);
|
|
15293
|
-
if (effectCallback && effectReturnsCleanup(effectCallback)) {
|
|
15294
|
-
didFindInvocation = true;
|
|
15295
|
-
return false;
|
|
15296
|
-
}
|
|
15297
|
-
});
|
|
15298
|
-
return didFindInvocation;
|
|
15299
|
-
};
|
|
15300
|
-
const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
|
|
15301
|
-
const isReturnedCallbackRefShape = () => {
|
|
15302
|
-
if (!isFunctionLike$1(storage.retainedFunction)) return false;
|
|
15303
|
-
const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
|
|
15304
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
|
|
15305
|
-
const nodeParameter = storage.retainedFunction.params?.[0];
|
|
15306
|
-
const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
|
|
15307
|
-
if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
|
|
15308
|
-
if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
|
|
15309
|
-
const usageStart = getRangeStart(usage.node);
|
|
15310
|
-
if (usageStart === null) return false;
|
|
15311
|
-
let hasNullExit = false;
|
|
15312
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15313
|
-
if (hasNullExit) return false;
|
|
15314
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15315
|
-
if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
|
|
15316
|
-
const test = stripParenExpression(child.test);
|
|
15317
|
-
if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
|
|
15318
|
-
const consequent = child.consequent;
|
|
15319
|
-
hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
|
|
15320
|
-
if (hasNullExit) return false;
|
|
15321
|
-
});
|
|
15322
|
-
return hasNullExit;
|
|
15323
|
-
};
|
|
15324
|
-
if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
|
|
15325
|
-
const cleanupCalls = [];
|
|
15326
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15327
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15328
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
|
|
15329
|
-
});
|
|
15330
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
|
|
15331
|
-
};
|
|
15332
|
-
const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
15333
|
-
const disposerFunction = findEnclosingFunction$1(releaseNode);
|
|
15334
|
-
if (!disposerFunction) return false;
|
|
15335
|
-
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15336
|
-
};
|
|
15337
|
-
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15338
|
-
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15339
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15340
|
-
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15341
|
-
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15342
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15343
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15344
|
-
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
15345
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
15346
|
-
const triggerRegistrations = [];
|
|
15347
|
-
walkAst(ownerFunction.body, (child) => {
|
|
15348
|
-
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
15349
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15350
|
-
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15351
|
-
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
15352
|
-
});
|
|
15353
|
-
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
15354
|
-
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
15355
|
-
};
|
|
15356
14975
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
15357
14976
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
15358
14977
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
15359
14978
|
if (!releaseFunction) return true;
|
|
15360
14979
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15361
|
-
if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
|
|
15362
14980
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15363
14981
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15364
|
-
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
15365
14982
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15366
14983
|
};
|
|
15367
14984
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -15693,7 +15310,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
15693
15310
|
return false;
|
|
15694
15311
|
}
|
|
15695
15312
|
}
|
|
15696
|
-
if (
|
|
15313
|
+
if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
|
|
15697
15314
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15698
15315
|
const subscriptionUsage = {
|
|
15699
15316
|
kind: "subscribe",
|
|
@@ -15901,7 +15518,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
15901
15518
|
if (!isFunctionLike$1(functionNode)) return false;
|
|
15902
15519
|
const functionRoot = findTransparentExpressionRoot(functionNode);
|
|
15903
15520
|
const callbackCall = functionRoot.parent;
|
|
15904
|
-
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot &&
|
|
15521
|
+
if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
|
|
15905
15522
|
const parentNode = functionNode.parent;
|
|
15906
15523
|
if (isDirectJsxEventHandlerValue(functionNode)) return true;
|
|
15907
15524
|
if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
|
|
@@ -15937,12 +15554,12 @@ const effectNeedsCleanup = defineRule({
|
|
|
15937
15554
|
};
|
|
15938
15555
|
return {
|
|
15939
15556
|
CallExpression(node) {
|
|
15940
|
-
if (
|
|
15557
|
+
if (isHookCall$2(node, "useCallback")) {
|
|
15941
15558
|
const retainedCallback = getEffectCallback(node);
|
|
15942
15559
|
if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
|
|
15943
15560
|
return;
|
|
15944
15561
|
}
|
|
15945
|
-
if (!
|
|
15562
|
+
if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
15946
15563
|
const callback = getEffectCallback(node);
|
|
15947
15564
|
if (!callback) return;
|
|
15948
15565
|
const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
|
|
@@ -15950,7 +15567,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
15950
15567
|
const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
|
|
15951
15568
|
if (!firstUsage) return;
|
|
15952
15569
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
|
|
15953
|
-
const hookName = getCalleeName$
|
|
15570
|
+
const hookName = getCalleeName$2(node) ?? "effect";
|
|
15954
15571
|
context.report({
|
|
15955
15572
|
node,
|
|
15956
15573
|
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
|
|
@@ -17293,38 +16910,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
|
|
|
17293
16910
|
const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
|
|
17294
16911
|
return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
|
|
17295
16912
|
};
|
|
17296
|
-
const
|
|
17297
|
-
let currentNode = findTransparentExpressionRoot(node);
|
|
17298
|
-
while (currentNode.parent) {
|
|
17299
|
-
const parentNode = currentNode.parent;
|
|
17300
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
17301
|
-
if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
|
|
17302
|
-
if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
|
|
17303
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
17304
|
-
if (isNodeOfType(parentNode, "ArrayPattern") && parentNode.elements.some((element) => element === currentNode) || isNodeOfType(parentNode, "ObjectPattern") && parentNode.properties.some((property) => property === currentNode) || isNodeOfType(parentNode, "RestElement") && parentNode.argument === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.left === currentNode || isNodeOfType(parentNode, "Property") && parentNode.value === currentNode && isNodeOfType(parentNode.parent, "ObjectPattern")) {
|
|
17305
|
-
currentNode = parentNode;
|
|
17306
|
-
continue;
|
|
17307
|
-
}
|
|
17308
|
-
return false;
|
|
17309
|
-
}
|
|
17310
|
-
return false;
|
|
17311
|
-
};
|
|
17312
|
-
const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
|
|
17313
|
-
if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
17314
|
-
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
17315
|
-
if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
|
|
17316
|
-
const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
|
|
17317
|
-
if (!refSymbol) return false;
|
|
17318
|
-
return refSymbol.references.every((reference) => {
|
|
17319
|
-
const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
|
|
17320
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
|
|
17321
|
-
const referenceRoot = findTransparentExpressionRoot(memberExpression);
|
|
17322
|
-
const parentNode = referenceRoot.parent;
|
|
17323
|
-
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
|
|
17324
|
-
return !isAssignmentTarget(referenceRoot);
|
|
17325
|
-
});
|
|
17326
|
-
};
|
|
17327
|
-
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableRefLazyInitialization(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
16913
|
+
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
17328
16914
|
//#endregion
|
|
17329
16915
|
//#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
|
|
17330
16916
|
const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
|
|
@@ -17526,7 +17112,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17526
17112
|
keys.add(depKey);
|
|
17527
17113
|
continue;
|
|
17528
17114
|
}
|
|
17529
|
-
const identitySourceKeys =
|
|
17115
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17530
17116
|
if (identitySourceKeys) {
|
|
17531
17117
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17532
17118
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17609,161 +17195,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17609
17195
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
17610
17196
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17611
17197
|
};
|
|
17612
|
-
const isPureDerivedExpression = (expression) => {
|
|
17613
|
-
const candidate = unwrapExpression$3(expression);
|
|
17614
|
-
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17615
|
-
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17616
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17617
|
-
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17618
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17619
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17620
|
-
return false;
|
|
17621
|
-
};
|
|
17622
|
-
const isPureDerivedStatement = (statement) => {
|
|
17623
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17624
|
-
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17625
|
-
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17626
|
-
return false;
|
|
17627
|
-
};
|
|
17628
|
-
const isPureDerivedFunction = (functionNode) => {
|
|
17629
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17630
|
-
if (functionNode.async || functionNode.generator) return false;
|
|
17631
|
-
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17632
|
-
};
|
|
17633
|
-
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17634
|
-
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17635
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17636
|
-
const callExpression = referenceRoot.parent;
|
|
17637
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17638
|
-
const functionNode = getFunctionValueNode(symbol);
|
|
17639
|
-
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17640
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17641
|
-
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17642
|
-
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17643
|
-
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17644
|
-
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17645
|
-
const capturedKey = computeDepKey(capturedReference);
|
|
17646
|
-
if (!capturedKey) return null;
|
|
17647
|
-
if (capturedKey === capturedSymbol.name) {
|
|
17648
|
-
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17649
|
-
if (nestedSourceKeys) {
|
|
17650
|
-
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17651
|
-
continue;
|
|
17652
|
-
}
|
|
17653
|
-
}
|
|
17654
|
-
sourceKeys.add(capturedKey);
|
|
17655
|
-
}
|
|
17656
|
-
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17657
|
-
};
|
|
17658
|
-
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17659
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17660
|
-
for (const expression of expressions) {
|
|
17661
|
-
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17662
|
-
if (!expressionSourceKeys) return null;
|
|
17663
|
-
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17664
|
-
}
|
|
17665
|
-
return sourceKeys;
|
|
17666
|
-
};
|
|
17667
|
-
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17668
|
-
const candidate = unwrapExpression$3(expression);
|
|
17669
|
-
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17670
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
17671
|
-
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17672
|
-
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17673
|
-
if (!sourceSymbol) return null;
|
|
17674
|
-
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17675
|
-
if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
|
|
17676
|
-
visitedSymbolIds.add(sourceSymbol.id);
|
|
17677
|
-
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17678
|
-
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17679
|
-
if (sourceKeys) return sourceKeys;
|
|
17680
|
-
}
|
|
17681
|
-
return new Set([sourceSymbol.name]);
|
|
17682
|
-
}
|
|
17683
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17684
|
-
if (hasComputedMemberExpression(candidate)) return null;
|
|
17685
|
-
const sourceKey = stringifyMemberChain(candidate);
|
|
17686
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17687
|
-
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17688
|
-
if (!sourceKey || !rootSymbol) return null;
|
|
17689
|
-
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17690
|
-
return new Set([sourceKey]);
|
|
17691
|
-
}
|
|
17692
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17693
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17694
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17695
|
-
candidate.test,
|
|
17696
|
-
candidate.consequent,
|
|
17697
|
-
candidate.alternate
|
|
17698
|
-
], scopes, visitedSymbolIds);
|
|
17699
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17700
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17701
|
-
const callee = unwrapExpression$3(candidate.callee);
|
|
17702
|
-
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17703
|
-
const argumentsToAnalyze = [];
|
|
17704
|
-
for (const argument of candidate.arguments) {
|
|
17705
|
-
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17706
|
-
argumentsToAnalyze.push(argument);
|
|
17707
|
-
}
|
|
17708
|
-
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17709
|
-
}
|
|
17710
|
-
return null;
|
|
17711
|
-
};
|
|
17712
|
-
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17713
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17714
|
-
let currentNode = assignment;
|
|
17715
|
-
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17716
|
-
const parentNode = currentNode.parent;
|
|
17717
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17718
|
-
if (parentNode.test === currentNode) return null;
|
|
17719
|
-
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17720
|
-
if (!testSourceKeys) return null;
|
|
17721
|
-
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17722
|
-
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17723
|
-
currentNode = parentNode;
|
|
17724
|
-
}
|
|
17725
|
-
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17726
|
-
};
|
|
17727
|
-
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17728
|
-
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17729
|
-
const callExpression = referenceRoot.parent;
|
|
17730
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17731
|
-
allowGlobalReactNamespace: true,
|
|
17732
|
-
allowUnboundBareCalls: true,
|
|
17733
|
-
resolveNamedAliases: true
|
|
17734
|
-
});
|
|
17735
|
-
};
|
|
17736
|
-
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17737
|
-
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17738
|
-
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17739
|
-
if (!boundaryFunction) return null;
|
|
17740
|
-
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17741
|
-
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17742
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17743
|
-
if (symbol.initializer) {
|
|
17744
|
-
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17745
|
-
if (!initializerSourceKeys) return null;
|
|
17746
|
-
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17747
|
-
}
|
|
17748
|
-
let writeCount = 0;
|
|
17749
|
-
for (const symbolReference of symbol.references) {
|
|
17750
|
-
if (symbolReference.flag === "read") {
|
|
17751
|
-
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17752
|
-
continue;
|
|
17753
|
-
}
|
|
17754
|
-
if (symbolReference.flag !== "write") return null;
|
|
17755
|
-
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17756
|
-
const assignment = referenceRoot.parent;
|
|
17757
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17758
|
-
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17759
|
-
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17760
|
-
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17761
|
-
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17762
|
-
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17763
|
-
writeCount += 1;
|
|
17764
|
-
}
|
|
17765
|
-
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17766
|
-
};
|
|
17767
17198
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17768
17199
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17769
17200
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -18507,7 +17938,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
18507
17938
|
if (!isUsed) continue;
|
|
18508
17939
|
const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
|
|
18509
17940
|
const rootSymbol = getRootSymbol(reportNode, context.scopes);
|
|
18510
|
-
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) ||
|
|
17941
|
+
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
|
|
18511
17942
|
context.report({
|
|
18512
17943
|
node: reportNode,
|
|
18513
17944
|
message: buildUnstableDepMessage(hookName, declaredKey)
|
|
@@ -18823,7 +18254,7 @@ const flattenCalleeName = (callee) => {
|
|
|
18823
18254
|
const PRAGMA = "React";
|
|
18824
18255
|
const isReactFunctionCall = (node, expectedCall) => {
|
|
18825
18256
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
18826
|
-
if (getCalleeName$
|
|
18257
|
+
if (getCalleeName$2(node) !== expectedCall) return false;
|
|
18827
18258
|
if (isNodeOfType(node.callee, "MemberExpression")) {
|
|
18828
18259
|
const receiver = stripParenExpression(node.callee.object);
|
|
18829
18260
|
return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
|
|
@@ -19021,14 +18452,10 @@ const hookUseState = defineRule({
|
|
|
19021
18452
|
create: (context) => {
|
|
19022
18453
|
const { allowDestructuredState } = resolveSettings$40(context.settings);
|
|
19023
18454
|
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;
|
|
18455
|
+
if (!isReactFunctionCall(node, "useState")) return;
|
|
19030
18456
|
const parent = node.parent;
|
|
19031
18457
|
if (!parent) return;
|
|
18458
|
+
if (isNodeOfType(parent, "ReturnStatement")) return;
|
|
19032
18459
|
if (!isNodeOfType(parent, "VariableDeclarator")) {
|
|
19033
18460
|
context.report({
|
|
19034
18461
|
node,
|
|
@@ -19136,8 +18563,8 @@ const hooksNoNanInDeps = defineRule({
|
|
|
19136
18563
|
severity: "warn",
|
|
19137
18564
|
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
18565
|
create: (context) => ({ CallExpression(node) {
|
|
19139
|
-
if (!
|
|
19140
|
-
const depsIndex =
|
|
18566
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
|
|
18567
|
+
const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
|
|
19141
18568
|
const depsArgument = node.arguments[depsIndex];
|
|
19142
18569
|
if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
|
|
19143
18570
|
for (const element of depsArgument.elements) {
|
|
@@ -20308,7 +19735,7 @@ const isImportedSelectAtom = (callExpression) => {
|
|
|
20308
19735
|
const isDeferredCallbackPosition$1 = (functionNode) => {
|
|
20309
19736
|
const parent = functionNode.parent;
|
|
20310
19737
|
if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
|
|
20311
|
-
const hookName = getCalleeName$
|
|
19738
|
+
const hookName = getCalleeName$2(parent);
|
|
20312
19739
|
if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
|
|
20313
19740
|
if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
|
|
20314
19741
|
}
|
|
@@ -30379,7 +29806,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
|
|
|
30379
29806
|
"on",
|
|
30380
29807
|
"once"
|
|
30381
29808
|
]);
|
|
30382
|
-
const getCalleeName = (callee) => {
|
|
29809
|
+
const getCalleeName$1 = (callee) => {
|
|
30383
29810
|
if (!callee) return null;
|
|
30384
29811
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
30385
29812
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
@@ -30391,11 +29818,11 @@ const isDeferredCallbackPosition = (expression) => {
|
|
|
30391
29818
|
const parent = parentOf(expression);
|
|
30392
29819
|
if (!parent) return false;
|
|
30393
29820
|
if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30394
|
-
const name = getCalleeName(parent.callee);
|
|
29821
|
+
const name = getCalleeName$1(parent.callee);
|
|
30395
29822
|
if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
|
|
30396
29823
|
}
|
|
30397
29824
|
if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
|
|
30398
|
-
const name = getCalleeName(parent.callee);
|
|
29825
|
+
const name = getCalleeName$1(parent.callee);
|
|
30399
29826
|
if (name && (name.endsWith("Observer") || name === "Promise")) return true;
|
|
30400
29827
|
}
|
|
30401
29828
|
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 +30242,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
|
|
|
30815
30242
|
if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30816
30243
|
return false;
|
|
30817
30244
|
};
|
|
30245
|
+
const isUseEffect = (node) => {
|
|
30246
|
+
if (!node || !isNodeOfType(node, "CallExpression")) return false;
|
|
30247
|
+
const callee = node.callee;
|
|
30248
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
|
|
30249
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
30250
|
+
const receiver = stripParenExpression(callee.object);
|
|
30251
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
|
|
30252
|
+
};
|
|
30818
30253
|
const getEffectFn = (analysis, node) => {
|
|
30819
30254
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
30820
30255
|
const fn = node.arguments?.[0];
|
|
@@ -30912,27 +30347,7 @@ const isRefCurrent = (ref) => {
|
|
|
30912
30347
|
if (!isNodeOfType(parent.property, "Identifier")) return false;
|
|
30913
30348
|
return parent.property.name === "current";
|
|
30914
30349
|
};
|
|
30915
|
-
const
|
|
30916
|
-
const visitedReferences = /* @__PURE__ */ new Set();
|
|
30917
|
-
let currentReference = ref;
|
|
30918
|
-
while (currentReference && !visitedReferences.has(currentReference)) {
|
|
30919
|
-
if (isStateSetter(analysis, currentReference)) return currentReference;
|
|
30920
|
-
visitedReferences.add(currentReference);
|
|
30921
|
-
const definitions = currentReference.resolved?.defs ?? [];
|
|
30922
|
-
if (definitions.length !== 1) return null;
|
|
30923
|
-
const definitionNode = definitions[0].node;
|
|
30924
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
|
|
30925
|
-
if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
|
|
30926
|
-
const declaration = definitionNode.parent;
|
|
30927
|
-
if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
|
|
30928
|
-
if (!definitionNode.init) return null;
|
|
30929
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
30930
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
30931
|
-
currentReference = getRef(analysis, initializer);
|
|
30932
|
-
}
|
|
30933
|
-
return null;
|
|
30934
|
-
};
|
|
30935
|
-
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
|
|
30350
|
+
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
30936
30351
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
30937
30352
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
30938
30353
|
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
@@ -31083,11 +30498,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
|
31083
30498
|
};
|
|
31084
30499
|
const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
|
|
31085
30500
|
const getUseStateDecl = (analysis, ref) => {
|
|
31086
|
-
|
|
31087
|
-
|
|
31088
|
-
|
|
31089
|
-
});
|
|
31090
|
-
return definition ? definition.node : null;
|
|
30501
|
+
let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
|
|
30502
|
+
while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
|
|
30503
|
+
return node ?? null;
|
|
31091
30504
|
};
|
|
31092
30505
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
31093
30506
|
if (isFunctionLike$1(node)) return true;
|
|
@@ -32276,7 +31689,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32276
31689
|
sourceReferences,
|
|
32277
31690
|
isDeferred: frame.isDeferred,
|
|
32278
31691
|
isRenderKnownCopy,
|
|
32279
|
-
isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
|
|
32280
31692
|
matchesStateInitializer: doesMatchStateInitializer,
|
|
32281
31693
|
resetsSourceState: false
|
|
32282
31694
|
});
|
|
@@ -32292,71 +31704,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32292
31704
|
});
|
|
32293
31705
|
};
|
|
32294
31706
|
//#endregion
|
|
32295
|
-
//#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
|
|
32296
|
-
const DEFERRED_MEMBER_NAMES = new Set([
|
|
32297
|
-
"catch",
|
|
32298
|
-
"finally",
|
|
32299
|
-
"then"
|
|
32300
|
-
]);
|
|
32301
|
-
const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
|
|
32302
|
-
const effectFunction = getEffectFn(analysis, effectNode);
|
|
32303
|
-
if (!effectFunction) return false;
|
|
32304
|
-
if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
|
|
32305
|
-
const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
|
|
32306
|
-
let didFindDeferredOrExternalWork = false;
|
|
32307
|
-
walkAst(effectFunction, (child) => {
|
|
32308
|
-
if (didFindDeferredOrExternalWork) return false;
|
|
32309
|
-
if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
|
|
32310
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
32311
|
-
const assignmentTarget = child.left;
|
|
32312
|
-
if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
|
|
32313
|
-
didFindDeferredOrExternalWork = true;
|
|
32314
|
-
return false;
|
|
32315
|
-
}
|
|
32316
|
-
}
|
|
32317
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32318
|
-
if (isSubscribeOrObserveCallExpression(child)) {
|
|
32319
|
-
didFindDeferredOrExternalWork = true;
|
|
32320
|
-
return false;
|
|
32321
|
-
}
|
|
32322
|
-
const localFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
32323
|
-
if (isFunctionLike$1(localFunction) && localFunction.async) {
|
|
32324
|
-
didFindDeferredOrExternalWork = true;
|
|
32325
|
-
return false;
|
|
32326
|
-
}
|
|
32327
|
-
const callee = child.callee;
|
|
32328
|
-
if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
|
|
32329
|
-
didFindDeferredOrExternalWork = true;
|
|
32330
|
-
return false;
|
|
32331
|
-
}
|
|
32332
|
-
const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
32333
|
-
if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
|
|
32334
|
-
didFindDeferredOrExternalWork = true;
|
|
32335
|
-
return false;
|
|
32336
|
-
}
|
|
32337
|
-
});
|
|
32338
|
-
return didFindDeferredOrExternalWork;
|
|
32339
|
-
};
|
|
32340
|
-
//#endregion
|
|
32341
31707
|
//#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
|
|
32342
31708
|
const noAdjustStateOnPropChange = defineRule({
|
|
32343
31709
|
id: "no-adjust-state-on-prop-change",
|
|
32344
|
-
title: "State
|
|
31710
|
+
title: "State synced to a prop inside an effect",
|
|
32345
31711
|
severity: "warn",
|
|
32346
31712
|
tags: ["test-noise"],
|
|
32347
|
-
recommendation: "
|
|
31713
|
+
recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
|
|
32348
31714
|
create: (context) => ({ CallExpression(node) {
|
|
32349
|
-
if (!
|
|
31715
|
+
if (!isUseEffect(node)) return;
|
|
32350
31716
|
const analysis = getProgramAnalysis(node);
|
|
32351
31717
|
if (!analysis) return;
|
|
32352
31718
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32353
31719
|
if (!dependencyReferences) return;
|
|
32354
31720
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
32355
|
-
const
|
|
32356
|
-
|
|
32357
|
-
for (const fact of facts) {
|
|
32358
|
-
if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
|
|
32359
|
-
if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
|
|
31721
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
31722
|
+
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32360
31723
|
context.report({
|
|
32361
31724
|
node: fact.callExpression,
|
|
32362
31725
|
message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
|
|
@@ -34369,7 +33732,7 @@ const declarationBodyContainsHookCall = (symbol) => {
|
|
|
34369
33732
|
walkAst(componentFunction, (descendant) => {
|
|
34370
33733
|
if (didFindHookCall) return false;
|
|
34371
33734
|
if (!isNodeOfType(descendant, "CallExpression")) return;
|
|
34372
|
-
const calleeName = getCalleeName$
|
|
33735
|
+
const calleeName = getCalleeName$2(descendant);
|
|
34373
33736
|
if (calleeName && isReactHookName(calleeName)) {
|
|
34374
33737
|
didFindHookCall = true;
|
|
34375
33738
|
return false;
|
|
@@ -34394,7 +33757,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
|
|
|
34394
33757
|
if (isNodeOfType(parent, "ArrowFunctionExpression")) {
|
|
34395
33758
|
if (parent.body !== current) return false;
|
|
34396
33759
|
const grandparent = parent.parent;
|
|
34397
|
-
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$
|
|
33760
|
+
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
|
|
34398
33761
|
}
|
|
34399
33762
|
if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
|
|
34400
33763
|
current = parent;
|
|
@@ -35076,7 +34439,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35076
34439
|
if (!callExpr) continue;
|
|
35077
34440
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35078
34441
|
if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
|
|
35079
|
-
const declarator = getUseStateDeclarator(
|
|
34442
|
+
const declarator = getUseStateDeclarator(ref);
|
|
35080
34443
|
if (declarator) domSyncedStateDeclarators.add(declarator);
|
|
35081
34444
|
}
|
|
35082
34445
|
for (const ref of effectFnRefs) {
|
|
@@ -35085,7 +34448,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35085
34448
|
if (!callExpr) continue;
|
|
35086
34449
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35087
34450
|
if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
|
|
35088
|
-
const setterDeclarator = getUseStateDeclarator(
|
|
34451
|
+
const setterDeclarator = getUseStateDeclarator(ref);
|
|
35089
34452
|
if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
|
|
35090
34453
|
const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
|
|
35091
34454
|
const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
|
|
@@ -36926,7 +36289,7 @@ const noDerivedState = defineRule({
|
|
|
36926
36289
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36927
36290
|
} }).visitors,
|
|
36928
36291
|
CallExpression(node) {
|
|
36929
|
-
if (!
|
|
36292
|
+
if (!isUseEffect(node)) return;
|
|
36930
36293
|
const analysis = getProgramAnalysis(node);
|
|
36931
36294
|
if (!analysis) return;
|
|
36932
36295
|
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
@@ -36946,7 +36309,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36946
36309
|
tags: ["test-noise"],
|
|
36947
36310
|
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
36311
|
create: (context) => ({ CallExpression(node) {
|
|
36949
|
-
if (!
|
|
36312
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36950
36313
|
const analysis = getProgramAnalysis(node);
|
|
36951
36314
|
if (!analysis) return;
|
|
36952
36315
|
if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
@@ -37069,7 +36432,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
37069
36432
|
if (isFunctionLike$1(cursor)) {
|
|
37070
36433
|
const parent = cursor.parent ?? null;
|
|
37071
36434
|
if (parent && isNodeOfType(parent, "CallExpression")) {
|
|
37072
|
-
const calleeName = getCalleeName$
|
|
36435
|
+
const calleeName = getCalleeName$2(parent);
|
|
37073
36436
|
if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
|
|
37074
36437
|
}
|
|
37075
36438
|
}
|
|
@@ -37140,7 +36503,7 @@ const isNonHandlerHookCallback = (functionNode) => {
|
|
|
37140
36503
|
const parent = functionNode.parent ?? null;
|
|
37141
36504
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
37142
36505
|
if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
|
|
37143
|
-
const calleeName = getCalleeName$
|
|
36506
|
+
const calleeName = getCalleeName$2(parent);
|
|
37144
36507
|
return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
|
|
37145
36508
|
};
|
|
37146
36509
|
const isHandlerShapedReseed = (setterCall, componentFunction) => {
|
|
@@ -37222,7 +36585,7 @@ const noDerivedUseState = defineRule({
|
|
|
37222
36585
|
return {
|
|
37223
36586
|
...propStackTracker.visitors,
|
|
37224
36587
|
CallExpression(node) {
|
|
37225
|
-
if (!
|
|
36588
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
37226
36589
|
const seed = unwrapInitializerSeed(node.arguments[0]);
|
|
37227
36590
|
const reportStalePropCopy = (propName) => {
|
|
37228
36591
|
if (isIntentionalSnapshotState(node)) return;
|
|
@@ -37898,7 +37261,7 @@ const noDirectMutationState = defineRule({
|
|
|
37898
37261
|
const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
37899
37262
|
//#endregion
|
|
37900
37263
|
//#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
|
|
37901
|
-
const collectUseStateBindings = (componentBody
|
|
37264
|
+
const collectUseStateBindings = (componentBody) => {
|
|
37902
37265
|
const bindings = [];
|
|
37903
37266
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
37904
37267
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -37911,7 +37274,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37911
37274
|
const setterElement = elements[1];
|
|
37912
37275
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37913
37276
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37914
|
-
if (!
|
|
37277
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
37915
37278
|
bindings.push({
|
|
37916
37279
|
valueName: valueElement.name,
|
|
37917
37280
|
setterName: setterElement.name,
|
|
@@ -38078,7 +37441,7 @@ const noDirectStateMutation = defineRule({
|
|
|
38078
37441
|
create: (context) => {
|
|
38079
37442
|
const checkComponent = (componentBody) => {
|
|
38080
37443
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
38081
|
-
const bindings = collectUseStateBindings(componentBody
|
|
37444
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
38082
37445
|
if (bindings.length === 0) return;
|
|
38083
37446
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
38084
37447
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
@@ -38638,31 +38001,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
|
|
|
38638
38001
|
};
|
|
38639
38002
|
//#endregion
|
|
38640
38003
|
//#region src/plugin/rules/state-and-effects/no-effect-chain.ts
|
|
38641
|
-
const findTopLevelEffectCalls = (componentBody
|
|
38004
|
+
const findTopLevelEffectCalls = (componentBody) => {
|
|
38642
38005
|
const effectCalls = [];
|
|
38643
38006
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
38644
38007
|
for (const statement of componentBody.body ?? []) {
|
|
38645
38008
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38646
38009
|
const expression = unwrapDiscardedExpression(statement);
|
|
38647
38010
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38648
|
-
if (!
|
|
38011
|
+
if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
|
|
38649
38012
|
effectCalls.push(expression);
|
|
38650
38013
|
}
|
|
38651
38014
|
return effectCalls;
|
|
38652
38015
|
};
|
|
38653
|
-
const
|
|
38654
|
-
const
|
|
38655
|
-
if (!isNodeOfType(effectNode, "CallExpression")) return
|
|
38016
|
+
const collectDepIdentifierNames = (effectNode) => {
|
|
38017
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
38018
|
+
if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
|
|
38656
38019
|
const depsNode = effectNode.arguments?.[1];
|
|
38657
|
-
if (!isNodeOfType(depsNode, "ArrayExpression")) return
|
|
38658
|
-
for (const element of depsNode.elements ?? [])
|
|
38659
|
-
|
|
38660
|
-
const rootIdentifier = getRootIdentifier$1(element);
|
|
38661
|
-
if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
|
|
38662
|
-
const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
|
|
38663
|
-
if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
|
|
38664
|
-
}
|
|
38665
|
-
return dependencyStateSymbolIds;
|
|
38020
|
+
if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
|
|
38021
|
+
for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
|
|
38022
|
+
return depNames;
|
|
38666
38023
|
};
|
|
38667
38024
|
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38668
38025
|
const analysisFunctions = new Set([effectCallback]);
|
|
@@ -38768,13 +38125,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
|
|
|
38768
38125
|
if (updater) return readStaticUpdaterReturnValue(updater, scopes);
|
|
38769
38126
|
return readStaticEffectValue(argument, scopes, null, null);
|
|
38770
38127
|
};
|
|
38771
|
-
const collectStateWritesInEffect = (analysisFunctions,
|
|
38128
|
+
const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
|
|
38772
38129
|
const stateWrites = /* @__PURE__ */ new Map();
|
|
38773
38130
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
38774
38131
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38775
38132
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
38776
|
-
const
|
|
38777
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
38133
|
+
const stateName = setterToStateName.get(child.callee.name);
|
|
38778
38134
|
if (!stateName) return;
|
|
38779
38135
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
38780
38136
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -38860,12 +38216,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
38860
38216
|
"keys",
|
|
38861
38217
|
"values"
|
|
38862
38218
|
]);
|
|
38863
|
-
const isFunctionShapedReturn = (returnedValue, setterToStateName,
|
|
38219
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
38864
38220
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
38865
38221
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
38866
38222
|
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
38867
|
-
|
|
38868
|
-
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
38223
|
+
if (setterToStateName.has(returnedValue.callee.name)) return false;
|
|
38869
38224
|
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
38870
38225
|
}
|
|
38871
38226
|
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
@@ -38889,7 +38244,7 @@ const collectStorageHookSetterNames = (componentBody) => {
|
|
|
38889
38244
|
for (const declarator of statement.declarations ?? []) {
|
|
38890
38245
|
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
38891
38246
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
38892
|
-
const calleeName = getCalleeName$
|
|
38247
|
+
const calleeName = getCalleeName$2(declarator.init);
|
|
38893
38248
|
if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
|
|
38894
38249
|
for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
|
|
38895
38250
|
}
|
|
@@ -39032,11 +38387,11 @@ const isExternalSyncNode = (node) => {
|
|
|
39032
38387
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39033
38388
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39034
38389
|
};
|
|
39035
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName,
|
|
38390
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
|
|
39036
38391
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
39037
38392
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
39038
|
-
if (isFunctionShapedReturn(effectCallback.body, setterToStateName,
|
|
39039
|
-
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName,
|
|
38393
|
+
if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
|
|
38394
|
+
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
|
|
39040
38395
|
let didFindExternalCall = false;
|
|
39041
38396
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39042
38397
|
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
@@ -39052,11 +38407,10 @@ const noEffectChain = defineRule({
|
|
|
39052
38407
|
create: (context) => {
|
|
39053
38408
|
const checkComponent = (componentBody) => {
|
|
39054
38409
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
39055
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
38410
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
39056
38411
|
if (useStateBindings.length === 0) return;
|
|
39057
38412
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
39058
38413
|
const stateSymbolIds = /* @__PURE__ */ new Map();
|
|
39059
|
-
const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
|
|
39060
38414
|
for (const binding of useStateBindings) {
|
|
39061
38415
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39062
38416
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
@@ -39065,27 +38419,21 @@ const noEffectChain = defineRule({
|
|
|
39065
38419
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39066
38420
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39067
38421
|
}
|
|
39068
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39069
|
-
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39070
|
-
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39071
|
-
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
39072
|
-
}
|
|
39073
38422
|
}
|
|
39074
38423
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
39075
|
-
const stateSymbolIdSet = new Set(stateSymbolIds.values());
|
|
39076
38424
|
const effectInfos = [];
|
|
39077
|
-
for (const effectCall of findTopLevelEffectCalls(componentBody
|
|
38425
|
+
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
39078
38426
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39079
38427
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39080
38428
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39081
|
-
const stateWrites = collectStateWritesInEffect(analysisFunctions,
|
|
38429
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
39082
38430
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39083
38431
|
effectInfos.push({
|
|
39084
38432
|
node: effectCall,
|
|
39085
|
-
|
|
38433
|
+
depNames: collectDepIdentifierNames(effectCall),
|
|
39086
38434
|
stateWrites,
|
|
39087
38435
|
analysisFunctions,
|
|
39088
|
-
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName,
|
|
38436
|
+
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
39089
38437
|
});
|
|
39090
38438
|
}
|
|
39091
38439
|
if (effectInfos.length < 2) return;
|
|
@@ -39096,11 +38444,10 @@ const noEffectChain = defineRule({
|
|
|
39096
38444
|
for (const readerEffect of effectInfos) {
|
|
39097
38445
|
if (readerEffect === writerEffect) continue;
|
|
39098
38446
|
if (readerEffect.isExternalSync) continue;
|
|
39099
|
-
if (readerEffect.
|
|
38447
|
+
if (readerEffect.depNames.size === 0) continue;
|
|
39100
38448
|
let chainedStateName = null;
|
|
39101
38449
|
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
39102
|
-
|
|
39103
|
-
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
38450
|
+
if (!readerEffect.depNames.has(writtenName)) continue;
|
|
39104
38451
|
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
39105
38452
|
chainedStateName = writtenName;
|
|
39106
38453
|
break;
|
|
@@ -39377,7 +38724,7 @@ const noEffectEventHandler = defineRule({
|
|
|
39377
38724
|
return {
|
|
39378
38725
|
...propStackTracker.visitors,
|
|
39379
38726
|
CallExpression(node) {
|
|
39380
|
-
if (!
|
|
38727
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
39381
38728
|
const callback = getEffectCallback(node);
|
|
39382
38729
|
if (!callback) return;
|
|
39383
38730
|
const analysis = getProgramAnalysis(node);
|
|
@@ -39497,14 +38844,14 @@ const noEffectEventInDeps = defineRule({
|
|
|
39497
38844
|
if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
|
|
39498
38845
|
const initializer = declaratorNode.init;
|
|
39499
38846
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
39500
|
-
if (!
|
|
38847
|
+
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
39501
38848
|
if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
|
|
39502
38849
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
39503
38850
|
} });
|
|
39504
38851
|
return {
|
|
39505
38852
|
...componentBindings.visitors,
|
|
39506
38853
|
CallExpression(node) {
|
|
39507
|
-
if (!
|
|
38854
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
39508
38855
|
if (!componentBindings.isInsideComponent()) return;
|
|
39509
38856
|
const depsNode = node.arguments[1];
|
|
39510
38857
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -39561,7 +38908,7 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
39561
38908
|
node: finding.reportNode,
|
|
39562
38909
|
message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
|
|
39563
38910
|
});
|
|
39564
|
-
if (!
|
|
38911
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
|
|
39565
38912
|
const args = node.arguments ?? [];
|
|
39566
38913
|
if (args.length < 2) return;
|
|
39567
38914
|
const depsNode = args[1];
|
|
@@ -39822,7 +39169,7 @@ const noEventHandler = defineRule({
|
|
|
39822
39169
|
severity: "warn",
|
|
39823
39170
|
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
39171
|
create: (context) => ({ CallExpression(node) {
|
|
39825
|
-
if (!
|
|
39172
|
+
if (!isUseEffect(node)) return;
|
|
39826
39173
|
const analysis = getProgramAnalysis(node);
|
|
39827
39174
|
if (!analysis || hasCleanup(analysis, node)) return;
|
|
39828
39175
|
const frames = collectBoundedEffectExecutionFrames(analysis, node);
|
|
@@ -40284,25 +39631,25 @@ const addDeclarationBindings = (statement, scope) => {
|
|
|
40284
39631
|
}
|
|
40285
39632
|
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
|
|
40286
39633
|
};
|
|
40287
|
-
const collectRenderReachableNamesFromStatements = (statements, names, scope,
|
|
39634
|
+
const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40288
39635
|
let hasReturn = false;
|
|
40289
|
-
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope,
|
|
39636
|
+
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
|
|
40290
39637
|
else addDeclarationBindings(statement, scope);
|
|
40291
39638
|
return hasReturn;
|
|
40292
39639
|
};
|
|
40293
|
-
const collectRenderReachableNamesFromStatement = (statement, names, scope,
|
|
39640
|
+
const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
|
|
40294
39641
|
if (isNodeOfType(statement, "ReturnStatement")) {
|
|
40295
39642
|
if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
|
|
40296
39643
|
return true;
|
|
40297
39644
|
}
|
|
40298
|
-
if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") &&
|
|
39645
|
+
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
39646
|
for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
|
|
40300
39647
|
return false;
|
|
40301
39648
|
}
|
|
40302
|
-
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope),
|
|
39649
|
+
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
|
|
40303
39650
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40304
|
-
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope,
|
|
40305
|
-
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope,
|
|
39651
|
+
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
|
|
39652
|
+
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
|
|
40306
39653
|
if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40307
39654
|
return consequentHasReturn || alternateHasReturn;
|
|
40308
39655
|
}
|
|
@@ -40310,7 +39657,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40310
39657
|
let hasReturn = false;
|
|
40311
39658
|
for (const switchCase of statement.cases ?? []) {
|
|
40312
39659
|
const caseScope = createBlockBindingScope(scope);
|
|
40313
|
-
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope,
|
|
39660
|
+
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
|
|
40314
39661
|
hasReturn = true;
|
|
40315
39662
|
if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
|
|
40316
39663
|
}
|
|
@@ -40318,25 +39665,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40318
39665
|
return hasReturn;
|
|
40319
39666
|
}
|
|
40320
39667
|
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,
|
|
39668
|
+
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
|
|
39669
|
+
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
|
|
39670
|
+
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
|
|
40324
39671
|
return blockHasReturn || handlerHasReturn || finalizerHasReturn;
|
|
40325
39672
|
}
|
|
40326
39673
|
if (isNodeOfType(statement, "CatchClause")) {
|
|
40327
39674
|
const catchScope = createBlockBindingScope(scope);
|
|
40328
39675
|
addPatternBindings(statement.param, catchScope);
|
|
40329
|
-
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope,
|
|
39676
|
+
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
|
|
40330
39677
|
}
|
|
40331
39678
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40332
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39679
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40333
39680
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40334
39681
|
return bodyHasReturn;
|
|
40335
39682
|
}
|
|
40336
39683
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40337
39684
|
const loopScope = createBlockBindingScope(scope);
|
|
40338
39685
|
if (statement.init) addDeclarationBindings(statement.init, loopScope);
|
|
40339
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
39686
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40340
39687
|
if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
|
|
40341
39688
|
if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
|
|
40342
39689
|
if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
|
|
@@ -40346,22 +39693,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40346
39693
|
const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
|
|
40347
39694
|
const loopScope = createBlockBindingScope(scope);
|
|
40348
39695
|
if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
|
|
40349
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
39696
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40350
39697
|
addNames(names, rightNames);
|
|
40351
39698
|
return true;
|
|
40352
39699
|
}
|
|
40353
|
-
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39700
|
+
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40354
39701
|
if (isNodeOfType(statement, "WithStatement")) {
|
|
40355
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39702
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40356
39703
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
|
|
40357
39704
|
return bodyHasReturn;
|
|
40358
39705
|
}
|
|
40359
39706
|
return false;
|
|
40360
39707
|
};
|
|
40361
|
-
const collectRenderReachableNames = (componentBody,
|
|
39708
|
+
const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40362
39709
|
const names = /* @__PURE__ */ new Set();
|
|
40363
39710
|
if (!isNodeOfType(componentBody, "BlockStatement")) return names;
|
|
40364
|
-
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(),
|
|
39711
|
+
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
|
|
40365
39712
|
return names;
|
|
40366
39713
|
};
|
|
40367
39714
|
//#endregion
|
|
@@ -40384,36 +39731,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
|
|
|
40384
39731
|
};
|
|
40385
39732
|
//#endregion
|
|
40386
39733
|
//#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
|
|
40387
|
-
const
|
|
40388
|
-
|
|
39734
|
+
const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
|
|
39735
|
+
const getCalleeName = (node) => {
|
|
39736
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
39737
|
+
if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
|
|
39738
|
+
return null;
|
|
39739
|
+
};
|
|
39740
|
+
const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
|
|
39741
|
+
if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
|
|
40389
39742
|
if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
|
|
40390
39743
|
const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
|
|
40391
39744
|
return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
|
|
40392
39745
|
};
|
|
40393
|
-
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope
|
|
39746
|
+
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
|
|
40394
39747
|
if (!isNodeOfType(node, "ObjectExpression")) return;
|
|
40395
39748
|
for (const property of node.properties ?? []) {
|
|
40396
39749
|
if (!isNodeOfType(property, "Property")) continue;
|
|
40397
39750
|
const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
|
|
40398
39751
|
if (!propertyName) continue;
|
|
40399
|
-
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope
|
|
39752
|
+
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
|
|
40400
39753
|
functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
|
|
40401
39754
|
}
|
|
40402
39755
|
};
|
|
40403
|
-
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope
|
|
39756
|
+
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40404
39757
|
if (!isNodeOfType(statement, "VariableDeclaration")) return;
|
|
40405
39758
|
const declarationScope = getVariableDeclarationScope(statement, scope);
|
|
40406
39759
|
for (const declarator of statement.declarations ?? []) {
|
|
40407
39760
|
const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
|
|
40408
39761
|
if (!declarator.init) continue;
|
|
40409
|
-
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope
|
|
39762
|
+
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
|
|
40410
39763
|
for (const declaredBindingName of declaredBindingNames) {
|
|
40411
39764
|
if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
|
|
40412
|
-
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope
|
|
39765
|
+
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
|
|
40413
39766
|
}
|
|
40414
39767
|
}
|
|
40415
39768
|
};
|
|
40416
|
-
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope
|
|
39769
|
+
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40417
39770
|
if (isNodeOfType(statement, "FunctionDeclaration")) {
|
|
40418
39771
|
if (statement.id) {
|
|
40419
39772
|
const declaredBindingNames = addPatternBindings(statement.id, scope);
|
|
@@ -40422,61 +39775,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
|
|
|
40422
39775
|
return;
|
|
40423
39776
|
}
|
|
40424
39777
|
if (isNodeOfType(statement, "VariableDeclaration")) {
|
|
40425
|
-
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope
|
|
39778
|
+
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40426
39779
|
return;
|
|
40427
39780
|
}
|
|
40428
39781
|
if (isNodeOfType(statement, "BlockStatement")) {
|
|
40429
|
-
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
39782
|
+
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40430
39783
|
return;
|
|
40431
39784
|
}
|
|
40432
39785
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40433
|
-
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope
|
|
40434
|
-
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope
|
|
39786
|
+
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
|
|
39787
|
+
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
|
|
40435
39788
|
return;
|
|
40436
39789
|
}
|
|
40437
39790
|
if (isNodeOfType(statement, "SwitchStatement")) {
|
|
40438
|
-
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
39791
|
+
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40439
39792
|
return;
|
|
40440
39793
|
}
|
|
40441
39794
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40442
|
-
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope
|
|
39795
|
+
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
|
|
40443
39796
|
if (statement.handler) {
|
|
40444
39797
|
const catchScope = createBlockBindingScope(scope);
|
|
40445
39798
|
addPatternBindings(statement.handler.param, catchScope);
|
|
40446
|
-
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope
|
|
39799
|
+
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
|
|
40447
39800
|
}
|
|
40448
|
-
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope
|
|
39801
|
+
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
|
|
40449
39802
|
return;
|
|
40450
39803
|
}
|
|
40451
39804
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40452
39805
|
const loopScope = createBlockBindingScope(scope);
|
|
40453
|
-
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope
|
|
40454
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
39806
|
+
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
|
|
39807
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40455
39808
|
return;
|
|
40456
39809
|
}
|
|
40457
39810
|
if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
|
|
40458
39811
|
const loopScope = createBlockBindingScope(scope);
|
|
40459
|
-
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope
|
|
39812
|
+
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
|
|
40460
39813
|
else addPatternBindings(statement.left, loopScope);
|
|
40461
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
39814
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40462
39815
|
return;
|
|
40463
39816
|
}
|
|
40464
39817
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40465
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
39818
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40466
39819
|
return;
|
|
40467
39820
|
}
|
|
40468
|
-
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
39821
|
+
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40469
39822
|
};
|
|
40470
|
-
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope
|
|
40471
|
-
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope
|
|
39823
|
+
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
|
|
39824
|
+
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40472
39825
|
};
|
|
40473
|
-
const collectFunctionLikeLocalNames = (componentBody
|
|
39826
|
+
const collectFunctionLikeLocalNames = (componentBody) => {
|
|
40474
39827
|
const functionLikeLocalNames = /* @__PURE__ */ new Set();
|
|
40475
39828
|
if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
|
|
40476
39829
|
let previousSize = -1;
|
|
40477
39830
|
while (previousSize !== functionLikeLocalNames.size) {
|
|
40478
39831
|
previousSize = functionLikeLocalNames.size;
|
|
40479
|
-
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope()
|
|
39832
|
+
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
|
|
40480
39833
|
}
|
|
40481
39834
|
return functionLikeLocalNames;
|
|
40482
39835
|
};
|
|
@@ -40516,17 +39869,17 @@ const noEventTriggerState = defineRule({
|
|
|
40516
39869
|
create: (context) => {
|
|
40517
39870
|
const checkComponent = (componentBody) => {
|
|
40518
39871
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
40519
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
39872
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
40520
39873
|
if (useStateBindings.length === 0) return;
|
|
40521
39874
|
const analysis = getProgramAnalysis(componentBody);
|
|
40522
39875
|
if (!analysis) return;
|
|
40523
39876
|
const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
|
|
40524
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
39877
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
40525
39878
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
40526
|
-
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody,
|
|
39879
|
+
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
|
|
40527
39880
|
walkAst(componentBody, (effectCall) => {
|
|
40528
39881
|
if (!isNodeOfType(effectCall, "CallExpression")) return;
|
|
40529
|
-
if (!
|
|
39882
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
|
|
40530
39883
|
if ((effectCall.arguments?.length ?? 0) < 2) return;
|
|
40531
39884
|
const depsNode = effectCall.arguments[1];
|
|
40532
39885
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -40591,17 +39944,10 @@ const isShadowedByLocalBinding = (identifier) => {
|
|
|
40591
39944
|
};
|
|
40592
39945
|
const isRealFetchCall = (node) => {
|
|
40593
39946
|
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";
|
|
39947
|
+
if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
|
|
39948
|
+
return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
|
|
40604
39949
|
};
|
|
39950
|
+
const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
|
|
40605
39951
|
const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
|
|
40606
39952
|
const resolveLocalFunction = (expression, context) => {
|
|
40607
39953
|
if (!expression) return null;
|
|
@@ -40917,17 +40263,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
|
|
|
40917
40263
|
"scrollHeight"
|
|
40918
40264
|
]);
|
|
40919
40265
|
const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
|
|
40920
|
-
const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
|
|
40921
|
-
"blur",
|
|
40922
|
-
"focus",
|
|
40923
|
-
"restoreSelection",
|
|
40924
|
-
"scroll",
|
|
40925
|
-
"scrollBy",
|
|
40926
|
-
"scrollIntoView",
|
|
40927
|
-
"scrollTo",
|
|
40928
|
-
"setRangeText",
|
|
40929
|
-
"setSelectionRange"
|
|
40930
|
-
]);
|
|
40931
40266
|
const subtreeReadsDomMeasurement = (root) => {
|
|
40932
40267
|
if (!root) return false;
|
|
40933
40268
|
let found = false;
|
|
@@ -40946,66 +40281,29 @@ const subtreeReadsDomMeasurement = (root) => {
|
|
|
40946
40281
|
});
|
|
40947
40282
|
return found;
|
|
40948
40283
|
};
|
|
40949
|
-
const
|
|
40284
|
+
const collectMeasuringFunctionNames = (program) => {
|
|
40950
40285
|
const names = /* @__PURE__ */ new Set();
|
|
40951
40286
|
walkAst(program, (child) => {
|
|
40952
40287
|
if (isNodeOfType(child, "FunctionDeclaration")) {
|
|
40953
|
-
if (child.id && isNodeOfType(child.id, "Identifier") &&
|
|
40288
|
+
if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
|
|
40954
40289
|
return;
|
|
40955
40290
|
}
|
|
40956
40291
|
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
|
|
40957
40292
|
let functionValue = child.init;
|
|
40958
40293
|
if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
|
|
40959
|
-
if (functionValue && isFunctionLike$1(functionValue) &&
|
|
40294
|
+
if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
|
|
40960
40295
|
});
|
|
40961
40296
|
return names;
|
|
40962
40297
|
};
|
|
40963
|
-
const
|
|
40964
|
-
|
|
40965
|
-
if (!root || isFunctionLike$1(root)) return false;
|
|
40966
|
-
let found = false;
|
|
40967
|
-
walkAst(root, (child) => {
|
|
40968
|
-
if (found) return false;
|
|
40969
|
-
if (child !== root && isFunctionLike$1(child)) return false;
|
|
40970
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
40971
|
-
const callee = stripParenExpression(child.callee);
|
|
40972
|
-
const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
40973
|
-
if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
|
|
40974
|
-
found = true;
|
|
40975
|
-
return false;
|
|
40976
|
-
}
|
|
40977
|
-
});
|
|
40978
|
-
return found;
|
|
40979
|
-
};
|
|
40980
|
-
const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
|
|
40981
|
-
const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
|
|
40982
|
-
if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
|
|
40298
|
+
const callsAnyName = (root, names) => {
|
|
40299
|
+
if (!root || names.size === 0) return false;
|
|
40983
40300
|
let found = false;
|
|
40984
40301
|
walkAst(root, (child) => {
|
|
40985
40302
|
if (found) return false;
|
|
40986
|
-
if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
|
|
40987
40303
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
|
|
40988
40304
|
});
|
|
40989
40305
|
return found;
|
|
40990
40306
|
};
|
|
40991
|
-
const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
|
|
40992
|
-
let statement = call;
|
|
40993
|
-
let parent = statement.parent;
|
|
40994
|
-
while (parent) {
|
|
40995
|
-
const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
|
|
40996
|
-
if (statements) {
|
|
40997
|
-
const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
|
|
40998
|
-
if (statementIndex >= 0) {
|
|
40999
|
-
const nextStatement = statements[statementIndex + 1];
|
|
41000
|
-
return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
|
|
41001
|
-
}
|
|
41002
|
-
}
|
|
41003
|
-
if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
|
|
41004
|
-
statement = parent;
|
|
41005
|
-
parent = parent.parent;
|
|
41006
|
-
}
|
|
41007
|
-
return false;
|
|
41008
|
-
};
|
|
41009
40307
|
const isInsideStartViewTransition = (node) => {
|
|
41010
40308
|
let cursor = node.parent;
|
|
41011
40309
|
while (cursor) {
|
|
@@ -41046,12 +40344,11 @@ const importsImperativeDomLibrary = (program) => {
|
|
|
41046
40344
|
};
|
|
41047
40345
|
const hasExemptFlushSyncCall = (program, localName) => {
|
|
41048
40346
|
const measuringFunctionNames = collectMeasuringFunctionNames(program);
|
|
41049
|
-
const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
|
|
41050
40347
|
let exempt = false;
|
|
41051
40348
|
walkAst(program, (child) => {
|
|
41052
40349
|
if (exempt) return false;
|
|
41053
40350
|
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
|
|
41054
|
-
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)
|
|
40351
|
+
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
|
|
41055
40352
|
exempt = true;
|
|
41056
40353
|
return false;
|
|
41057
40354
|
}
|
|
@@ -41616,223 +40913,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41616
40913
|
if (leftResult === false && rightResult === false) return false;
|
|
41617
40914
|
return null;
|
|
41618
40915
|
};
|
|
41619
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40916
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41620
40917
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41621
40918
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41622
40919
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41623
40920
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41624
40921
|
if (staticResult !== null) return staticResult;
|
|
41625
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41626
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41627
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41628
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41629
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41630
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41631
|
-
return result;
|
|
41632
|
-
}
|
|
41633
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41634
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41635
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41636
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41637
|
-
return result;
|
|
41638
|
-
}
|
|
41639
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41640
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41641
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41642
|
-
allowGlobalReactNamespace: true,
|
|
41643
|
-
resolveNamedAliases: true
|
|
41644
|
-
})) {
|
|
41645
|
-
const callbackArgument = callArguments[0];
|
|
41646
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41647
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41648
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41649
|
-
}
|
|
41650
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41651
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
41652
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41653
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41654
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41655
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41656
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41657
|
-
const argument = callArguments[parameterIndex];
|
|
41658
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41659
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41660
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41661
|
-
}
|
|
41662
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41663
|
-
...state,
|
|
41664
|
-
parameterValuesBySymbolId
|
|
41665
|
-
});
|
|
41666
|
-
}
|
|
41667
40922
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41668
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40923
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41669
40924
|
return argumentResult === null ? null : !argumentResult;
|
|
41670
40925
|
}
|
|
41671
40926
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41672
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
40927
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41673
40928
|
};
|
|
41674
|
-
const
|
|
41675
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41676
|
-
didReturn: true,
|
|
41677
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41678
|
-
};
|
|
41679
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41680
|
-
for (const childStatement of statement.body) {
|
|
41681
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41682
|
-
if (result.didReturn) return result;
|
|
41683
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41684
|
-
}
|
|
41685
|
-
return {
|
|
41686
|
-
didReturn: false,
|
|
41687
|
-
value: null
|
|
41688
|
-
};
|
|
41689
|
-
}
|
|
41690
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41691
|
-
didReturn: false,
|
|
41692
|
-
value: null
|
|
41693
|
-
};
|
|
41694
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41695
|
-
if (conditionResult !== null) {
|
|
41696
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41697
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41698
|
-
didReturn: false,
|
|
41699
|
-
value: null
|
|
41700
|
-
};
|
|
41701
|
-
}
|
|
41702
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41703
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41704
|
-
didReturn: false,
|
|
41705
|
-
value: null
|
|
41706
|
-
};
|
|
41707
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41708
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41709
|
-
value: null
|
|
41710
|
-
};
|
|
41711
|
-
};
|
|
41712
|
-
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41713
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41714
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41715
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41716
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41717
|
-
return result;
|
|
41718
|
-
};
|
|
41719
|
-
const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
|
|
41720
|
-
const left = stripParenExpression(leftExpression);
|
|
41721
|
-
const right = stripParenExpression(rightExpression);
|
|
41722
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41723
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41724
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41725
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41726
|
-
}
|
|
41727
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41728
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41729
|
-
const rightArguments = right.arguments ?? [];
|
|
41730
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41731
|
-
const rightArgument = rightArguments[index];
|
|
41732
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41733
|
-
});
|
|
41734
|
-
}
|
|
41735
|
-
return true;
|
|
41736
|
-
};
|
|
41737
|
-
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41738
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41739
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41740
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41741
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41742
|
-
};
|
|
41743
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41744
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41745
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41746
|
-
};
|
|
41747
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40929
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41748
40930
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41749
40931
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41750
40932
|
if (predicateMatch) return {
|
|
41751
40933
|
predicateMatch,
|
|
41752
40934
|
predicateNode: unwrappedExpression
|
|
41753
40935
|
};
|
|
41754
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41755
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41756
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41757
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41758
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41759
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41760
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41761
|
-
return match;
|
|
41762
|
-
}
|
|
41763
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41764
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41765
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41766
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41767
|
-
return match;
|
|
41768
|
-
}
|
|
41769
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41770
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41771
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41772
|
-
allowGlobalReactNamespace: true,
|
|
41773
|
-
resolveNamedAliases: true
|
|
41774
|
-
})) {
|
|
41775
|
-
const callbackArgument = callArguments[0];
|
|
41776
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41777
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41778
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41779
|
-
}
|
|
41780
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41781
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41782
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41783
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41784
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41785
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41786
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41787
|
-
const argument = callArguments[parameterIndex];
|
|
41788
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41789
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41790
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41791
|
-
}
|
|
41792
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41793
|
-
...state,
|
|
41794
|
-
parameterValuesBySymbolId
|
|
41795
|
-
});
|
|
41796
|
-
}
|
|
41797
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40936
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41798
40937
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41799
|
-
const leftMatch =
|
|
41800
|
-
const rightMatch =
|
|
40938
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40939
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40940
|
+
if (leftMatch && rightMatch) {
|
|
40941
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40942
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40943
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40944
|
+
}
|
|
41801
40945
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41802
40946
|
if (!nestedMatch) return null;
|
|
41803
|
-
const
|
|
41804
|
-
|
|
41805
|
-
return
|
|
41806
|
-
};
|
|
41807
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41808
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41809
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41810
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41811
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41812
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41813
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41814
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41815
|
-
}
|
|
41816
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41817
|
-
for (const childStatement of statement.body) {
|
|
41818
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41819
|
-
if (match) return match;
|
|
41820
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41821
|
-
}
|
|
41822
|
-
return null;
|
|
40947
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40948
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40949
|
+
return nestedMatch;
|
|
41823
40950
|
};
|
|
41824
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41825
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41826
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41827
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41828
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41829
|
-
return match;
|
|
41830
|
-
};
|
|
41831
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41832
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41833
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41834
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41835
|
-
});
|
|
41836
40951
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41837
40952
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41838
40953
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41975,17 +41090,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41975
41090
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41976
41091
|
if (reportedNodes.has(predicateNode)) return;
|
|
41977
41092
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41978
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
41093
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41979
41094
|
if (!componentOrHookNode) return;
|
|
41980
41095
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41981
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
41096
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41982
41097
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41983
|
-
const attribute = findEnclosingJsxAttribute(
|
|
41098
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41984
41099
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41985
41100
|
}
|
|
41986
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41987
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41988
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
41101
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
41102
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41103
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41989
41104
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41990
41105
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41991
41106
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -42362,7 +41477,7 @@ const noInitializeState = defineRule({
|
|
|
42362
41477
|
tags: ["test-noise"],
|
|
42363
41478
|
recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
|
|
42364
41479
|
create: (context) => ({ CallExpression(node) {
|
|
42365
|
-
if (!
|
|
41480
|
+
if (!isUseEffect(node)) return;
|
|
42366
41481
|
const dependencies = node.arguments?.[1];
|
|
42367
41482
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42368
41483
|
const analysis = getProgramAnalysis(node);
|
|
@@ -42725,8 +41840,7 @@ const noJsxElementType = defineRule({
|
|
|
42725
41840
|
create: (context) => {
|
|
42726
41841
|
let isJsxImported = false;
|
|
42727
41842
|
const flaggedAnnotations = [];
|
|
42728
|
-
const
|
|
42729
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41843
|
+
const checkReturnType = (returnType) => {
|
|
42730
41844
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42731
41845
|
if (!typeAnnotation) return;
|
|
42732
41846
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42736,16 +41850,19 @@ const noJsxElementType = defineRule({
|
|
|
42736
41850
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42737
41851
|
},
|
|
42738
41852
|
FunctionDeclaration(node) {
|
|
42739
|
-
|
|
41853
|
+
checkReturnType(node.returnType);
|
|
42740
41854
|
},
|
|
42741
41855
|
ArrowFunctionExpression(node) {
|
|
42742
|
-
|
|
41856
|
+
checkReturnType(node.returnType);
|
|
42743
41857
|
},
|
|
42744
41858
|
FunctionExpression(node) {
|
|
42745
|
-
|
|
41859
|
+
checkReturnType(node.returnType);
|
|
42746
41860
|
},
|
|
42747
41861
|
TSDeclareFunction(node) {
|
|
42748
|
-
|
|
41862
|
+
checkReturnType(node.returnType);
|
|
41863
|
+
},
|
|
41864
|
+
TSMethodSignature(node) {
|
|
41865
|
+
checkReturnType(node.returnType);
|
|
42749
41866
|
},
|
|
42750
41867
|
"Program:exit"() {
|
|
42751
41868
|
if (isJsxImported) return;
|
|
@@ -43974,7 +43091,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43974
43091
|
const setterElement = elements[1];
|
|
43975
43092
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
43976
43093
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
43977
|
-
if (!
|
|
43094
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
43978
43095
|
const initializer = declarator.init.arguments?.[0];
|
|
43979
43096
|
if (!initializer) continue;
|
|
43980
43097
|
const propRootName = getPropRootName(initializer, propNames);
|
|
@@ -43992,7 +43109,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43992
43109
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
43993
43110
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
43994
43111
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
43995
|
-
if (!
|
|
43112
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
43996
43113
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
43997
43114
|
const depsNode = effectCall.arguments[1];
|
|
43998
43115
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -44401,7 +43518,7 @@ const noMultiComp = defineRule({
|
|
|
44401
43518
|
});
|
|
44402
43519
|
//#endregion
|
|
44403
43520
|
//#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
|
|
44404
|
-
const collectUseRefBindingNames = (componentBody
|
|
43521
|
+
const collectUseRefBindingNames = (componentBody) => {
|
|
44405
43522
|
const useRefBindings = /* @__PURE__ */ new Set();
|
|
44406
43523
|
if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
|
|
44407
43524
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -44409,7 +43526,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
|
|
|
44409
43526
|
for (const declarator of statement.declarations ?? []) {
|
|
44410
43527
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
44411
43528
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
44412
|
-
if (!
|
|
43529
|
+
if (!isHookCall$2(declarator.init, "useRef")) continue;
|
|
44413
43530
|
useRefBindings.add(declarator.id.name);
|
|
44414
43531
|
}
|
|
44415
43532
|
}
|
|
@@ -44444,12 +43561,12 @@ const noMutableInDeps = defineRule({
|
|
|
44444
43561
|
create: (context) => {
|
|
44445
43562
|
const checkComponent = (componentBody, componentParams = []) => {
|
|
44446
43563
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
44447
|
-
const useRefBindingNames = collectUseRefBindingNames(componentBody
|
|
43564
|
+
const useRefBindingNames = collectUseRefBindingNames(componentBody);
|
|
44448
43565
|
const localBindingNames = collectLocalBindingNames(componentBody);
|
|
44449
43566
|
for (const param of componentParams) collectPatternNames(param, localBindingNames);
|
|
44450
43567
|
walkAst(componentBody, (child) => {
|
|
44451
43568
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
44452
|
-
if (!
|
|
43569
|
+
if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
|
|
44453
43570
|
if ((child.arguments?.length ?? 0) < 2) return;
|
|
44454
43571
|
const depsNode = child.arguments[1];
|
|
44455
43572
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -46279,7 +45396,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
46279
45396
|
"useMatchMedia",
|
|
46280
45397
|
"useMediaJobProgress",
|
|
46281
45398
|
"useMediaQuery",
|
|
46282
|
-
"useMediaQueryState",
|
|
46283
45399
|
"useResizeObserver",
|
|
46284
45400
|
"useVisibility",
|
|
46285
45401
|
"useWindowSize"
|
|
@@ -46316,30 +45432,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46316
45432
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46317
45433
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
46318
45434
|
};
|
|
46319
|
-
const
|
|
46320
|
-
let hookFunction = resolveToFunction(ref);
|
|
46321
|
-
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
46322
|
-
const definitionNode = definition.node;
|
|
46323
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
46324
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
46325
|
-
if (!isNodeOfType(initializer, "CallExpression")) continue;
|
|
46326
|
-
const callee = stripParenExpression(initializer.callee);
|
|
46327
|
-
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
46328
|
-
const calleeReference = getRef(analysis, callee);
|
|
46329
|
-
if (!calleeReference) continue;
|
|
46330
|
-
hookFunction = resolveToFunction(calleeReference);
|
|
46331
|
-
if (hookFunction) break;
|
|
46332
|
-
}
|
|
46333
|
-
if (!hookFunction) return null;
|
|
46334
|
-
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46335
|
-
if (returnedReferences.length === 0) return null;
|
|
46336
|
-
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
46337
|
-
};
|
|
46338
|
-
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
45435
|
+
const isExternalSubscriptionHookRef = (ref) => {
|
|
46339
45436
|
const identifier = ref.identifier;
|
|
46340
45437
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46341
|
-
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
46342
|
-
if (localHookProof !== null) return localHookProof;
|
|
46343
45438
|
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46344
45439
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46345
45440
|
const node = def.node;
|
|
@@ -46362,10 +45457,16 @@ const noPassDataToParent = defineRule({
|
|
|
46362
45457
|
tags: ["test-noise"],
|
|
46363
45458
|
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
45459
|
create: (context) => {
|
|
46365
|
-
const isReactUseRefCall = (node) =>
|
|
46366
|
-
|
|
45460
|
+
const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
|
|
45461
|
+
allowGlobalReactNamespace: true,
|
|
45462
|
+
allowUnboundBareCalls: true
|
|
45463
|
+
});
|
|
45464
|
+
const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
|
|
45465
|
+
allowGlobalReactNamespace: true,
|
|
45466
|
+
allowUnboundBareCalls: true
|
|
45467
|
+
});
|
|
46367
45468
|
return { CallExpression(node) {
|
|
46368
|
-
if (!
|
|
45469
|
+
if (!isUseEffect(node)) return;
|
|
46369
45470
|
const analysis = getProgramAnalysis(node);
|
|
46370
45471
|
if (!analysis) return;
|
|
46371
45472
|
if (hasCleanup(analysis, node)) return;
|
|
@@ -46417,11 +45518,11 @@ const noPassDataToParent = defineRule({
|
|
|
46417
45518
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46418
45519
|
}
|
|
46419
45520
|
return getDownstreamRefs(analysis, argument);
|
|
46420
|
-
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef)
|
|
45521
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
46421
45522
|
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
46422
45523
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46423
45524
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46424
|
-
if (isExternalSubscriptionHookRef(
|
|
45525
|
+
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
46425
45526
|
if (isProp(analysis, argRef)) return false;
|
|
46426
45527
|
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
46427
45528
|
if (isRefCurrent(argRef)) return false;
|
|
@@ -46652,7 +45753,7 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46652
45753
|
tags: ["test-noise"],
|
|
46653
45754
|
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
45755
|
create: (context) => ({ CallExpression(node) {
|
|
46655
|
-
if (!
|
|
45756
|
+
if (!isUseEffect(node)) return;
|
|
46656
45757
|
const analysis = getProgramAnalysis(node);
|
|
46657
45758
|
if (!analysis) return;
|
|
46658
45759
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -47067,7 +46168,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
47067
46168
|
if (!isNodeOfType(element, "Identifier")) continue;
|
|
47068
46169
|
const binding = findVariableInitializer(effectNode, element.name);
|
|
47069
46170
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
|
|
47070
|
-
const calleeName = getCalleeName$
|
|
46171
|
+
const calleeName = getCalleeName$2(binding.initializer);
|
|
47071
46172
|
if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
|
|
47072
46173
|
}
|
|
47073
46174
|
return false;
|
|
@@ -47089,7 +46190,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47089
46190
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
47090
46191
|
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
47091
46192
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
47092
|
-
if (getCalleeName$
|
|
46193
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
47093
46194
|
const callbackArgument = binding.initializer.arguments?.[0];
|
|
47094
46195
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47095
46196
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
@@ -47121,7 +46222,7 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47121
46222
|
return {
|
|
47122
46223
|
...propStackTracker.visitors,
|
|
47123
46224
|
CallExpression(node) {
|
|
47124
|
-
if (!
|
|
46225
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
47125
46226
|
const callback = getEffectCallback(node);
|
|
47126
46227
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
47127
46228
|
const depsNode = node.arguments[1];
|
|
@@ -47985,69 +47086,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47985
47086
|
}
|
|
47986
47087
|
});
|
|
47987
47088
|
//#endregion
|
|
47988
|
-
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
47989
|
-
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47990
|
-
const expression = stripParenExpression(rawExpression);
|
|
47991
|
-
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
47992
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
47993
|
-
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
47994
|
-
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
47995
|
-
}
|
|
47996
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
47997
|
-
if (isNodeOfType(expression.left, "Literal")) {
|
|
47998
|
-
const isLeftTruthy = Boolean(expression.left.value);
|
|
47999
|
-
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
48000
|
-
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
48001
|
-
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
48002
|
-
}
|
|
48003
|
-
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
48004
|
-
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
48005
|
-
}
|
|
48006
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
48007
|
-
const finalExpression = expression.expressions.at(-1);
|
|
48008
|
-
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
48009
|
-
}
|
|
48010
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
48011
|
-
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
48012
|
-
const callback = expression.arguments[0];
|
|
48013
|
-
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
48014
|
-
}
|
|
48015
|
-
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
48016
|
-
const symbol = scopes.symbolFor(expression);
|
|
48017
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
48018
|
-
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
48019
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
48020
|
-
if (!initializer) return [];
|
|
48021
|
-
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
48022
|
-
};
|
|
48023
|
-
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
48024
|
-
if (!isFunctionLike$1(functionExpression)) return false;
|
|
48025
|
-
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
48026
|
-
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
48027
|
-
};
|
|
48028
|
-
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
48029
|
-
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
48030
|
-
};
|
|
48031
|
-
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
48032
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
48033
|
-
title: "Ref cleanup requires React 19",
|
|
48034
|
-
requires: ["react:18"],
|
|
48035
|
-
disabledWhen: ["react:19"],
|
|
48036
|
-
severity: "warn",
|
|
48037
|
-
recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
|
|
48038
|
-
create: (context) => ({ JSXAttribute(node) {
|
|
48039
|
-
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
48040
|
-
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
48041
|
-
const callback = node.value.expression;
|
|
48042
|
-
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
48043
|
-
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
48044
|
-
context.report({
|
|
48045
|
-
node,
|
|
48046
|
-
message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
|
|
48047
|
-
});
|
|
48048
|
-
} })
|
|
48049
|
-
});
|
|
48050
|
-
//#endregion
|
|
48051
47089
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
48052
47090
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
48053
47091
|
"DoWhileStatement",
|
|
@@ -49014,7 +48052,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
49014
48052
|
tags: ["test-noise"],
|
|
49015
48053
|
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
48054
|
create: (context) => ({ CallExpression(node) {
|
|
49017
|
-
if (!
|
|
48055
|
+
if (!isUseEffect(node)) return;
|
|
49018
48056
|
const analysis = getProgramAnalysis(node);
|
|
49019
48057
|
if (!analysis) return;
|
|
49020
48058
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -49283,7 +48321,7 @@ const isTanStackServerFnHandlerCall = (node) => {
|
|
|
49283
48321
|
if (node.callee.property.name !== "handler") return false;
|
|
49284
48322
|
let currentNode = node.callee.object;
|
|
49285
48323
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
49286
|
-
const calleeName = getCalleeName$
|
|
48324
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
49287
48325
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
|
|
49288
48326
|
if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
|
|
49289
48327
|
currentNode = currentNode.callee.object;
|
|
@@ -49784,7 +48822,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49784
48822
|
create: (context) => {
|
|
49785
48823
|
const checkFunctionScope = (functionBody) => {
|
|
49786
48824
|
if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
|
|
49787
|
-
const useStateBindings = collectUseStateBindings(functionBody
|
|
48825
|
+
const useStateBindings = collectUseStateBindings(functionBody);
|
|
49788
48826
|
if (useStateBindings.length === 0) return;
|
|
49789
48827
|
const setterNameToStateName = /* @__PURE__ */ new Map();
|
|
49790
48828
|
for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
|
|
@@ -49793,7 +48831,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49793
48831
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
49794
48832
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
49795
48833
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
49796
|
-
if (!
|
|
48834
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
49797
48835
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
49798
48836
|
const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
|
|
49799
48837
|
if (dependencyStateNames.size === 0) continue;
|
|
@@ -49879,7 +48917,7 @@ const noSetStateInRender = defineRule({
|
|
|
49879
48917
|
create: (context) => {
|
|
49880
48918
|
const checkComponent = (componentBody) => {
|
|
49881
48919
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
49882
|
-
const setterNames = new Set(collectUseStateBindings(componentBody
|
|
48920
|
+
const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
|
|
49883
48921
|
if (setterNames.size === 0) return;
|
|
49884
48922
|
for (const statement of componentBody.body ?? []) {
|
|
49885
48923
|
const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
|
|
@@ -50128,10 +49166,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
|
|
|
50128
49166
|
});
|
|
50129
49167
|
return facts;
|
|
50130
49168
|
};
|
|
50131
|
-
const isEffectCallbackFunction = (functionNode
|
|
49169
|
+
const isEffectCallbackFunction = (functionNode) => {
|
|
50132
49170
|
const parent = functionNode.parent;
|
|
50133
49171
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
50134
|
-
return
|
|
49172
|
+
return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
|
|
50135
49173
|
};
|
|
50136
49174
|
const doesEffectCallbackReturnName = (effectCallback, name) => {
|
|
50137
49175
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -50150,24 +49188,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
|
|
|
50150
49188
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50151
49189
|
return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
|
|
50152
49190
|
};
|
|
50153
|
-
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope
|
|
49191
|
+
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
50154
49192
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50155
49193
|
if (cleanupBindingName === null) return false;
|
|
50156
49194
|
let isReturnedFromEffect = false;
|
|
50157
49195
|
walkAst(ownerScope, (child) => {
|
|
50158
49196
|
if (isReturnedFromEffect) return false;
|
|
50159
|
-
if (!isNodeOfType(child, "CallExpression") || !
|
|
49197
|
+
if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
50160
49198
|
const effectCallback = getEffectCallback(child);
|
|
50161
49199
|
if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
|
|
50162
49200
|
});
|
|
50163
49201
|
return isReturnedFromEffect;
|
|
50164
49202
|
};
|
|
50165
|
-
const isInsideEffectCleanupReturn = (node, ownerScope
|
|
49203
|
+
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
50166
49204
|
let functionNode = findEnclosingFunction$1(node);
|
|
50167
49205
|
while (functionNode) {
|
|
50168
49206
|
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
50169
|
-
if (outerFunction && isEffectCallbackFunction(outerFunction
|
|
50170
|
-
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope
|
|
49207
|
+
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
49208
|
+
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
50171
49209
|
functionNode = outerFunction;
|
|
50172
49210
|
}
|
|
50173
49211
|
return false;
|
|
@@ -50203,10 +49241,10 @@ const noStaleTimerRef = defineRule({
|
|
|
50203
49241
|
if (isShadowedTimerGlobal(node)) return;
|
|
50204
49242
|
const { clearCalleeName, refName } = clearCall;
|
|
50205
49243
|
const refBinding = findVariableInitializer(node, refName);
|
|
50206
|
-
if (!refBinding?.initializer || !
|
|
49244
|
+
if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
|
|
50207
49245
|
const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
|
|
50208
49246
|
if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
|
|
50209
|
-
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner
|
|
49247
|
+
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
|
|
50210
49248
|
if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
|
|
50211
49249
|
context.report({
|
|
50212
49250
|
node,
|
|
@@ -56010,7 +55048,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
|
|
|
56010
55048
|
allReadsAreInSubHandlers = false;
|
|
56011
55049
|
return;
|
|
56012
55050
|
}
|
|
56013
|
-
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$
|
|
55051
|
+
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
|
|
56014
55052
|
});
|
|
56015
55053
|
return {
|
|
56016
55054
|
hasAnyRead,
|
|
@@ -56033,7 +55071,7 @@ const preferUseEffectEvent = defineRule({
|
|
|
56033
55071
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
56034
55072
|
const effectCall = statement.expression;
|
|
56035
55073
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56036
|
-
if (!
|
|
55074
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
56037
55075
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56038
55076
|
const depsNode = effectCall.arguments[1];
|
|
56039
55077
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -56065,11 +55103,11 @@ const preferUseEffectEvent = defineRule({
|
|
|
56065
55103
|
});
|
|
56066
55104
|
//#endregion
|
|
56067
55105
|
//#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
|
|
56068
|
-
const findUseEffectsInComponent = (componentBody
|
|
55106
|
+
const findUseEffectsInComponent = (componentBody) => {
|
|
56069
55107
|
const effectCalls = [];
|
|
56070
55108
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
56071
55109
|
for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
|
|
56072
|
-
if (isNodeOfType(child, "CallExpression") &&
|
|
55110
|
+
if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
|
|
56073
55111
|
});
|
|
56074
55112
|
return effectCalls;
|
|
56075
55113
|
};
|
|
@@ -56272,7 +55310,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56272
55310
|
};
|
|
56273
55311
|
const checkComponent = (componentBody) => {
|
|
56274
55312
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
56275
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
55313
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
56276
55314
|
if (useStateBindings.length === 0) return;
|
|
56277
55315
|
const useStateInitializerByValueName = /* @__PURE__ */ new Map();
|
|
56278
55316
|
for (const binding of useStateBindings) {
|
|
@@ -56285,7 +55323,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56285
55323
|
}
|
|
56286
55324
|
const setterNameToValueName = /* @__PURE__ */ new Map();
|
|
56287
55325
|
for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
|
|
56288
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
55326
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56289
55327
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56290
55328
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56291
55329
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56327,7 +55365,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56327
55365
|
})).filter((candidate) => candidate.storeName !== null);
|
|
56328
55366
|
if (snapshotBindings.length === 0) return;
|
|
56329
55367
|
const reportedDeclarators = /* @__PURE__ */ new Set();
|
|
56330
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
55368
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56331
55369
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56332
55370
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56333
55371
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56424,7 +55462,7 @@ const preferUseReducer = defineRule({
|
|
|
56424
55462
|
create: (context) => {
|
|
56425
55463
|
const reportCoUpdatedUseState = (body, componentName) => {
|
|
56426
55464
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
56427
|
-
const bindings = collectUseStateBindings(body
|
|
55465
|
+
const bindings = collectUseStateBindings(body);
|
|
56428
55466
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
56429
55467
|
if (setterNames.size < 5) return;
|
|
56430
55468
|
const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
|
|
@@ -56640,7 +55678,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
|
|
|
56640
55678
|
]);
|
|
56641
55679
|
const isQueryCacheSourceCall = (initializer) => {
|
|
56642
55680
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
56643
|
-
const hookName = getCalleeName$
|
|
55681
|
+
const hookName = getCalleeName$2(initializer);
|
|
56644
55682
|
if (!hookName) return false;
|
|
56645
55683
|
return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
|
|
56646
55684
|
};
|
|
@@ -56772,7 +55810,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
56772
55810
|
},
|
|
56773
55811
|
CallExpression(node) {
|
|
56774
55812
|
if (!hasQueryReadUsage) {
|
|
56775
|
-
const callName = getCalleeName$
|
|
55813
|
+
const callName = getCalleeName$2(node);
|
|
56776
55814
|
if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
|
|
56777
55815
|
}
|
|
56778
55816
|
const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
|
|
@@ -57265,30 +56303,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
|
|
|
57265
56303
|
["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
|
|
57266
56304
|
["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
|
|
57267
56305
|
]);
|
|
57268
|
-
const resolveReactApiNameForIdentifier = (callee
|
|
56306
|
+
const resolveReactApiNameForIdentifier = (callee) => {
|
|
57269
56307
|
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
57270
|
-
if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
|
|
57271
56308
|
const importedName = getImportedNameFromModule(callee, callee.name, "react");
|
|
57272
56309
|
if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
|
|
57273
56310
|
return null;
|
|
57274
56311
|
};
|
|
57275
|
-
const resolveReactApiNameForMemberExpression = (callee
|
|
56312
|
+
const resolveReactApiNameForMemberExpression = (callee) => {
|
|
57276
56313
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
57277
56314
|
if (callee.computed) return null;
|
|
57278
|
-
const namespaceIdentifier =
|
|
56315
|
+
const namespaceIdentifier = callee.object;
|
|
57279
56316
|
const propertyIdentifier = callee.property;
|
|
57280
56317
|
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
57281
56318
|
if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
|
|
57282
56319
|
if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
|
|
57283
56320
|
const namespaceName = namespaceIdentifier.name;
|
|
57284
|
-
if (
|
|
57285
|
-
if (
|
|
56321
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
|
|
56322
|
+
if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
|
|
57286
56323
|
return null;
|
|
57287
56324
|
};
|
|
57288
|
-
const resolveReactApiNameForCallee = (callee
|
|
57289
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
57290
|
-
return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
|
|
57291
|
-
};
|
|
56325
|
+
const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
|
|
57292
56326
|
const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
|
|
57293
56327
|
const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
57294
56328
|
const calleeTrailingName = (callee) => {
|
|
@@ -57314,7 +56348,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
57314
56348
|
requires: ["react-compiler"],
|
|
57315
56349
|
recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
|
|
57316
56350
|
create: (context) => ({ CallExpression(node) {
|
|
57317
|
-
const apiName = resolveReactApiNameForCallee(node.callee
|
|
56351
|
+
const apiName = resolveReactApiNameForCallee(node.callee);
|
|
57318
56352
|
if (!apiName) return;
|
|
57319
56353
|
if (apiName === "memo") {
|
|
57320
56354
|
const comparatorArgument = node.arguments?.[1];
|
|
@@ -58439,125 +57473,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
58439
57473
|
const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
|
|
58440
57474
|
const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
|
|
58441
57475
|
const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
|
|
58442
|
-
const FILE_READER_READ_METHOD_NAMES = new Set([
|
|
58443
|
-
"readAsArrayBuffer",
|
|
58444
|
-
"readAsBinaryString",
|
|
58445
|
-
"readAsDataURL",
|
|
58446
|
-
"readAsText"
|
|
58447
|
-
]);
|
|
58448
|
-
const isGlobalFileReaderConstruction = (expression, context) => {
|
|
58449
|
-
if (!expression) return false;
|
|
58450
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58451
|
-
if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
58452
|
-
return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
|
|
58453
|
-
};
|
|
58454
|
-
const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
|
|
58455
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58456
|
-
let latestValue = null;
|
|
58457
|
-
let latestStart = null;
|
|
58458
|
-
if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
|
|
58459
|
-
latestValue = readerSymbol.initializer;
|
|
58460
|
-
latestStart = readerSymbol.declarationNode.range[0];
|
|
58461
|
-
}
|
|
58462
|
-
for (const reference of readerSymbol.references) {
|
|
58463
|
-
if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
|
|
58464
|
-
const assignment = reference.identifier.parent;
|
|
58465
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
|
|
58466
|
-
latestValue = assignment.right;
|
|
58467
|
-
latestStart = reference.identifier.range[0];
|
|
58468
|
-
}
|
|
58469
|
-
return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
|
|
58470
|
-
};
|
|
58471
|
-
const resolveLoadingCompletionFunction = (expression, context) => {
|
|
58472
|
-
const directFunction = resolveExactLocalFunction(expression, context.scopes);
|
|
58473
|
-
if (directFunction) return directFunction;
|
|
58474
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58475
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
58476
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
58477
|
-
const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
|
|
58478
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
|
|
58479
|
-
const callback = initializer.arguments?.[0];
|
|
58480
|
-
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
58481
|
-
};
|
|
58482
|
-
const isSetterBooleanCall = (node, setterSymbol, value, context) => {
|
|
58483
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
58484
|
-
const callee = stripParenExpression(node.callee);
|
|
58485
|
-
const argument = node.arguments?.[0];
|
|
58486
|
-
const unwrappedArgument = argument ? stripParenExpression(argument) : null;
|
|
58487
|
-
return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
|
|
58488
|
-
};
|
|
58489
|
-
const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
|
|
58490
|
-
if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
|
|
58491
|
-
visitedFunctions.add(functionNode);
|
|
58492
|
-
let didClearLoadingState = false;
|
|
58493
|
-
walkAst(functionNode.body, (child) => {
|
|
58494
|
-
if (didClearLoadingState) return false;
|
|
58495
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
58496
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
58497
|
-
if (isSetterBooleanCall(child, setterSymbol, false, context)) {
|
|
58498
|
-
didClearLoadingState = true;
|
|
58499
|
-
return false;
|
|
58500
|
-
}
|
|
58501
|
-
const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
|
|
58502
|
-
if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
|
|
58503
|
-
didClearLoadingState = true;
|
|
58504
|
-
return false;
|
|
58505
|
-
}
|
|
58506
|
-
});
|
|
58507
|
-
return didClearLoadingState;
|
|
58508
|
-
};
|
|
58509
|
-
const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
|
|
58510
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58511
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return null;
|
|
58512
|
-
let callback = null;
|
|
58513
|
-
let callbackStart = originStart;
|
|
58514
|
-
walkAst(readFunction.body, (child) => {
|
|
58515
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58516
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
|
|
58517
|
-
const receiver = stripParenExpression(child.left.object);
|
|
58518
|
-
if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
|
|
58519
|
-
callback = child.right;
|
|
58520
|
-
callbackStart = child.range[0];
|
|
58521
|
-
});
|
|
58522
|
-
return callback;
|
|
58523
|
-
};
|
|
58524
|
-
const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
|
|
58525
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58526
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return false;
|
|
58527
|
-
let didStartLoading = false;
|
|
58528
|
-
walkAst(readFunction.body, (child) => {
|
|
58529
|
-
if (didStartLoading) return false;
|
|
58530
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58531
|
-
if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
|
|
58532
|
-
if (isSetterBooleanCall(child, setterSymbol, true, context)) {
|
|
58533
|
-
didStartLoading = true;
|
|
58534
|
-
return false;
|
|
58535
|
-
}
|
|
58536
|
-
});
|
|
58537
|
-
return didStartLoading;
|
|
58538
|
-
};
|
|
58539
|
-
const setterTracksFileReader = (functionBody, setterSymbol, context) => {
|
|
58540
|
-
let didFindFileReaderLifecycle = false;
|
|
58541
|
-
walkAst(functionBody, (child) => {
|
|
58542
|
-
if (didFindFileReaderLifecycle) return false;
|
|
58543
|
-
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
|
|
58544
|
-
const receiver = stripParenExpression(child.callee.object);
|
|
58545
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
58546
|
-
const readerSymbol = context.scopes.symbolFor(receiver);
|
|
58547
|
-
if (!readerSymbol) return;
|
|
58548
|
-
const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
|
|
58549
|
-
if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
|
|
58550
|
-
const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
|
|
58551
|
-
const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
|
|
58552
|
-
const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
|
|
58553
|
-
const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
|
|
58554
|
-
if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
|
|
58555
|
-
didFindFileReaderLifecycle = true;
|
|
58556
|
-
return false;
|
|
58557
|
-
}
|
|
58558
|
-
});
|
|
58559
|
-
return didFindFileReaderLifecycle;
|
|
58560
|
-
};
|
|
58561
57476
|
const hasAsyncLoadingWork = (fnBody, setterName) => {
|
|
58562
57477
|
let found = false;
|
|
58563
57478
|
walkAst(fnBody, (child) => {
|
|
@@ -58731,8 +57646,6 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
58731
57646
|
const fnBody = enclosingFunctionBody(node);
|
|
58732
57647
|
if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
|
|
58733
57648
|
if (fnBody && setterName) {
|
|
58734
|
-
const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
|
|
58735
|
-
if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
|
|
58736
57649
|
if (setterEscapes(fnBody, setterName, node)) return;
|
|
58737
57650
|
if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
|
|
58738
57651
|
if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
|
|
@@ -59064,7 +57977,7 @@ const rerenderDependencies = defineRule({
|
|
|
59064
57977
|
severity: "error",
|
|
59065
57978
|
recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
|
|
59066
57979
|
create: (context) => ({ CallExpression(node) {
|
|
59067
|
-
if (!
|
|
57980
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
59068
57981
|
const depsNode = node.arguments[1];
|
|
59069
57982
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
59070
57983
|
for (const element of depsNode.elements ?? []) {
|
|
@@ -59319,7 +58232,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
59319
58232
|
category: "Performance",
|
|
59320
58233
|
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
59321
58234
|
create: (context) => ({ CallExpression(node) {
|
|
59322
|
-
if (!
|
|
58235
|
+
if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
|
|
59323
58236
|
const initializer = stripParenExpression(node.arguments[0]);
|
|
59324
58237
|
const isPlainCall = isNodeOfType(initializer, "CallExpression");
|
|
59325
58238
|
const isNewCall = isNodeOfType(initializer, "NewExpression");
|
|
@@ -59383,7 +58296,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
59383
58296
|
category: "Performance",
|
|
59384
58297
|
recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
|
|
59385
58298
|
create: (context) => ({ CallExpression(node) {
|
|
59386
|
-
if (!
|
|
58299
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
59387
58300
|
const initializer = findEagerInitializerCall(node.arguments[0]);
|
|
59388
58301
|
if (!initializer) return;
|
|
59389
58302
|
const isConstructor = isNodeOfType(initializer, "NewExpression");
|
|
@@ -60146,11 +59059,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
|
|
|
60146
59059
|
}
|
|
60147
59060
|
return false;
|
|
60148
59061
|
};
|
|
60149
|
-
const collectEffectDependencyInfos = (componentBody, setterNames
|
|
59062
|
+
const collectEffectDependencyInfos = (componentBody, setterNames) => {
|
|
60150
59063
|
const effectInfos = [];
|
|
60151
59064
|
walkAst(componentBody, (child) => {
|
|
60152
59065
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60153
|
-
if (!
|
|
59066
|
+
if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
60154
59067
|
const dependencyNames = /* @__PURE__ */ new Set();
|
|
60155
59068
|
for (const argument of child.arguments ?? []) {
|
|
60156
59069
|
if (!isNodeOfType(argument, "ArrayExpression")) continue;
|
|
@@ -60206,14 +59119,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
|
|
|
60206
59119
|
});
|
|
60207
59120
|
return effectInfos;
|
|
60208
59121
|
};
|
|
60209
|
-
const collectCustomHookArgumentNames = (componentBody
|
|
59122
|
+
const collectCustomHookArgumentNames = (componentBody) => {
|
|
60210
59123
|
const argumentNames = /* @__PURE__ */ new Set();
|
|
60211
59124
|
walkAst(componentBody, (child) => {
|
|
60212
59125
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60213
59126
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
60214
59127
|
const calleeName = child.callee.name;
|
|
60215
59128
|
if (!isReactHookName(calleeName)) return;
|
|
60216
|
-
if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
|
|
60217
59129
|
if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
|
|
60218
59130
|
if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
|
|
60219
59131
|
for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
|
|
@@ -60254,21 +59166,21 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60254
59166
|
create: (context) => {
|
|
60255
59167
|
const checkComponent = (componentBody) => {
|
|
60256
59168
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60257
|
-
const bindings = collectUseStateBindings(componentBody
|
|
59169
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
60258
59170
|
if (bindings.length === 0) return;
|
|
60259
59171
|
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
60260
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
59172
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
60261
59173
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60262
|
-
const directRenderNames = collectRenderReachableNames(componentBody,
|
|
59174
|
+
const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
|
|
60263
59175
|
if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
|
|
60264
59176
|
const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
|
|
60265
59177
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
60266
|
-
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames
|
|
59178
|
+
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
|
|
60267
59179
|
const selfEchoValueNames = /* @__PURE__ */ new Set();
|
|
60268
59180
|
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
59181
|
const effectConsumedNames = /* @__PURE__ */ new Set();
|
|
60270
59182
|
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
|
|
59183
|
+
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
|
|
60272
59184
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
60273
59185
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60274
59186
|
walkAst(componentBody, (child) => {
|
|
@@ -68169,7 +67081,7 @@ const declarationAwaitsGate = (declaration, context) => {
|
|
|
68169
67081
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
68170
67082
|
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
68171
67083
|
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
68172
|
-
const calleeName = getCalleeName$
|
|
67084
|
+
const calleeName = getCalleeName$2(argument);
|
|
68173
67085
|
if (!calleeName) continue;
|
|
68174
67086
|
if (isAuthGuardName(calleeName)) return true;
|
|
68175
67087
|
const [leadingToken] = tokenizeIdentifierWords(calleeName);
|
|
@@ -68809,7 +67721,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
68809
67721
|
if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
|
|
68810
67722
|
let currentNode = stripParenExpression(outerNode.callee.object);
|
|
68811
67723
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
68812
|
-
const calleeName = getCalleeName$
|
|
67724
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
68813
67725
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
|
|
68814
67726
|
result.isServerFnChain = true;
|
|
68815
67727
|
const optionsArgument = currentNode.arguments?.[0];
|
|
@@ -72941,17 +71853,6 @@ const reactDoctorRules = [
|
|
|
72941
71853
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72942
71854
|
}
|
|
72943
71855
|
},
|
|
72944
|
-
{
|
|
72945
|
-
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72946
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
72947
|
-
source: "react-doctor",
|
|
72948
|
-
originallyExternal: false,
|
|
72949
|
-
rule: {
|
|
72950
|
-
...noRefCallbackCleanupBeforeReact19,
|
|
72951
|
-
framework: "global",
|
|
72952
|
-
category: "Bugs"
|
|
72953
|
-
}
|
|
72954
|
-
},
|
|
72955
71856
|
{
|
|
72956
71857
|
key: "react-doctor/no-ref-current-in-render",
|
|
72957
71858
|
id: "no-ref-current-in-render",
|