oxlint-plugin-react-doctor 0.7.9-dev.2450582 → 0.7.9-dev.2ba83c3
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 +430 -1815
- 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 = (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(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(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;
|
|
@@ -30785,12 +30212,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30785
30212
|
const importDeclaration = declarationNode.parent;
|
|
30786
30213
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30787
30214
|
}));
|
|
30788
|
-
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30789
|
-
const receiver = stripParenExpression(node);
|
|
30790
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30791
|
-
const namespaceReference = getRef(analysis, receiver);
|
|
30792
|
-
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30793
|
-
};
|
|
30794
30215
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30795
30216
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30796
30217
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30799,22 +30220,34 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30799
30220
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30800
30221
|
return isReactNamedImportReference(reference, hookName);
|
|
30801
30222
|
}
|
|
30802
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30803
|
-
|
|
30223
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30224
|
+
const namespaceReference = getRef(analysis, callee.object);
|
|
30225
|
+
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30226
|
+
return isReactNamespaceImportReference(namespaceReference);
|
|
30804
30227
|
};
|
|
30805
30228
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30806
30229
|
if (!node) return false;
|
|
30807
30230
|
if (isNodeOfType(node, "Identifier")) {
|
|
30808
30231
|
if (node.name === hookName) return true;
|
|
30809
30232
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30810
|
-
const
|
|
30811
|
-
|
|
30812
|
-
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30233
|
+
const parent = node.parent;
|
|
30234
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30813
30235
|
return false;
|
|
30814
30236
|
}
|
|
30815
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30237
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
30238
|
+
const receiver = stripParenExpression(node.object);
|
|
30239
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30240
|
+
}
|
|
30816
30241
|
return false;
|
|
30817
30242
|
};
|
|
30243
|
+
const isUseEffect = (node) => {
|
|
30244
|
+
if (!node || !isNodeOfType(node, "CallExpression")) return false;
|
|
30245
|
+
const callee = node.callee;
|
|
30246
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
|
|
30247
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
30248
|
+
const receiver = stripParenExpression(callee.object);
|
|
30249
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
|
|
30250
|
+
};
|
|
30818
30251
|
const getEffectFn = (analysis, node) => {
|
|
30819
30252
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
30820
30253
|
const fn = node.arguments?.[0];
|
|
@@ -30912,27 +30345,7 @@ const isRefCurrent = (ref) => {
|
|
|
30912
30345
|
if (!isNodeOfType(parent.property, "Identifier")) return false;
|
|
30913
30346
|
return parent.property.name === "current";
|
|
30914
30347
|
};
|
|
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);
|
|
30348
|
+
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
30936
30349
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
30937
30350
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
30938
30351
|
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
@@ -31083,11 +30496,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
|
31083
30496
|
};
|
|
31084
30497
|
const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
|
|
31085
30498
|
const getUseStateDecl = (analysis, ref) => {
|
|
31086
|
-
|
|
31087
|
-
|
|
31088
|
-
|
|
31089
|
-
});
|
|
31090
|
-
return definition ? definition.node : null;
|
|
30499
|
+
let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
|
|
30500
|
+
while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
|
|
30501
|
+
return node ?? null;
|
|
31091
30502
|
};
|
|
31092
30503
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
31093
30504
|
if (isFunctionLike$1(node)) return true;
|
|
@@ -31250,88 +30661,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
31250
30661
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
31251
30662
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
31252
30663
|
};
|
|
31253
|
-
const
|
|
31254
|
-
const functionMetadata = functionNode;
|
|
31255
|
-
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
31256
|
-
};
|
|
31257
|
-
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
31258
|
-
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
31259
|
-
return null;
|
|
31260
|
-
};
|
|
31261
|
-
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
31262
|
-
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
31263
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
31264
|
-
if (!bindingIdentifier) return null;
|
|
31265
|
-
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
31266
|
-
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
31267
|
-
const definition = variable.defs[0];
|
|
31268
|
-
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
31269
|
-
if (definition.type !== "Variable") return null;
|
|
31270
|
-
const declarator = definition.node;
|
|
31271
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
31272
|
-
if (declarator.init === functionNode) return variable;
|
|
31273
|
-
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
31274
|
-
return null;
|
|
31275
|
-
};
|
|
31276
|
-
const getJsxEventValueAttribute = (identifier) => {
|
|
31277
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31278
|
-
const expressionContainer = expression.parent;
|
|
31279
|
-
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
31280
|
-
const attribute = expressionContainer.parent;
|
|
31281
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
31282
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
31283
|
-
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
31284
|
-
};
|
|
31285
|
-
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
31286
|
-
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
31287
|
-
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
31288
|
-
return getJsxEventValueAttribute(callbackFunction);
|
|
31289
|
-
};
|
|
31290
|
-
const isReactHookDependencyReference = (identifier) => {
|
|
31291
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31292
|
-
const dependencyArray = expression.parent;
|
|
31293
|
-
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
31294
|
-
const hookCall = dependencyArray.parent;
|
|
31295
|
-
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
31296
|
-
const callee = hookCall.callee;
|
|
31297
|
-
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
31298
|
-
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
31299
|
-
};
|
|
31300
|
-
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
31301
|
-
if (visitedVariables.has(functionVariable)) return false;
|
|
31302
|
-
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
31303
|
-
const callExpressions = [];
|
|
31304
|
-
let hasDirectJsxEventReference = false;
|
|
31305
|
-
for (const reference of functionVariable.references) {
|
|
31306
|
-
if (reference.init) continue;
|
|
31307
|
-
const identifier = reference.identifier;
|
|
31308
|
-
if (reference.isWrite()) return false;
|
|
31309
|
-
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
31310
|
-
if (jsxEventValueAttribute) {
|
|
31311
|
-
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
31312
|
-
continue;
|
|
31313
|
-
}
|
|
31314
|
-
if (isReactHookDependencyReference(identifier)) continue;
|
|
31315
|
-
const callExpression = getCallExpr(reference);
|
|
31316
|
-
if (!callExpression) return false;
|
|
31317
|
-
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
31318
|
-
if (jsxEventCallbackAttribute) {
|
|
31319
|
-
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
31320
|
-
continue;
|
|
31321
|
-
}
|
|
31322
|
-
callExpressions.push(callExpression);
|
|
31323
|
-
}
|
|
31324
|
-
if (hasDirectJsxEventReference) return true;
|
|
31325
|
-
for (const callExpression of callExpressions) {
|
|
31326
|
-
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
31327
|
-
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
31328
|
-
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
31329
|
-
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
31330
|
-
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
31331
|
-
}
|
|
31332
|
-
return false;
|
|
31333
|
-
};
|
|
31334
|
-
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30664
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
31335
30665
|
if (!setterRef.resolved) return false;
|
|
31336
30666
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
31337
30667
|
if (!componentFunction) return false;
|
|
@@ -31340,11 +30670,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
31340
30670
|
const identifier = reference.identifier;
|
|
31341
30671
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
31342
30672
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
31343
|
-
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
31344
|
-
const writerFunction = findEnclosingFunction$1(identifier);
|
|
31345
|
-
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
31346
|
-
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
31347
|
-
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
31348
30673
|
}
|
|
31349
30674
|
return false;
|
|
31350
30675
|
};
|
|
@@ -32234,7 +31559,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
32234
31559
|
}
|
|
32235
31560
|
return false;
|
|
32236
31561
|
};
|
|
32237
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31562
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
32238
31563
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
32239
31564
|
if (frames.length === 0) return [];
|
|
32240
31565
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -32264,7 +31589,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32264
31589
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
32265
31590
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
32266
31591
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
32267
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31592
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
32268
31593
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
32269
31594
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
32270
31595
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -32276,7 +31601,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32276
31601
|
sourceReferences,
|
|
32277
31602
|
isDeferred: frame.isDeferred,
|
|
32278
31603
|
isRenderKnownCopy,
|
|
32279
|
-
isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
|
|
32280
31604
|
matchesStateInitializer: doesMatchStateInitializer,
|
|
32281
31605
|
resetsSourceState: false
|
|
32282
31606
|
});
|
|
@@ -32292,71 +31616,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32292
31616
|
});
|
|
32293
31617
|
};
|
|
32294
31618
|
//#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
31619
|
//#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
|
|
32342
31620
|
const noAdjustStateOnPropChange = defineRule({
|
|
32343
31621
|
id: "no-adjust-state-on-prop-change",
|
|
32344
|
-
title: "State
|
|
31622
|
+
title: "State synced to a prop inside an effect",
|
|
32345
31623
|
severity: "warn",
|
|
32346
31624
|
tags: ["test-noise"],
|
|
32347
|
-
recommendation: "
|
|
31625
|
+
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
31626
|
create: (context) => ({ CallExpression(node) {
|
|
32349
|
-
if (!
|
|
31627
|
+
if (!isUseEffect(node)) return;
|
|
32350
31628
|
const analysis = getProgramAnalysis(node);
|
|
32351
31629
|
if (!analysis) return;
|
|
32352
31630
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32353
31631
|
if (!dependencyReferences) return;
|
|
32354
31632
|
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;
|
|
31633
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31634
|
+
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32360
31635
|
context.report({
|
|
32361
31636
|
node: fact.callExpression,
|
|
32362
31637
|
message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
|
|
@@ -34369,7 +33644,7 @@ const declarationBodyContainsHookCall = (symbol) => {
|
|
|
34369
33644
|
walkAst(componentFunction, (descendant) => {
|
|
34370
33645
|
if (didFindHookCall) return false;
|
|
34371
33646
|
if (!isNodeOfType(descendant, "CallExpression")) return;
|
|
34372
|
-
const calleeName = getCalleeName$
|
|
33647
|
+
const calleeName = getCalleeName$2(descendant);
|
|
34373
33648
|
if (calleeName && isReactHookName(calleeName)) {
|
|
34374
33649
|
didFindHookCall = true;
|
|
34375
33650
|
return false;
|
|
@@ -34394,7 +33669,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
|
|
|
34394
33669
|
if (isNodeOfType(parent, "ArrowFunctionExpression")) {
|
|
34395
33670
|
if (parent.body !== current) return false;
|
|
34396
33671
|
const grandparent = parent.parent;
|
|
34397
|
-
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$
|
|
33672
|
+
return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
|
|
34398
33673
|
}
|
|
34399
33674
|
if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
|
|
34400
33675
|
current = parent;
|
|
@@ -35076,7 +34351,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35076
34351
|
if (!callExpr) continue;
|
|
35077
34352
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35078
34353
|
if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
|
|
35079
|
-
const declarator = getUseStateDeclarator(
|
|
34354
|
+
const declarator = getUseStateDeclarator(ref);
|
|
35080
34355
|
if (declarator) domSyncedStateDeclarators.add(declarator);
|
|
35081
34356
|
}
|
|
35082
34357
|
for (const ref of effectFnRefs) {
|
|
@@ -35085,7 +34360,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35085
34360
|
if (!callExpr) continue;
|
|
35086
34361
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35087
34362
|
if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
|
|
35088
|
-
const setterDeclarator = getUseStateDeclarator(
|
|
34363
|
+
const setterDeclarator = getUseStateDeclarator(ref);
|
|
35089
34364
|
if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
|
|
35090
34365
|
const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
|
|
35091
34366
|
const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
|
|
@@ -36926,10 +36201,10 @@ const noDerivedState = defineRule({
|
|
|
36926
36201
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36927
36202
|
} }).visitors,
|
|
36928
36203
|
CallExpression(node) {
|
|
36929
|
-
if (!
|
|
36204
|
+
if (!isUseEffect(node)) return;
|
|
36930
36205
|
const analysis = getProgramAnalysis(node);
|
|
36931
36206
|
if (!analysis) return;
|
|
36932
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36207
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36933
36208
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36934
36209
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36935
36210
|
}
|
|
@@ -36946,10 +36221,10 @@ const noDerivedStateEffect = defineRule({
|
|
|
36946
36221
|
tags: ["test-noise"],
|
|
36947
36222
|
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
36223
|
create: (context) => ({ CallExpression(node) {
|
|
36949
|
-
if (!
|
|
36224
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36950
36225
|
const analysis = getProgramAnalysis(node);
|
|
36951
36226
|
if (!analysis) return;
|
|
36952
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36227
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36953
36228
|
context.report({
|
|
36954
36229
|
node,
|
|
36955
36230
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -37069,7 +36344,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
|
|
|
37069
36344
|
if (isFunctionLike$1(cursor)) {
|
|
37070
36345
|
const parent = cursor.parent ?? null;
|
|
37071
36346
|
if (parent && isNodeOfType(parent, "CallExpression")) {
|
|
37072
|
-
const calleeName = getCalleeName$
|
|
36347
|
+
const calleeName = getCalleeName$2(parent);
|
|
37073
36348
|
if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
|
|
37074
36349
|
}
|
|
37075
36350
|
}
|
|
@@ -37140,7 +36415,7 @@ const isNonHandlerHookCallback = (functionNode) => {
|
|
|
37140
36415
|
const parent = functionNode.parent ?? null;
|
|
37141
36416
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
37142
36417
|
if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
|
|
37143
|
-
const calleeName = getCalleeName$
|
|
36418
|
+
const calleeName = getCalleeName$2(parent);
|
|
37144
36419
|
return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
|
|
37145
36420
|
};
|
|
37146
36421
|
const isHandlerShapedReseed = (setterCall, componentFunction) => {
|
|
@@ -37222,7 +36497,7 @@ const noDerivedUseState = defineRule({
|
|
|
37222
36497
|
return {
|
|
37223
36498
|
...propStackTracker.visitors,
|
|
37224
36499
|
CallExpression(node) {
|
|
37225
|
-
if (!
|
|
36500
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
37226
36501
|
const seed = unwrapInitializerSeed(node.arguments[0]);
|
|
37227
36502
|
const reportStalePropCopy = (propName) => {
|
|
37228
36503
|
if (isIntentionalSnapshotState(node)) return;
|
|
@@ -37898,7 +37173,7 @@ const noDirectMutationState = defineRule({
|
|
|
37898
37173
|
const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
37899
37174
|
//#endregion
|
|
37900
37175
|
//#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
|
|
37901
|
-
const collectUseStateBindings = (componentBody
|
|
37176
|
+
const collectUseStateBindings = (componentBody) => {
|
|
37902
37177
|
const bindings = [];
|
|
37903
37178
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
37904
37179
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -37911,7 +37186,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37911
37186
|
const setterElement = elements[1];
|
|
37912
37187
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37913
37188
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37914
|
-
if (!
|
|
37189
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
37915
37190
|
bindings.push({
|
|
37916
37191
|
valueName: valueElement.name,
|
|
37917
37192
|
setterName: setterElement.name,
|
|
@@ -38078,7 +37353,7 @@ const noDirectStateMutation = defineRule({
|
|
|
38078
37353
|
create: (context) => {
|
|
38079
37354
|
const checkComponent = (componentBody) => {
|
|
38080
37355
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
38081
|
-
const bindings = collectUseStateBindings(componentBody
|
|
37356
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
38082
37357
|
if (bindings.length === 0) return;
|
|
38083
37358
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
38084
37359
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
@@ -38638,31 +37913,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
|
|
|
38638
37913
|
};
|
|
38639
37914
|
//#endregion
|
|
38640
37915
|
//#region src/plugin/rules/state-and-effects/no-effect-chain.ts
|
|
38641
|
-
const findTopLevelEffectCalls = (componentBody
|
|
37916
|
+
const findTopLevelEffectCalls = (componentBody) => {
|
|
38642
37917
|
const effectCalls = [];
|
|
38643
37918
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
38644
37919
|
for (const statement of componentBody.body ?? []) {
|
|
38645
37920
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38646
37921
|
const expression = unwrapDiscardedExpression(statement);
|
|
38647
37922
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38648
|
-
if (!
|
|
37923
|
+
if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
|
|
38649
37924
|
effectCalls.push(expression);
|
|
38650
37925
|
}
|
|
38651
37926
|
return effectCalls;
|
|
38652
37927
|
};
|
|
38653
|
-
const
|
|
38654
|
-
const
|
|
38655
|
-
if (!isNodeOfType(effectNode, "CallExpression")) return
|
|
37928
|
+
const collectDepIdentifierNames = (effectNode) => {
|
|
37929
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
37930
|
+
if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
|
|
38656
37931
|
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;
|
|
37932
|
+
if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
|
|
37933
|
+
for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
|
|
37934
|
+
return depNames;
|
|
38666
37935
|
};
|
|
38667
37936
|
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38668
37937
|
const analysisFunctions = new Set([effectCallback]);
|
|
@@ -38768,13 +38037,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
|
|
|
38768
38037
|
if (updater) return readStaticUpdaterReturnValue(updater, scopes);
|
|
38769
38038
|
return readStaticEffectValue(argument, scopes, null, null);
|
|
38770
38039
|
};
|
|
38771
|
-
const collectStateWritesInEffect = (analysisFunctions,
|
|
38040
|
+
const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
|
|
38772
38041
|
const stateWrites = /* @__PURE__ */ new Map();
|
|
38773
38042
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
38774
38043
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38775
38044
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
38776
|
-
const
|
|
38777
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
38045
|
+
const stateName = setterToStateName.get(child.callee.name);
|
|
38778
38046
|
if (!stateName) return;
|
|
38779
38047
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
38780
38048
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -38860,12 +38128,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
38860
38128
|
"keys",
|
|
38861
38129
|
"values"
|
|
38862
38130
|
]);
|
|
38863
|
-
const isFunctionShapedReturn = (returnedValue, setterToStateName,
|
|
38131
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
38864
38132
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
38865
38133
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
38866
38134
|
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
38867
|
-
|
|
38868
|
-
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
38135
|
+
if (setterToStateName.has(returnedValue.callee.name)) return false;
|
|
38869
38136
|
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
38870
38137
|
}
|
|
38871
38138
|
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
@@ -38889,7 +38156,7 @@ const collectStorageHookSetterNames = (componentBody) => {
|
|
|
38889
38156
|
for (const declarator of statement.declarations ?? []) {
|
|
38890
38157
|
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
38891
38158
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
38892
|
-
const calleeName = getCalleeName$
|
|
38159
|
+
const calleeName = getCalleeName$2(declarator.init);
|
|
38893
38160
|
if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
|
|
38894
38161
|
for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
|
|
38895
38162
|
}
|
|
@@ -39032,11 +38299,11 @@ const isExternalSyncNode = (node) => {
|
|
|
39032
38299
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39033
38300
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39034
38301
|
};
|
|
39035
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName,
|
|
38302
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
|
|
39036
38303
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
39037
38304
|
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,
|
|
38305
|
+
if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
|
|
38306
|
+
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
|
|
39040
38307
|
let didFindExternalCall = false;
|
|
39041
38308
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39042
38309
|
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
@@ -39052,11 +38319,10 @@ const noEffectChain = defineRule({
|
|
|
39052
38319
|
create: (context) => {
|
|
39053
38320
|
const checkComponent = (componentBody) => {
|
|
39054
38321
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
39055
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
38322
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
39056
38323
|
if (useStateBindings.length === 0) return;
|
|
39057
38324
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
39058
38325
|
const stateSymbolIds = /* @__PURE__ */ new Map();
|
|
39059
|
-
const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
|
|
39060
38326
|
for (const binding of useStateBindings) {
|
|
39061
38327
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39062
38328
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
@@ -39065,27 +38331,21 @@ const noEffectChain = defineRule({
|
|
|
39065
38331
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39066
38332
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39067
38333
|
}
|
|
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
38334
|
}
|
|
39074
38335
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
39075
|
-
const stateSymbolIdSet = new Set(stateSymbolIds.values());
|
|
39076
38336
|
const effectInfos = [];
|
|
39077
|
-
for (const effectCall of findTopLevelEffectCalls(componentBody
|
|
38337
|
+
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
39078
38338
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39079
38339
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39080
38340
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39081
|
-
const stateWrites = collectStateWritesInEffect(analysisFunctions,
|
|
38341
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
39082
38342
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39083
38343
|
effectInfos.push({
|
|
39084
38344
|
node: effectCall,
|
|
39085
|
-
|
|
38345
|
+
depNames: collectDepIdentifierNames(effectCall),
|
|
39086
38346
|
stateWrites,
|
|
39087
38347
|
analysisFunctions,
|
|
39088
|
-
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName,
|
|
38348
|
+
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
39089
38349
|
});
|
|
39090
38350
|
}
|
|
39091
38351
|
if (effectInfos.length < 2) return;
|
|
@@ -39096,11 +38356,10 @@ const noEffectChain = defineRule({
|
|
|
39096
38356
|
for (const readerEffect of effectInfos) {
|
|
39097
38357
|
if (readerEffect === writerEffect) continue;
|
|
39098
38358
|
if (readerEffect.isExternalSync) continue;
|
|
39099
|
-
if (readerEffect.
|
|
38359
|
+
if (readerEffect.depNames.size === 0) continue;
|
|
39100
38360
|
let chainedStateName = null;
|
|
39101
38361
|
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
39102
|
-
|
|
39103
|
-
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
38362
|
+
if (!readerEffect.depNames.has(writtenName)) continue;
|
|
39104
38363
|
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
39105
38364
|
chainedStateName = writtenName;
|
|
39106
38365
|
break;
|
|
@@ -39377,7 +38636,7 @@ const noEffectEventHandler = defineRule({
|
|
|
39377
38636
|
return {
|
|
39378
38637
|
...propStackTracker.visitors,
|
|
39379
38638
|
CallExpression(node) {
|
|
39380
|
-
if (!
|
|
38639
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
39381
38640
|
const callback = getEffectCallback(node);
|
|
39382
38641
|
if (!callback) return;
|
|
39383
38642
|
const analysis = getProgramAnalysis(node);
|
|
@@ -39497,14 +38756,14 @@ const noEffectEventInDeps = defineRule({
|
|
|
39497
38756
|
if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
|
|
39498
38757
|
const initializer = declaratorNode.init;
|
|
39499
38758
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
39500
|
-
if (!
|
|
38759
|
+
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
39501
38760
|
if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
|
|
39502
38761
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
39503
38762
|
} });
|
|
39504
38763
|
return {
|
|
39505
38764
|
...componentBindings.visitors,
|
|
39506
38765
|
CallExpression(node) {
|
|
39507
|
-
if (!
|
|
38766
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
39508
38767
|
if (!componentBindings.isInsideComponent()) return;
|
|
39509
38768
|
const depsNode = node.arguments[1];
|
|
39510
38769
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -39561,7 +38820,7 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
39561
38820
|
node: finding.reportNode,
|
|
39562
38821
|
message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
|
|
39563
38822
|
});
|
|
39564
|
-
if (!
|
|
38823
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
|
|
39565
38824
|
const args = node.arguments ?? [];
|
|
39566
38825
|
if (args.length < 2) return;
|
|
39567
38826
|
const depsNode = args[1];
|
|
@@ -39822,7 +39081,7 @@ const noEventHandler = defineRule({
|
|
|
39822
39081
|
severity: "warn",
|
|
39823
39082
|
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
39083
|
create: (context) => ({ CallExpression(node) {
|
|
39825
|
-
if (!
|
|
39084
|
+
if (!isUseEffect(node)) return;
|
|
39826
39085
|
const analysis = getProgramAnalysis(node);
|
|
39827
39086
|
if (!analysis || hasCleanup(analysis, node)) return;
|
|
39828
39087
|
const frames = collectBoundedEffectExecutionFrames(analysis, node);
|
|
@@ -40284,25 +39543,25 @@ const addDeclarationBindings = (statement, scope) => {
|
|
|
40284
39543
|
}
|
|
40285
39544
|
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
|
|
40286
39545
|
};
|
|
40287
|
-
const collectRenderReachableNamesFromStatements = (statements, names, scope,
|
|
39546
|
+
const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40288
39547
|
let hasReturn = false;
|
|
40289
|
-
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope,
|
|
39548
|
+
for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
|
|
40290
39549
|
else addDeclarationBindings(statement, scope);
|
|
40291
39550
|
return hasReturn;
|
|
40292
39551
|
};
|
|
40293
|
-
const collectRenderReachableNamesFromStatement = (statement, names, scope,
|
|
39552
|
+
const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
|
|
40294
39553
|
if (isNodeOfType(statement, "ReturnStatement")) {
|
|
40295
39554
|
if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
|
|
40296
39555
|
return true;
|
|
40297
39556
|
}
|
|
40298
|
-
if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") &&
|
|
39557
|
+
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
39558
|
for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
|
|
40300
39559
|
return false;
|
|
40301
39560
|
}
|
|
40302
|
-
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope),
|
|
39561
|
+
if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
|
|
40303
39562
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40304
|
-
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope,
|
|
40305
|
-
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope,
|
|
39563
|
+
const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
|
|
39564
|
+
const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
|
|
40306
39565
|
if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40307
39566
|
return consequentHasReturn || alternateHasReturn;
|
|
40308
39567
|
}
|
|
@@ -40310,7 +39569,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40310
39569
|
let hasReturn = false;
|
|
40311
39570
|
for (const switchCase of statement.cases ?? []) {
|
|
40312
39571
|
const caseScope = createBlockBindingScope(scope);
|
|
40313
|
-
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope,
|
|
39572
|
+
if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
|
|
40314
39573
|
hasReturn = true;
|
|
40315
39574
|
if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
|
|
40316
39575
|
}
|
|
@@ -40318,25 +39577,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40318
39577
|
return hasReturn;
|
|
40319
39578
|
}
|
|
40320
39579
|
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,
|
|
39580
|
+
const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
|
|
39581
|
+
const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
|
|
39582
|
+
const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
|
|
40324
39583
|
return blockHasReturn || handlerHasReturn || finalizerHasReturn;
|
|
40325
39584
|
}
|
|
40326
39585
|
if (isNodeOfType(statement, "CatchClause")) {
|
|
40327
39586
|
const catchScope = createBlockBindingScope(scope);
|
|
40328
39587
|
addPatternBindings(statement.param, catchScope);
|
|
40329
|
-
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope,
|
|
39588
|
+
return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
|
|
40330
39589
|
}
|
|
40331
39590
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40332
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39591
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40333
39592
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
|
|
40334
39593
|
return bodyHasReturn;
|
|
40335
39594
|
}
|
|
40336
39595
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40337
39596
|
const loopScope = createBlockBindingScope(scope);
|
|
40338
39597
|
if (statement.init) addDeclarationBindings(statement.init, loopScope);
|
|
40339
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
39598
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40340
39599
|
if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
|
|
40341
39600
|
if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
|
|
40342
39601
|
if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
|
|
@@ -40346,22 +39605,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
|
|
|
40346
39605
|
const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
|
|
40347
39606
|
const loopScope = createBlockBindingScope(scope);
|
|
40348
39607
|
if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
|
|
40349
|
-
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope,
|
|
39608
|
+
if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
|
|
40350
39609
|
addNames(names, rightNames);
|
|
40351
39610
|
return true;
|
|
40352
39611
|
}
|
|
40353
|
-
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39612
|
+
if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40354
39613
|
if (isNodeOfType(statement, "WithStatement")) {
|
|
40355
|
-
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope,
|
|
39614
|
+
const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
|
|
40356
39615
|
if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
|
|
40357
39616
|
return bodyHasReturn;
|
|
40358
39617
|
}
|
|
40359
39618
|
return false;
|
|
40360
39619
|
};
|
|
40361
|
-
const collectRenderReachableNames = (componentBody,
|
|
39620
|
+
const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
|
|
40362
39621
|
const names = /* @__PURE__ */ new Set();
|
|
40363
39622
|
if (!isNodeOfType(componentBody, "BlockStatement")) return names;
|
|
40364
|
-
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(),
|
|
39623
|
+
collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
|
|
40365
39624
|
return names;
|
|
40366
39625
|
};
|
|
40367
39626
|
//#endregion
|
|
@@ -40384,36 +39643,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
|
|
|
40384
39643
|
};
|
|
40385
39644
|
//#endregion
|
|
40386
39645
|
//#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
|
|
40387
|
-
const
|
|
40388
|
-
|
|
39646
|
+
const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
|
|
39647
|
+
const getCalleeName = (node) => {
|
|
39648
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
39649
|
+
if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
|
|
39650
|
+
return null;
|
|
39651
|
+
};
|
|
39652
|
+
const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
|
|
39653
|
+
if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
|
|
40389
39654
|
if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
|
|
40390
39655
|
const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
|
|
40391
39656
|
return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
|
|
40392
39657
|
};
|
|
40393
|
-
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope
|
|
39658
|
+
const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
|
|
40394
39659
|
if (!isNodeOfType(node, "ObjectExpression")) return;
|
|
40395
39660
|
for (const property of node.properties ?? []) {
|
|
40396
39661
|
if (!isNodeOfType(property, "Property")) continue;
|
|
40397
39662
|
const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
|
|
40398
39663
|
if (!propertyName) continue;
|
|
40399
|
-
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope
|
|
39664
|
+
if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
|
|
40400
39665
|
functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
|
|
40401
39666
|
}
|
|
40402
39667
|
};
|
|
40403
|
-
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope
|
|
39668
|
+
const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40404
39669
|
if (!isNodeOfType(statement, "VariableDeclaration")) return;
|
|
40405
39670
|
const declarationScope = getVariableDeclarationScope(statement, scope);
|
|
40406
39671
|
for (const declarator of statement.declarations ?? []) {
|
|
40407
39672
|
const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
|
|
40408
39673
|
if (!declarator.init) continue;
|
|
40409
|
-
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope
|
|
39674
|
+
const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
|
|
40410
39675
|
for (const declaredBindingName of declaredBindingNames) {
|
|
40411
39676
|
if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
|
|
40412
|
-
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope
|
|
39677
|
+
addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
|
|
40413
39678
|
}
|
|
40414
39679
|
}
|
|
40415
39680
|
};
|
|
40416
|
-
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope
|
|
39681
|
+
const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
|
|
40417
39682
|
if (isNodeOfType(statement, "FunctionDeclaration")) {
|
|
40418
39683
|
if (statement.id) {
|
|
40419
39684
|
const declaredBindingNames = addPatternBindings(statement.id, scope);
|
|
@@ -40422,61 +39687,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
|
|
|
40422
39687
|
return;
|
|
40423
39688
|
}
|
|
40424
39689
|
if (isNodeOfType(statement, "VariableDeclaration")) {
|
|
40425
|
-
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope
|
|
39690
|
+
addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40426
39691
|
return;
|
|
40427
39692
|
}
|
|
40428
39693
|
if (isNodeOfType(statement, "BlockStatement")) {
|
|
40429
|
-
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
39694
|
+
collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40430
39695
|
return;
|
|
40431
39696
|
}
|
|
40432
39697
|
if (isNodeOfType(statement, "IfStatement")) {
|
|
40433
|
-
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope
|
|
40434
|
-
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope
|
|
39698
|
+
collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
|
|
39699
|
+
if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
|
|
40435
39700
|
return;
|
|
40436
39701
|
}
|
|
40437
39702
|
if (isNodeOfType(statement, "SwitchStatement")) {
|
|
40438
|
-
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope)
|
|
39703
|
+
for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
|
|
40439
39704
|
return;
|
|
40440
39705
|
}
|
|
40441
39706
|
if (isNodeOfType(statement, "TryStatement")) {
|
|
40442
|
-
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope
|
|
39707
|
+
collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
|
|
40443
39708
|
if (statement.handler) {
|
|
40444
39709
|
const catchScope = createBlockBindingScope(scope);
|
|
40445
39710
|
addPatternBindings(statement.handler.param, catchScope);
|
|
40446
|
-
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope
|
|
39711
|
+
collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
|
|
40447
39712
|
}
|
|
40448
|
-
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope
|
|
39713
|
+
if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
|
|
40449
39714
|
return;
|
|
40450
39715
|
}
|
|
40451
39716
|
if (isNodeOfType(statement, "ForStatement")) {
|
|
40452
39717
|
const loopScope = createBlockBindingScope(scope);
|
|
40453
|
-
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope
|
|
40454
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
39718
|
+
if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
|
|
39719
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40455
39720
|
return;
|
|
40456
39721
|
}
|
|
40457
39722
|
if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
|
|
40458
39723
|
const loopScope = createBlockBindingScope(scope);
|
|
40459
|
-
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope
|
|
39724
|
+
if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
|
|
40460
39725
|
else addPatternBindings(statement.left, loopScope);
|
|
40461
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope
|
|
39726
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
|
|
40462
39727
|
return;
|
|
40463
39728
|
}
|
|
40464
39729
|
if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
|
|
40465
|
-
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
39730
|
+
collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40466
39731
|
return;
|
|
40467
39732
|
}
|
|
40468
|
-
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope
|
|
39733
|
+
if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
|
|
40469
39734
|
};
|
|
40470
|
-
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope
|
|
40471
|
-
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope
|
|
39735
|
+
const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
|
|
39736
|
+
for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
|
|
40472
39737
|
};
|
|
40473
|
-
const collectFunctionLikeLocalNames = (componentBody
|
|
39738
|
+
const collectFunctionLikeLocalNames = (componentBody) => {
|
|
40474
39739
|
const functionLikeLocalNames = /* @__PURE__ */ new Set();
|
|
40475
39740
|
if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
|
|
40476
39741
|
let previousSize = -1;
|
|
40477
39742
|
while (previousSize !== functionLikeLocalNames.size) {
|
|
40478
39743
|
previousSize = functionLikeLocalNames.size;
|
|
40479
|
-
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope()
|
|
39744
|
+
collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
|
|
40480
39745
|
}
|
|
40481
39746
|
return functionLikeLocalNames;
|
|
40482
39747
|
};
|
|
@@ -40516,17 +39781,17 @@ const noEventTriggerState = defineRule({
|
|
|
40516
39781
|
create: (context) => {
|
|
40517
39782
|
const checkComponent = (componentBody) => {
|
|
40518
39783
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
40519
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
39784
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
40520
39785
|
if (useStateBindings.length === 0) return;
|
|
40521
39786
|
const analysis = getProgramAnalysis(componentBody);
|
|
40522
39787
|
if (!analysis) return;
|
|
40523
39788
|
const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
|
|
40524
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
39789
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
40525
39790
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
40526
|
-
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody,
|
|
39791
|
+
const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
|
|
40527
39792
|
walkAst(componentBody, (effectCall) => {
|
|
40528
39793
|
if (!isNodeOfType(effectCall, "CallExpression")) return;
|
|
40529
|
-
if (!
|
|
39794
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
|
|
40530
39795
|
if ((effectCall.arguments?.length ?? 0) < 2) return;
|
|
40531
39796
|
const depsNode = effectCall.arguments[1];
|
|
40532
39797
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -40591,17 +39856,10 @@ const isShadowedByLocalBinding = (identifier) => {
|
|
|
40591
39856
|
};
|
|
40592
39857
|
const isRealFetchCall = (node) => {
|
|
40593
39858
|
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";
|
|
39859
|
+
if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
|
|
39860
|
+
return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
|
|
40604
39861
|
};
|
|
39862
|
+
const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
|
|
40605
39863
|
const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
|
|
40606
39864
|
const resolveLocalFunction = (expression, context) => {
|
|
40607
39865
|
if (!expression) return null;
|
|
@@ -40917,17 +40175,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
|
|
|
40917
40175
|
"scrollHeight"
|
|
40918
40176
|
]);
|
|
40919
40177
|
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
40178
|
const subtreeReadsDomMeasurement = (root) => {
|
|
40932
40179
|
if (!root) return false;
|
|
40933
40180
|
let found = false;
|
|
@@ -40946,66 +40193,29 @@ const subtreeReadsDomMeasurement = (root) => {
|
|
|
40946
40193
|
});
|
|
40947
40194
|
return found;
|
|
40948
40195
|
};
|
|
40949
|
-
const
|
|
40196
|
+
const collectMeasuringFunctionNames = (program) => {
|
|
40950
40197
|
const names = /* @__PURE__ */ new Set();
|
|
40951
40198
|
walkAst(program, (child) => {
|
|
40952
40199
|
if (isNodeOfType(child, "FunctionDeclaration")) {
|
|
40953
|
-
if (child.id && isNodeOfType(child.id, "Identifier") &&
|
|
40200
|
+
if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
|
|
40954
40201
|
return;
|
|
40955
40202
|
}
|
|
40956
40203
|
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
|
|
40957
40204
|
let functionValue = child.init;
|
|
40958
40205
|
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) &&
|
|
40206
|
+
if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
|
|
40960
40207
|
});
|
|
40961
40208
|
return names;
|
|
40962
40209
|
};
|
|
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;
|
|
40210
|
+
const callsAnyName = (root, names) => {
|
|
40211
|
+
if (!root || names.size === 0) return false;
|
|
40983
40212
|
let found = false;
|
|
40984
40213
|
walkAst(root, (child) => {
|
|
40985
40214
|
if (found) return false;
|
|
40986
|
-
if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
|
|
40987
40215
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
|
|
40988
40216
|
});
|
|
40989
40217
|
return found;
|
|
40990
40218
|
};
|
|
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
40219
|
const isInsideStartViewTransition = (node) => {
|
|
41010
40220
|
let cursor = node.parent;
|
|
41011
40221
|
while (cursor) {
|
|
@@ -41046,12 +40256,11 @@ const importsImperativeDomLibrary = (program) => {
|
|
|
41046
40256
|
};
|
|
41047
40257
|
const hasExemptFlushSyncCall = (program, localName) => {
|
|
41048
40258
|
const measuringFunctionNames = collectMeasuringFunctionNames(program);
|
|
41049
|
-
const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
|
|
41050
40259
|
let exempt = false;
|
|
41051
40260
|
walkAst(program, (child) => {
|
|
41052
40261
|
if (exempt) return false;
|
|
41053
40262
|
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
|
|
41054
|
-
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)
|
|
40263
|
+
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
|
|
41055
40264
|
exempt = true;
|
|
41056
40265
|
return false;
|
|
41057
40266
|
}
|
|
@@ -41616,223 +40825,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41616
40825
|
if (leftResult === false && rightResult === false) return false;
|
|
41617
40826
|
return null;
|
|
41618
40827
|
};
|
|
41619
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40828
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41620
40829
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41621
40830
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41622
40831
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41623
40832
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41624
40833
|
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
40834
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41668
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40835
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41669
40836
|
return argumentResult === null ? null : !argumentResult;
|
|
41670
40837
|
}
|
|
41671
40838
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41672
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
41673
|
-
};
|
|
41674
|
-
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
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;
|
|
40839
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41742
40840
|
};
|
|
41743
|
-
const
|
|
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) => {
|
|
40841
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41748
40842
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41749
40843
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41750
40844
|
if (predicateMatch) return {
|
|
41751
40845
|
predicateMatch,
|
|
41752
40846
|
predicateNode: unwrappedExpression
|
|
41753
40847
|
};
|
|
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);
|
|
40848
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41798
40849
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41799
|
-
const leftMatch =
|
|
41800
|
-
const rightMatch =
|
|
40850
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40851
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40852
|
+
if (leftMatch && rightMatch) {
|
|
40853
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40854
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40855
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40856
|
+
}
|
|
41801
40857
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41802
40858
|
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;
|
|
40859
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40860
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40861
|
+
return nestedMatch;
|
|
41823
40862
|
};
|
|
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
40863
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41837
40864
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41838
40865
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41975,17 +41002,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41975
41002
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41976
41003
|
if (reportedNodes.has(predicateNode)) return;
|
|
41977
41004
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41978
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
41005
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41979
41006
|
if (!componentOrHookNode) return;
|
|
41980
41007
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41981
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
41008
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41982
41009
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41983
|
-
const attribute = findEnclosingJsxAttribute(
|
|
41010
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41984
41011
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41985
41012
|
}
|
|
41986
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41987
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41988
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
41013
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
41014
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41015
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41989
41016
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41990
41017
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41991
41018
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -42362,12 +41389,12 @@ const noInitializeState = defineRule({
|
|
|
42362
41389
|
tags: ["test-noise"],
|
|
42363
41390
|
recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
|
|
42364
41391
|
create: (context) => ({ CallExpression(node) {
|
|
42365
|
-
if (!
|
|
41392
|
+
if (!isUseEffect(node)) return;
|
|
42366
41393
|
const dependencies = node.arguments?.[1];
|
|
42367
41394
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42368
41395
|
const analysis = getProgramAnalysis(node);
|
|
42369
41396
|
if (!analysis) return;
|
|
42370
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41397
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
42371
41398
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
42372
41399
|
const stateName = getStateName(fact.stateDeclarator);
|
|
42373
41400
|
context.report({
|
|
@@ -42725,8 +41752,7 @@ const noJsxElementType = defineRule({
|
|
|
42725
41752
|
create: (context) => {
|
|
42726
41753
|
let isJsxImported = false;
|
|
42727
41754
|
const flaggedAnnotations = [];
|
|
42728
|
-
const
|
|
42729
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41755
|
+
const checkReturnType = (returnType) => {
|
|
42730
41756
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42731
41757
|
if (!typeAnnotation) return;
|
|
42732
41758
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42736,16 +41762,19 @@ const noJsxElementType = defineRule({
|
|
|
42736
41762
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42737
41763
|
},
|
|
42738
41764
|
FunctionDeclaration(node) {
|
|
42739
|
-
|
|
41765
|
+
checkReturnType(node.returnType);
|
|
42740
41766
|
},
|
|
42741
41767
|
ArrowFunctionExpression(node) {
|
|
42742
|
-
|
|
41768
|
+
checkReturnType(node.returnType);
|
|
42743
41769
|
},
|
|
42744
41770
|
FunctionExpression(node) {
|
|
42745
|
-
|
|
41771
|
+
checkReturnType(node.returnType);
|
|
42746
41772
|
},
|
|
42747
41773
|
TSDeclareFunction(node) {
|
|
42748
|
-
|
|
41774
|
+
checkReturnType(node.returnType);
|
|
41775
|
+
},
|
|
41776
|
+
TSMethodSignature(node) {
|
|
41777
|
+
checkReturnType(node.returnType);
|
|
42749
41778
|
},
|
|
42750
41779
|
"Program:exit"() {
|
|
42751
41780
|
if (isJsxImported) return;
|
|
@@ -43974,7 +43003,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43974
43003
|
const setterElement = elements[1];
|
|
43975
43004
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
43976
43005
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
43977
|
-
if (!
|
|
43006
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
43978
43007
|
const initializer = declarator.init.arguments?.[0];
|
|
43979
43008
|
if (!initializer) continue;
|
|
43980
43009
|
const propRootName = getPropRootName(initializer, propNames);
|
|
@@ -43992,7 +43021,7 @@ const noMirrorPropEffect = defineRule({
|
|
|
43992
43021
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
43993
43022
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
43994
43023
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
43995
|
-
if (!
|
|
43024
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
43996
43025
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
43997
43026
|
const depsNode = effectCall.arguments[1];
|
|
43998
43027
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -44401,7 +43430,7 @@ const noMultiComp = defineRule({
|
|
|
44401
43430
|
});
|
|
44402
43431
|
//#endregion
|
|
44403
43432
|
//#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
|
|
44404
|
-
const collectUseRefBindingNames = (componentBody
|
|
43433
|
+
const collectUseRefBindingNames = (componentBody) => {
|
|
44405
43434
|
const useRefBindings = /* @__PURE__ */ new Set();
|
|
44406
43435
|
if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
|
|
44407
43436
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -44409,7 +43438,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
|
|
|
44409
43438
|
for (const declarator of statement.declarations ?? []) {
|
|
44410
43439
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
44411
43440
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
44412
|
-
if (!
|
|
43441
|
+
if (!isHookCall$2(declarator.init, "useRef")) continue;
|
|
44413
43442
|
useRefBindings.add(declarator.id.name);
|
|
44414
43443
|
}
|
|
44415
43444
|
}
|
|
@@ -44444,12 +43473,12 @@ const noMutableInDeps = defineRule({
|
|
|
44444
43473
|
create: (context) => {
|
|
44445
43474
|
const checkComponent = (componentBody, componentParams = []) => {
|
|
44446
43475
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
44447
|
-
const useRefBindingNames = collectUseRefBindingNames(componentBody
|
|
43476
|
+
const useRefBindingNames = collectUseRefBindingNames(componentBody);
|
|
44448
43477
|
const localBindingNames = collectLocalBindingNames(componentBody);
|
|
44449
43478
|
for (const param of componentParams) collectPatternNames(param, localBindingNames);
|
|
44450
43479
|
walkAst(componentBody, (child) => {
|
|
44451
43480
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
44452
|
-
if (!
|
|
43481
|
+
if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
|
|
44453
43482
|
if ((child.arguments?.length ?? 0) < 2) return;
|
|
44454
43483
|
const depsNode = child.arguments[1];
|
|
44455
43484
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
@@ -46279,7 +45308,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
46279
45308
|
"useMatchMedia",
|
|
46280
45309
|
"useMediaJobProgress",
|
|
46281
45310
|
"useMediaQuery",
|
|
46282
|
-
"useMediaQueryState",
|
|
46283
45311
|
"useResizeObserver",
|
|
46284
45312
|
"useVisibility",
|
|
46285
45313
|
"useWindowSize"
|
|
@@ -46316,30 +45344,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46316
45344
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46317
45345
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
46318
45346
|
};
|
|
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) => {
|
|
45347
|
+
const isExternalSubscriptionHookRef = (ref) => {
|
|
46339
45348
|
const identifier = ref.identifier;
|
|
46340
45349
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46341
|
-
const localHookProof = getLocalHookExternalStateProof(analysis, ref);
|
|
46342
|
-
if (localHookProof !== null) return localHookProof;
|
|
46343
45350
|
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46344
45351
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46345
45352
|
const node = def.node;
|
|
@@ -46362,10 +45369,16 @@ const noPassDataToParent = defineRule({
|
|
|
46362
45369
|
tags: ["test-noise"],
|
|
46363
45370
|
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
45371
|
create: (context) => {
|
|
46365
|
-
const isReactUseRefCall = (node) =>
|
|
46366
|
-
|
|
45372
|
+
const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
|
|
45373
|
+
allowGlobalReactNamespace: true,
|
|
45374
|
+
allowUnboundBareCalls: true
|
|
45375
|
+
});
|
|
45376
|
+
const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
|
|
45377
|
+
allowGlobalReactNamespace: true,
|
|
45378
|
+
allowUnboundBareCalls: true
|
|
45379
|
+
});
|
|
46367
45380
|
return { CallExpression(node) {
|
|
46368
|
-
if (!
|
|
45381
|
+
if (!isUseEffect(node)) return;
|
|
46369
45382
|
const analysis = getProgramAnalysis(node);
|
|
46370
45383
|
if (!analysis) return;
|
|
46371
45384
|
if (hasCleanup(analysis, node)) return;
|
|
@@ -46417,11 +45430,11 @@ const noPassDataToParent = defineRule({
|
|
|
46417
45430
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46418
45431
|
}
|
|
46419
45432
|
return getDownstreamRefs(analysis, argument);
|
|
46420
|
-
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef)
|
|
45433
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
46421
45434
|
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
46422
45435
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46423
45436
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46424
|
-
if (isExternalSubscriptionHookRef(
|
|
45437
|
+
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
46425
45438
|
if (isProp(analysis, argRef)) return false;
|
|
46426
45439
|
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
46427
45440
|
if (isRefCurrent(argRef)) return false;
|
|
@@ -46652,7 +45665,7 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46652
45665
|
tags: ["test-noise"],
|
|
46653
45666
|
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
45667
|
create: (context) => ({ CallExpression(node) {
|
|
46655
|
-
if (!
|
|
45668
|
+
if (!isUseEffect(node)) return;
|
|
46656
45669
|
const analysis = getProgramAnalysis(node);
|
|
46657
45670
|
if (!analysis) return;
|
|
46658
45671
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -47067,7 +46080,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
47067
46080
|
if (!isNodeOfType(element, "Identifier")) continue;
|
|
47068
46081
|
const binding = findVariableInitializer(effectNode, element.name);
|
|
47069
46082
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
|
|
47070
|
-
const calleeName = getCalleeName$
|
|
46083
|
+
const calleeName = getCalleeName$2(binding.initializer);
|
|
47071
46084
|
if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
|
|
47072
46085
|
}
|
|
47073
46086
|
return false;
|
|
@@ -47089,7 +46102,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47089
46102
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
47090
46103
|
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
47091
46104
|
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
47092
|
-
if (getCalleeName$
|
|
46105
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
47093
46106
|
const callbackArgument = binding.initializer.arguments?.[0];
|
|
47094
46107
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47095
46108
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
@@ -47121,7 +46134,7 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47121
46134
|
return {
|
|
47122
46135
|
...propStackTracker.visitors,
|
|
47123
46136
|
CallExpression(node) {
|
|
47124
|
-
if (!
|
|
46137
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
|
|
47125
46138
|
const callback = getEffectCallback(node);
|
|
47126
46139
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
47127
46140
|
const depsNode = node.arguments[1];
|
|
@@ -47985,69 +46998,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47985
46998
|
}
|
|
47986
46999
|
});
|
|
47987
47000
|
//#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
47001
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
48052
47002
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
48053
47003
|
"DoWhileStatement",
|
|
@@ -49014,7 +47964,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
49014
47964
|
tags: ["test-noise"],
|
|
49015
47965
|
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
47966
|
create: (context) => ({ CallExpression(node) {
|
|
49017
|
-
if (!
|
|
47967
|
+
if (!isUseEffect(node)) return;
|
|
49018
47968
|
const analysis = getProgramAnalysis(node);
|
|
49019
47969
|
if (!analysis) return;
|
|
49020
47970
|
const effectFnRefs = getEffectFnRefs(analysis, node);
|
|
@@ -49283,7 +48233,7 @@ const isTanStackServerFnHandlerCall = (node) => {
|
|
|
49283
48233
|
if (node.callee.property.name !== "handler") return false;
|
|
49284
48234
|
let currentNode = node.callee.object;
|
|
49285
48235
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
49286
|
-
const calleeName = getCalleeName$
|
|
48236
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
49287
48237
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
|
|
49288
48238
|
if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
|
|
49289
48239
|
currentNode = currentNode.callee.object;
|
|
@@ -49784,7 +48734,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49784
48734
|
create: (context) => {
|
|
49785
48735
|
const checkFunctionScope = (functionBody) => {
|
|
49786
48736
|
if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
|
|
49787
|
-
const useStateBindings = collectUseStateBindings(functionBody
|
|
48737
|
+
const useStateBindings = collectUseStateBindings(functionBody);
|
|
49788
48738
|
if (useStateBindings.length === 0) return;
|
|
49789
48739
|
const setterNameToStateName = /* @__PURE__ */ new Map();
|
|
49790
48740
|
for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
|
|
@@ -49793,7 +48743,7 @@ const noSelfUpdatingEffect = defineRule({
|
|
|
49793
48743
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
49794
48744
|
const effectCall = unwrapDiscardedExpression(statement);
|
|
49795
48745
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
49796
|
-
if (!
|
|
48746
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
49797
48747
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
49798
48748
|
const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
|
|
49799
48749
|
if (dependencyStateNames.size === 0) continue;
|
|
@@ -49879,7 +48829,7 @@ const noSetStateInRender = defineRule({
|
|
|
49879
48829
|
create: (context) => {
|
|
49880
48830
|
const checkComponent = (componentBody) => {
|
|
49881
48831
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
49882
|
-
const setterNames = new Set(collectUseStateBindings(componentBody
|
|
48832
|
+
const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
|
|
49883
48833
|
if (setterNames.size === 0) return;
|
|
49884
48834
|
for (const statement of componentBody.body ?? []) {
|
|
49885
48835
|
const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
|
|
@@ -50128,10 +49078,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
|
|
|
50128
49078
|
});
|
|
50129
49079
|
return facts;
|
|
50130
49080
|
};
|
|
50131
|
-
const isEffectCallbackFunction = (functionNode
|
|
49081
|
+
const isEffectCallbackFunction = (functionNode) => {
|
|
50132
49082
|
const parent = functionNode.parent;
|
|
50133
49083
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
50134
|
-
return
|
|
49084
|
+
return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
|
|
50135
49085
|
};
|
|
50136
49086
|
const doesEffectCallbackReturnName = (effectCallback, name) => {
|
|
50137
49087
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -50150,24 +49100,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
|
|
|
50150
49100
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50151
49101
|
return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
|
|
50152
49102
|
};
|
|
50153
|
-
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope
|
|
49103
|
+
const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
|
|
50154
49104
|
const cleanupBindingName = getFunctionBindingName$1(functionNode);
|
|
50155
49105
|
if (cleanupBindingName === null) return false;
|
|
50156
49106
|
let isReturnedFromEffect = false;
|
|
50157
49107
|
walkAst(ownerScope, (child) => {
|
|
50158
49108
|
if (isReturnedFromEffect) return false;
|
|
50159
|
-
if (!isNodeOfType(child, "CallExpression") || !
|
|
49109
|
+
if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
50160
49110
|
const effectCallback = getEffectCallback(child);
|
|
50161
49111
|
if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
|
|
50162
49112
|
});
|
|
50163
49113
|
return isReturnedFromEffect;
|
|
50164
49114
|
};
|
|
50165
|
-
const isInsideEffectCleanupReturn = (node, ownerScope
|
|
49115
|
+
const isInsideEffectCleanupReturn = (node, ownerScope) => {
|
|
50166
49116
|
let functionNode = findEnclosingFunction$1(node);
|
|
50167
49117
|
while (functionNode) {
|
|
50168
49118
|
const outerFunction = findEnclosingFunction$1(functionNode);
|
|
50169
|
-
if (outerFunction && isEffectCallbackFunction(outerFunction
|
|
50170
|
-
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope
|
|
49119
|
+
if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
|
|
49120
|
+
if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
|
|
50171
49121
|
functionNode = outerFunction;
|
|
50172
49122
|
}
|
|
50173
49123
|
return false;
|
|
@@ -50203,10 +49153,10 @@ const noStaleTimerRef = defineRule({
|
|
|
50203
49153
|
if (isShadowedTimerGlobal(node)) return;
|
|
50204
49154
|
const { clearCalleeName, refName } = clearCall;
|
|
50205
49155
|
const refBinding = findVariableInitializer(node, refName);
|
|
50206
|
-
if (!refBinding?.initializer || !
|
|
49156
|
+
if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
|
|
50207
49157
|
const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
|
|
50208
49158
|
if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
|
|
50209
|
-
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner
|
|
49159
|
+
if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
|
|
50210
49160
|
if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
|
|
50211
49161
|
context.report({
|
|
50212
49162
|
node,
|
|
@@ -56010,7 +54960,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
|
|
|
56010
54960
|
allReadsAreInSubHandlers = false;
|
|
56011
54961
|
return;
|
|
56012
54962
|
}
|
|
56013
|
-
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$
|
|
54963
|
+
if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
|
|
56014
54964
|
});
|
|
56015
54965
|
return {
|
|
56016
54966
|
hasAnyRead,
|
|
@@ -56033,7 +54983,7 @@ const preferUseEffectEvent = defineRule({
|
|
|
56033
54983
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
56034
54984
|
const effectCall = statement.expression;
|
|
56035
54985
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56036
|
-
if (!
|
|
54986
|
+
if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
56037
54987
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56038
54988
|
const depsNode = effectCall.arguments[1];
|
|
56039
54989
|
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
@@ -56065,11 +55015,11 @@ const preferUseEffectEvent = defineRule({
|
|
|
56065
55015
|
});
|
|
56066
55016
|
//#endregion
|
|
56067
55017
|
//#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
|
|
56068
|
-
const findUseEffectsInComponent = (componentBody
|
|
55018
|
+
const findUseEffectsInComponent = (componentBody) => {
|
|
56069
55019
|
const effectCalls = [];
|
|
56070
55020
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
56071
55021
|
for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
|
|
56072
|
-
if (isNodeOfType(child, "CallExpression") &&
|
|
55022
|
+
if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
|
|
56073
55023
|
});
|
|
56074
55024
|
return effectCalls;
|
|
56075
55025
|
};
|
|
@@ -56272,7 +55222,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56272
55222
|
};
|
|
56273
55223
|
const checkComponent = (componentBody) => {
|
|
56274
55224
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
56275
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
55225
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
56276
55226
|
if (useStateBindings.length === 0) return;
|
|
56277
55227
|
const useStateInitializerByValueName = /* @__PURE__ */ new Map();
|
|
56278
55228
|
for (const binding of useStateBindings) {
|
|
@@ -56285,7 +55235,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56285
55235
|
}
|
|
56286
55236
|
const setterNameToValueName = /* @__PURE__ */ new Map();
|
|
56287
55237
|
for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
|
|
56288
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
55238
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56289
55239
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56290
55240
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56291
55241
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56327,7 +55277,7 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
56327
55277
|
})).filter((candidate) => candidate.storeName !== null);
|
|
56328
55278
|
if (snapshotBindings.length === 0) return;
|
|
56329
55279
|
const reportedDeclarators = /* @__PURE__ */ new Set();
|
|
56330
|
-
for (const effectCall of findUseEffectsInComponent(componentBody
|
|
55280
|
+
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
56331
55281
|
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
56332
55282
|
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
56333
55283
|
const depsNode = effectCall.arguments[1];
|
|
@@ -56424,7 +55374,7 @@ const preferUseReducer = defineRule({
|
|
|
56424
55374
|
create: (context) => {
|
|
56425
55375
|
const reportCoUpdatedUseState = (body, componentName) => {
|
|
56426
55376
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
56427
|
-
const bindings = collectUseStateBindings(body
|
|
55377
|
+
const bindings = collectUseStateBindings(body);
|
|
56428
55378
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
56429
55379
|
if (setterNames.size < 5) return;
|
|
56430
55380
|
const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
|
|
@@ -56640,7 +55590,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
|
|
|
56640
55590
|
]);
|
|
56641
55591
|
const isQueryCacheSourceCall = (initializer) => {
|
|
56642
55592
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
56643
|
-
const hookName = getCalleeName$
|
|
55593
|
+
const hookName = getCalleeName$2(initializer);
|
|
56644
55594
|
if (!hookName) return false;
|
|
56645
55595
|
return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
|
|
56646
55596
|
};
|
|
@@ -56772,7 +55722,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
56772
55722
|
},
|
|
56773
55723
|
CallExpression(node) {
|
|
56774
55724
|
if (!hasQueryReadUsage) {
|
|
56775
|
-
const callName = getCalleeName$
|
|
55725
|
+
const callName = getCalleeName$2(node);
|
|
56776
55726
|
if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
|
|
56777
55727
|
}
|
|
56778
55728
|
const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
|
|
@@ -57265,30 +56215,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
|
|
|
57265
56215
|
["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
|
|
57266
56216
|
["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
|
|
57267
56217
|
]);
|
|
57268
|
-
const resolveReactApiNameForIdentifier = (callee
|
|
56218
|
+
const resolveReactApiNameForIdentifier = (callee) => {
|
|
57269
56219
|
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
57270
|
-
if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
|
|
57271
56220
|
const importedName = getImportedNameFromModule(callee, callee.name, "react");
|
|
57272
56221
|
if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
|
|
57273
56222
|
return null;
|
|
57274
56223
|
};
|
|
57275
|
-
const resolveReactApiNameForMemberExpression = (callee
|
|
56224
|
+
const resolveReactApiNameForMemberExpression = (callee) => {
|
|
57276
56225
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
57277
56226
|
if (callee.computed) return null;
|
|
57278
|
-
const namespaceIdentifier =
|
|
56227
|
+
const namespaceIdentifier = callee.object;
|
|
57279
56228
|
const propertyIdentifier = callee.property;
|
|
57280
56229
|
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
57281
56230
|
if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
|
|
57282
56231
|
if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
|
|
57283
56232
|
const namespaceName = namespaceIdentifier.name;
|
|
57284
|
-
if (
|
|
57285
|
-
if (
|
|
56233
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
|
|
56234
|
+
if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
|
|
57286
56235
|
return null;
|
|
57287
56236
|
};
|
|
57288
|
-
const resolveReactApiNameForCallee = (callee
|
|
57289
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
57290
|
-
return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
|
|
57291
|
-
};
|
|
56237
|
+
const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
|
|
57292
56238
|
const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
|
|
57293
56239
|
const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
57294
56240
|
const calleeTrailingName = (callee) => {
|
|
@@ -57314,7 +56260,7 @@ const reactCompilerNoManualMemoization = defineRule({
|
|
|
57314
56260
|
requires: ["react-compiler"],
|
|
57315
56261
|
recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
|
|
57316
56262
|
create: (context) => ({ CallExpression(node) {
|
|
57317
|
-
const apiName = resolveReactApiNameForCallee(node.callee
|
|
56263
|
+
const apiName = resolveReactApiNameForCallee(node.callee);
|
|
57318
56264
|
if (!apiName) return;
|
|
57319
56265
|
if (apiName === "memo") {
|
|
57320
56266
|
const comparatorArgument = node.arguments?.[1];
|
|
@@ -58013,39 +56959,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
58013
56959
|
destructureIndex: 1
|
|
58014
56960
|
});
|
|
58015
56961
|
//#endregion
|
|
58016
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
58017
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
58018
|
-
//#endregion
|
|
58019
56962
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
58020
56963
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
58021
|
-
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
58022
|
-
const USE_STATE_ONLY = new Set(["useState"]);
|
|
58023
|
-
const REACT_API_CALL_OPTIONS = {
|
|
58024
|
-
allowGlobalReactNamespace: true,
|
|
58025
|
-
allowUnboundBareCalls: true,
|
|
58026
|
-
resolveNamedAliases: true
|
|
58027
|
-
};
|
|
58028
|
-
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
58029
|
-
let readsDerivedSymbol = false;
|
|
58030
|
-
walkAst(expression, (node) => {
|
|
58031
|
-
if (readsDerivedSymbol) return false;
|
|
58032
|
-
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
58033
|
-
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
58034
|
-
});
|
|
58035
|
-
return readsDerivedSymbol;
|
|
58036
|
-
};
|
|
58037
|
-
const getStaticObjectPropertyName = (property) => {
|
|
58038
|
-
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
58039
|
-
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
58040
|
-
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
58041
|
-
return null;
|
|
58042
|
-
};
|
|
58043
|
-
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
58044
|
-
const isTransparentAssignmentTarget = (identifier) => {
|
|
58045
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
58046
|
-
const parent = expressionRoot.parent;
|
|
58047
|
-
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
58048
|
-
};
|
|
58049
56964
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
58050
56965
|
let readsCurrent = false;
|
|
58051
56966
|
walkAst(argument, (child) => {
|
|
@@ -58097,166 +57012,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
58097
57012
|
});
|
|
58098
57013
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
58099
57014
|
};
|
|
58100
|
-
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
58101
|
-
const member = stripParenExpression(node);
|
|
58102
|
-
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
58103
|
-
const receiver = stripParenExpression(member.object);
|
|
58104
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
58105
|
-
};
|
|
58106
|
-
const getDirectWindowWidthSetter = (context, statement) => {
|
|
58107
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58108
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
58109
|
-
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
58110
|
-
const argument = call.arguments[0];
|
|
58111
|
-
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
58112
|
-
};
|
|
58113
|
-
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
58114
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58115
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
58116
|
-
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
58117
|
-
const eventName = call.arguments[0];
|
|
58118
|
-
const handler = call.arguments[1];
|
|
58119
|
-
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
58120
|
-
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
58121
|
-
};
|
|
58122
|
-
const getCleanupResizeHandler = (context, statement) => {
|
|
58123
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
58124
|
-
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
58125
|
-
if (cleanupStatements.length !== 1) return null;
|
|
58126
|
-
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
58127
|
-
};
|
|
58128
|
-
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
58129
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
58130
|
-
const componentBody = componentFunction.body;
|
|
58131
|
-
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
58132
|
-
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
58133
|
-
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
58134
|
-
const declarator = setterSymbol.declarationNode;
|
|
58135
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
58136
|
-
const stateIdentifier = declarator.id.elements?.[0];
|
|
58137
|
-
const setterIdentifier = declarator.id.elements?.[1];
|
|
58138
|
-
if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
|
|
58139
|
-
const initializer = declarator.init.arguments?.[0];
|
|
58140
|
-
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
58141
|
-
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
58142
|
-
if (!stateSymbol) return null;
|
|
58143
|
-
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
58144
|
-
let didAddDerivedSymbol = true;
|
|
58145
|
-
while (didAddDerivedSymbol) {
|
|
58146
|
-
didAddDerivedSymbol = false;
|
|
58147
|
-
for (const statement of componentBody.body ?? []) {
|
|
58148
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
58149
|
-
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
58150
|
-
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
58151
|
-
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
58152
|
-
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
58153
|
-
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
58154
|
-
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
58155
|
-
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
58156
|
-
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
58157
|
-
didAddDerivedSymbol = true;
|
|
58158
|
-
}
|
|
58159
|
-
}
|
|
58160
|
-
}
|
|
58161
|
-
}
|
|
58162
|
-
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
58163
|
-
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58164
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58165
|
-
if (!symbol) return false;
|
|
58166
|
-
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
58167
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58168
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58169
|
-
let hasUnknownReference = false;
|
|
58170
|
-
walkAst(componentBody, (node) => {
|
|
58171
|
-
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
58172
|
-
const referenceRoot = findTransparentExpressionRoot(node);
|
|
58173
|
-
const parent = referenceRoot.parent;
|
|
58174
|
-
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
58175
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
58176
|
-
hasUnknownReference = true;
|
|
58177
|
-
return false;
|
|
58178
|
-
});
|
|
58179
|
-
return !hasUnknownReference;
|
|
58180
|
-
};
|
|
58181
|
-
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58182
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58183
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
58184
|
-
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
58185
|
-
if (cachedVisibility) return cachedVisibility;
|
|
58186
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
58187
|
-
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
58188
|
-
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
58189
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58190
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58191
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
58192
|
-
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
58193
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58194
|
-
return visibility;
|
|
58195
|
-
}
|
|
58196
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
58197
|
-
let visibility = "non-visible";
|
|
58198
|
-
for (const property of initializer.properties ?? []) {
|
|
58199
|
-
const propertyName = getStaticObjectPropertyName(property);
|
|
58200
|
-
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
58201
|
-
visibility = "unknown";
|
|
58202
|
-
break;
|
|
58203
|
-
}
|
|
58204
|
-
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
58205
|
-
}
|
|
58206
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58207
|
-
return visibility;
|
|
58208
|
-
};
|
|
58209
|
-
let hasNonAriaReference = false;
|
|
58210
|
-
walkAst(componentBody, (node) => {
|
|
58211
|
-
if (hasNonAriaReference) return false;
|
|
58212
|
-
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
58213
|
-
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
58214
|
-
const parent = node.parent;
|
|
58215
|
-
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
58216
|
-
let cursor = parent;
|
|
58217
|
-
while (cursor && cursor !== componentBody) {
|
|
58218
|
-
if (isFunctionLike$1(cursor)) return;
|
|
58219
|
-
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
58220
|
-
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
58221
|
-
return;
|
|
58222
|
-
}
|
|
58223
|
-
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
58224
|
-
if (isEventHandlerAttribute(cursor)) return;
|
|
58225
|
-
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
58226
|
-
return;
|
|
58227
|
-
}
|
|
58228
|
-
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
58229
|
-
hasNonAriaReference = true;
|
|
58230
|
-
return;
|
|
58231
|
-
}
|
|
58232
|
-
cursor = cursor.parent;
|
|
58233
|
-
}
|
|
58234
|
-
});
|
|
58235
|
-
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
58236
|
-
};
|
|
58237
|
-
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
58238
|
-
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
58239
|
-
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
58240
|
-
const statements = getCallbackStatements(callback);
|
|
58241
|
-
if (statements.length !== 4) return false;
|
|
58242
|
-
const handlerDeclaration = statements[0];
|
|
58243
|
-
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
58244
|
-
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
58245
|
-
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
58246
|
-
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
58247
|
-
if (handlerStatements.length !== 1) return false;
|
|
58248
|
-
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
58249
|
-
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
58250
|
-
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
58251
|
-
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
58252
|
-
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
58253
|
-
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
58254
|
-
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
58255
|
-
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
58256
|
-
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
58257
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
58258
|
-
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
58259
|
-
};
|
|
58260
57015
|
const renderingHydrationNoFlicker = defineRule({
|
|
58261
57016
|
id: "rendering-hydration-no-flicker",
|
|
58262
57017
|
title: "useEffect setState flashes on mount",
|
|
@@ -58269,14 +57024,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
58269
57024
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
58270
57025
|
const callback = getEffectCallback(node);
|
|
58271
57026
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
58272
|
-
|
|
58273
|
-
context.report({
|
|
58274
|
-
node,
|
|
58275
|
-
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
58276
|
-
});
|
|
58277
|
-
return;
|
|
58278
|
-
}
|
|
58279
|
-
const bodyStatements = getCallbackStatements(callback);
|
|
57027
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
58280
57028
|
if (bodyStatements.length !== 1) return;
|
|
58281
57029
|
const soleStatement = bodyStatements[0];
|
|
58282
57030
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -58439,125 +57187,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
58439
57187
|
const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
|
|
58440
57188
|
const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
|
|
58441
57189
|
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
57190
|
const hasAsyncLoadingWork = (fnBody, setterName) => {
|
|
58562
57191
|
let found = false;
|
|
58563
57192
|
walkAst(fnBody, (child) => {
|
|
@@ -58731,8 +57360,6 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
58731
57360
|
const fnBody = enclosingFunctionBody(node);
|
|
58732
57361
|
if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
|
|
58733
57362
|
if (fnBody && setterName) {
|
|
58734
|
-
const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
|
|
58735
|
-
if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
|
|
58736
57363
|
if (setterEscapes(fnBody, setterName, node)) return;
|
|
58737
57364
|
if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
|
|
58738
57365
|
if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
|
|
@@ -59064,7 +57691,7 @@ const rerenderDependencies = defineRule({
|
|
|
59064
57691
|
severity: "error",
|
|
59065
57692
|
recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
|
|
59066
57693
|
create: (context) => ({ CallExpression(node) {
|
|
59067
|
-
if (!
|
|
57694
|
+
if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
|
|
59068
57695
|
const depsNode = node.arguments[1];
|
|
59069
57696
|
if (!isNodeOfType(depsNode, "ArrayExpression")) return;
|
|
59070
57697
|
for (const element of depsNode.elements ?? []) {
|
|
@@ -59319,7 +57946,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
59319
57946
|
category: "Performance",
|
|
59320
57947
|
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
59321
57948
|
create: (context) => ({ CallExpression(node) {
|
|
59322
|
-
if (!
|
|
57949
|
+
if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
|
|
59323
57950
|
const initializer = stripParenExpression(node.arguments[0]);
|
|
59324
57951
|
const isPlainCall = isNodeOfType(initializer, "CallExpression");
|
|
59325
57952
|
const isNewCall = isNodeOfType(initializer, "NewExpression");
|
|
@@ -59383,7 +58010,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
59383
58010
|
category: "Performance",
|
|
59384
58011
|
recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
|
|
59385
58012
|
create: (context) => ({ CallExpression(node) {
|
|
59386
|
-
if (!
|
|
58013
|
+
if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
|
|
59387
58014
|
const initializer = findEagerInitializerCall(node.arguments[0]);
|
|
59388
58015
|
if (!initializer) return;
|
|
59389
58016
|
const isConstructor = isNodeOfType(initializer, "NewExpression");
|
|
@@ -60146,11 +58773,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
|
|
|
60146
58773
|
}
|
|
60147
58774
|
return false;
|
|
60148
58775
|
};
|
|
60149
|
-
const collectEffectDependencyInfos = (componentBody, setterNames
|
|
58776
|
+
const collectEffectDependencyInfos = (componentBody, setterNames) => {
|
|
60150
58777
|
const effectInfos = [];
|
|
60151
58778
|
walkAst(componentBody, (child) => {
|
|
60152
58779
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60153
|
-
if (!
|
|
58780
|
+
if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
|
|
60154
58781
|
const dependencyNames = /* @__PURE__ */ new Set();
|
|
60155
58782
|
for (const argument of child.arguments ?? []) {
|
|
60156
58783
|
if (!isNodeOfType(argument, "ArrayExpression")) continue;
|
|
@@ -60206,14 +58833,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
|
|
|
60206
58833
|
});
|
|
60207
58834
|
return effectInfos;
|
|
60208
58835
|
};
|
|
60209
|
-
const collectCustomHookArgumentNames = (componentBody
|
|
58836
|
+
const collectCustomHookArgumentNames = (componentBody) => {
|
|
60210
58837
|
const argumentNames = /* @__PURE__ */ new Set();
|
|
60211
58838
|
walkAst(componentBody, (child) => {
|
|
60212
58839
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
60213
58840
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
60214
58841
|
const calleeName = child.callee.name;
|
|
60215
58842
|
if (!isReactHookName(calleeName)) return;
|
|
60216
|
-
if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
|
|
60217
58843
|
if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
|
|
60218
58844
|
if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
|
|
60219
58845
|
for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
|
|
@@ -60254,21 +58880,21 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60254
58880
|
create: (context) => {
|
|
60255
58881
|
const checkComponent = (componentBody) => {
|
|
60256
58882
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60257
|
-
const bindings = collectUseStateBindings(componentBody
|
|
58883
|
+
const bindings = collectUseStateBindings(componentBody);
|
|
60258
58884
|
if (bindings.length === 0) return;
|
|
60259
58885
|
if (collectRenderReachableExpressions(componentBody).length === 0) return;
|
|
60260
|
-
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody
|
|
58886
|
+
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
|
|
60261
58887
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60262
|
-
const directRenderNames = collectRenderReachableNames(componentBody,
|
|
58888
|
+
const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
|
|
60263
58889
|
if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
|
|
60264
58890
|
const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
|
|
60265
58891
|
const setterNames = new Set(bindings.map((binding) => binding.setterName));
|
|
60266
|
-
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames
|
|
58892
|
+
const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
|
|
60267
58893
|
const selfEchoValueNames = /* @__PURE__ */ new Set();
|
|
60268
58894
|
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
58895
|
const effectConsumedNames = /* @__PURE__ */ new Set();
|
|
60270
58896
|
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
|
|
58897
|
+
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
|
|
60272
58898
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
60273
58899
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60274
58900
|
walkAst(componentBody, (child) => {
|
|
@@ -68169,7 +66795,7 @@ const declarationAwaitsGate = (declaration, context) => {
|
|
|
68169
66795
|
if (!isNodeOfType(argument, "CallExpression")) continue;
|
|
68170
66796
|
if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
|
|
68171
66797
|
if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
|
|
68172
|
-
const calleeName = getCalleeName$
|
|
66798
|
+
const calleeName = getCalleeName$2(argument);
|
|
68173
66799
|
if (!calleeName) continue;
|
|
68174
66800
|
if (isAuthGuardName(calleeName)) return true;
|
|
68175
66801
|
const [leadingToken] = tokenizeIdentifierWords(calleeName);
|
|
@@ -68809,7 +67435,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
68809
67435
|
if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
|
|
68810
67436
|
let currentNode = stripParenExpression(outerNode.callee.object);
|
|
68811
67437
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
68812
|
-
const calleeName = getCalleeName$
|
|
67438
|
+
const calleeName = getCalleeName$2(currentNode);
|
|
68813
67439
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
|
|
68814
67440
|
result.isServerFnChain = true;
|
|
68815
67441
|
const optionsArgument = currentNode.arguments?.[0];
|
|
@@ -72941,17 +71567,6 @@ const reactDoctorRules = [
|
|
|
72941
71567
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72942
71568
|
}
|
|
72943
71569
|
},
|
|
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
71570
|
{
|
|
72956
71571
|
key: "react-doctor/no-ref-current-in-render",
|
|
72957
71572
|
id: "no-ref-current-in-render",
|