oxlint-plugin-react-doctor 0.8.1-dev.a2f9bf4 → 0.8.1-dev.a667b45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1892 -217
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -762,7 +762,7 @@ const EXTERNAL_SYNC_MEMBER_METHOD_NAMES = new Set([
|
|
|
762
762
|
"put",
|
|
763
763
|
"patch"
|
|
764
764
|
]);
|
|
765
|
-
|
|
765
|
+
new Set([
|
|
766
766
|
...FETCH_MEMBER_OBJECTS,
|
|
767
767
|
"api",
|
|
768
768
|
"client",
|
|
@@ -9008,6 +9008,16 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
|
|
|
9008
9008
|
}
|
|
9009
9009
|
};
|
|
9010
9010
|
//#endregion
|
|
9011
|
+
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
9012
|
+
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
9013
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
9014
|
+
const callee = stripParenExpression(node.callee);
|
|
9015
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
9016
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
9017
|
+
const receiver = stripParenExpression(callee.object);
|
|
9018
|
+
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
9019
|
+
};
|
|
9020
|
+
//#endregion
|
|
9011
9021
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
9012
9022
|
const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
|
|
9013
9023
|
"AbortSignal",
|
|
@@ -9154,8 +9164,10 @@ const getProvenDomEventTargetPrototypeOwnerNames = (rawExpression, scopes, visit
|
|
|
9154
9164
|
if (memberName === "body" || memberName === "documentElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
|
|
9155
9165
|
}
|
|
9156
9166
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
9167
|
+
if (isGlobalMatchMediaCall(expression, scopes)) return getDomPrototypeOwnerNamesForType("MediaQueryList");
|
|
9157
9168
|
const callee = stripParenExpression(expression.callee);
|
|
9158
9169
|
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
9170
|
+
if (methodName === "matchMedia" && isNodeOfType(callee, "MemberExpression") && getProvenDomEventTargetPrototypeOwnerNames(callee.object, scopes, visitedSymbolIds).includes("Window")) return getDomPrototypeOwnerNamesForType("MediaQueryList");
|
|
9159
9171
|
if (methodName === "createElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
|
|
9160
9172
|
if (methodName === "createElementNS") return getDomPrototypeOwnerNamesForType("SVGElement");
|
|
9161
9173
|
if (methodName && DOM_EVENT_TARGET_FACTORY_METHOD_NAMES.has(methodName)) return getDomPrototypeOwnerNamesForType("Element");
|
|
@@ -13815,6 +13827,116 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
13815
13827
|
return null;
|
|
13816
13828
|
};
|
|
13817
13829
|
//#endregion
|
|
13830
|
+
//#region src/plugin/utils/get-final-sequence-expression-value.ts
|
|
13831
|
+
const getFinalSequenceExpressionValue = (expression) => {
|
|
13832
|
+
let finalExpression = stripParenExpression(expression);
|
|
13833
|
+
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
13834
|
+
const sequenceResult = finalExpression.expressions.at(-1);
|
|
13835
|
+
if (!sequenceResult) break;
|
|
13836
|
+
finalExpression = stripParenExpression(sequenceResult);
|
|
13837
|
+
}
|
|
13838
|
+
return finalExpression;
|
|
13839
|
+
};
|
|
13840
|
+
//#endregion
|
|
13841
|
+
//#region src/plugin/utils/collect-expression-path-coverage-nodes.ts
|
|
13842
|
+
const collectExpressionPathCoverageNodes = (owner, matchingNodes, context, expressionBoundary = owner) => {
|
|
13843
|
+
const coverageNodes = /* @__PURE__ */ new Set();
|
|
13844
|
+
const pendingNodes = matchingNodes.filter((matchingNode) => {
|
|
13845
|
+
if (context.cfg.enclosingFunction(matchingNode) !== owner) return false;
|
|
13846
|
+
let currentNode = matchingNode;
|
|
13847
|
+
while (currentNode && currentNode !== expressionBoundary) currentNode = currentNode.parent ?? null;
|
|
13848
|
+
return currentNode === expressionBoundary;
|
|
13849
|
+
});
|
|
13850
|
+
const visitedNodes = new Set(pendingNodes);
|
|
13851
|
+
const coveredBranchesByConditional = /* @__PURE__ */ new Map();
|
|
13852
|
+
while (pendingNodes.length > 0) {
|
|
13853
|
+
const coverageCandidate = pendingNodes.pop();
|
|
13854
|
+
if (!coverageCandidate) break;
|
|
13855
|
+
if (coverageCandidate === expressionBoundary) {
|
|
13856
|
+
coverageNodes.add(coverageCandidate);
|
|
13857
|
+
continue;
|
|
13858
|
+
}
|
|
13859
|
+
let currentChild = coverageCandidate;
|
|
13860
|
+
let currentParent = currentChild.parent ?? null;
|
|
13861
|
+
let conditionalExpression = null;
|
|
13862
|
+
let conditionalBranch = null;
|
|
13863
|
+
let isBlockedByNonExhaustiveExpression = false;
|
|
13864
|
+
while (currentParent && currentParent !== expressionBoundary) {
|
|
13865
|
+
if (isNodeOfType(currentParent, "ConditionalExpression")) {
|
|
13866
|
+
const isConsequent = currentParent.consequent === currentChild;
|
|
13867
|
+
const isAlternate = currentParent.alternate === currentChild;
|
|
13868
|
+
if (isConsequent || isAlternate) {
|
|
13869
|
+
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
13870
|
+
if (staticTestValue !== null) {
|
|
13871
|
+
if (staticTestValue !== isConsequent) {
|
|
13872
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13873
|
+
break;
|
|
13874
|
+
}
|
|
13875
|
+
currentChild = currentParent;
|
|
13876
|
+
currentParent = currentChild.parent ?? null;
|
|
13877
|
+
continue;
|
|
13878
|
+
}
|
|
13879
|
+
conditionalExpression = currentParent;
|
|
13880
|
+
conditionalBranch = isConsequent ? "consequent" : "alternate";
|
|
13881
|
+
break;
|
|
13882
|
+
}
|
|
13883
|
+
}
|
|
13884
|
+
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild) {
|
|
13885
|
+
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
13886
|
+
if (!(currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false)) {
|
|
13887
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13888
|
+
break;
|
|
13889
|
+
}
|
|
13890
|
+
}
|
|
13891
|
+
if (isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) {
|
|
13892
|
+
isBlockedByNonExhaustiveExpression = true;
|
|
13893
|
+
break;
|
|
13894
|
+
}
|
|
13895
|
+
currentChild = currentParent;
|
|
13896
|
+
currentParent = currentChild.parent ?? null;
|
|
13897
|
+
}
|
|
13898
|
+
if (isBlockedByNonExhaustiveExpression) continue;
|
|
13899
|
+
if (!conditionalExpression || !conditionalBranch) {
|
|
13900
|
+
coverageNodes.add(coverageCandidate);
|
|
13901
|
+
continue;
|
|
13902
|
+
}
|
|
13903
|
+
const coveredBranches = coveredBranchesByConditional.get(conditionalExpression) ?? /* @__PURE__ */ new Set();
|
|
13904
|
+
coveredBranches.add(conditionalBranch);
|
|
13905
|
+
coveredBranchesByConditional.set(conditionalExpression, coveredBranches);
|
|
13906
|
+
if (coveredBranches.has("alternate") && coveredBranches.has("consequent") && !visitedNodes.has(conditionalExpression)) {
|
|
13907
|
+
visitedNodes.add(conditionalExpression);
|
|
13908
|
+
pendingNodes.push(conditionalExpression);
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
13911
|
+
return coverageNodes;
|
|
13912
|
+
};
|
|
13913
|
+
//#endregion
|
|
13914
|
+
//#region src/plugin/utils/do-nodes-cover-every-path-from-function-entry.ts
|
|
13915
|
+
const doNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context, options = {}) => {
|
|
13916
|
+
const functionCfg = context.cfg.cfgFor(owner);
|
|
13917
|
+
if (!functionCfg) return false;
|
|
13918
|
+
const matchingBlocks = new Set([...collectExpressionPathCoverageNodes(owner, matchingNodes, context)].flatMap((matchingNode) => {
|
|
13919
|
+
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
13920
|
+
return matchingBlock ? [matchingBlock] : [];
|
|
13921
|
+
}));
|
|
13922
|
+
if (matchingBlocks.size === 0) return false;
|
|
13923
|
+
const visitedBlocks = new Set([functionCfg.entry]);
|
|
13924
|
+
const pendingBlocks = [functionCfg.entry];
|
|
13925
|
+
while (pendingBlocks.length > 0) {
|
|
13926
|
+
const currentBlock = pendingBlocks.pop();
|
|
13927
|
+
if (!currentBlock) break;
|
|
13928
|
+
if (matchingBlocks.has(currentBlock)) continue;
|
|
13929
|
+
for (const edge of currentBlock.successors) {
|
|
13930
|
+
if (options.ignoreThrowEdges && edge.kind === "throw") continue;
|
|
13931
|
+
if (edge.to === functionCfg.exit) return false;
|
|
13932
|
+
if (visitedBlocks.has(edge.to)) continue;
|
|
13933
|
+
visitedBlocks.add(edge.to);
|
|
13934
|
+
pendingBlocks.push(edge.to);
|
|
13935
|
+
}
|
|
13936
|
+
}
|
|
13937
|
+
return true;
|
|
13938
|
+
};
|
|
13939
|
+
//#endregion
|
|
13818
13940
|
//#region src/plugin/utils/get-function-binding-name.ts
|
|
13819
13941
|
const getFunctionBindingIdentifier$1 = (functionNode) => {
|
|
13820
13942
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
@@ -13852,14 +13974,19 @@ const isAstDescendant = (inner, outer) => {
|
|
|
13852
13974
|
};
|
|
13853
13975
|
//#endregion
|
|
13854
13976
|
//#region src/plugin/utils/react-ref-origin.ts
|
|
13855
|
-
const
|
|
13977
|
+
const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
|
|
13978
|
+
const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
|
|
13856
13979
|
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
13857
13980
|
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
13858
13981
|
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
13859
13982
|
if (!symbol?.initializer) return null;
|
|
13860
13983
|
const initializer = stripParenExpression(symbol.initializer);
|
|
13861
13984
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
13862
|
-
return isReactApiCall(initializer, "useRef", scopes, {
|
|
13985
|
+
return isReactApiCall(initializer, options.includeCreateRef ? REACT_REF_API_NAMES : "useRef", scopes, {
|
|
13986
|
+
allowGlobalReactNamespace: true,
|
|
13987
|
+
allowUnboundBareCalls: options.allowUnboundBareCalls,
|
|
13988
|
+
resolveNamedAliases: options.resolveNamedAliases
|
|
13989
|
+
}) ? symbol : null;
|
|
13863
13990
|
};
|
|
13864
13991
|
const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13865
13992
|
const expression = stripParenExpression(node);
|
|
@@ -13958,6 +14085,20 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
13958
14085
|
return false;
|
|
13959
14086
|
};
|
|
13960
14087
|
//#endregion
|
|
14088
|
+
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
14089
|
+
const isWithinAssignmentTarget = (identifier) => {
|
|
14090
|
+
let currentNode = identifier;
|
|
14091
|
+
let parentNode = currentNode.parent;
|
|
14092
|
+
while (parentNode) {
|
|
14093
|
+
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14094
|
+
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14095
|
+
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14096
|
+
currentNode = parentNode;
|
|
14097
|
+
parentNode = currentNode.parent;
|
|
14098
|
+
}
|
|
14099
|
+
return false;
|
|
14100
|
+
};
|
|
14101
|
+
//#endregion
|
|
13961
14102
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
13962
14103
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
13963
14104
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -14110,6 +14251,30 @@ const findAssignedResourceKey = (resourceNode, context) => {
|
|
|
14110
14251
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
14111
14252
|
return null;
|
|
14112
14253
|
};
|
|
14254
|
+
const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14255
|
+
if (!expression) return null;
|
|
14256
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
14257
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
14258
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
14259
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
|
|
14260
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
14261
|
+
if (!initializer) return `symbol:${symbol.id}`;
|
|
14262
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
14263
|
+
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
|
|
14264
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
14265
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
14266
|
+
return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
|
|
14267
|
+
}
|
|
14268
|
+
if (isFunctionLike$1(unwrappedExpression)) {
|
|
14269
|
+
const rangeStart = getRangeStart(unwrappedExpression);
|
|
14270
|
+
return rangeStart === null ? null : `function:${rangeStart}`;
|
|
14271
|
+
}
|
|
14272
|
+
return null;
|
|
14273
|
+
};
|
|
14274
|
+
const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
|
|
14275
|
+
const callee = stripParenExpression(callNode.callee);
|
|
14276
|
+
return callNode.arguments?.length === 1 && isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName && getProvenDomEventTargetPrototypeOwnerNames(callee.object, context.scopes).includes("MediaQueryList");
|
|
14277
|
+
};
|
|
14113
14278
|
const getCallRegistrationDetails = (callNode, context) => {
|
|
14114
14279
|
const callee = stripParenExpression(callNode.callee);
|
|
14115
14280
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
|
|
@@ -14118,6 +14283,12 @@ const getCallRegistrationDetails = (callNode, context) => {
|
|
|
14118
14283
|
eventKey: null,
|
|
14119
14284
|
handlerKey: null
|
|
14120
14285
|
};
|
|
14286
|
+
if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
|
|
14287
|
+
receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
|
|
14288
|
+
registrationVerbName: callee.property.name,
|
|
14289
|
+
eventKey: null,
|
|
14290
|
+
handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
|
|
14291
|
+
};
|
|
14121
14292
|
return {
|
|
14122
14293
|
receiverKey: resolveResourceIdentityKey(callee.object, context),
|
|
14123
14294
|
registrationVerbName: callee.property.name,
|
|
@@ -14222,30 +14393,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
14222
14393
|
}
|
|
14223
14394
|
return matchingBlocks.size > 0;
|
|
14224
14395
|
};
|
|
14225
|
-
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
14226
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
14227
|
-
if (!functionCfg) return false;
|
|
14228
|
-
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
14229
|
-
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
14230
|
-
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
14231
|
-
return matchingBlock ? [matchingBlock] : [];
|
|
14232
|
-
}));
|
|
14233
|
-
if (matchingBlocks.size === 0) return false;
|
|
14234
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
14235
|
-
const pendingBlocks = [functionCfg.entry];
|
|
14236
|
-
while (pendingBlocks.length > 0) {
|
|
14237
|
-
const currentBlock = pendingBlocks.pop();
|
|
14238
|
-
if (!currentBlock) break;
|
|
14239
|
-
if (matchingBlocks.has(currentBlock)) continue;
|
|
14240
|
-
for (const edge of currentBlock.successors) {
|
|
14241
|
-
if (edge.to === functionCfg.exit) return false;
|
|
14242
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
14243
|
-
visitedBlocks.add(edge.to);
|
|
14244
|
-
pendingBlocks.push(edge.to);
|
|
14245
|
-
}
|
|
14246
|
-
}
|
|
14247
|
-
return true;
|
|
14248
|
-
};
|
|
14249
14396
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
14250
14397
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
14251
14398
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -14488,18 +14635,6 @@ const findPushedResourceCollectionKey = (usage, context) => {
|
|
|
14488
14635
|
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14489
14636
|
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14490
14637
|
};
|
|
14491
|
-
const isWithinAssignmentTarget = (identifier) => {
|
|
14492
|
-
let currentNode = identifier;
|
|
14493
|
-
let parentNode = currentNode.parent;
|
|
14494
|
-
while (parentNode) {
|
|
14495
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
14496
|
-
if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
|
|
14497
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
14498
|
-
currentNode = parentNode;
|
|
14499
|
-
parentNode = currentNode.parent;
|
|
14500
|
-
}
|
|
14501
|
-
return false;
|
|
14502
|
-
};
|
|
14503
14638
|
const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
14504
14639
|
if (!expression) return null;
|
|
14505
14640
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14623,7 +14758,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14623
14758
|
const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
|
|
14624
14759
|
if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
|
|
14625
14760
|
});
|
|
14626
|
-
return didCleanupFunctionMatch ||
|
|
14761
|
+
return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
|
|
14627
14762
|
};
|
|
14628
14763
|
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
14629
14764
|
const callExpression = stripParenExpression(expression);
|
|
@@ -14652,7 +14787,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14652
14787
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
14653
14788
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
14654
14789
|
});
|
|
14655
|
-
return
|
|
14790
|
+
return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14656
14791
|
};
|
|
14657
14792
|
const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
|
|
14658
14793
|
if (resolveExpressionKey(test, context) === expressionKey) return true;
|
|
@@ -14695,7 +14830,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
|
14695
14830
|
const helperFunction = resolveStableValue(child.callee, context);
|
|
14696
14831
|
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
14697
14832
|
});
|
|
14698
|
-
return
|
|
14833
|
+
return doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
14699
14834
|
};
|
|
14700
14835
|
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
14701
14836
|
const componentFunction = findEnclosingFunction$1(callback);
|
|
@@ -14946,7 +15081,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
|
|
|
14946
15081
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14947
15082
|
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14948
15083
|
const usageAssignment = usageExpression.parent;
|
|
14949
|
-
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) || !
|
|
15084
|
+
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) || !doNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
14950
15085
|
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14951
15086
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14952
15087
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
@@ -14994,7 +15129,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
14994
15129
|
});
|
|
14995
15130
|
}
|
|
14996
15131
|
});
|
|
14997
|
-
if (!
|
|
15132
|
+
if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
14998
15133
|
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
14999
15134
|
}
|
|
15000
15135
|
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
@@ -15207,7 +15342,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
15207
15342
|
});
|
|
15208
15343
|
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15209
15344
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15210
|
-
return
|
|
15345
|
+
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15211
15346
|
};
|
|
15212
15347
|
const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
|
|
15213
15348
|
let currentNode = findTransparentExpressionRoot(releaseNode);
|
|
@@ -15220,7 +15355,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
|
|
|
15220
15355
|
const isDirectConciseBody = ownerFunction.body === currentNode;
|
|
15221
15356
|
const statementNode = currentNode.parent;
|
|
15222
15357
|
const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
|
|
15223
|
-
if (!isDirectConciseBody && !isDirectBlockStatement || !
|
|
15358
|
+
if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
|
|
15224
15359
|
const forEachCall = findEnclosingForEachCall(ownerFunction);
|
|
15225
15360
|
if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
|
|
15226
15361
|
const forEachCallee = stripParenExpression(forEachCall.callee);
|
|
@@ -15308,7 +15443,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
|
|
|
15308
15443
|
const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
|
|
15309
15444
|
const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
|
|
15310
15445
|
const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
|
|
15311
|
-
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) &&
|
|
15446
|
+
if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
|
|
15312
15447
|
const projections = [
|
|
15313
15448
|
registrationCallee.object,
|
|
15314
15449
|
registrationCall.arguments?.[0],
|
|
@@ -15355,6 +15490,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15355
15490
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15356
15491
|
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;
|
|
15357
15492
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15493
|
+
if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
|
|
15358
15494
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15359
15495
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
15360
15496
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
@@ -15506,7 +15642,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
|
15506
15642
|
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15507
15643
|
matchingReturns.push(child);
|
|
15508
15644
|
});
|
|
15509
|
-
return
|
|
15645
|
+
return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15510
15646
|
};
|
|
15511
15647
|
let didFindInvocation = false;
|
|
15512
15648
|
walkAst(componentFunction.body, (child) => {
|
|
@@ -15558,7 +15694,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
|
15558
15694
|
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15559
15695
|
};
|
|
15560
15696
|
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15561
|
-
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") || !
|
|
15697
|
+
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") || !doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15562
15698
|
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15563
15699
|
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15564
15700
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
|
|
@@ -15786,7 +15922,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
|
|
|
15786
15922
|
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15787
15923
|
if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
|
|
15788
15924
|
});
|
|
15789
|
-
return
|
|
15925
|
+
return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15790
15926
|
};
|
|
15791
15927
|
const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
|
|
15792
15928
|
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
@@ -15829,15 +15965,6 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
|
|
|
15829
15965
|
const returnStatement = expressionRoot.parent;
|
|
15830
15966
|
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
|
|
15831
15967
|
};
|
|
15832
|
-
const getFinalSequenceExpressionValue = (expression) => {
|
|
15833
|
-
let finalExpression = stripParenExpression(expression);
|
|
15834
|
-
while (isNodeOfType(finalExpression, "SequenceExpression")) {
|
|
15835
|
-
const sequenceResult = finalExpression.expressions.at(-1);
|
|
15836
|
-
if (!sequenceResult) break;
|
|
15837
|
-
finalExpression = stripParenExpression(sequenceResult);
|
|
15838
|
-
}
|
|
15839
|
-
return finalExpression;
|
|
15840
|
-
};
|
|
15841
15968
|
const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
|
|
15842
15969
|
if (!allowReturnedResourceEscape) return false;
|
|
15843
15970
|
let currentNode = resourceNode;
|
|
@@ -16086,7 +16213,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
|
|
|
16086
16213
|
if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
16087
16214
|
if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
|
|
16088
16215
|
});
|
|
16089
|
-
return
|
|
16216
|
+
return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
|
|
16090
16217
|
};
|
|
16091
16218
|
const matchingReturns = [];
|
|
16092
16219
|
walkInsideStatementBlocks(retainedFunction.body, (child) => {
|
|
@@ -22436,25 +22563,49 @@ const jsLengthCheckFirst = defineRule({
|
|
|
22436
22563
|
});
|
|
22437
22564
|
//#endregion
|
|
22438
22565
|
//#region src/plugin/utils/is-proven-global-namespace-reference.ts
|
|
22566
|
+
const GLOBAL_SELF_PROPERTY_NAMES = new Set([
|
|
22567
|
+
"global",
|
|
22568
|
+
"globalThis",
|
|
22569
|
+
"self",
|
|
22570
|
+
"window"
|
|
22571
|
+
]);
|
|
22439
22572
|
const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22440
|
-
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22573
|
+
let currentExpression = expression;
|
|
22574
|
+
while (true) {
|
|
22575
|
+
const strippedExpression = stripParenExpression(currentExpression);
|
|
22576
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
22577
|
+
if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22578
|
+
const symbol = scopes.symbolFor(strippedExpression);
|
|
22579
|
+
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
22580
|
+
const declaration = symbol.declarationNode;
|
|
22581
|
+
const initializer = declaration.init;
|
|
22582
|
+
if (!initializer) return false;
|
|
22583
|
+
const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
|
|
22584
|
+
const bindingProperty = symbol.bindingIdentifier.parent;
|
|
22585
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
22586
|
+
const isDirectGlobalSelfAlias = isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === symbol.bindingIdentifier && bindingProperty.parent === declaration.id && destructuredPropertyName !== null && GLOBAL_SELF_PROPERTY_NAMES.has(destructuredPropertyName);
|
|
22587
|
+
if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
|
|
22588
|
+
visitedSymbolIds.add(symbol.id);
|
|
22589
|
+
currentExpression = initializer;
|
|
22590
|
+
}
|
|
22447
22591
|
};
|
|
22448
22592
|
const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
22449
|
-
|
|
22450
|
-
|
|
22593
|
+
let currentExpression = expression;
|
|
22594
|
+
while (true) {
|
|
22595
|
+
const strippedExpression = stripParenExpression(currentExpression);
|
|
22596
|
+
if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22451
22597
|
if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
|
|
22452
22598
|
const symbol = scopes.symbolFor(strippedExpression);
|
|
22453
|
-
if (!symbol
|
|
22599
|
+
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
|
|
22454
22600
|
visitedSymbolIds.add(symbol.id);
|
|
22455
|
-
|
|
22601
|
+
const declaration = symbol.declarationNode;
|
|
22602
|
+
const bindingIdentifier = symbol.bindingIdentifier;
|
|
22603
|
+
const bindingProperty = bindingIdentifier.parent;
|
|
22604
|
+
if (getDestructuredBindingPropertyName(bindingIdentifier) === namespaceName && isNodeOfType(declaration, "VariableDeclarator") && isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === bindingIdentifier && bindingProperty.parent === declaration.id && declaration.init) return isProvenGlobalObjectReference(declaration.init, scopes);
|
|
22605
|
+
const directInitializer = getDirectConstInitializer(symbol);
|
|
22606
|
+
if (!directInitializer) return false;
|
|
22607
|
+
currentExpression = directInitializer;
|
|
22456
22608
|
}
|
|
22457
|
-
return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
|
|
22458
22609
|
};
|
|
22459
22610
|
//#endregion
|
|
22460
22611
|
//#region src/plugin/rules/js-performance/js-min-max-loop.ts
|
|
@@ -34716,6 +34867,19 @@ const noCascadingSetState = defineRetiredRule({
|
|
|
34716
34867
|
recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
|
|
34717
34868
|
});
|
|
34718
34869
|
//#endregion
|
|
34870
|
+
//#region src/plugin/utils/get-react-use-callback-call.ts
|
|
34871
|
+
const getReactUseCallbackCall = (expression, scopes) => {
|
|
34872
|
+
const identifier = stripParenExpression(expression);
|
|
34873
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
34874
|
+
const symbol = resolveConstIdentifierAlias(identifier, scopes);
|
|
34875
|
+
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
34876
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
34877
|
+
return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
|
|
34878
|
+
allowGlobalReactNamespace: true,
|
|
34879
|
+
resolveNamedAliases: true
|
|
34880
|
+
}) ? initializer : null;
|
|
34881
|
+
};
|
|
34882
|
+
//#endregion
|
|
34719
34883
|
//#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
|
|
34720
34884
|
const getRefCurrentSymbol = (node, context) => {
|
|
34721
34885
|
const expression = stripParenExpression(node);
|
|
@@ -35244,19 +35408,8 @@ const isBuiltinNamespaceCallee = (callee) => {
|
|
|
35244
35408
|
}
|
|
35245
35409
|
return false;
|
|
35246
35410
|
};
|
|
35247
|
-
const getReactUseCallbackSource = (reference, context) => {
|
|
35248
|
-
const identifier = reference.identifier;
|
|
35249
|
-
const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
|
|
35250
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
35251
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
35252
|
-
if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
|
|
35253
|
-
allowGlobalReactNamespace: true,
|
|
35254
|
-
resolveNamedAliases: true
|
|
35255
|
-
})) return initializer;
|
|
35256
|
-
return null;
|
|
35257
|
-
};
|
|
35258
35411
|
const getDependencyStateRefs = (analysis, context, dependencyReference) => {
|
|
35259
|
-
const useCallbackCall =
|
|
35412
|
+
const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
|
|
35260
35413
|
if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
35261
35414
|
const dependencyList = useCallbackCall.arguments?.[1];
|
|
35262
35415
|
if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
|
|
@@ -38770,6 +38923,17 @@ const noDynamicImportPath = defineRule({
|
|
|
38770
38923
|
})
|
|
38771
38924
|
});
|
|
38772
38925
|
//#endregion
|
|
38926
|
+
//#region src/plugin/utils/get-require-call-source.ts
|
|
38927
|
+
const getRequireCallSource = (expression) => {
|
|
38928
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
38929
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
38930
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
38931
|
+
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
38932
|
+
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
38933
|
+
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
38934
|
+
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
38935
|
+
};
|
|
38936
|
+
//#endregion
|
|
38773
38937
|
//#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
|
|
38774
38938
|
const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
|
|
38775
38939
|
"each",
|
|
@@ -38914,7 +39078,16 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
|
|
|
38914
39078
|
}
|
|
38915
39079
|
return dependencyStateSymbolIds;
|
|
38916
39080
|
};
|
|
38917
|
-
const
|
|
39081
|
+
const resolveSynchronouslyInvokedFunction = (expression, scopes) => {
|
|
39082
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
39083
|
+
if (localFunction) return localFunction;
|
|
39084
|
+
const useCallbackCall = getReactUseCallbackCall(expression, scopes);
|
|
39085
|
+
if (!useCallbackCall) return null;
|
|
39086
|
+
const callback = useCallbackCall.arguments[0];
|
|
39087
|
+
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
39088
|
+
return resolveExactLocalFunction(callback, scopes);
|
|
39089
|
+
};
|
|
39090
|
+
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
|
|
38918
39091
|
const analysisFunctions = new Set([effectCallback]);
|
|
38919
39092
|
const pendingFunctions = [effectCallback];
|
|
38920
39093
|
while (pendingFunctions.length > 0) {
|
|
@@ -38922,9 +39095,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
|
38922
39095
|
if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
|
|
38923
39096
|
walkInsideStatementBlocks(currentFunction.body, (child) => {
|
|
38924
39097
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38925
|
-
const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
39098
|
+
const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
|
|
38926
39099
|
if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
|
|
38927
|
-
if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
|
|
39100
|
+
if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
|
|
38928
39101
|
analysisFunctions.add(invokedFunction);
|
|
38929
39102
|
pendingFunctions.push(invokedFunction);
|
|
38930
39103
|
});
|
|
@@ -38937,7 +39110,51 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
38937
39110
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
38938
39111
|
}
|
|
38939
39112
|
};
|
|
38940
|
-
const
|
|
39113
|
+
const getStateNameForSetterCall = (node, setterSymbolIdToStateName, scopes) => {
|
|
39114
|
+
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
|
|
39115
|
+
const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
|
|
39116
|
+
return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
|
|
39117
|
+
};
|
|
39118
|
+
const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
|
|
39119
|
+
const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
|
|
39120
|
+
const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
|
|
39121
|
+
const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
|
|
39122
|
+
if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
|
|
39123
|
+
let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
|
|
39124
|
+
if (!isFunctionLike$1(analysisFunction)) return false;
|
|
39125
|
+
let hasExecutableDefault = false;
|
|
39126
|
+
for (const parameter of analysisFunction.params) {
|
|
39127
|
+
walkInsideStatementBlocks(parameter, (child) => {
|
|
39128
|
+
if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasExecutableDefault = true;
|
|
39129
|
+
});
|
|
39130
|
+
if (hasExecutableDefault) return true;
|
|
39131
|
+
}
|
|
39132
|
+
return false;
|
|
39133
|
+
});
|
|
39134
|
+
const stableWrittenStateNames = /* @__PURE__ */ new Set();
|
|
39135
|
+
const allWrittenStateNames = /* @__PURE__ */ new Set();
|
|
39136
|
+
visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
|
|
39137
|
+
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
39138
|
+
if (writtenStateName) allWrittenStateNames.add(writtenStateName);
|
|
39139
|
+
});
|
|
39140
|
+
visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
|
|
39141
|
+
if (hasUnprovenObservableWork) return;
|
|
39142
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
39143
|
+
const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
39144
|
+
if (writtenStateName) {
|
|
39145
|
+
stableWrittenStateNames.add(writtenStateName);
|
|
39146
|
+
return;
|
|
39147
|
+
}
|
|
39148
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
39149
|
+
if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
|
|
39150
|
+
hasUnprovenObservableWork = true;
|
|
39151
|
+
return;
|
|
39152
|
+
}
|
|
39153
|
+
if (isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasUnprovenObservableWork = true;
|
|
39154
|
+
});
|
|
39155
|
+
return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
|
|
39156
|
+
};
|
|
39157
|
+
const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
38941
39158
|
const unwrappedExpression = stripParenExpression(expression);
|
|
38942
39159
|
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
38943
39160
|
const literalValue = unwrappedExpression.value;
|
|
@@ -38945,46 +39162,49 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
|
|
|
38945
39162
|
return null;
|
|
38946
39163
|
}
|
|
38947
39164
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
38948
|
-
|
|
39165
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
39166
|
+
if (symbol?.id === stateSymbolId) return stateValue;
|
|
39167
|
+
if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
|
|
38949
39168
|
if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
|
|
39169
|
+
if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
|
|
38950
39170
|
const immutableSymbol = scopes.symbolFor(unwrappedExpression);
|
|
38951
39171
|
if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
|
|
38952
|
-
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
|
|
39172
|
+
return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id), additionalStateValues);
|
|
38953
39173
|
}
|
|
38954
39174
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
|
|
38955
39175
|
if (unwrappedExpression.operator === "void") return { value: void 0 };
|
|
38956
39176
|
if (unwrappedExpression.operator !== "!") return null;
|
|
38957
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39177
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38958
39178
|
return argumentValue ? { value: !argumentValue.value } : null;
|
|
38959
39179
|
}
|
|
38960
39180
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
38961
39181
|
if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
|
|
38962
|
-
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39182
|
+
const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38963
39183
|
return argumentValue ? { value: Boolean(argumentValue.value) } : null;
|
|
38964
39184
|
}
|
|
38965
39185
|
return null;
|
|
38966
39186
|
}
|
|
38967
39187
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
38968
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39188
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38969
39189
|
if (!leftValue) return null;
|
|
38970
39190
|
if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
|
|
38971
39191
|
if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
|
|
38972
39192
|
if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
|
|
38973
|
-
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39193
|
+
return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38974
39194
|
}
|
|
38975
39195
|
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
38976
|
-
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39196
|
+
const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38977
39197
|
if (!testValue) return null;
|
|
38978
|
-
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39198
|
+
return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38979
39199
|
}
|
|
38980
39200
|
if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
|
|
38981
|
-
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39201
|
+
const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38982
39202
|
if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
|
|
38983
39203
|
return null;
|
|
38984
39204
|
}
|
|
38985
39205
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
38986
|
-
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
38987
|
-
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
|
|
39206
|
+
const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
39207
|
+
const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
|
|
38988
39208
|
if (!leftValue || !rightValue) return null;
|
|
38989
39209
|
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
38990
39210
|
const areEqual = leftValue.value === rightValue.value;
|
|
@@ -39023,8 +39243,7 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39023
39243
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39024
39244
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39025
39245
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
39026
|
-
const
|
|
39027
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
39246
|
+
const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
|
|
39028
39247
|
if (!stateName) return;
|
|
39029
39248
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
39030
39249
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -39040,27 +39259,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
|
|
|
39040
39259
|
const isGlobalBooleanCall = (node, scopes) => {
|
|
39041
39260
|
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
|
|
39042
39261
|
};
|
|
39043
|
-
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
|
|
39262
|
+
const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
39044
39263
|
let currentNode = workNode;
|
|
39045
39264
|
while (currentNode.parent) {
|
|
39046
39265
|
const parentNode = currentNode.parent;
|
|
39047
39266
|
if (isFunctionLike$1(parentNode)) break;
|
|
39048
39267
|
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
39049
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39268
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39050
39269
|
if (testValue) {
|
|
39051
39270
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39052
39271
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39053
39272
|
}
|
|
39054
39273
|
}
|
|
39055
39274
|
if (isNodeOfType(parentNode, "ConditionalExpression")) {
|
|
39056
|
-
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
|
|
39275
|
+
const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39057
39276
|
if (testValue) {
|
|
39058
39277
|
if (currentNode === parentNode.consequent && !testValue.value) return false;
|
|
39059
39278
|
if (currentNode === parentNode.alternate && testValue.value) return false;
|
|
39060
39279
|
}
|
|
39061
39280
|
}
|
|
39062
39281
|
if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
|
|
39063
|
-
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
|
|
39282
|
+
const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
|
|
39064
39283
|
if (leftValue) {
|
|
39065
39284
|
if (parentNode.operator === "&&" && !leftValue.value) return false;
|
|
39066
39285
|
if (parentNode.operator === "||" && leftValue.value) return false;
|
|
@@ -39072,7 +39291,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39072
39291
|
if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
|
|
39073
39292
|
const earlierStatement = parentNode.body[index];
|
|
39074
39293
|
if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
|
|
39075
|
-
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
|
|
39294
|
+
if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
|
|
39076
39295
|
}
|
|
39077
39296
|
}
|
|
39078
39297
|
currentNode = parentNode;
|
|
@@ -39082,24 +39301,93 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
|
|
|
39082
39301
|
const isReaderWorkNode = (node, analysisFunctions, scopes) => {
|
|
39083
39302
|
if (isNodeOfType(node, "CallExpression")) {
|
|
39084
39303
|
if (isGlobalBooleanCall(node, scopes)) return false;
|
|
39085
|
-
const invokedFunction =
|
|
39304
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
|
|
39086
39305
|
return !invokedFunction || !analysisFunctions.has(invokedFunction);
|
|
39087
39306
|
}
|
|
39088
39307
|
return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
|
|
39089
39308
|
};
|
|
39090
|
-
const
|
|
39091
|
-
if (
|
|
39092
|
-
|
|
39093
|
-
|
|
39309
|
+
const mergeKnownStateValues = (additionalStateValues, parameterStateValues) => {
|
|
39310
|
+
if (parameterStateValues.size === 0) return additionalStateValues;
|
|
39311
|
+
const knownStateValues = new Map(additionalStateValues);
|
|
39312
|
+
for (const [symbolId, value] of parameterStateValues) knownStateValues.set(symbolId, value);
|
|
39313
|
+
return knownStateValues;
|
|
39314
|
+
};
|
|
39315
|
+
const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
|
|
39316
|
+
if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
|
|
39317
|
+
const parameterStateValues = /* @__PURE__ */ new Map();
|
|
39318
|
+
const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
|
|
39319
|
+
for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
|
|
39320
|
+
const rawParameter = invokedFunction.params[parameterIndex];
|
|
39321
|
+
const rawArgument = invocation.arguments[parameterIndex];
|
|
39322
|
+
if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
|
|
39323
|
+
const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
|
|
39324
|
+
if (!isNodeOfType(parameter, "Identifier")) continue;
|
|
39325
|
+
const parameterSymbol = scopes.symbolFor(parameter);
|
|
39326
|
+
if (!parameterSymbol) continue;
|
|
39327
|
+
let argumentValue = null;
|
|
39328
|
+
if (rawArgument) {
|
|
39329
|
+
argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
|
|
39330
|
+
if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39331
|
+
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39332
|
+
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39333
|
+
}
|
|
39334
|
+
} else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
|
|
39335
|
+
const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
|
|
39336
|
+
argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
|
|
39337
|
+
} else argumentValue = { value: void 0 };
|
|
39338
|
+
if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
|
|
39339
|
+
}
|
|
39340
|
+
return parameterStateValues;
|
|
39341
|
+
};
|
|
39342
|
+
const haveEqualParameterStateValues = (leftValues, rightValues) => {
|
|
39343
|
+
if (leftValues.size !== rightValues.size) return false;
|
|
39344
|
+
for (const [symbolId, leftValue] of leftValues) {
|
|
39345
|
+
const rightValue = rightValues.get(symbolId);
|
|
39346
|
+
if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
|
|
39347
|
+
}
|
|
39348
|
+
return true;
|
|
39349
|
+
};
|
|
39350
|
+
const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
|
|
39351
|
+
const pendingFrames = [{
|
|
39352
|
+
functionNode: readerEffect.callback,
|
|
39353
|
+
parameterStateValues: /* @__PURE__ */ new Map()
|
|
39354
|
+
}];
|
|
39355
|
+
const visitedParameterStateValues = /* @__PURE__ */ new Map();
|
|
39356
|
+
while (pendingFrames.length > 0) {
|
|
39357
|
+
const frame = pendingFrames.pop();
|
|
39358
|
+
if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
|
|
39359
|
+
const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
|
|
39360
|
+
if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
|
|
39361
|
+
functionParameterStateValues.push(frame.parameterStateValues);
|
|
39362
|
+
visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
|
|
39363
|
+
const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
|
|
39094
39364
|
let didFindReachableWork = false;
|
|
39095
|
-
|
|
39096
|
-
if (didFindReachableWork
|
|
39097
|
-
if (
|
|
39365
|
+
walkInsideStatementBlocks(frame.functionNode.body, (child) => {
|
|
39366
|
+
if (didFindReachableWork) return;
|
|
39367
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
39368
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
|
|
39369
|
+
if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
|
|
39370
|
+
if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
|
|
39371
|
+
functionNode: invokedFunction,
|
|
39372
|
+
parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
|
|
39373
|
+
});
|
|
39374
|
+
return;
|
|
39375
|
+
}
|
|
39376
|
+
}
|
|
39377
|
+
if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
|
|
39098
39378
|
});
|
|
39099
39379
|
if (didFindReachableWork) return true;
|
|
39100
39380
|
}
|
|
39101
39381
|
return false;
|
|
39102
39382
|
};
|
|
39383
|
+
const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
|
|
39384
|
+
const writeInfo = writerEffect.stateWrites.get(writtenStateName);
|
|
39385
|
+
const stateSymbolId = stateSymbolIds.get(writtenStateName);
|
|
39386
|
+
if (!writeInfo || stateSymbolId === void 0) return true;
|
|
39387
|
+
if (writeInfo.hasUnknownValue) return true;
|
|
39388
|
+
for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
|
|
39389
|
+
return false;
|
|
39390
|
+
};
|
|
39103
39391
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
39104
39392
|
const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
39105
39393
|
"clear",
|
|
@@ -39110,26 +39398,499 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
39110
39398
|
"keys",
|
|
39111
39399
|
"values"
|
|
39112
39400
|
]);
|
|
39113
|
-
const
|
|
39114
|
-
|
|
39115
|
-
|
|
39116
|
-
|
|
39117
|
-
|
|
39118
|
-
|
|
39119
|
-
|
|
39401
|
+
const EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES = new Map([
|
|
39402
|
+
["fetch", new Set([
|
|
39403
|
+
"cross-fetch",
|
|
39404
|
+
"node-fetch",
|
|
39405
|
+
"undici"
|
|
39406
|
+
])],
|
|
39407
|
+
["ky", new Set(["ky"])],
|
|
39408
|
+
["got", new Set(["got"])],
|
|
39409
|
+
["wretch", new Set(["wretch"])],
|
|
39410
|
+
["ofetch", new Set(["ofetch"])],
|
|
39411
|
+
["setTimeout", new Set([
|
|
39412
|
+
"node:timers",
|
|
39413
|
+
"node:timers/promises",
|
|
39414
|
+
"timers",
|
|
39415
|
+
"timers/promises"
|
|
39416
|
+
])],
|
|
39417
|
+
["setInterval", new Set([
|
|
39418
|
+
"node:timers",
|
|
39419
|
+
"node:timers/promises",
|
|
39420
|
+
"timers",
|
|
39421
|
+
"timers/promises"
|
|
39422
|
+
])]
|
|
39423
|
+
]);
|
|
39424
|
+
const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
|
|
39425
|
+
["axios", "axios"],
|
|
39426
|
+
["cross-fetch", "fetch"],
|
|
39427
|
+
["got", "got"],
|
|
39428
|
+
["ky", "ky"],
|
|
39429
|
+
["node-fetch", "fetch"],
|
|
39430
|
+
["wretch", "wretch"]
|
|
39431
|
+
]);
|
|
39432
|
+
const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
|
|
39433
|
+
"axios",
|
|
39434
|
+
"cross-fetch",
|
|
39435
|
+
"got",
|
|
39436
|
+
"ky",
|
|
39437
|
+
"node-fetch",
|
|
39438
|
+
"ofetch",
|
|
39439
|
+
"undici",
|
|
39440
|
+
"wretch"
|
|
39441
|
+
]);
|
|
39442
|
+
const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
|
|
39443
|
+
"axios",
|
|
39444
|
+
"got",
|
|
39445
|
+
"ky",
|
|
39446
|
+
"ofetch",
|
|
39447
|
+
"wretch"
|
|
39448
|
+
]);
|
|
39449
|
+
const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
|
|
39450
|
+
...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
|
|
39451
|
+
"fetch",
|
|
39452
|
+
"patch",
|
|
39453
|
+
"post",
|
|
39454
|
+
"put",
|
|
39455
|
+
"request"
|
|
39456
|
+
]);
|
|
39457
|
+
const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
|
|
39458
|
+
const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
|
|
39459
|
+
const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
|
|
39460
|
+
...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
|
|
39461
|
+
...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
|
|
39462
|
+
"EventTarget",
|
|
39463
|
+
"XMLHttpRequest"
|
|
39464
|
+
]);
|
|
39465
|
+
const getDestructuredBindingDepth = (bindingIdentifier) => {
|
|
39466
|
+
let bindingNode = bindingIdentifier;
|
|
39467
|
+
let depth = 0;
|
|
39468
|
+
while (true) {
|
|
39469
|
+
if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
|
|
39470
|
+
const property = bindingNode.parent;
|
|
39471
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
|
|
39472
|
+
depth += 1;
|
|
39473
|
+
bindingNode = property.parent;
|
|
39474
|
+
}
|
|
39475
|
+
};
|
|
39476
|
+
const isMutatedMemberExpression = (rawMemberExpression) => {
|
|
39477
|
+
let expression = findTransparentExpressionRoot(rawMemberExpression);
|
|
39478
|
+
while (expression.parent) {
|
|
39479
|
+
const parent = expression.parent;
|
|
39480
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
|
|
39481
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39482
|
+
continue;
|
|
39120
39483
|
}
|
|
39121
|
-
|
|
39484
|
+
if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
|
|
39485
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39486
|
+
continue;
|
|
39487
|
+
}
|
|
39488
|
+
if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
|
|
39489
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39490
|
+
continue;
|
|
39491
|
+
}
|
|
39492
|
+
if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
|
|
39493
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39494
|
+
continue;
|
|
39495
|
+
}
|
|
39496
|
+
if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
|
|
39497
|
+
expression = findTransparentExpressionRoot(parent);
|
|
39498
|
+
continue;
|
|
39499
|
+
}
|
|
39500
|
+
break;
|
|
39501
|
+
}
|
|
39502
|
+
const parent = expression.parent;
|
|
39503
|
+
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expression || isNodeOfType(parent, "UpdateExpression") && parent.argument === expression || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression || (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) && parent.left === expression);
|
|
39504
|
+
};
|
|
39505
|
+
const isReactHookDependencyArgument = (expression, scopes) => {
|
|
39506
|
+
const call = expression.parent;
|
|
39507
|
+
return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
|
|
39508
|
+
allowGlobalReactNamespace: true,
|
|
39509
|
+
allowUnboundBareCalls: true,
|
|
39510
|
+
resolveNamedAliases: true
|
|
39511
|
+
}));
|
|
39512
|
+
};
|
|
39513
|
+
const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
|
|
39514
|
+
const getContainingReactDependencyValue = (rawExpression, containerKind) => {
|
|
39515
|
+
const expression = findTransparentExpressionRoot(rawExpression);
|
|
39516
|
+
const parent = expression.parent;
|
|
39517
|
+
if (isNodeOfType(parent, "ArrayExpression")) return {
|
|
39518
|
+
expression: parent,
|
|
39519
|
+
containerKind: "array"
|
|
39520
|
+
};
|
|
39521
|
+
if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
|
|
39522
|
+
expression: parent.parent,
|
|
39523
|
+
containerKind: "array"
|
|
39524
|
+
};
|
|
39525
|
+
if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
|
|
39526
|
+
expression: parent.parent,
|
|
39527
|
+
containerKind: "object"
|
|
39528
|
+
};
|
|
39529
|
+
return null;
|
|
39530
|
+
};
|
|
39531
|
+
const getFullArrayRestCopySymbol = (initializer, scopes) => {
|
|
39532
|
+
const declaration = initializer.parent;
|
|
39533
|
+
if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
|
|
39534
|
+
const restElement = declaration.id.elements[0];
|
|
39535
|
+
if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
|
|
39536
|
+
const symbol = scopes.symbolFor(restElement.argument);
|
|
39537
|
+
return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
|
|
39538
|
+
};
|
|
39539
|
+
const isReactDependencyArrayReference = (initialExpression, scopes) => {
|
|
39540
|
+
const pendingPaths = [{
|
|
39541
|
+
expression: initialExpression,
|
|
39542
|
+
containerKind: null
|
|
39543
|
+
}];
|
|
39544
|
+
const visitedExpressions = /* @__PURE__ */ new Set();
|
|
39545
|
+
let didFindReactDependencyArgument = false;
|
|
39546
|
+
while (pendingPaths.length > 0) {
|
|
39547
|
+
const pendingPath = pendingPaths.pop();
|
|
39548
|
+
if (!pendingPath) continue;
|
|
39549
|
+
const expression = findTransparentExpressionRoot(pendingPath.expression);
|
|
39550
|
+
if (visitedExpressions.has(expression)) continue;
|
|
39551
|
+
visitedExpressions.add(expression);
|
|
39552
|
+
if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
|
|
39553
|
+
didFindReactDependencyArgument = true;
|
|
39554
|
+
continue;
|
|
39555
|
+
}
|
|
39556
|
+
const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
|
|
39557
|
+
if (containingValue) {
|
|
39558
|
+
pendingPaths.push(containingValue);
|
|
39559
|
+
continue;
|
|
39560
|
+
}
|
|
39561
|
+
const declaration = expression.parent;
|
|
39562
|
+
if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
|
|
39563
|
+
const symbol = scopes.symbolFor(declaration.id);
|
|
39564
|
+
if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
|
|
39565
|
+
if (symbol.references.length === 0) return false;
|
|
39566
|
+
for (const reference of symbol.references) pendingPaths.push({
|
|
39567
|
+
expression: reference.identifier,
|
|
39568
|
+
containerKind: pendingPath.containerKind
|
|
39569
|
+
});
|
|
39570
|
+
continue;
|
|
39571
|
+
}
|
|
39572
|
+
if (pendingPath.containerKind === "array") {
|
|
39573
|
+
const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
|
|
39574
|
+
if (restCopySymbol) {
|
|
39575
|
+
if (restCopySymbol.references.length === 0) return false;
|
|
39576
|
+
for (const reference of restCopySymbol.references) pendingPaths.push({
|
|
39577
|
+
expression: reference.identifier,
|
|
39578
|
+
containerKind: "array"
|
|
39579
|
+
});
|
|
39580
|
+
continue;
|
|
39581
|
+
}
|
|
39582
|
+
}
|
|
39583
|
+
return false;
|
|
39122
39584
|
}
|
|
39123
|
-
|
|
39585
|
+
return didFindReactDependencyArgument;
|
|
39586
|
+
};
|
|
39587
|
+
const hasSafeReceiverAliases = (rootSymbols, scopes) => {
|
|
39588
|
+
const pendingSymbols = [...rootSymbols];
|
|
39589
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
39590
|
+
while (pendingSymbols.length > 0) {
|
|
39591
|
+
const symbol = pendingSymbols.pop();
|
|
39592
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
|
|
39593
|
+
visitedSymbolIds.add(symbol.id);
|
|
39594
|
+
for (const reference of symbol.references) {
|
|
39595
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39596
|
+
const container = expression.parent;
|
|
39597
|
+
if (isReactDependencyArrayReference(expression, scopes)) continue;
|
|
39598
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
|
|
39599
|
+
if (isMutatedMemberExpression(container)) return false;
|
|
39600
|
+
continue;
|
|
39601
|
+
}
|
|
39602
|
+
if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
|
|
39603
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
|
|
39604
|
+
if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
|
|
39605
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
|
|
39606
|
+
const aliasSymbol = scopes.symbolFor(container.id);
|
|
39607
|
+
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39608
|
+
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39609
|
+
pendingSymbols.push(aliasSymbol);
|
|
39610
|
+
continue;
|
|
39611
|
+
}
|
|
39612
|
+
return false;
|
|
39613
|
+
}
|
|
39614
|
+
}
|
|
39615
|
+
return true;
|
|
39616
|
+
};
|
|
39617
|
+
const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
|
|
39618
|
+
for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
|
|
39124
39619
|
return false;
|
|
39125
39620
|
};
|
|
39126
|
-
const
|
|
39127
|
-
|
|
39128
|
-
|
|
39129
|
-
if (isNodeOfType(
|
|
39130
|
-
|
|
39621
|
+
const getTypeScriptImportEqualsSource = (symbol) => {
|
|
39622
|
+
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return null;
|
|
39623
|
+
const moduleReference = symbol.declarationNode.moduleReference;
|
|
39624
|
+
if (!isNodeOfType(moduleReference, "TSExternalModuleReference") || !isNodeOfType(moduleReference.expression, "Literal")) return null;
|
|
39625
|
+
return typeof moduleReference.expression.value === "string" ? moduleReference.expression.value : null;
|
|
39626
|
+
};
|
|
39627
|
+
const getUnshadowedRequireSource = (rawExpression, scopes) => {
|
|
39628
|
+
let expression = stripParenExpression(rawExpression);
|
|
39629
|
+
while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
|
|
39630
|
+
if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
|
|
39631
|
+
return getRequireCallSource(rawExpression);
|
|
39632
|
+
};
|
|
39633
|
+
const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
|
|
39634
|
+
let expression = stripParenExpression(rawExpression);
|
|
39635
|
+
let outermostExportedName = null;
|
|
39636
|
+
let exportedPropertyDepth = 0;
|
|
39637
|
+
let resolvedBinding = null;
|
|
39638
|
+
let isCommonJsModuleObject = false;
|
|
39639
|
+
const traversedSymbolIds = new Set(visitedSymbolIds);
|
|
39640
|
+
const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
|
|
39641
|
+
while (true) {
|
|
39642
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39643
|
+
const propertyName = getStaticPropertyName(expression);
|
|
39644
|
+
if (propertyName === null) return null;
|
|
39645
|
+
outermostExportedName ??= propertyName;
|
|
39646
|
+
exportedPropertyDepth += 1;
|
|
39647
|
+
expression = stripParenExpression(expression.object);
|
|
39648
|
+
continue;
|
|
39649
|
+
}
|
|
39650
|
+
const requireSource = getUnshadowedRequireSource(expression, scopes);
|
|
39651
|
+
if (requireSource !== null) {
|
|
39652
|
+
resolvedBinding = {
|
|
39653
|
+
source: requireSource,
|
|
39654
|
+
exportedName: null,
|
|
39655
|
+
isModuleObject: true
|
|
39656
|
+
};
|
|
39657
|
+
isCommonJsModuleObject = true;
|
|
39658
|
+
break;
|
|
39659
|
+
}
|
|
39660
|
+
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39661
|
+
const symbol = scopes.symbolFor(expression);
|
|
39662
|
+
if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
|
|
39663
|
+
if (symbol.kind === "import") {
|
|
39664
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
39665
|
+
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
|
|
39666
|
+
if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
|
|
39667
|
+
source: importDeclaration.source.value,
|
|
39668
|
+
exportedName: getImportedName(symbol.declarationNode) ?? null,
|
|
39669
|
+
isModuleObject: false
|
|
39670
|
+
};
|
|
39671
|
+
else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
|
|
39672
|
+
source: importDeclaration.source.value,
|
|
39673
|
+
exportedName: "default",
|
|
39674
|
+
isModuleObject: false
|
|
39675
|
+
};
|
|
39676
|
+
else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
|
|
39677
|
+
source: importDeclaration.source.value,
|
|
39678
|
+
exportedName: null,
|
|
39679
|
+
isModuleObject: true
|
|
39680
|
+
};
|
|
39681
|
+
break;
|
|
39682
|
+
}
|
|
39683
|
+
const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
|
|
39684
|
+
if (importEqualsSource !== null) {
|
|
39685
|
+
resolvedBinding = {
|
|
39686
|
+
source: importEqualsSource,
|
|
39687
|
+
exportedName: null,
|
|
39688
|
+
isModuleObject: true
|
|
39689
|
+
};
|
|
39690
|
+
isCommonJsModuleObject = true;
|
|
39691
|
+
commonJsModuleObjectSymbols.add(symbol);
|
|
39692
|
+
break;
|
|
39693
|
+
}
|
|
39694
|
+
const directInitializer = getDirectUnreassignedInitializer(symbol);
|
|
39695
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
39696
|
+
const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
|
|
39697
|
+
if (!initializer) return null;
|
|
39698
|
+
traversedSymbolIds.add(symbol.id);
|
|
39699
|
+
outermostExportedName ??= destructuredPropertyName;
|
|
39700
|
+
if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
|
|
39701
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
39702
|
+
if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
|
|
39703
|
+
expression = unwrappedInitializer;
|
|
39704
|
+
}
|
|
39705
|
+
if (!resolvedBinding) return null;
|
|
39706
|
+
if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
|
|
39707
|
+
if (outermostExportedName === null) return resolvedBinding;
|
|
39708
|
+
if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
|
|
39709
|
+
return {
|
|
39710
|
+
source: resolvedBinding.source,
|
|
39711
|
+
exportedName: outermostExportedName,
|
|
39712
|
+
isModuleObject: false
|
|
39713
|
+
};
|
|
39714
|
+
};
|
|
39715
|
+
const getCanonicalExternalSyncExportName = (binding) => {
|
|
39716
|
+
if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
|
|
39717
|
+
return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
|
|
39718
|
+
};
|
|
39719
|
+
const isKnownExternalSyncDirectModuleBinding = (binding) => {
|
|
39720
|
+
const exportedName = getCanonicalExternalSyncExportName(binding);
|
|
39721
|
+
return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
|
|
39722
|
+
};
|
|
39723
|
+
const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
|
|
39724
|
+
"Array",
|
|
39725
|
+
"BigInt",
|
|
39726
|
+
"Boolean",
|
|
39727
|
+
"Date",
|
|
39728
|
+
"Number",
|
|
39729
|
+
"String",
|
|
39730
|
+
"Symbol"
|
|
39731
|
+
]);
|
|
39732
|
+
const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
|
|
39733
|
+
"Array",
|
|
39734
|
+
"Boolean",
|
|
39735
|
+
"Date",
|
|
39736
|
+
"Map",
|
|
39737
|
+
"Number",
|
|
39738
|
+
"Promise",
|
|
39739
|
+
"RegExp",
|
|
39740
|
+
"Set",
|
|
39741
|
+
"String",
|
|
39742
|
+
"URL",
|
|
39743
|
+
"URLSearchParams",
|
|
39744
|
+
"WeakMap",
|
|
39745
|
+
"WeakSet"
|
|
39746
|
+
]);
|
|
39747
|
+
const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
|
|
39748
|
+
"all",
|
|
39749
|
+
"allSettled",
|
|
39750
|
+
"any",
|
|
39751
|
+
"race",
|
|
39752
|
+
"reject",
|
|
39753
|
+
"resolve",
|
|
39754
|
+
"withResolvers"
|
|
39755
|
+
]);
|
|
39756
|
+
const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39757
|
+
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
|
|
39758
|
+
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39759
|
+
const isNonCleanupValue = (returnValue) => {
|
|
39760
|
+
const expression = stripParenExpression(returnValue);
|
|
39761
|
+
if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
|
|
39762
|
+
if (isNodeOfType(expression, "Literal") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "JSXElement") || isNodeOfType(expression, "JSXFragment") || isNodeOfType(expression, "TemplateLiteral") || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
|
|
39763
|
+
if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
|
|
39764
|
+
if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
|
|
39765
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39766
|
+
const finalExpression = expression.expressions.at(-1);
|
|
39767
|
+
return Boolean(finalExpression && isNonCleanupValue(finalExpression));
|
|
39768
|
+
}
|
|
39769
|
+
if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39770
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
39771
|
+
if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
|
|
39772
|
+
if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.object, "Identifier") && expression.callee.object.name === "Promise" && scopes.isGlobalReference(expression.callee.object) && PROMISE_STATIC_RESULT_METHOD_NAMES.has(getStaticPropertyName(expression.callee) ?? "")) return true;
|
|
39773
|
+
if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
|
|
39774
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39775
|
+
return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
|
|
39776
|
+
};
|
|
39777
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
|
|
39778
|
+
let returnsOnlyNonCleanup = true;
|
|
39779
|
+
walkAst(functionNode.body, (child) => {
|
|
39780
|
+
if (!returnsOnlyNonCleanup) return false;
|
|
39781
|
+
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39782
|
+
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39783
|
+
if (child.argument && !isNonCleanupValue(child.argument)) {
|
|
39784
|
+
returnsOnlyNonCleanup = false;
|
|
39785
|
+
return false;
|
|
39786
|
+
}
|
|
39787
|
+
});
|
|
39788
|
+
return returnsOnlyNonCleanup;
|
|
39789
|
+
};
|
|
39790
|
+
const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
|
|
39791
|
+
if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
|
|
39792
|
+
hasCleanup: false,
|
|
39793
|
+
isValid: false
|
|
39794
|
+
};
|
|
39795
|
+
const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
|
|
39796
|
+
const getCleanupReturnProof = (returnValue) => {
|
|
39797
|
+
const expression = stripParenExpression(returnValue);
|
|
39798
|
+
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
|
|
39799
|
+
hasCleanup: true,
|
|
39800
|
+
isValid: true
|
|
39801
|
+
};
|
|
39802
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
39803
|
+
if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
|
|
39804
|
+
hasCleanup: false,
|
|
39805
|
+
isValid: true
|
|
39806
|
+
};
|
|
39807
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
39808
|
+
return {
|
|
39809
|
+
hasCleanup: Boolean(localFunction),
|
|
39810
|
+
isValid: Boolean(localFunction)
|
|
39811
|
+
};
|
|
39812
|
+
}
|
|
39813
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
|
|
39814
|
+
hasCleanup: false,
|
|
39815
|
+
isValid: true
|
|
39816
|
+
};
|
|
39817
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
39818
|
+
const consequentProof = getCleanupReturnProof(expression.consequent);
|
|
39819
|
+
const alternateProof = getCleanupReturnProof(expression.alternate);
|
|
39820
|
+
return {
|
|
39821
|
+
hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
|
|
39822
|
+
isValid: consequentProof.isValid && alternateProof.isValid
|
|
39823
|
+
};
|
|
39824
|
+
}
|
|
39825
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
39826
|
+
const leftProof = getCleanupReturnProof(expression.left);
|
|
39827
|
+
const rightProof = getCleanupReturnProof(expression.right);
|
|
39828
|
+
return {
|
|
39829
|
+
hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
|
|
39830
|
+
isValid: leftProof.isValid && rightProof.isValid
|
|
39831
|
+
};
|
|
39832
|
+
}
|
|
39833
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
39834
|
+
const finalExpression = expression.expressions.at(-1);
|
|
39835
|
+
return finalExpression ? getCleanupReturnProof(finalExpression) : {
|
|
39836
|
+
hasCleanup: false,
|
|
39837
|
+
isValid: false
|
|
39838
|
+
};
|
|
39839
|
+
}
|
|
39840
|
+
if (!isNodeOfType(expression, "CallExpression")) return {
|
|
39841
|
+
hasCleanup: false,
|
|
39842
|
+
isValid: false
|
|
39843
|
+
};
|
|
39844
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
|
|
39845
|
+
return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
|
|
39846
|
+
hasCleanup: false,
|
|
39847
|
+
isValid: false
|
|
39848
|
+
};
|
|
39849
|
+
};
|
|
39850
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
|
|
39851
|
+
let cleanupReturnProof = {
|
|
39852
|
+
hasCleanup: false,
|
|
39853
|
+
isValid: true
|
|
39854
|
+
};
|
|
39855
|
+
walkAst(functionNode.body, (child) => {
|
|
39856
|
+
if (!cleanupReturnProof.isValid) return false;
|
|
39857
|
+
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
39858
|
+
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
39859
|
+
if (!child.argument) return;
|
|
39860
|
+
const returnProof = getCleanupReturnProof(child.argument);
|
|
39861
|
+
if (!returnProof.isValid) {
|
|
39862
|
+
cleanupReturnProof = {
|
|
39863
|
+
hasCleanup: false,
|
|
39864
|
+
isValid: false
|
|
39865
|
+
};
|
|
39866
|
+
return false;
|
|
39867
|
+
}
|
|
39868
|
+
cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
|
|
39869
|
+
});
|
|
39870
|
+
return cleanupReturnProof;
|
|
39871
|
+
};
|
|
39872
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
|
|
39873
|
+
const unwrappedReturnedValue = stripParenExpression(returnedValue);
|
|
39874
|
+
if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
|
|
39875
|
+
if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
|
|
39876
|
+
if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
|
|
39877
|
+
const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
|
|
39878
|
+
if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
39879
|
+
}
|
|
39880
|
+
const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
|
|
39881
|
+
if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
|
|
39882
|
+
if (invokedFunction) {
|
|
39883
|
+
const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
|
|
39884
|
+
if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
|
|
39885
|
+
}
|
|
39886
|
+
if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
|
|
39887
|
+
return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
39888
|
+
}
|
|
39889
|
+
if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
|
|
39131
39890
|
return false;
|
|
39132
39891
|
};
|
|
39892
|
+
const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
|
|
39893
|
+
const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
|
|
39133
39894
|
const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
|
|
39134
39895
|
const collectStorageHookSetterNames = (componentBody) => {
|
|
39135
39896
|
const setterNames = /* @__PURE__ */ new Set();
|
|
@@ -39161,23 +39922,16 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
|
|
|
39161
39922
|
});
|
|
39162
39923
|
return didFindOpaqueSetterCall;
|
|
39163
39924
|
};
|
|
39164
|
-
const
|
|
39925
|
+
const isReactUseRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "useRef", scopes, {
|
|
39165
39926
|
allowGlobalReactNamespace: true,
|
|
39166
39927
|
allowUnboundBareCalls: true,
|
|
39167
39928
|
resolveNamedAliases: true
|
|
39168
|
-
})
|
|
39929
|
+
});
|
|
39930
|
+
const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
|
|
39169
39931
|
allowGlobalReactNamespace: true,
|
|
39170
39932
|
allowUnboundBareCalls: true,
|
|
39171
39933
|
resolveNamedAliases: true
|
|
39172
|
-
})
|
|
39173
|
-
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
39174
|
-
const expression = stripParenExpression(rawExpression);
|
|
39175
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
39176
|
-
const symbol = scopes.symbolFor(expression);
|
|
39177
|
-
if (!symbol) return null;
|
|
39178
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
39179
|
-
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
39180
|
-
};
|
|
39934
|
+
});
|
|
39181
39935
|
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
39182
39936
|
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
39183
39937
|
const symbol = scopes.symbolFor(openingElement.name);
|
|
@@ -39185,19 +39939,33 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
|
39185
39939
|
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
39186
39940
|
};
|
|
39187
39941
|
const isDirectHostJsxRef = (symbol, scopes) => {
|
|
39188
|
-
let
|
|
39189
|
-
|
|
39190
|
-
|
|
39191
|
-
|
|
39192
|
-
|
|
39193
|
-
if (!
|
|
39194
|
-
|
|
39195
|
-
|
|
39196
|
-
|
|
39197
|
-
|
|
39198
|
-
|
|
39199
|
-
|
|
39200
|
-
|
|
39942
|
+
let didFindHostRef = false;
|
|
39943
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
39944
|
+
const pendingSymbols = [symbol];
|
|
39945
|
+
while (pendingSymbols.length > 0) {
|
|
39946
|
+
const currentSymbol = pendingSymbols.pop();
|
|
39947
|
+
if (!currentSymbol || visitedSymbolIds.has(currentSymbol.id)) continue;
|
|
39948
|
+
visitedSymbolIds.add(currentSymbol.id);
|
|
39949
|
+
for (const reference of currentSymbol.references) {
|
|
39950
|
+
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
39951
|
+
const container = expression.parent;
|
|
39952
|
+
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
39953
|
+
if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
|
|
39954
|
+
const aliasSymbol = scopes.symbolFor(container.id);
|
|
39955
|
+
const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
|
|
39956
|
+
if (!aliasSymbol || aliasInitializer !== expression) return false;
|
|
39957
|
+
pendingSymbols.push(aliasSymbol);
|
|
39958
|
+
continue;
|
|
39959
|
+
}
|
|
39960
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
39961
|
+
const attribute = container.parent;
|
|
39962
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
39963
|
+
const openingElement = attribute.parent;
|
|
39964
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
39965
|
+
didFindHostRef = true;
|
|
39966
|
+
}
|
|
39967
|
+
}
|
|
39968
|
+
return didFindHostRef;
|
|
39201
39969
|
};
|
|
39202
39970
|
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
39203
39971
|
const identifier = stripParenExpression(expression);
|
|
@@ -39207,7 +39975,18 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
|
39207
39975
|
const rawFirstParameter = callback.params?.[0];
|
|
39208
39976
|
const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
|
|
39209
39977
|
const symbol = scopes.symbolFor(identifier);
|
|
39210
|
-
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter)
|
|
39978
|
+
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter && symbol.references.every((reference) => {
|
|
39979
|
+
if (reference.flag !== "read") return false;
|
|
39980
|
+
let referenceRoot = reference.identifier;
|
|
39981
|
+
while (referenceRoot.parent) {
|
|
39982
|
+
const parent = referenceRoot.parent;
|
|
39983
|
+
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
|
|
39984
|
+
if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
|
|
39985
|
+
if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
|
|
39986
|
+
referenceRoot = parent;
|
|
39987
|
+
}
|
|
39988
|
+
return true;
|
|
39989
|
+
}));
|
|
39211
39990
|
};
|
|
39212
39991
|
const getDirectReactRefCall = (symbol, scopes) => {
|
|
39213
39992
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
@@ -39215,15 +39994,38 @@ const getDirectReactRefCall = (symbol, scopes) => {
|
|
|
39215
39994
|
const expression = stripParenExpression(initializer);
|
|
39216
39995
|
return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
|
|
39217
39996
|
};
|
|
39997
|
+
const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
|
|
39998
|
+
const expression = stripParenExpression(rawExpression);
|
|
39999
|
+
return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
|
|
40000
|
+
};
|
|
40001
|
+
const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
|
|
40002
|
+
if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
|
|
40003
|
+
const [initialValue] = refCall.arguments;
|
|
40004
|
+
return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
|
|
40005
|
+
};
|
|
40006
|
+
const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
|
|
40007
|
+
const assignment = currentExpression.parent;
|
|
40008
|
+
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
|
|
40009
|
+
const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
40010
|
+
return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
|
|
40011
|
+
};
|
|
39218
40012
|
const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
39219
|
-
const
|
|
39220
|
-
|
|
40013
|
+
const refCall = getDirectReactRefCall(symbol, scopes);
|
|
40014
|
+
const initialValue = refCall?.arguments[0];
|
|
40015
|
+
const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
|
|
40016
|
+
const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
|
|
40017
|
+
if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
|
|
39221
40018
|
let intrinsicValueWriteCount = 0;
|
|
40019
|
+
let lazyEmptyMapInitializationCount = 0;
|
|
39222
40020
|
for (const reference of symbol.references) {
|
|
39223
40021
|
const identifier = findTransparentExpressionRoot(reference.identifier);
|
|
39224
40022
|
const currentMember = identifier.parent;
|
|
39225
40023
|
if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
|
|
39226
40024
|
const currentExpression = findTransparentExpressionRoot(currentMember);
|
|
40025
|
+
if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
|
|
40026
|
+
lazyEmptyMapInitializationCount += 1;
|
|
40027
|
+
continue;
|
|
40028
|
+
}
|
|
39227
40029
|
const methodMember = currentExpression.parent;
|
|
39228
40030
|
if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
|
|
39229
40031
|
const methodName = getStaticPropertyName(methodMember);
|
|
@@ -39236,7 +40038,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
|
39236
40038
|
if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
|
|
39237
40039
|
intrinsicValueWriteCount += 1;
|
|
39238
40040
|
}
|
|
39239
|
-
return intrinsicValueWriteCount > 0;
|
|
40041
|
+
return intrinsicValueWriteCount > 0 && (hasDirectEmptyMapInitializer || lazyEmptyMapInitializationCount === 1);
|
|
39240
40042
|
};
|
|
39241
40043
|
const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39242
40044
|
const expression = stripParenExpression(rawExpression);
|
|
@@ -39250,7 +40052,11 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
|
|
|
39250
40052
|
}
|
|
39251
40053
|
if (isNodeOfType(expression, "MemberExpression")) {
|
|
39252
40054
|
if (getStaticPropertyName(expression) === "current") {
|
|
39253
|
-
const symbol =
|
|
40055
|
+
const symbol = resolveReactRefSymbol(expression, scopes, {
|
|
40056
|
+
allowUnboundBareCalls: true,
|
|
40057
|
+
includeCreateRef: true,
|
|
40058
|
+
resolveNamedAliases: true
|
|
40059
|
+
});
|
|
39254
40060
|
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
39255
40061
|
}
|
|
39256
40062
|
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
@@ -39268,19 +40074,80 @@ const isCommittedDomSyncNode = (node, scopes) => {
|
|
|
39268
40074
|
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
39269
40075
|
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
39270
40076
|
};
|
|
39271
|
-
const
|
|
39272
|
-
|
|
39273
|
-
if (isNodeOfType(
|
|
40077
|
+
const isProvenHttpClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
40078
|
+
const expression = stripParenExpression(rawExpression);
|
|
40079
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
40080
|
+
const receiverSymbol = scopes.symbolFor(expression);
|
|
40081
|
+
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
40082
|
+
}
|
|
40083
|
+
if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
|
|
40084
|
+
const moduleBinding = resolveExternalModuleBinding(expression, scopes);
|
|
40085
|
+
if (moduleBinding && EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES.has(moduleBinding.source) && (moduleBinding.isModuleObject || moduleBinding.exportedName === "default" && EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.has(moduleBinding.source) || isKnownExternalSyncDirectModuleBinding(moduleBinding))) return true;
|
|
40086
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
40087
|
+
const callee = stripParenExpression(expression.callee);
|
|
40088
|
+
return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
|
|
40089
|
+
}
|
|
40090
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
40091
|
+
const symbol = scopes.symbolFor(expression);
|
|
40092
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
40093
|
+
if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
|
|
40094
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
40095
|
+
return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
40096
|
+
};
|
|
40097
|
+
const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
40098
|
+
const expression = stripParenExpression(rawExpression);
|
|
40099
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
40100
|
+
const receiverSymbol = scopes.symbolFor(expression);
|
|
40101
|
+
if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
|
|
40102
|
+
}
|
|
40103
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
40104
|
+
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
40105
|
+
return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
|
|
40106
|
+
}
|
|
40107
|
+
if (isNodeOfType(expression, "NewExpression")) {
|
|
40108
|
+
const binding = resolveExternalModuleBinding(expression.callee, scopes);
|
|
40109
|
+
return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
|
|
40110
|
+
}
|
|
40111
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
40112
|
+
const symbol = scopes.symbolFor(expression);
|
|
40113
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
40114
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
40115
|
+
return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
40116
|
+
};
|
|
40117
|
+
const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
40118
|
+
const expression = stripParenExpression(rawExpression);
|
|
40119
|
+
if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
|
|
40120
|
+
if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
|
|
40121
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
40122
|
+
const symbol = scopes.symbolFor(expression);
|
|
40123
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
40124
|
+
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
40125
|
+
return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
|
|
40126
|
+
};
|
|
40127
|
+
const isProvenExternalSyncDirectCallee = (callee, scopes) => {
|
|
40128
|
+
if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
|
|
40129
|
+
const moduleBinding = resolveExternalModuleBinding(callee, scopes);
|
|
40130
|
+
return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
|
|
40131
|
+
};
|
|
40132
|
+
const isExternalSyncNode = (node, scopes) => {
|
|
40133
|
+
if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
|
|
40134
|
+
if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
|
|
40135
|
+
const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
|
|
40136
|
+
return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
|
|
40137
|
+
allowUnboundBareCalls: true,
|
|
40138
|
+
includeCreateRef: true,
|
|
40139
|
+
resolveNamedAliases: true
|
|
40140
|
+
}));
|
|
40141
|
+
}
|
|
39274
40142
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
39275
|
-
if (
|
|
40143
|
+
if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
|
|
39276
40144
|
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
39277
40145
|
const propertyName = getStaticPropertyName(node.callee);
|
|
39278
40146
|
if (propertyName === null) return false;
|
|
39279
|
-
if (
|
|
39280
|
-
if (
|
|
39281
|
-
if (
|
|
39282
|
-
|
|
39283
|
-
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
40147
|
+
if (isBrowserStorageReceiver(node.callee.object, scopes)) return true;
|
|
40148
|
+
if (TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES.has(propertyName)) return isProvenTanStackQueryClientReceiver(node.callee.object, scopes);
|
|
40149
|
+
if (EXTERNAL_SYNC_HTTP_METHOD_NAMES.has(propertyName)) return isProvenHttpClientReceiver(node.callee.object, scopes);
|
|
40150
|
+
return EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName) && isProvenExternalResourceReceiver(node.callee.object, scopes);
|
|
39284
40151
|
};
|
|
39285
40152
|
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
|
|
39286
40153
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
@@ -39289,7 +40156,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
|
|
|
39289
40156
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
|
|
39290
40157
|
let didFindExternalCall = false;
|
|
39291
40158
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39292
|
-
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
40159
|
+
if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
39293
40160
|
});
|
|
39294
40161
|
return didFindExternalCall;
|
|
39295
40162
|
};
|
|
@@ -39311,11 +40178,11 @@ const noEffectChain = defineRule({
|
|
|
39311
40178
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39312
40179
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
39313
40180
|
const stateIdentifier = binding.declarator.id.elements[0];
|
|
40181
|
+
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39314
40182
|
if (isNodeOfType(stateIdentifier, "Identifier")) {
|
|
39315
40183
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39316
40184
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39317
40185
|
}
|
|
39318
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39319
40186
|
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39320
40187
|
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39321
40188
|
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
@@ -39328,10 +40195,11 @@ const noEffectChain = defineRule({
|
|
|
39328
40195
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39329
40196
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39330
40197
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39331
|
-
const stateWrites = collectStateWritesInEffect(
|
|
40198
|
+
const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
|
|
39332
40199
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39333
40200
|
effectInfos.push({
|
|
39334
40201
|
node: effectCall,
|
|
40202
|
+
callback,
|
|
39335
40203
|
dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
|
|
39336
40204
|
stateWrites,
|
|
39337
40205
|
analysisFunctions,
|
|
@@ -39348,10 +40216,10 @@ const noEffectChain = defineRule({
|
|
|
39348
40216
|
if (readerEffect.isExternalSync) continue;
|
|
39349
40217
|
if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
|
|
39350
40218
|
let chainedStateName = null;
|
|
39351
|
-
for (const
|
|
40219
|
+
for (const writtenName of writerEffect.stateWrites.keys()) {
|
|
39352
40220
|
const writtenStateSymbolId = stateSymbolIds.get(writtenName);
|
|
39353
40221
|
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
39354
|
-
if (!canStateWriteReachReaderWork(
|
|
40222
|
+
if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
|
|
39355
40223
|
chainedStateName = writtenName;
|
|
39356
40224
|
break;
|
|
39357
40225
|
}
|
|
@@ -44136,16 +45004,6 @@ const noManyBooleanProps = defineRule({
|
|
|
44136
45004
|
}
|
|
44137
45005
|
});
|
|
44138
45006
|
//#endregion
|
|
44139
|
-
//#region src/plugin/utils/is-global-match-media-call.ts
|
|
44140
|
-
const isGlobalMatchMediaCall = (node, scopes) => {
|
|
44141
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
44142
|
-
const callee = stripParenExpression(node.callee);
|
|
44143
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
|
|
44144
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
|
|
44145
|
-
const receiver = stripParenExpression(callee.object);
|
|
44146
|
-
return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
|
|
44147
|
-
};
|
|
44148
|
-
//#endregion
|
|
44149
45007
|
//#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
|
|
44150
45008
|
const REACT_USE_STATE_OPTIONS = {
|
|
44151
45009
|
allowGlobalReactNamespace: true,
|
|
@@ -46535,6 +47393,103 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
|
46535
47393
|
"useVisibility",
|
|
46536
47394
|
"useWindowSize"
|
|
46537
47395
|
]);
|
|
47396
|
+
const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
|
|
47397
|
+
"useMatchMedia",
|
|
47398
|
+
"useMediaQuery",
|
|
47399
|
+
"useVisibility"
|
|
47400
|
+
]);
|
|
47401
|
+
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
47402
|
+
const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
|
|
47403
|
+
const callee = stripParenExpression(rawCallee);
|
|
47404
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
47405
|
+
const calleeRef = getRef(analysis, callee);
|
|
47406
|
+
if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
|
|
47407
|
+
const importBinding = getImportBindingForName(callee, callee.name);
|
|
47408
|
+
if (!importBinding || importBinding.isNamespace) return null;
|
|
47409
|
+
if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
|
|
47410
|
+
return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
|
|
47411
|
+
}
|
|
47412
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
47413
|
+
const hookName = getStaticMemberPropertyName(callee);
|
|
47414
|
+
const namespaceIdentifier = stripParenExpression(callee.object);
|
|
47415
|
+
if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
|
|
47416
|
+
const namespaceRef = getRef(analysis, namespaceIdentifier);
|
|
47417
|
+
if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
|
|
47418
|
+
return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
|
|
47419
|
+
};
|
|
47420
|
+
const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
|
|
47421
|
+
let current = bindingIdentifier;
|
|
47422
|
+
let didCrossDestructuringBoundary = false;
|
|
47423
|
+
while (current !== bindingPattern) {
|
|
47424
|
+
const parent = current.parent;
|
|
47425
|
+
if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
|
|
47426
|
+
if (isNodeOfType(parent, "Property")) {
|
|
47427
|
+
if (parent.value !== current) return false;
|
|
47428
|
+
didCrossDestructuringBoundary = true;
|
|
47429
|
+
}
|
|
47430
|
+
if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
|
|
47431
|
+
current = parent;
|
|
47432
|
+
}
|
|
47433
|
+
return didCrossDestructuringBoundary;
|
|
47434
|
+
};
|
|
47435
|
+
const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
|
|
47436
|
+
const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
47437
|
+
if (visitedVariables.has(variable)) return false;
|
|
47438
|
+
visitedVariables.add(variable);
|
|
47439
|
+
return variable.references.some((candidateReference) => {
|
|
47440
|
+
if (candidateReference.init) return false;
|
|
47441
|
+
if (candidateReference.isWrite()) return true;
|
|
47442
|
+
let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
|
|
47443
|
+
while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
|
|
47444
|
+
const usageParent = usageRoot.parent;
|
|
47445
|
+
if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
|
|
47446
|
+
let usageAncestor = usageRoot.parent;
|
|
47447
|
+
while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
|
|
47448
|
+
if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
|
|
47449
|
+
const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
|
|
47450
|
+
return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
|
|
47451
|
+
}
|
|
47452
|
+
return false;
|
|
47453
|
+
});
|
|
47454
|
+
};
|
|
47455
|
+
const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
|
|
47456
|
+
return variables.length > 0 && variables.every((variable) => {
|
|
47457
|
+
const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
|
|
47458
|
+
return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
|
|
47459
|
+
});
|
|
47460
|
+
};
|
|
47461
|
+
const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
|
|
47462
|
+
const callee = stripParenExpression(rawCallee);
|
|
47463
|
+
const callExpression = callee.parent;
|
|
47464
|
+
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
|
|
47465
|
+
const initializer = findTransparentExpressionRoot(callExpression);
|
|
47466
|
+
const declarator = initializer.parent;
|
|
47467
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47468
|
+
const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
|
|
47469
|
+
if (relatedVariables.length === 0) return false;
|
|
47470
|
+
return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
|
|
47471
|
+
};
|
|
47472
|
+
const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
|
|
47473
|
+
const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
|
|
47474
|
+
return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
|
|
47475
|
+
};
|
|
47476
|
+
const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
47477
|
+
const declarator = def.node;
|
|
47478
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
|
|
47479
|
+
const initializer = stripParenExpression(declarator.init);
|
|
47480
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
47481
|
+
const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
|
|
47482
|
+
if (!hookName) return false;
|
|
47483
|
+
if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47484
|
+
const bindingIdentifier = def.name;
|
|
47485
|
+
return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
|
|
47486
|
+
}));
|
|
47487
|
+
const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
|
|
47488
|
+
const unwrappedArgument = stripParenExpression(argument);
|
|
47489
|
+
if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
|
|
47490
|
+
const argumentRef = getRef(analysis, unwrappedArgument);
|
|
47491
|
+
return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
|
|
47492
|
+
};
|
|
46538
47493
|
const isCallbackPropReference = (analysis, ref) => {
|
|
46539
47494
|
if (!isProp(analysis, ref)) return false;
|
|
46540
47495
|
const identifier = ref.identifier;
|
|
@@ -46567,8 +47522,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46567
47522
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46568
47523
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
46569
47524
|
};
|
|
46570
|
-
const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
47525
|
+
const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
|
|
46571
47526
|
let hookFunction = resolveToFunction(ref);
|
|
47527
|
+
let didResolveThroughResultBinding = false;
|
|
46572
47528
|
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
46573
47529
|
const definitionNode = definition.node;
|
|
46574
47530
|
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
@@ -46579,29 +47535,27 @@ const getLocalHookExternalStateProof = (analysis, ref) => {
|
|
|
46579
47535
|
const calleeReference = getRef(analysis, callee);
|
|
46580
47536
|
if (!calleeReference) continue;
|
|
46581
47537
|
hookFunction = resolveToFunction(calleeReference);
|
|
46582
|
-
if (hookFunction)
|
|
47538
|
+
if (hookFunction) {
|
|
47539
|
+
didResolveThroughResultBinding = true;
|
|
47540
|
+
break;
|
|
47541
|
+
}
|
|
46583
47542
|
}
|
|
46584
47543
|
if (!hookFunction) return null;
|
|
46585
|
-
|
|
47544
|
+
if (didResolveThroughResultBinding && (!ref.resolved || hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved))) return false;
|
|
47545
|
+
if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
|
|
47546
|
+
const returnStatements = collectFunctionReturnStatements(hookFunction);
|
|
47547
|
+
if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
|
|
47548
|
+
const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46586
47549
|
if (returnedReferences.length === 0) return null;
|
|
46587
47550
|
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
46588
47551
|
};
|
|
46589
|
-
const
|
|
46590
|
-
const
|
|
46591
|
-
if (!isNodeOfType(
|
|
46592
|
-
const
|
|
46593
|
-
if (
|
|
46594
|
-
|
|
46595
|
-
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46596
|
-
const node = def.node;
|
|
46597
|
-
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46598
|
-
const initializer = stripParenExpression(node.init);
|
|
46599
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46600
|
-
const callee = stripParenExpression(initializer.callee);
|
|
46601
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
46602
|
-
}));
|
|
47552
|
+
const isImmutableLocalExternalStoreHookCallee = (analysis, rawCallee, relatedRefs, scopes) => {
|
|
47553
|
+
const callee = stripParenExpression(rawCallee);
|
|
47554
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
47555
|
+
const calleeReference = getRef(analysis, callee);
|
|
47556
|
+
if (!calleeReference || getLocalHookExternalStateProof(analysis, calleeReference, scopes) !== true) return false;
|
|
47557
|
+
return hasImmutableExternalSubscriptionCallResult(analysis, callee, relatedRefs, true);
|
|
46603
47558
|
};
|
|
46604
|
-
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
46605
47559
|
const isCalleePosition = (identifier) => {
|
|
46606
47560
|
const parent = identifier.parent;
|
|
46607
47561
|
return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
|
|
@@ -46662,25 +47616,35 @@ const noPassDataToParent = defineRule({
|
|
|
46662
47616
|
return getFunctionalUpdaterDataRefs(analysis, argument);
|
|
46663
47617
|
}
|
|
46664
47618
|
if (isHandlerBagArgument(analysis, argument)) return [];
|
|
47619
|
+
if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
|
|
46665
47620
|
if (isParentWiredHookResultArgument(analysis, argument)) return [];
|
|
46666
47621
|
if (isNodeOfType(argument, "Identifier")) {
|
|
46667
47622
|
const argumentRef = getRef(analysis, argument);
|
|
46668
47623
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46669
47624
|
}
|
|
46670
47625
|
return getDownstreamRefs(analysis, argument);
|
|
46671
|
-
}).flatMap((argumentRef) =>
|
|
46672
|
-
|
|
47626
|
+
}).flatMap((argumentRef) => {
|
|
47627
|
+
if (isExternallyDrivenState(analysis, argumentRef) || getLocalHookExternalStateProof(analysis, argumentRef, context.scopes) === true) return [];
|
|
47628
|
+
const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
|
|
47629
|
+
return upstreamRefs.filter((upstreamRef) => {
|
|
47630
|
+
if (!isLeafRef(upstreamRef)) return false;
|
|
47631
|
+
return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
|
|
47632
|
+
});
|
|
47633
|
+
});
|
|
47634
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
|
|
47635
|
+
const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
|
|
47636
|
+
argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
|
|
47637
|
+
}
|
|
46673
47638
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46674
|
-
|
|
46675
|
-
if (
|
|
47639
|
+
const argIdentifier = argRef.identifier;
|
|
47640
|
+
if (isUseStateIdentifier(argIdentifier)) return false;
|
|
46676
47641
|
if (isProp(analysis, argRef)) return false;
|
|
46677
|
-
if (isUseRefIdentifier(
|
|
47642
|
+
if (isUseRefIdentifier(argIdentifier)) return false;
|
|
46678
47643
|
if (isRefCurrent(argRef)) return false;
|
|
46679
47644
|
if (isConstant(argRef)) return false;
|
|
46680
47645
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
46681
47646
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
46682
47647
|
if (resolveToFunction(argRef)) return false;
|
|
46683
|
-
const argIdentifier = argRef.identifier;
|
|
46684
47648
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
46685
47649
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|
|
46686
47650
|
return true;
|
|
@@ -55671,17 +56635,6 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
55671
56635
|
}
|
|
55672
56636
|
});
|
|
55673
56637
|
//#endregion
|
|
55674
|
-
//#region src/plugin/utils/get-require-call-source.ts
|
|
55675
|
-
const getRequireCallSource = (expression) => {
|
|
55676
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
55677
|
-
if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
|
|
55678
|
-
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
|
|
55679
|
-
if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
|
|
55680
|
-
const [firstArgument] = unwrappedExpression.arguments ?? [];
|
|
55681
|
-
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55682
|
-
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55683
|
-
};
|
|
55684
|
-
//#endregion
|
|
55685
56638
|
//#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
|
|
55686
56639
|
const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
|
|
55687
56640
|
const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
|
|
@@ -60389,6 +61342,717 @@ const isSetterCalledDuringRender = (root, setterName) => {
|
|
|
60389
61342
|
return found;
|
|
60390
61343
|
};
|
|
60391
61344
|
//#endregion
|
|
61345
|
+
//#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
|
|
61346
|
+
const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
|
|
61347
|
+
const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
61348
|
+
const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61349
|
+
"assign",
|
|
61350
|
+
"defineProperties",
|
|
61351
|
+
"defineProperty",
|
|
61352
|
+
"setPrototypeOf"
|
|
61353
|
+
]);
|
|
61354
|
+
const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
|
|
61355
|
+
"defineProperty",
|
|
61356
|
+
"deleteProperty",
|
|
61357
|
+
"set",
|
|
61358
|
+
"setPrototypeOf"
|
|
61359
|
+
]);
|
|
61360
|
+
const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
|
|
61361
|
+
const containsGlobalLocationSnapshotRead = (node, scopes) => {
|
|
61362
|
+
let didFindLocationSnapshotRead = false;
|
|
61363
|
+
walkAst(node, (child) => {
|
|
61364
|
+
if (didFindLocationSnapshotRead) return false;
|
|
61365
|
+
if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61366
|
+
if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
|
|
61367
|
+
didFindLocationSnapshotRead = true;
|
|
61368
|
+
return false;
|
|
61369
|
+
});
|
|
61370
|
+
return didFindLocationSnapshotRead;
|
|
61371
|
+
};
|
|
61372
|
+
const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
|
|
61373
|
+
const localFunction = resolveExactLocalFunction(expression, scopes);
|
|
61374
|
+
if (isFunctionLike$1(localFunction)) return localFunction;
|
|
61375
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
61376
|
+
let callbackSource = unwrappedExpression;
|
|
61377
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
61378
|
+
const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
|
|
61379
|
+
if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
|
|
61380
|
+
callbackSource = callbackSymbol.initializer;
|
|
61381
|
+
}
|
|
61382
|
+
const unwrappedCallbackSource = stripParenExpression(callbackSource);
|
|
61383
|
+
if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
|
|
61384
|
+
const callback = unwrappedCallbackSource.arguments?.[0];
|
|
61385
|
+
if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
|
|
61386
|
+
return resolveExactLocalFunction(stripParenExpression(callback), scopes);
|
|
61387
|
+
};
|
|
61388
|
+
const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61389
|
+
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
61390
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
61391
|
+
if ((symbol.kind === "function" ? scopes.scopeFor(symbol.declarationNode) : symbol.scope).symbols.some((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.references.some((reference) => reference.flag !== "read"))) return false;
|
|
61392
|
+
const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
|
|
61393
|
+
const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
|
|
61394
|
+
if (!initializer) return false;
|
|
61395
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61396
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
61397
|
+
if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
|
|
61398
|
+
let didFindAliasedLocationSnapshotRead = false;
|
|
61399
|
+
walkAst(initializer, (child) => {
|
|
61400
|
+
if (didFindAliasedLocationSnapshotRead) return false;
|
|
61401
|
+
if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
|
|
61402
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
61403
|
+
if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
|
|
61404
|
+
didFindAliasedLocationSnapshotRead = true;
|
|
61405
|
+
return false;
|
|
61406
|
+
}
|
|
61407
|
+
});
|
|
61408
|
+
return didFindAliasedLocationSnapshotRead;
|
|
61409
|
+
};
|
|
61410
|
+
const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
|
|
61411
|
+
if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
|
|
61412
|
+
for (const statement of componentBody.body ?? []) {
|
|
61413
|
+
if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
|
|
61414
|
+
if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
|
|
61415
|
+
continue;
|
|
61416
|
+
}
|
|
61417
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
61418
|
+
for (const declarator of statement.declarations ?? []) {
|
|
61419
|
+
if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
61420
|
+
if (!directRenderNames.has(declarator.id.name)) continue;
|
|
61421
|
+
if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
|
|
61422
|
+
}
|
|
61423
|
+
}
|
|
61424
|
+
return false;
|
|
61425
|
+
};
|
|
61426
|
+
const isGlobalHistoryLocationMutation = (node, scopes) => {
|
|
61427
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
61428
|
+
const callee = stripParenExpression(node.callee);
|
|
61429
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61430
|
+
const methodName = getStaticPropertyName(callee);
|
|
61431
|
+
return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
|
|
61432
|
+
};
|
|
61433
|
+
const getStaticLocationChangeEvent = (node) => {
|
|
61434
|
+
if (!node) return null;
|
|
61435
|
+
const eventNameNode = stripParenExpression(node);
|
|
61436
|
+
if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
|
|
61437
|
+
return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
|
|
61438
|
+
};
|
|
61439
|
+
const addToSetIndex = (index, key, value) => {
|
|
61440
|
+
const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
|
|
61441
|
+
indexedValues.add(value);
|
|
61442
|
+
index.set(key, indexedValues);
|
|
61443
|
+
};
|
|
61444
|
+
const getSynchronousInvocationExpression = (functionNode, scopes) => {
|
|
61445
|
+
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61446
|
+
if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
|
|
61447
|
+
const parent = functionExpressionRoot.parent;
|
|
61448
|
+
return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
|
|
61449
|
+
};
|
|
61450
|
+
const getLocationListenerOperation = (callExpression, scopes) => {
|
|
61451
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61452
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
61453
|
+
const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
61454
|
+
if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
|
|
61455
|
+
if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
|
|
61456
|
+
const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
|
|
61457
|
+
if (!eventName) return null;
|
|
61458
|
+
const listenerExpression = callExpression.arguments?.[1];
|
|
61459
|
+
if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
|
|
61460
|
+
const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
|
|
61461
|
+
if (!isFunctionLike$1(listenerFunction)) return null;
|
|
61462
|
+
const captureArgument = callExpression.arguments?.[2];
|
|
61463
|
+
const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
|
|
61464
|
+
allowComputedString: true,
|
|
61465
|
+
allowIndeterminateEntries: true
|
|
61466
|
+
});
|
|
61467
|
+
return {
|
|
61468
|
+
operation: methodName === "addEventListener" ? "add" : "remove",
|
|
61469
|
+
registration: {
|
|
61470
|
+
callExpression,
|
|
61471
|
+
listenerFunction,
|
|
61472
|
+
capture,
|
|
61473
|
+
eventName
|
|
61474
|
+
}
|
|
61475
|
+
};
|
|
61476
|
+
};
|
|
61477
|
+
const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
|
|
61478
|
+
const index = {
|
|
61479
|
+
componentFunction,
|
|
61480
|
+
context,
|
|
61481
|
+
effectCallbacks: /* @__PURE__ */ new Set(),
|
|
61482
|
+
expressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61483
|
+
historyMutationsByOwner: /* @__PURE__ */ new Map(),
|
|
61484
|
+
awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
|
|
61485
|
+
callSitesByFunction: /* @__PURE__ */ new Map(),
|
|
61486
|
+
calledFunctionByExpression: /* @__PURE__ */ new Map(),
|
|
61487
|
+
synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
|
|
61488
|
+
synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
|
|
61489
|
+
callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
|
|
61490
|
+
identifierCalls: /* @__PURE__ */ new Set(),
|
|
61491
|
+
listenerRegistrations: [],
|
|
61492
|
+
listenerRemovals: [],
|
|
61493
|
+
mountedListenerFunctions: /* @__PURE__ */ new Set(),
|
|
61494
|
+
mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
|
|
61495
|
+
synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
|
|
61496
|
+
};
|
|
61497
|
+
walkAst(componentBody, (child) => {
|
|
61498
|
+
if (isFunctionLike$1(child)) {
|
|
61499
|
+
const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
|
|
61500
|
+
if (invocationExpression) {
|
|
61501
|
+
addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
|
|
61502
|
+
addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
|
|
61503
|
+
}
|
|
61504
|
+
return;
|
|
61505
|
+
}
|
|
61506
|
+
const owner = context.cfg.enclosingFunction(child);
|
|
61507
|
+
if (!owner) return;
|
|
61508
|
+
if (isNodeOfType(child, "AwaitExpression")) {
|
|
61509
|
+
addToSetIndex(index.awaitExpressionsByOwner, owner, child);
|
|
61510
|
+
return;
|
|
61511
|
+
}
|
|
61512
|
+
if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
|
|
61513
|
+
addToSetIndex(index.expressionsByOwner, owner, child);
|
|
61514
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
61515
|
+
if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
|
|
61516
|
+
if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
|
|
61517
|
+
const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
|
|
61518
|
+
if (isFunctionLike$1(calledFunction)) {
|
|
61519
|
+
addToSetIndex(index.callSitesByFunction, calledFunction, child);
|
|
61520
|
+
index.calledFunctionByExpression.set(child, calledFunction);
|
|
61521
|
+
}
|
|
61522
|
+
if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
61523
|
+
allowGlobalReactNamespace: true,
|
|
61524
|
+
allowUnboundBareCalls: true,
|
|
61525
|
+
resolveNamedAliases: true
|
|
61526
|
+
})) {
|
|
61527
|
+
const effectCallback = getEffectCallback(child, context.scopes);
|
|
61528
|
+
if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
|
|
61529
|
+
}
|
|
61530
|
+
const listenerOperation = getLocationListenerOperation(child, context.scopes);
|
|
61531
|
+
if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
|
|
61532
|
+
else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
|
|
61533
|
+
}
|
|
61534
|
+
});
|
|
61535
|
+
return index;
|
|
61536
|
+
};
|
|
61537
|
+
const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
|
|
61538
|
+
let current = descendant;
|
|
61539
|
+
while (current && current !== ancestor) {
|
|
61540
|
+
if (current !== descendant && isFunctionLike$1(current)) return false;
|
|
61541
|
+
current = current.parent;
|
|
61542
|
+
}
|
|
61543
|
+
return current === ancestor;
|
|
61544
|
+
};
|
|
61545
|
+
const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
|
|
61546
|
+
const firstBranches = /* @__PURE__ */ new Map();
|
|
61547
|
+
let current = firstNode;
|
|
61548
|
+
while (current?.parent) {
|
|
61549
|
+
const parent = current.parent;
|
|
61550
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
|
|
61551
|
+
if (current !== firstNode && isFunctionLike$1(current)) break;
|
|
61552
|
+
current = parent;
|
|
61553
|
+
}
|
|
61554
|
+
current = secondNode;
|
|
61555
|
+
while (current?.parent) {
|
|
61556
|
+
const parent = current.parent;
|
|
61557
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
|
|
61558
|
+
const firstBranch = firstBranches.get(parent);
|
|
61559
|
+
if (firstBranch && firstBranch !== current) return true;
|
|
61560
|
+
}
|
|
61561
|
+
if (current !== secondNode && isFunctionLike$1(current)) break;
|
|
61562
|
+
current = parent;
|
|
61563
|
+
}
|
|
61564
|
+
return false;
|
|
61565
|
+
};
|
|
61566
|
+
const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
|
|
61567
|
+
const constraintsBySymbolId = /* @__PURE__ */ new Map();
|
|
61568
|
+
const recordConstraint = (test, requiredTestValue) => {
|
|
61569
|
+
let expression = stripParenExpression(test);
|
|
61570
|
+
let requiredValue = requiredTestValue;
|
|
61571
|
+
while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
61572
|
+
requiredValue = !requiredValue;
|
|
61573
|
+
expression = stripParenExpression(expression.argument);
|
|
61574
|
+
}
|
|
61575
|
+
if (!isNodeOfType(expression, "Identifier")) return;
|
|
61576
|
+
const symbol = scopes.symbolFor(expression);
|
|
61577
|
+
if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
61578
|
+
const existingConstraint = constraintsBySymbolId.get(symbol.id);
|
|
61579
|
+
constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
|
|
61580
|
+
};
|
|
61581
|
+
let current = node;
|
|
61582
|
+
while (current?.parent && current !== functionBoundary) {
|
|
61583
|
+
const parent = current.parent;
|
|
61584
|
+
if (isNodeOfType(parent, "IfStatement")) {
|
|
61585
|
+
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61586
|
+
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61587
|
+
} else if (isNodeOfType(parent, "ConditionalExpression")) {
|
|
61588
|
+
if (parent.consequent === current) recordConstraint(parent.test, true);
|
|
61589
|
+
if (parent.alternate === current) recordConstraint(parent.test, false);
|
|
61590
|
+
} else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
|
|
61591
|
+
if (parent.operator === "&&") recordConstraint(parent.left, true);
|
|
61592
|
+
if (parent.operator === "||") recordConstraint(parent.left, false);
|
|
61593
|
+
}
|
|
61594
|
+
if (current !== node && isFunctionLike$1(current)) break;
|
|
61595
|
+
current = parent;
|
|
61596
|
+
}
|
|
61597
|
+
return constraintsBySymbolId;
|
|
61598
|
+
};
|
|
61599
|
+
const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
|
|
61600
|
+
const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
|
|
61601
|
+
const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
|
|
61602
|
+
for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
|
|
61603
|
+
for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
|
|
61604
|
+
for (const [symbolId, firstRequiredValue] of firstConstraints) {
|
|
61605
|
+
const secondRequiredValue = secondConstraints.get(symbolId);
|
|
61606
|
+
if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
|
|
61607
|
+
}
|
|
61608
|
+
return false;
|
|
61609
|
+
};
|
|
61610
|
+
const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
|
|
61611
|
+
let current = node;
|
|
61612
|
+
while (current && current !== functionBoundary) {
|
|
61613
|
+
if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
|
|
61614
|
+
current = current.parent;
|
|
61615
|
+
}
|
|
61616
|
+
return false;
|
|
61617
|
+
};
|
|
61618
|
+
const getTransparentExpressionBindingIdentifier = (expression) => {
|
|
61619
|
+
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
61620
|
+
const parent = expressionRoot.parent;
|
|
61621
|
+
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
61622
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
|
|
61623
|
+
return null;
|
|
61624
|
+
};
|
|
61625
|
+
const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
|
|
61626
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
61627
|
+
const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
|
|
61628
|
+
const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
|
|
61629
|
+
if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
|
|
61630
|
+
const callbackCall = functionExpressionRoot.parent;
|
|
61631
|
+
if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61632
|
+
return getTransparentExpressionBindingIdentifier(callbackCall);
|
|
61633
|
+
};
|
|
61634
|
+
const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
|
|
61635
|
+
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61636
|
+
if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
|
|
61637
|
+
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61638
|
+
return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
|
|
61639
|
+
};
|
|
61640
|
+
const canNodeReachNode = (sourceNode, targetNode, index) => {
|
|
61641
|
+
const { context } = index;
|
|
61642
|
+
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
61643
|
+
if (!isNodeReachableWithinFunction(targetNode, context)) return false;
|
|
61644
|
+
const sourceOwner = context.cfg.enclosingFunction(sourceNode);
|
|
61645
|
+
const targetOwner = context.cfg.enclosingFunction(targetNode);
|
|
61646
|
+
if (!sourceOwner || sourceOwner !== targetOwner) return false;
|
|
61647
|
+
if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
|
|
61648
|
+
if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
|
|
61649
|
+
if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
|
|
61650
|
+
if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
|
|
61651
|
+
const functionCfg = context.cfg.cfgFor(sourceOwner);
|
|
61652
|
+
const sourceBlock = functionCfg?.blockOf(sourceNode);
|
|
61653
|
+
const targetBlock = functionCfg?.blockOf(targetNode);
|
|
61654
|
+
if (!functionCfg || !sourceBlock || !targetBlock) return false;
|
|
61655
|
+
if (sourceBlock === targetBlock) {
|
|
61656
|
+
const sourceStart = getRangeStart(sourceNode);
|
|
61657
|
+
const targetStart = getRangeStart(targetNode);
|
|
61658
|
+
return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
|
|
61659
|
+
}
|
|
61660
|
+
const visitedBlocks = new Set([sourceBlock]);
|
|
61661
|
+
const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
|
|
61662
|
+
while (pendingBlocks.length > 0) {
|
|
61663
|
+
const block = pendingBlocks.pop();
|
|
61664
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61665
|
+
if (block === targetBlock) return true;
|
|
61666
|
+
visitedBlocks.add(block);
|
|
61667
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61668
|
+
}
|
|
61669
|
+
return false;
|
|
61670
|
+
};
|
|
61671
|
+
const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
|
|
61672
|
+
let currentNode = functionNode;
|
|
61673
|
+
while (currentNode) {
|
|
61674
|
+
if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
|
|
61675
|
+
const openingElement = currentNode.parent;
|
|
61676
|
+
return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
|
|
61677
|
+
}
|
|
61678
|
+
if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
|
|
61679
|
+
currentNode = currentNode.parent;
|
|
61680
|
+
}
|
|
61681
|
+
return null;
|
|
61682
|
+
};
|
|
61683
|
+
const isProvenNonEscapingVoidRead = (identifier) => {
|
|
61684
|
+
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
61685
|
+
return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
|
|
61686
|
+
};
|
|
61687
|
+
const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
|
|
61688
|
+
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61689
|
+
const parent = referenceRoot.parent;
|
|
61690
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
|
|
61691
|
+
const aliasSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61692
|
+
const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
|
|
61693
|
+
const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
|
|
61694
|
+
return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
|
|
61695
|
+
}
|
|
61696
|
+
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
|
|
61697
|
+
const callbackResultRoot = findTransparentExpressionRoot(parent);
|
|
61698
|
+
const resultParent = callbackResultRoot.parent;
|
|
61699
|
+
if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
|
|
61700
|
+
const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
|
|
61701
|
+
return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
|
|
61702
|
+
};
|
|
61703
|
+
const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
|
|
61704
|
+
const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
|
|
61705
|
+
const parent = referenceRoot.parent;
|
|
61706
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
|
|
61707
|
+
const memberRoot = findTransparentExpressionRoot(parent);
|
|
61708
|
+
const callExpression = memberRoot.parent;
|
|
61709
|
+
const methodName = getStaticPropertyName(parent);
|
|
61710
|
+
if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
|
|
61711
|
+
}
|
|
61712
|
+
if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
|
|
61713
|
+
const callee = stripParenExpression(parent.callee);
|
|
61714
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
61715
|
+
const methodName = getStaticPropertyName(callee);
|
|
61716
|
+
if (methodName === null) return false;
|
|
61717
|
+
return OBJECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Object", index.context.scopes) || REFLECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Reflect", index.context.scopes);
|
|
61718
|
+
};
|
|
61719
|
+
const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
61720
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61721
|
+
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
|
|
61722
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
61723
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
61724
|
+
const escapeAnchors = [];
|
|
61725
|
+
for (const reference of symbol.references) {
|
|
61726
|
+
if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
|
|
61727
|
+
const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
|
|
61728
|
+
if (!aliasSymbol) {
|
|
61729
|
+
escapeAnchors.push(reference.identifier);
|
|
61730
|
+
continue;
|
|
61731
|
+
}
|
|
61732
|
+
const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
|
|
61733
|
+
if (!aliasEscapeAnchors) return null;
|
|
61734
|
+
escapeAnchors.push(...aliasEscapeAnchors);
|
|
61735
|
+
}
|
|
61736
|
+
return escapeAnchors.length > 0 ? escapeAnchors : null;
|
|
61737
|
+
};
|
|
61738
|
+
const findReadonlyJsxAggregateRoot = (jsxElement) => {
|
|
61739
|
+
let aggregateRoot = findTransparentExpressionRoot(jsxElement);
|
|
61740
|
+
while (aggregateRoot.parent) {
|
|
61741
|
+
const parent = aggregateRoot.parent;
|
|
61742
|
+
if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
|
|
61743
|
+
aggregateRoot = parent;
|
|
61744
|
+
continue;
|
|
61745
|
+
}
|
|
61746
|
+
if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
|
|
61747
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61748
|
+
continue;
|
|
61749
|
+
}
|
|
61750
|
+
if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
|
|
61751
|
+
aggregateRoot = findTransparentExpressionRoot(parent.parent);
|
|
61752
|
+
continue;
|
|
61753
|
+
}
|
|
61754
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
|
|
61755
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61756
|
+
continue;
|
|
61757
|
+
}
|
|
61758
|
+
if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
|
|
61759
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61760
|
+
continue;
|
|
61761
|
+
}
|
|
61762
|
+
if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
|
|
61763
|
+
aggregateRoot = findTransparentExpressionRoot(parent);
|
|
61764
|
+
continue;
|
|
61765
|
+
}
|
|
61766
|
+
break;
|
|
61767
|
+
}
|
|
61768
|
+
return aggregateRoot;
|
|
61769
|
+
};
|
|
61770
|
+
const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
|
|
61771
|
+
const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
|
|
61772
|
+
const parent = jsxValueRoot.parent;
|
|
61773
|
+
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
|
|
61774
|
+
if (!isNodeOfType(parent.id, "Identifier")) return null;
|
|
61775
|
+
const bindingSymbol = index.context.scopes.symbolFor(parent.id);
|
|
61776
|
+
if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
|
|
61777
|
+
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
|
|
61778
|
+
};
|
|
61779
|
+
const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
|
|
61780
|
+
if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
|
|
61781
|
+
const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
|
|
61782
|
+
if (!bindingIdentifier) {
|
|
61783
|
+
const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
|
|
61784
|
+
return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
|
|
61785
|
+
}
|
|
61786
|
+
return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
|
|
61787
|
+
};
|
|
61788
|
+
const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
|
|
61789
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
61790
|
+
if (!bindingIdentifier) return null;
|
|
61791
|
+
const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
|
|
61792
|
+
if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
|
|
61793
|
+
return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
|
|
61794
|
+
};
|
|
61795
|
+
const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
61796
|
+
if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
|
|
61797
|
+
const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
|
|
61798
|
+
const executionOwner = index.context.cfg.enclosingFunction(executionNode);
|
|
61799
|
+
if (!aliasOwner || !executionOwner) return false;
|
|
61800
|
+
if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
|
|
61801
|
+
if (visitedFunctionNodes.has(executionOwner)) return false;
|
|
61802
|
+
const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
|
|
61803
|
+
nextVisitedFunctionNodes.add(executionOwner);
|
|
61804
|
+
if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
|
|
61805
|
+
const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
|
|
61806
|
+
if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
|
|
61807
|
+
const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
|
|
61808
|
+
if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
|
|
61809
|
+
const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
|
|
61810
|
+
return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
|
|
61811
|
+
};
|
|
61812
|
+
const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
|
|
61813
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
61814
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
61815
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
61816
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
61817
|
+
let symbol = index.context.scopes.symbolFor(callee);
|
|
61818
|
+
while (symbol) {
|
|
61819
|
+
if (visitedSymbolIds.has(symbol.id)) return null;
|
|
61820
|
+
visitedSymbolIds.add(symbol.id);
|
|
61821
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
61822
|
+
if (!initializer) return symbol.id;
|
|
61823
|
+
if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
|
|
61824
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
61825
|
+
if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
|
|
61826
|
+
const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
|
|
61827
|
+
if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
|
|
61828
|
+
symbol = initializerSymbol;
|
|
61829
|
+
}
|
|
61830
|
+
return null;
|
|
61831
|
+
};
|
|
61832
|
+
const collectExactReadonlyCalleeCalls = (index) => {
|
|
61833
|
+
for (const callExpression of index.identifierCalls) {
|
|
61834
|
+
const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
|
|
61835
|
+
if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
|
|
61836
|
+
}
|
|
61837
|
+
};
|
|
61838
|
+
const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
|
|
61839
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61840
|
+
const sourceBlock = functionCfg?.blockOf(node);
|
|
61841
|
+
if (!functionCfg || !sourceBlock) return false;
|
|
61842
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61843
|
+
const pendingBlocks = [sourceBlock];
|
|
61844
|
+
while (pendingBlocks.length > 0) {
|
|
61845
|
+
const block = pendingBlocks.pop();
|
|
61846
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61847
|
+
visitedBlocks.add(block);
|
|
61848
|
+
for (const edge of block.successors) {
|
|
61849
|
+
if (edge.kind === "throw") continue;
|
|
61850
|
+
if (edge.to === functionCfg.exit) return true;
|
|
61851
|
+
pendingBlocks.push(edge.to);
|
|
61852
|
+
}
|
|
61853
|
+
}
|
|
61854
|
+
return false;
|
|
61855
|
+
};
|
|
61856
|
+
const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
|
|
61857
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
|
|
61858
|
+
const functionCfg = index.context.cfg.cfgFor(functionNode);
|
|
61859
|
+
const targetBlock = functionCfg?.blockOf(node);
|
|
61860
|
+
if (!functionCfg || !targetBlock) return false;
|
|
61861
|
+
const awaitsByBlock = /* @__PURE__ */ new Map();
|
|
61862
|
+
for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
|
|
61863
|
+
const awaitBlock = functionCfg.blockOf(awaitExpression);
|
|
61864
|
+
if (!awaitBlock) continue;
|
|
61865
|
+
const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
|
|
61866
|
+
blockAwaits.push(awaitExpression);
|
|
61867
|
+
awaitsByBlock.set(awaitBlock, blockAwaits);
|
|
61868
|
+
}
|
|
61869
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61870
|
+
const pendingBlocks = [functionCfg.entry];
|
|
61871
|
+
const targetStart = getRangeStart(node);
|
|
61872
|
+
while (pendingBlocks.length > 0) {
|
|
61873
|
+
const block = pendingBlocks.pop();
|
|
61874
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61875
|
+
visitedBlocks.add(block);
|
|
61876
|
+
const blockAwaits = awaitsByBlock.get(block) ?? [];
|
|
61877
|
+
if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
|
|
61878
|
+
if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
|
|
61879
|
+
if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
|
|
61880
|
+
const awaitStart = getRangeStart(awaitExpression);
|
|
61881
|
+
return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
|
|
61882
|
+
});
|
|
61883
|
+
if (blockAwaits.length > 0) continue;
|
|
61884
|
+
for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
|
|
61885
|
+
}
|
|
61886
|
+
return false;
|
|
61887
|
+
};
|
|
61888
|
+
const canReactBatchMutationAfterExecution = (executionNode, mutationNode, owner, index) => (isExclusiveIntrinsicReactEventHandler(owner, index) || index.effectCallbacks.has(owner)) && canNodeReachNode(executionNode, mutationNode, index) && canExecuteBeforeAsyncSuspension(mutationNode, owner, index) && canNodeReachNormalFunctionExit(mutationNode, owner, index);
|
|
61889
|
+
const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
|
|
61890
|
+
const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
|
|
61891
|
+
if (cachedResult !== void 0) return cachedResult;
|
|
61892
|
+
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61893
|
+
if (visitingFunctions.has(functionNode)) {
|
|
61894
|
+
let didReachCycleEntry = false;
|
|
61895
|
+
for (const visitingFunction of visitingFunctions) {
|
|
61896
|
+
if (visitingFunction === functionNode) didReachCycleEntry = true;
|
|
61897
|
+
if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
|
|
61898
|
+
}
|
|
61899
|
+
return false;
|
|
61900
|
+
}
|
|
61901
|
+
visitingFunctions.add(functionNode);
|
|
61902
|
+
const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
|
|
61903
|
+
visitingFunctions.delete(functionNode);
|
|
61904
|
+
if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
|
|
61905
|
+
return doesMutateSynchronously;
|
|
61906
|
+
};
|
|
61907
|
+
const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
|
|
61908
|
+
const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
|
|
61909
|
+
if (cachedExecutions) return cachedExecutions;
|
|
61910
|
+
const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
|
|
61911
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61912
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61913
|
+
if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61914
|
+
for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
|
|
61915
|
+
}
|
|
61916
|
+
if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
|
|
61917
|
+
return mutationExecutions;
|
|
61918
|
+
};
|
|
61919
|
+
const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
|
|
61920
|
+
const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
|
|
61921
|
+
if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
|
|
61922
|
+
if (visitingFunctions.has(functionNode)) return false;
|
|
61923
|
+
const nextVisitingFunctions = new Set(visitingFunctions);
|
|
61924
|
+
nextVisitingFunctions.add(functionNode);
|
|
61925
|
+
const removalExecutions = [];
|
|
61926
|
+
for (const removal of index.listenerRemovals) {
|
|
61927
|
+
if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
|
|
61928
|
+
if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
|
|
61929
|
+
if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
|
|
61930
|
+
}
|
|
61931
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61932
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61933
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
|
|
61934
|
+
}
|
|
61935
|
+
return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
|
|
61936
|
+
};
|
|
61937
|
+
const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
|
|
61938
|
+
const removalExecutions = /* @__PURE__ */ new Set();
|
|
61939
|
+
for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.add(removal.callExpression);
|
|
61940
|
+
for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
|
|
61941
|
+
const calledFunction = index.calledFunctionByExpression.get(expression);
|
|
61942
|
+
if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
|
|
61943
|
+
}
|
|
61944
|
+
return removalExecutions;
|
|
61945
|
+
};
|
|
61946
|
+
const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
|
|
61947
|
+
const expressionBoundaries = [];
|
|
61948
|
+
let currentChild = node;
|
|
61949
|
+
let currentParent = currentChild.parent ?? null;
|
|
61950
|
+
while (currentParent && currentParent !== owner) {
|
|
61951
|
+
if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
|
|
61952
|
+
const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
|
|
61953
|
+
if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
|
|
61954
|
+
}
|
|
61955
|
+
if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
|
|
61956
|
+
const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
|
|
61957
|
+
if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
|
|
61958
|
+
}
|
|
61959
|
+
if (isNodeOfType(currentParent, "ConditionalExpression") && (currentParent.consequent === currentChild || currentParent.alternate === currentChild) || isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild || isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) expressionBoundaries.push(currentChild);
|
|
61960
|
+
currentChild = currentParent;
|
|
61961
|
+
currentParent = currentChild.parent ?? null;
|
|
61962
|
+
}
|
|
61963
|
+
return expressionBoundaries;
|
|
61964
|
+
};
|
|
61965
|
+
const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
|
|
61966
|
+
if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
|
|
61967
|
+
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
61968
|
+
if (!owner) return false;
|
|
61969
|
+
const functionCfg = index.context.cfg.cfgFor(owner);
|
|
61970
|
+
const sourceBlock = functionCfg?.blockOf(executionNode);
|
|
61971
|
+
if (!functionCfg || !sourceBlock) return false;
|
|
61972
|
+
const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
|
|
61973
|
+
const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
|
|
61974
|
+
for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
|
|
61975
|
+
const removalBlock = functionCfg.blockOf(removalExecution);
|
|
61976
|
+
if (!removalBlock) continue;
|
|
61977
|
+
const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
|
|
61978
|
+
blockRemovals.push(removalExecution);
|
|
61979
|
+
matchingRemovalsByBlock.set(removalBlock, blockRemovals);
|
|
61980
|
+
}
|
|
61981
|
+
const sourceStart = getRangeStart(executionNode);
|
|
61982
|
+
const hasRemovalAfterExecution = (block) => {
|
|
61983
|
+
const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
|
|
61984
|
+
if (block !== sourceBlock) return true;
|
|
61985
|
+
const removalStart = getRangeStart(removal);
|
|
61986
|
+
return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
|
|
61987
|
+
});
|
|
61988
|
+
if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
|
|
61989
|
+
if (block !== sourceBlock) return false;
|
|
61990
|
+
return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
|
|
61991
|
+
};
|
|
61992
|
+
const visitedBlocks = /* @__PURE__ */ new Set();
|
|
61993
|
+
const pendingBlocks = [sourceBlock];
|
|
61994
|
+
while (pendingBlocks.length > 0) {
|
|
61995
|
+
const block = pendingBlocks.pop();
|
|
61996
|
+
if (!block || visitedBlocks.has(block)) continue;
|
|
61997
|
+
visitedBlocks.add(block);
|
|
61998
|
+
if (hasRemovalAfterExecution(block)) continue;
|
|
61999
|
+
for (const edge of block.successors) {
|
|
62000
|
+
if (edge.kind === "throw") continue;
|
|
62001
|
+
if (edge.to === functionCfg.exit) return true;
|
|
62002
|
+
pendingBlocks.push(edge.to);
|
|
62003
|
+
}
|
|
62004
|
+
}
|
|
62005
|
+
return false;
|
|
62006
|
+
};
|
|
62007
|
+
const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
62008
|
+
if (visitedExecutions.has(executionNode)) return false;
|
|
62009
|
+
if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
|
|
62010
|
+
const owner = index.context.cfg.enclosingFunction(executionNode);
|
|
62011
|
+
if (!owner) return false;
|
|
62012
|
+
if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
|
|
62013
|
+
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
62014
|
+
nextVisitedExecutions.add(executionNode);
|
|
62015
|
+
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
|
|
62016
|
+
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
|
|
62017
|
+
return false;
|
|
62018
|
+
};
|
|
62019
|
+
const collectMountedListenerFunctions = (index) => {
|
|
62020
|
+
for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
|
|
62021
|
+
};
|
|
62022
|
+
const setterArgumentMutatesLocation = (setterCall, index) => {
|
|
62023
|
+
if (!isNodeOfType(setterCall, "CallExpression")) return false;
|
|
62024
|
+
return (setterCall.arguments ?? []).some((argument) => {
|
|
62025
|
+
if (isNodeOfType(argument, "SpreadElement")) return false;
|
|
62026
|
+
const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
|
|
62027
|
+
return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
|
|
62028
|
+
});
|
|
62029
|
+
};
|
|
62030
|
+
const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
|
|
62031
|
+
if (visitedExecutions.has(executionAnchor)) return false;
|
|
62032
|
+
if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
|
|
62033
|
+
const owner = index.context.cfg.enclosingFunction(executionAnchor);
|
|
62034
|
+
if (!owner) return false;
|
|
62035
|
+
if (index.mountedListenerFunctions.has(owner)) return true;
|
|
62036
|
+
if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
|
|
62037
|
+
const nextVisitedExecutions = new Set(visitedExecutions);
|
|
62038
|
+
nextVisitedExecutions.add(executionAnchor);
|
|
62039
|
+
for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
|
|
62040
|
+
for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
|
|
62041
|
+
return false;
|
|
62042
|
+
};
|
|
62043
|
+
const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
|
|
62044
|
+
const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
|
|
62045
|
+
if (!setterSymbol) return false;
|
|
62046
|
+
return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
|
|
62047
|
+
};
|
|
62048
|
+
const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
|
|
62049
|
+
if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
|
|
62050
|
+
const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
|
|
62051
|
+
collectMountedListenerFunctions(locationInvalidationIndex);
|
|
62052
|
+
collectExactReadonlyCalleeCalls(locationInvalidationIndex);
|
|
62053
|
+
return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
|
|
62054
|
+
};
|
|
62055
|
+
//#endregion
|
|
60392
62056
|
//#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
|
|
60393
62057
|
const isInsideConditionTest = (identifier, stopAt) => {
|
|
60394
62058
|
let current = identifier;
|
|
@@ -60510,7 +62174,8 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60510
62174
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
60511
62175
|
const bindings = collectUseStateBindings(componentBody, context.scopes);
|
|
60512
62176
|
if (bindings.length === 0) return;
|
|
60513
|
-
|
|
62177
|
+
const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
|
|
62178
|
+
if (renderReachableExpressions.length === 0) return;
|
|
60514
62179
|
const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
|
|
60515
62180
|
const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
|
|
60516
62181
|
const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
|
|
@@ -60524,12 +62189,22 @@ const rerenderStateOnlyInHandlers = defineRule({
|
|
|
60524
62189
|
for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
|
|
60525
62190
|
for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
|
|
60526
62191
|
for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
|
|
62192
|
+
const componentFunction = context.cfg.enclosingFunction(componentBody);
|
|
62193
|
+
const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
|
|
62194
|
+
componentBody,
|
|
62195
|
+
componentFunction,
|
|
62196
|
+
context,
|
|
62197
|
+
directRenderNames,
|
|
62198
|
+
renderReachableExpressions
|
|
62199
|
+
}) : () => false;
|
|
60527
62200
|
const calledSetterNames = /* @__PURE__ */ new Set();
|
|
60528
62201
|
walkAst(componentBody, (child) => {
|
|
60529
62202
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
|
|
60530
62203
|
});
|
|
60531
62204
|
for (const binding of bindings) {
|
|
60532
62205
|
if (renderReachableNames.has(binding.valueName)) continue;
|
|
62206
|
+
const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
|
|
62207
|
+
if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
|
|
60533
62208
|
if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
|
|
60534
62209
|
const setterSuffix = binding.setterName.slice(3);
|
|
60535
62210
|
if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
|