oxlint-plugin-react-doctor 0.9.2-dev.1098b9c → 0.9.2-dev.16972ae
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 +1638 -149
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -388,7 +388,7 @@ const SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/i;
|
|
|
388
388
|
const SCRIPT_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py|php)$/i;
|
|
389
389
|
const DATABASE_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py)$/i;
|
|
390
390
|
const SERVER_CONTEXT_PATTERN = /(?:^|\/)(?:api|backend|server|servers|middleware|route|routes|functions|lambdas|workers)(?:\/|$)|(?:^|\/)[^/]+\.server\.[cm]?[jt]sx?$/i;
|
|
391
|
-
const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)
|
|
391
|
+
const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)|^(?:test|spec)-[^/]+\.[cm]?[jt]sx?$|\.(?:test|spec|e2e|e2e-spec|integration-test|fixture|fixtures|stories|story)\.[cm]?[jt]sx?$|(?:^|\/)(?:playwright|cypress|vitest|jest|karma)[^/]*\.conf(?:ig)?\.[cm]?[jt]s$|(?:^|\/)(?:test_[^/]+|[^/]+_test|conftest)\.py$|\.env\.[^/]*(?:test|e2e)[^/]*$/i;
|
|
392
392
|
const BUILD_CONFIG_FILE_PATTERN = /(?:^|\/)(?:vite|next|nuxt|astro|remix|webpack|rollup|rspack|rsbuild|esbuild|tsup|metro|expo|babel|tailwind|postcss|svelte|farm|parcel|snowpack)[^/]*\.config\.[cm]?[jt]sx?$/i;
|
|
393
393
|
const BUILD_SCRIPT_CONTEXT_PATTERN = /(?:^|\/)scripts(?:\/|$)/i;
|
|
394
394
|
const DEMO_CONTEXT_PATTERN = /(?:^|\/)(?:examples?|tutorials?|demos?|samples?|playgrounds?)(?:\/|$)/i;
|
|
@@ -665,19 +665,6 @@ const TRIVIAL_INITIALIZER_NAMES = new Set([
|
|
|
665
665
|
"parseInt",
|
|
666
666
|
"parseFloat"
|
|
667
667
|
]);
|
|
668
|
-
const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
|
|
669
|
-
"Date",
|
|
670
|
-
"Map",
|
|
671
|
-
"Set",
|
|
672
|
-
"WeakMap",
|
|
673
|
-
"WeakSet",
|
|
674
|
-
"WeakRef",
|
|
675
|
-
"RegExp",
|
|
676
|
-
"Error",
|
|
677
|
-
"URL",
|
|
678
|
-
"URLSearchParams",
|
|
679
|
-
"AbortController"
|
|
680
|
-
]);
|
|
681
668
|
const SETTER_PATTERN = /^set[A-Z]/;
|
|
682
669
|
const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
|
|
683
670
|
const UPPERCASE_PATTERN = /^[A-Z]/;
|
|
@@ -5887,6 +5874,7 @@ const isInlineFunctionExpression = (node) => Boolean(node && (isNodeOfType(node,
|
|
|
5887
5874
|
//#endregion
|
|
5888
5875
|
//#region src/plugin/rules/js-performance/async-await-in-loop.ts
|
|
5889
5876
|
const LOOP_STATEMENT_TYPES$1 = new Set(LOOP_TYPES);
|
|
5877
|
+
const ORDERED_OUTPUT_INSERTION_METHOD_NAMES = new Set(["push", "unshift"]);
|
|
5890
5878
|
const findFirstAwaitOutsideNestedFunctions = (block, skipNestedLoops = false) => {
|
|
5891
5879
|
let firstAwait = null;
|
|
5892
5880
|
walkAst(block, (child) => {
|
|
@@ -5953,7 +5941,151 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
5953
5941
|
});
|
|
5954
5942
|
return isWaitLike;
|
|
5955
5943
|
};
|
|
5956
|
-
const
|
|
5944
|
+
const getRootObjectIdentifierName = (node) => {
|
|
5945
|
+
let current = node;
|
|
5946
|
+
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
5947
|
+
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
5948
|
+
};
|
|
5949
|
+
const isScopeWithinFunction = (candidateScope, functionScope) => {
|
|
5950
|
+
let currentScope = candidateScope;
|
|
5951
|
+
while (currentScope) {
|
|
5952
|
+
if (currentScope === functionScope) return true;
|
|
5953
|
+
currentScope = currentScope.parent;
|
|
5954
|
+
}
|
|
5955
|
+
return false;
|
|
5956
|
+
};
|
|
5957
|
+
const isSymbolDirectlyReturned = (symbol, callerFunction) => Boolean(callerFunction) && symbol.references.some((reference) => {
|
|
5958
|
+
const expressionRoot = findTransparentExpressionRoot(reference.identifier);
|
|
5959
|
+
const parent = expressionRoot.parent;
|
|
5960
|
+
return isNodeOfType(parent, "ReturnStatement") && parent.argument === expressionRoot && findEnclosingFunction$1(parent) === callerFunction;
|
|
5961
|
+
});
|
|
5962
|
+
const collectPatternBindingSymbolIds = (pattern, scopes, target) => {
|
|
5963
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
5964
|
+
const symbol = scopes.symbolFor(pattern);
|
|
5965
|
+
if (symbol) target.add(symbol.id);
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
5969
|
+
for (const property of pattern.properties ?? []) if (isNodeOfType(property, "Property") && property.value) collectPatternBindingSymbolIds(property.value, scopes, target);
|
|
5970
|
+
else if (isNodeOfType(property, "RestElement") && property.argument) collectPatternBindingSymbolIds(property.argument, scopes, target);
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
5974
|
+
for (const element of pattern.elements ?? []) if (element) collectPatternBindingSymbolIds(element, scopes, target);
|
|
5975
|
+
return;
|
|
5976
|
+
}
|
|
5977
|
+
if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternBindingSymbolIds(pattern.left, scopes, target);
|
|
5978
|
+
};
|
|
5979
|
+
const collectReferencedSymbolIds = (expression, scopes) => {
|
|
5980
|
+
const referencedSymbolIds = /* @__PURE__ */ new Set();
|
|
5981
|
+
walkAst(expression, (child) => {
|
|
5982
|
+
if (child !== expression && isFunctionLike$1(child)) return false;
|
|
5983
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
5984
|
+
const symbol = scopes.symbolFor(child);
|
|
5985
|
+
if (symbol) referencedSymbolIds.add(symbol.id);
|
|
5986
|
+
});
|
|
5987
|
+
return referencedSymbolIds;
|
|
5988
|
+
};
|
|
5989
|
+
const collectAwaitDerivedSymbolIds = (block, scopes) => {
|
|
5990
|
+
const awaitDerivedSymbolIds = /* @__PURE__ */ new Set();
|
|
5991
|
+
const bindingDependencies = [];
|
|
5992
|
+
walkAst(block, (child) => {
|
|
5993
|
+
if (child !== block && isFunctionLike$1(child)) return false;
|
|
5994
|
+
if (isNodeOfType(child, "VariableDeclarator") && child.id && child.init) {
|
|
5995
|
+
const declaredSymbolIds = /* @__PURE__ */ new Set();
|
|
5996
|
+
collectPatternBindingSymbolIds(child.id, scopes, declaredSymbolIds);
|
|
5997
|
+
if (containsDirectAwait(child.init)) for (const symbolId of declaredSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
5998
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.init, scopes);
|
|
5999
|
+
for (const declaredSymbolId of declaredSymbolIds) bindingDependencies.push({
|
|
6000
|
+
declaredSymbolId,
|
|
6001
|
+
referencedSymbolIds
|
|
6002
|
+
});
|
|
6003
|
+
return;
|
|
6004
|
+
}
|
|
6005
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.left) {
|
|
6006
|
+
const assignedSymbolIds = /* @__PURE__ */ new Set();
|
|
6007
|
+
collectPatternBindingSymbolIds(child.left, scopes, assignedSymbolIds);
|
|
6008
|
+
if (containsDirectAwait(child.right)) for (const symbolId of assignedSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6009
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.right, scopes);
|
|
6010
|
+
for (const assignedSymbolId of assignedSymbolIds) bindingDependencies.push({
|
|
6011
|
+
declaredSymbolId: assignedSymbolId,
|
|
6012
|
+
referencedSymbolIds
|
|
6013
|
+
});
|
|
6014
|
+
}
|
|
6015
|
+
});
|
|
6016
|
+
let didGrow = true;
|
|
6017
|
+
while (didGrow) {
|
|
6018
|
+
didGrow = false;
|
|
6019
|
+
for (const { declaredSymbolId, referencedSymbolIds } of bindingDependencies) {
|
|
6020
|
+
if (awaitDerivedSymbolIds.has(declaredSymbolId)) continue;
|
|
6021
|
+
for (const referencedSymbolId of referencedSymbolIds) {
|
|
6022
|
+
if (!awaitDerivedSymbolIds.has(referencedSymbolId)) continue;
|
|
6023
|
+
awaitDerivedSymbolIds.add(declaredSymbolId);
|
|
6024
|
+
didGrow = true;
|
|
6025
|
+
break;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
return awaitDerivedSymbolIds;
|
|
6030
|
+
};
|
|
6031
|
+
const getSimpleParameterIdentifier = (parameter) => {
|
|
6032
|
+
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
6033
|
+
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
6034
|
+
return null;
|
|
6035
|
+
};
|
|
6036
|
+
const doesAwaitedLocalCallInsertAwaitDerivedOutput = (awaitNode, context) => {
|
|
6037
|
+
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
6038
|
+
const callExpression = awaitNode.argument;
|
|
6039
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
6040
|
+
const localFunction = resolveStaticLocalCallFunction(callExpression, context.scopes);
|
|
6041
|
+
if (!isFunctionLike$1(localFunction)) return false;
|
|
6042
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
6043
|
+
const functionScope = context.scopes.ownScopeFor(localFunction);
|
|
6044
|
+
if (!functionScope) return false;
|
|
6045
|
+
const awaitDerivedSymbolIds = collectAwaitDerivedSymbolIds(localFunction.body, context.scopes);
|
|
6046
|
+
const externallyReachableParameterSymbolIds = /* @__PURE__ */ new Set();
|
|
6047
|
+
for (const [parameterIndex, parameter] of localFunction.params.entries()) {
|
|
6048
|
+
const parameterIdentifier = getSimpleParameterIdentifier(parameter);
|
|
6049
|
+
if (!parameterIdentifier) continue;
|
|
6050
|
+
const argument = callExpression.arguments[parameterIndex];
|
|
6051
|
+
if (!isNodeOfType(argument, "Identifier")) continue;
|
|
6052
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
6053
|
+
const argumentSymbol = context.scopes.symbolFor(argument);
|
|
6054
|
+
if (parameterSymbol && argumentSymbol && (isSymbolDirectlyReturned(argumentSymbol, callerFunction) || argumentSymbol.id === parameterSymbol.id)) externallyReachableParameterSymbolIds.add(parameterSymbol.id);
|
|
6055
|
+
}
|
|
6056
|
+
let doesInsertAwaitDerivedOutput = false;
|
|
6057
|
+
walkAst(localFunction.body, (child) => {
|
|
6058
|
+
if (doesInsertAwaitDerivedOutput) return false;
|
|
6059
|
+
if (child !== localFunction.body && isFunctionLike$1(child)) return false;
|
|
6060
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6061
|
+
const callee = child.callee;
|
|
6062
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !ORDERED_OUTPUT_INSERTION_METHOD_NAMES.has(callee.property.name)) return;
|
|
6063
|
+
let doesMutationConsumeAwaitedValue = false;
|
|
6064
|
+
for (const mutationArgument of child.arguments ?? []) {
|
|
6065
|
+
if (containsDirectAwait(mutationArgument)) {
|
|
6066
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6067
|
+
break;
|
|
6068
|
+
}
|
|
6069
|
+
const referencedSymbolIds = collectReferencedSymbolIds(mutationArgument, context.scopes);
|
|
6070
|
+
for (const referencedSymbolId of referencedSymbolIds) if (awaitDerivedSymbolIds.has(referencedSymbolId)) {
|
|
6071
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6072
|
+
break;
|
|
6073
|
+
}
|
|
6074
|
+
if (doesMutationConsumeAwaitedValue) break;
|
|
6075
|
+
}
|
|
6076
|
+
if (!doesMutationConsumeAwaitedValue) return;
|
|
6077
|
+
let receiverIdentifier = callee.object;
|
|
6078
|
+
while (isNodeOfType(receiverIdentifier, "MemberExpression")) receiverIdentifier = receiverIdentifier.object;
|
|
6079
|
+
if (!isNodeOfType(receiverIdentifier, "Identifier")) return;
|
|
6080
|
+
const receiverSymbol = context.scopes.symbolFor(receiverIdentifier);
|
|
6081
|
+
if (receiverSymbol && (externallyReachableParameterSymbolIds.has(receiverSymbol.id) || !isScopeWithinFunction(receiverSymbol.scope, functionScope) && isSymbolDirectlyReturned(receiverSymbol, callerFunction))) {
|
|
6082
|
+
doesInsertAwaitDerivedOutput = true;
|
|
6083
|
+
return false;
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
return doesInsertAwaitDerivedOutput;
|
|
6087
|
+
};
|
|
6088
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode) || doesAwaitedLocalCallInsertAwaitDerivedOutput(awaitNode, context);
|
|
5957
6089
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
5958
6090
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
5959
6091
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -6099,11 +6231,6 @@ const loopBodyHasAwaitDependentEarlyExit = (block, loopLabelName) => {
|
|
|
6099
6231
|
});
|
|
6100
6232
|
return hasAwaitDependentExit;
|
|
6101
6233
|
};
|
|
6102
|
-
const getRootObjectIdentifierName = (node) => {
|
|
6103
|
-
let current = node;
|
|
6104
|
-
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
6105
|
-
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
6106
|
-
};
|
|
6107
6234
|
const MUTATING_ARRAY_METHOD_NAMES$2 = new Set([
|
|
6108
6235
|
...ARRAY_MUTATION_METHOD_NAMES,
|
|
6109
6236
|
"pop",
|
|
@@ -17872,6 +17999,14 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
17872
17999
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
17873
18000
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
17874
18001
|
//#endregion
|
|
18002
|
+
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
18003
|
+
const isEarlyExitStatement$1 = (statement) => {
|
|
18004
|
+
if (!statement) return false;
|
|
18005
|
+
if (statementAlwaysExits$1(statement)) return true;
|
|
18006
|
+
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
18007
|
+
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
18008
|
+
};
|
|
18009
|
+
//#endregion
|
|
17875
18010
|
//#region src/plugin/utils/is-ast-descendant.ts
|
|
17876
18011
|
/**
|
|
17877
18012
|
* True when `inner` is `outer` itself or any descendant in the AST
|
|
@@ -18090,13 +18225,15 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
|
|
|
18090
18225
|
}
|
|
18091
18226
|
return Boolean(methodName && EAGER_ITERATOR_METHOD_NAMES.has(methodName) && callNode.arguments[0] === callbackArgument && !isProvablyEmptyEagerCollection(callee.object, scopes) && (methodName === "forEach" ? isProvablyEagerForEachCollection(callee.object, scopes) : isProvablyEagerCollection(callee.object, scopes)));
|
|
18092
18227
|
};
|
|
18093
|
-
const
|
|
18094
|
-
const callNode = functionNode.parent;
|
|
18095
|
-
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
18228
|
+
const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
|
|
18096
18229
|
const callee = stripParenExpression(callNode.callee);
|
|
18097
18230
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
18098
|
-
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] ===
|
|
18099
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] ===
|
|
18231
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === callbackArgument;
|
|
18232
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === callbackArgument;
|
|
18233
|
+
};
|
|
18234
|
+
const isSynchronousIteratorCallback = (functionNode) => {
|
|
18235
|
+
const callNode = functionNode.parent;
|
|
18236
|
+
return Boolean(isNodeOfType(callNode, "CallExpression") && isSynchronousIteratorCallbackCall(callNode, functionNode));
|
|
18100
18237
|
};
|
|
18101
18238
|
//#endregion
|
|
18102
18239
|
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
@@ -18216,11 +18353,35 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
|
18216
18353
|
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
18217
18354
|
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
18218
18355
|
};
|
|
18356
|
+
const resolveReadOnlyEventListenerOptions = (optionsNode, context) => {
|
|
18357
|
+
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18358
|
+
if (!isNodeOfType(unwrappedOptions, "Identifier")) return resolveStableValue(unwrappedOptions, context);
|
|
18359
|
+
const optionsSymbol = context.scopes.symbolFor(unwrappedOptions);
|
|
18360
|
+
const initializer = optionsSymbol?.initializer ? stripParenExpression(optionsSymbol.initializer) : null;
|
|
18361
|
+
if (!optionsSymbol || !initializer) return resolveStableValue(unwrappedOptions, context);
|
|
18362
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) {
|
|
18363
|
+
if (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression")) return null;
|
|
18364
|
+
return resolveStableValue(unwrappedOptions, context);
|
|
18365
|
+
}
|
|
18366
|
+
if (optionsSymbol.kind !== "const") return null;
|
|
18367
|
+
return optionsSymbol.references.every((reference) => {
|
|
18368
|
+
if (reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) return false;
|
|
18369
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
18370
|
+
const callNode = referenceRoot.parent;
|
|
18371
|
+
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments[2] !== referenceRoot) return false;
|
|
18372
|
+
const callee = stripParenExpression(callNode.callee);
|
|
18373
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
18374
|
+
const methodName = getStaticPropertyKeyName(callee);
|
|
18375
|
+
return methodName === "addEventListener" || methodName === "removeEventListener";
|
|
18376
|
+
}) ? initializer : null;
|
|
18377
|
+
};
|
|
18219
18378
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
18220
|
-
const
|
|
18379
|
+
const stableOptionsNode = optionsNode ? resolveReadOnlyEventListenerOptions(optionsNode, context) : null;
|
|
18380
|
+
if (optionsNode && !stableOptionsNode) return null;
|
|
18381
|
+
const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
|
|
18221
18382
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
18222
|
-
if (!
|
|
18223
|
-
const unwrappedOptions = stripParenExpression(
|
|
18383
|
+
if (!stableOptionsNode) return null;
|
|
18384
|
+
const unwrappedOptions = stripParenExpression(stableOptionsNode);
|
|
18224
18385
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
18225
18386
|
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
18226
18387
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
@@ -18267,12 +18428,8 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
|
|
|
18267
18428
|
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
18268
18429
|
};
|
|
18269
18430
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
18270
|
-
|
|
18271
|
-
|
|
18272
|
-
while (isNodeOfType(parentNode, "ChainExpression")) {
|
|
18273
|
-
currentNode = parentNode;
|
|
18274
|
-
parentNode = currentNode.parent;
|
|
18275
|
-
}
|
|
18431
|
+
const currentNode = findTransparentExpressionRoot(resourceNode);
|
|
18432
|
+
const parentNode = currentNode.parent;
|
|
18276
18433
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
|
|
18277
18434
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
18278
18435
|
return null;
|
|
@@ -18543,6 +18700,18 @@ const resolveIteratorCollectionKey = (expression, context) => {
|
|
|
18543
18700
|
}
|
|
18544
18701
|
return null;
|
|
18545
18702
|
};
|
|
18703
|
+
const resolveReceiverIteratorCollectionKey = (expression, context) => {
|
|
18704
|
+
if (!expression) return null;
|
|
18705
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
18706
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
18707
|
+
const collectionExpression = findForOfStatementForIteratorExpression(unwrappedExpression, context)?.right;
|
|
18708
|
+
if (!collectionExpression) return null;
|
|
18709
|
+
const collectionIdentifier = stripParenExpression(collectionExpression);
|
|
18710
|
+
if (!isNodeOfType(collectionIdentifier, "Identifier") || !isPrivatePlainConstIdentifier(collectionIdentifier, context)) return null;
|
|
18711
|
+
const collectionSymbol = context.scopes.symbolFor(collectionIdentifier);
|
|
18712
|
+
const initializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
18713
|
+
return collectionSymbol && isNodeOfType(initializer, "ArrayExpression") && hasOnlyReplayableCollectionReferences(collectionIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${collectionSymbol.id}` : null;
|
|
18714
|
+
};
|
|
18546
18715
|
const isStableLoopReceiver = (expression, context) => {
|
|
18547
18716
|
if (!expression) return false;
|
|
18548
18717
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -19313,6 +19482,28 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
|
|
|
19313
19482
|
});
|
|
19314
19483
|
};
|
|
19315
19484
|
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
19485
|
+
const findCallbackRefReplacementReleaseGuard = (releaseCall, ownerFunction, releaseReceiverKey, registrationReceiverKey, context) => {
|
|
19486
|
+
let descendant = releaseCall;
|
|
19487
|
+
let ancestor = descendant.parent;
|
|
19488
|
+
while (ancestor && ancestor !== ownerFunction) {
|
|
19489
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && ancestor.alternate === null) {
|
|
19490
|
+
const test = stripParenExpression(ancestor.test);
|
|
19491
|
+
if (!isNodeOfType(test, "LogicalExpression") || test.operator !== "&&") return null;
|
|
19492
|
+
const operands = [stripParenExpression(test.left), stripParenExpression(test.right)];
|
|
19493
|
+
const hasLiveReceiverTest = operands.some((operand) => doesTestRequireLiveExpressionKey(operand, releaseReceiverKey, context));
|
|
19494
|
+
const hasDifferentReceiverTest = operands.some((operand) => {
|
|
19495
|
+
if (!isNodeOfType(operand, "BinaryExpression") || operand.operator !== "!==" && operand.operator !== "!=") return false;
|
|
19496
|
+
const leftKey = resolveExpressionKey(operand.left, context);
|
|
19497
|
+
const rightKey = resolveExpressionKey(operand.right, context);
|
|
19498
|
+
return leftKey === releaseReceiverKey && rightKey === registrationReceiverKey || rightKey === releaseReceiverKey && leftKey === registrationReceiverKey;
|
|
19499
|
+
});
|
|
19500
|
+
return hasLiveReceiverTest && hasDifferentReceiverTest ? ancestor : null;
|
|
19501
|
+
}
|
|
19502
|
+
descendant = ancestor;
|
|
19503
|
+
ancestor = descendant.parent;
|
|
19504
|
+
}
|
|
19505
|
+
return null;
|
|
19506
|
+
};
|
|
19316
19507
|
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
19317
19508
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
19318
19509
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
@@ -19333,7 +19524,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
19333
19524
|
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
19334
19525
|
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
|
|
19335
19526
|
});
|
|
19336
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
19527
|
+
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
|
|
19337
19528
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
19338
19529
|
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
19339
19530
|
};
|
|
@@ -19483,14 +19674,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19483
19674
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
19484
19675
|
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;
|
|
19485
19676
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
19486
|
-
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;
|
|
19487
19677
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
19678
|
+
if (usage.registrationVerbName === "addListener" && releaseVerbName === "removeListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) {
|
|
19679
|
+
if (callNode.arguments?.length !== 1) return false;
|
|
19680
|
+
const registrationHandler = resolveStableValue(usage.node.arguments[0], context);
|
|
19681
|
+
if (!isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && !isFunctionLike$1(registrationHandler)) return false;
|
|
19682
|
+
}
|
|
19488
19683
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
19489
19684
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
19490
19685
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
19491
19686
|
}
|
|
19492
19687
|
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
19493
|
-
|
|
19688
|
+
const registrationCallee = isNodeOfType(usage.node, "CallExpression") ? stripParenExpression(usage.node.callee) : null;
|
|
19689
|
+
const registrationReceiverCollectionKey = isNodeOfType(registrationCallee, "MemberExpression") ? resolveReceiverIteratorCollectionKey(registrationCallee.object, context) : null;
|
|
19690
|
+
const releaseReceiverCollectionKeyForPair = resolveReceiverIteratorCollectionKey(callee.object, context);
|
|
19691
|
+
if (!(registrationReceiverCollectionKey !== null && registrationReceiverCollectionKey === releaseReceiverCollectionKeyForPair) && (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey)) return false;
|
|
19494
19692
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
19495
19693
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
19496
19694
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
@@ -19527,7 +19725,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19527
19725
|
const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
|
|
19528
19726
|
const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
19529
19727
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
19530
|
-
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
|
|
19728
|
+
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.handlerKey ?? usage.eventKey : usage.handlerKey;
|
|
19531
19729
|
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
|
|
19532
19730
|
return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
19533
19731
|
}
|
|
@@ -20001,6 +20199,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
20001
20199
|
walkAst(body, (child) => {
|
|
20002
20200
|
if (leak !== null) return false;
|
|
20003
20201
|
if (isFunctionLike$1(child)) return false;
|
|
20202
|
+
if (!isNodeReachableWithinFunction(child, context)) return false;
|
|
20004
20203
|
if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
|
|
20005
20204
|
const socketUsage = {
|
|
20006
20205
|
kind: "socket",
|
|
@@ -20253,6 +20452,164 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
20253
20452
|
const objectParent = objectExpression.parent;
|
|
20254
20453
|
return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
|
|
20255
20454
|
};
|
|
20455
|
+
const readInvocationArgumentValue = (expression, context) => {
|
|
20456
|
+
if (!expression) return {
|
|
20457
|
+
isDefinitelyUndefined: true,
|
|
20458
|
+
truthiness: "falsy"
|
|
20459
|
+
};
|
|
20460
|
+
const target = stripParenExpression(expression);
|
|
20461
|
+
if (isNodeOfType(target, "Literal")) return {
|
|
20462
|
+
isDefinitelyUndefined: false,
|
|
20463
|
+
truthiness: target.value ? "truthy" : "falsy"
|
|
20464
|
+
};
|
|
20465
|
+
if (isNodeOfType(target, "Identifier") && target.name === "undefined" && context.scopes.isGlobalReference(target)) return {
|
|
20466
|
+
isDefinitelyUndefined: true,
|
|
20467
|
+
truthiness: "falsy"
|
|
20468
|
+
};
|
|
20469
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "void") return {
|
|
20470
|
+
isDefinitelyUndefined: true,
|
|
20471
|
+
truthiness: "falsy"
|
|
20472
|
+
};
|
|
20473
|
+
if (isNodeOfType(target, "ArrayExpression") || isNodeOfType(target, "ArrowFunctionExpression") || isNodeOfType(target, "ClassExpression") || isNodeOfType(target, "FunctionExpression") || isNodeOfType(target, "NewExpression") || isNodeOfType(target, "ObjectExpression")) return {
|
|
20474
|
+
isDefinitelyUndefined: false,
|
|
20475
|
+
truthiness: "truthy"
|
|
20476
|
+
};
|
|
20477
|
+
return {
|
|
20478
|
+
isDefinitelyUndefined: false,
|
|
20479
|
+
truthiness: "unknown"
|
|
20480
|
+
};
|
|
20481
|
+
};
|
|
20482
|
+
const readInvocationConditionTruthiness = (expression, parameterValues, context) => {
|
|
20483
|
+
const target = stripParenExpression(expression);
|
|
20484
|
+
const atomicValue = readInvocationArgumentValue(target, context);
|
|
20485
|
+
if (atomicValue.truthiness !== "unknown") return atomicValue.truthiness;
|
|
20486
|
+
if (isNodeOfType(target, "Identifier")) {
|
|
20487
|
+
const symbol = context.scopes.symbolFor(target);
|
|
20488
|
+
return symbol ? parameterValues.get(symbol.id)?.truthiness ?? "unknown" : "unknown";
|
|
20489
|
+
}
|
|
20490
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "!") {
|
|
20491
|
+
const argumentTruthiness = readInvocationConditionTruthiness(target.argument, parameterValues, context);
|
|
20492
|
+
return argumentTruthiness === "truthy" ? "falsy" : argumentTruthiness === "falsy" ? "truthy" : "unknown";
|
|
20493
|
+
}
|
|
20494
|
+
if (isNodeOfType(target, "LogicalExpression")) {
|
|
20495
|
+
const leftTruthiness = readInvocationConditionTruthiness(target.left, parameterValues, context);
|
|
20496
|
+
const rightTruthiness = readInvocationConditionTruthiness(target.right, parameterValues, context);
|
|
20497
|
+
if (target.operator === "&&") {
|
|
20498
|
+
if (leftTruthiness === "falsy" || rightTruthiness === "falsy") return "falsy";
|
|
20499
|
+
return leftTruthiness === "truthy" && rightTruthiness === "truthy" ? "truthy" : "unknown";
|
|
20500
|
+
}
|
|
20501
|
+
if (target.operator === "||") {
|
|
20502
|
+
if (leftTruthiness === "truthy" || rightTruthiness === "truthy") return "truthy";
|
|
20503
|
+
return leftTruthiness === "falsy" && rightTruthiness === "falsy" ? "falsy" : "unknown";
|
|
20504
|
+
}
|
|
20505
|
+
return "unknown";
|
|
20506
|
+
}
|
|
20507
|
+
if (isNodeOfType(target, "ConditionalExpression")) {
|
|
20508
|
+
const testTruthiness = readInvocationConditionTruthiness(target.test, parameterValues, context);
|
|
20509
|
+
if (testTruthiness === "truthy") return readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20510
|
+
if (testTruthiness === "falsy") return readInvocationConditionTruthiness(target.alternate, parameterValues, context);
|
|
20511
|
+
const consequentTruthiness = readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20512
|
+
return consequentTruthiness === readInvocationConditionTruthiness(target.alternate, parameterValues, context) ? consequentTruthiness : "unknown";
|
|
20513
|
+
}
|
|
20514
|
+
if (isNodeOfType(target, "CallExpression") && isNodeOfType(target.callee, "Identifier") && target.callee.name === "Boolean" && context.scopes.isGlobalReference(target.callee) && target.arguments[0] && isAstNode(target.arguments[0])) return readInvocationConditionTruthiness(target.arguments[0], parameterValues, context);
|
|
20515
|
+
return "unknown";
|
|
20516
|
+
};
|
|
20517
|
+
const getInvocationParameterValues = (retainedFunction, invocation, leakNode, context) => {
|
|
20518
|
+
const parameterValues = /* @__PURE__ */ new Map();
|
|
20519
|
+
if (!isFunctionLike$1(retainedFunction) || !invocation.isDirect) return parameterValues;
|
|
20520
|
+
for (const [parameterIndex, parameter] of retainedFunction.params.entries()) {
|
|
20521
|
+
const argument = invocation.call.arguments[parameterIndex];
|
|
20522
|
+
const argumentExpression = argument && isAstNode(argument) ? argument : null;
|
|
20523
|
+
let parameterIdentifier = null;
|
|
20524
|
+
let parameterValue = readInvocationArgumentValue(argumentExpression, context);
|
|
20525
|
+
if (isNodeOfType(parameter, "Identifier")) parameterIdentifier = parameter;
|
|
20526
|
+
else if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) {
|
|
20527
|
+
parameterIdentifier = parameter.left;
|
|
20528
|
+
if (parameterValue.isDefinitelyUndefined) parameterValue = readInvocationArgumentValue(parameter.right, context);
|
|
20529
|
+
} else if (isNodeOfType(parameter, "RestElement") && isNodeOfType(parameter.argument, "Identifier")) {
|
|
20530
|
+
parameterIdentifier = parameter.argument;
|
|
20531
|
+
parameterValue = {
|
|
20532
|
+
isDefinitelyUndefined: false,
|
|
20533
|
+
truthiness: "truthy"
|
|
20534
|
+
};
|
|
20535
|
+
}
|
|
20536
|
+
if (!parameterIdentifier) continue;
|
|
20537
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
20538
|
+
if (!parameterSymbol) continue;
|
|
20539
|
+
const isWrittenBeforeLeak = parameterSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] < leakNode.range[0]);
|
|
20540
|
+
parameterValues.set(parameterSymbol.id, isWrittenBeforeLeak ? {
|
|
20541
|
+
isDefinitelyUndefined: false,
|
|
20542
|
+
truthiness: "unknown"
|
|
20543
|
+
} : parameterValue);
|
|
20544
|
+
}
|
|
20545
|
+
return parameterValues;
|
|
20546
|
+
};
|
|
20547
|
+
const isLeakPathDisabledForInvocation = (retainedFunction, leakNode, invocation, context) => {
|
|
20548
|
+
if (!invocation.isDirect) return false;
|
|
20549
|
+
const parameterValues = getInvocationParameterValues(retainedFunction, invocation, leakNode, context);
|
|
20550
|
+
let child = leakNode;
|
|
20551
|
+
let ancestor = leakNode.parent ?? null;
|
|
20552
|
+
while (ancestor && ancestor !== retainedFunction) {
|
|
20553
|
+
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
20554
|
+
const childIndex = ancestor.body.findIndex((statement) => statement === child);
|
|
20555
|
+
for (const precedingStatement of ancestor.body.slice(0, childIndex)) {
|
|
20556
|
+
if (!isNodeOfType(precedingStatement, "IfStatement") || precedingStatement.alternate || !isEarlyExitStatement$1(precedingStatement.consequent)) continue;
|
|
20557
|
+
if (readInvocationConditionTruthiness(precedingStatement.test, parameterValues, context) === "truthy") return true;
|
|
20558
|
+
}
|
|
20559
|
+
}
|
|
20560
|
+
let requiredTruthiness = null;
|
|
20561
|
+
let condition = null;
|
|
20562
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
20563
|
+
condition = ancestor.test;
|
|
20564
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20565
|
+
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
20566
|
+
condition = ancestor.test;
|
|
20567
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20568
|
+
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === child && ancestor.operator !== "??") {
|
|
20569
|
+
condition = ancestor.left;
|
|
20570
|
+
requiredTruthiness = ancestor.operator === "&&" ? "truthy" : "falsy";
|
|
20571
|
+
} else if ((isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === child) {
|
|
20572
|
+
condition = ancestor.test;
|
|
20573
|
+
requiredTruthiness = "truthy";
|
|
20574
|
+
} else if (isNodeOfType(ancestor, "ForStatement") && ancestor.body === child && ancestor.test) {
|
|
20575
|
+
condition = ancestor.test;
|
|
20576
|
+
requiredTruthiness = "truthy";
|
|
20577
|
+
}
|
|
20578
|
+
if (condition && requiredTruthiness) {
|
|
20579
|
+
const conditionTruthiness = readInvocationConditionTruthiness(condition, parameterValues, context);
|
|
20580
|
+
if (conditionTruthiness !== "unknown" && conditionTruthiness !== requiredTruthiness) return true;
|
|
20581
|
+
}
|
|
20582
|
+
child = ancestor;
|
|
20583
|
+
ancestor = ancestor.parent ?? null;
|
|
20584
|
+
}
|
|
20585
|
+
return false;
|
|
20586
|
+
};
|
|
20587
|
+
const getEffectRetainedInvocations = (retainedFunction, context) => {
|
|
20588
|
+
if (!isFunctionLike$1(retainedFunction)) return [];
|
|
20589
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
20590
|
+
if (!componentFunction || !isFunctionLike$1(componentFunction)) return [];
|
|
20591
|
+
const invocations = [];
|
|
20592
|
+
walkAst(componentFunction.body, (child) => {
|
|
20593
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
20594
|
+
const effectCallback = getEffectCallback(child);
|
|
20595
|
+
if (!effectCallback || !isFunctionLike$1(effectCallback)) return;
|
|
20596
|
+
walkAst(effectCallback.body, (effectChild) => {
|
|
20597
|
+
if (effectChild !== effectCallback.body && isFunctionLike$1(effectChild)) return false;
|
|
20598
|
+
if (!isNodeOfType(effectChild, "CallExpression") || !isNodeReachableWithinFunction(effectChild, context)) return;
|
|
20599
|
+
const isDirectInvocation = resolveRefOwnedCleanupFunction(effectChild.callee, context) === retainedFunction;
|
|
20600
|
+
const isSynchronousIteratorInvocation = effectChild.arguments.some((argument) => isAstNode(argument) && resolveRefOwnedCleanupFunction(argument, context) === retainedFunction && isSynchronousIteratorCallbackCall(effectChild, argument));
|
|
20601
|
+
if (isDirectInvocation) invocations.push({
|
|
20602
|
+
call: effectChild,
|
|
20603
|
+
isDirect: true
|
|
20604
|
+
});
|
|
20605
|
+
if (isSynchronousIteratorInvocation) invocations.push({
|
|
20606
|
+
call: effectChild,
|
|
20607
|
+
isDirect: false
|
|
20608
|
+
});
|
|
20609
|
+
});
|
|
20610
|
+
});
|
|
20611
|
+
return invocations;
|
|
20612
|
+
};
|
|
20256
20613
|
const effectNeedsCleanup = defineRule({
|
|
20257
20614
|
id: "effect-needs-cleanup",
|
|
20258
20615
|
title: "Effect subscription or timer never cleaned up",
|
|
@@ -20263,13 +20620,19 @@ const effectNeedsCleanup = defineRule({
|
|
|
20263
20620
|
const reportRetainedLeak = (retainedFunction) => {
|
|
20264
20621
|
const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
|
|
20265
20622
|
if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
|
|
20623
|
+
const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
|
|
20624
|
+
const isEffectInvoked = effectInvocations.length > 0;
|
|
20266
20625
|
const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
|
|
20267
20626
|
allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
|
|
20268
20627
|
allowReturnedTimerEscape: false,
|
|
20269
20628
|
includeOneShotTimers: true,
|
|
20270
20629
|
requireCallableReturnedResource: true
|
|
20630
|
+
} : isEffectInvoked ? {
|
|
20631
|
+
allowReturnedTimerEscape: false,
|
|
20632
|
+
includeOneShotTimers: true
|
|
20271
20633
|
} : void 0);
|
|
20272
20634
|
if (!leak) return;
|
|
20635
|
+
if (isEffectInvoked && leak.resourceName === "setTimeout" && (!isNodeReachableWithinFunction(leak.node, context) || isFunctionLike$1(retainedFunction) && retainedFunction.params.length > 0 && !context.cfg.isUnconditionalFromEntry(leak.node) && effectInvocations.every((invocation) => isLeakPathDisabledForInvocation(retainedFunction, leak.node, invocation, context)))) return;
|
|
20273
20636
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
|
|
20274
20637
|
context.report({
|
|
20275
20638
|
node: leak.node,
|
|
@@ -24689,14 +25052,14 @@ const getFirstLegendChild = (children, targetNode) => {
|
|
|
24689
25052
|
if (isNodeOfType(child, "JSXExpressionContainer")) {
|
|
24690
25053
|
const potentialLegends = [];
|
|
24691
25054
|
collectPotentialLegends(child.expression, potentialLegends);
|
|
24692
|
-
const containingLegend = potentialLegends.find((legend) => isDescendantOf(targetNode, legend));
|
|
25055
|
+
const containingLegend = potentialLegends.find((legend) => isDescendantOf$1(targetNode, legend));
|
|
24693
25056
|
if (containingLegend) return containingLegend;
|
|
24694
25057
|
if (potentialLegends[0]) return potentialLegends[0];
|
|
24695
25058
|
}
|
|
24696
25059
|
}
|
|
24697
25060
|
return null;
|
|
24698
25061
|
};
|
|
24699
|
-
const isDescendantOf = (node, ancestor) => {
|
|
25062
|
+
const isDescendantOf$1 = (node, ancestor) => {
|
|
24700
25063
|
let current = node.parent;
|
|
24701
25064
|
while (current) {
|
|
24702
25065
|
if (current === ancestor) return true;
|
|
@@ -24721,7 +25084,7 @@ const isDisabledByFieldsetAncestor = (node, context) => {
|
|
|
24721
25084
|
while (ancestor) {
|
|
24722
25085
|
if (isNodeOfType(ancestor, "JSXElement") && resolveJsxElementType(ancestor.openingElement) === "fieldset" && openingElementMayBeDisabled(ancestor.openingElement, context)) {
|
|
24723
25086
|
const firstLegend = getFirstLegendChild(ancestor.children, node);
|
|
24724
|
-
if (!firstLegend || !isDescendantOf(node, firstLegend)) return true;
|
|
25087
|
+
if (!firstLegend || !isDescendantOf$1(node, firstLegend)) return true;
|
|
24725
25088
|
}
|
|
24726
25089
|
ancestor = ancestor.parent;
|
|
24727
25090
|
}
|
|
@@ -30534,7 +30897,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
|
|
|
30534
30897
|
"code",
|
|
30535
30898
|
"label",
|
|
30536
30899
|
"slug",
|
|
30537
|
-
"prefix"
|
|
30900
|
+
"prefix",
|
|
30901
|
+
"__html"
|
|
30538
30902
|
]);
|
|
30539
30903
|
const STRING_TYPED_IDENTIFIER_SUFFIXES = [
|
|
30540
30904
|
"Text",
|
|
@@ -30652,13 +31016,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
|
|
|
30652
31016
|
"title"
|
|
30653
31017
|
]);
|
|
30654
31018
|
const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
|
|
31019
|
+
const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
|
|
31020
|
+
"concat",
|
|
31021
|
+
"filter",
|
|
31022
|
+
"flat",
|
|
31023
|
+
"flatMap",
|
|
31024
|
+
"map",
|
|
31025
|
+
"slice",
|
|
31026
|
+
"split"
|
|
31027
|
+
]);
|
|
30655
31028
|
const isLikelyStringReceiver = (receiver) => {
|
|
30656
31029
|
if (!receiver) return false;
|
|
31030
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31031
|
+
if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
|
|
30657
31032
|
if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
|
|
30658
31033
|
if (isNodeOfType(receiver, "TemplateLiteral")) return true;
|
|
30659
31034
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
|
|
30660
31035
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
|
|
30661
31036
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
|
|
31037
|
+
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && (receiver.callee.property.name === "concat" || receiver.callee.property.name === "slice") && isLikelyStringReceiver(receiver.callee.object)) return true;
|
|
30662
31038
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
30663
31039
|
if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
|
|
30664
31040
|
}
|
|
@@ -30676,8 +31052,46 @@ const isLikelyStringReceiver = (receiver) => {
|
|
|
30676
31052
|
}
|
|
30677
31053
|
if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
|
|
30678
31054
|
if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
|
|
31055
|
+
if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
|
|
30679
31056
|
return false;
|
|
30680
31057
|
};
|
|
31058
|
+
const isFreshArrayReceiver = (receiver) => {
|
|
31059
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31060
|
+
if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
|
|
31061
|
+
if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
|
|
31062
|
+
if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
|
|
31063
|
+
if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
|
|
31064
|
+
const sourceReceiver = stripParenExpression(receiver.callee.object);
|
|
31065
|
+
return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
|
|
31066
|
+
};
|
|
31067
|
+
const isSmallRestHelperOmissionList = (node) => {
|
|
31068
|
+
if (!node) return false;
|
|
31069
|
+
const candidate = stripParenExpression(node);
|
|
31070
|
+
if (!isNodeOfType(candidate, "ArrayExpression")) return false;
|
|
31071
|
+
const elements = candidate.elements ?? [];
|
|
31072
|
+
return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
|
|
31073
|
+
};
|
|
31074
|
+
const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
|
|
31075
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
31076
|
+
const enclosingFunction = findEnclosingFunction$1(lookupCall);
|
|
31077
|
+
if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
|
|
31078
|
+
let bindingIdentifier = null;
|
|
31079
|
+
let ancestor = enclosingFunction.parent;
|
|
31080
|
+
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
31081
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
|
|
31082
|
+
bindingIdentifier = ancestor.id;
|
|
31083
|
+
break;
|
|
31084
|
+
}
|
|
31085
|
+
ancestor = ancestor.parent;
|
|
31086
|
+
}
|
|
31087
|
+
if (!bindingIdentifier) return false;
|
|
31088
|
+
const helperSymbol = scopes.symbolFor(bindingIdentifier);
|
|
31089
|
+
if (!helperSymbol || helperSymbol.references.length === 0) return false;
|
|
31090
|
+
return helperSymbol.references.every((reference) => {
|
|
31091
|
+
const callExpression = reference.identifier.parent;
|
|
31092
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
|
|
31093
|
+
});
|
|
31094
|
+
};
|
|
30681
31095
|
const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
|
|
30682
31096
|
"i",
|
|
30683
31097
|
"j",
|
|
@@ -31274,6 +31688,8 @@ const jsSetMapLookups = defineRule({
|
|
|
31274
31688
|
const query = node.arguments[0];
|
|
31275
31689
|
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
31276
31690
|
if (isLikelyStringReceiver(receiver)) return;
|
|
31691
|
+
if (isFreshArrayReceiver(receiver)) return;
|
|
31692
|
+
if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
|
|
31277
31693
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
31278
31694
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
31279
31695
|
if (isSmallFixedListMember(receiver)) return;
|
|
@@ -45187,14 +45603,6 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45187
45603
|
} })
|
|
45188
45604
|
});
|
|
45189
45605
|
//#endregion
|
|
45190
|
-
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
45191
|
-
const isEarlyExitStatement$1 = (statement) => {
|
|
45192
|
-
if (!statement) return false;
|
|
45193
|
-
if (statementAlwaysExits$1(statement)) return true;
|
|
45194
|
-
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
45195
|
-
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
45196
|
-
};
|
|
45197
|
-
//#endregion
|
|
45198
45606
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45199
45607
|
const unwrapNegativeGuardForm = (test) => {
|
|
45200
45608
|
const expression = stripParenExpression(test);
|
|
@@ -64090,6 +64498,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
|
|
|
64090
64498
|
};
|
|
64091
64499
|
//#endregion
|
|
64092
64500
|
//#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
|
|
64501
|
+
const findGuardingIfStatements = (node, functionBoundary) => {
|
|
64502
|
+
const guardingIfStatements = [];
|
|
64503
|
+
let currentNode = node.parent;
|
|
64504
|
+
while (currentNode && currentNode !== functionBoundary) {
|
|
64505
|
+
if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
|
|
64506
|
+
currentNode = currentNode.parent;
|
|
64507
|
+
}
|
|
64508
|
+
return guardingIfStatements;
|
|
64509
|
+
};
|
|
64510
|
+
const doesNodeReadSymbol = (node, symbol) => {
|
|
64511
|
+
let doesReadSymbol = false;
|
|
64512
|
+
walkAst(node, (childNode) => {
|
|
64513
|
+
if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
|
|
64514
|
+
doesReadSymbol = true;
|
|
64515
|
+
return false;
|
|
64516
|
+
}
|
|
64517
|
+
});
|
|
64518
|
+
return doesReadSymbol;
|
|
64519
|
+
};
|
|
64520
|
+
const collectWrittenSymbols = (node, scopes) => {
|
|
64521
|
+
const writtenSymbols = /* @__PURE__ */ new Set();
|
|
64522
|
+
walkAst(node, (childNode) => {
|
|
64523
|
+
if (childNode !== node && isFunctionLike$1(childNode)) return false;
|
|
64524
|
+
if (!isNodeOfType(childNode, "Identifier")) return;
|
|
64525
|
+
const reference = scopes.referenceFor(childNode);
|
|
64526
|
+
if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
|
|
64527
|
+
writtenSymbols.add(reference.resolvedSymbol);
|
|
64528
|
+
});
|
|
64529
|
+
return writtenSymbols;
|
|
64530
|
+
};
|
|
64531
|
+
const isDescendantOf = (node, ancestorNode) => {
|
|
64532
|
+
let currentNode = node.parent;
|
|
64533
|
+
while (currentNode) {
|
|
64534
|
+
if (currentNode === ancestorNode) return true;
|
|
64535
|
+
currentNode = currentNode.parent;
|
|
64536
|
+
}
|
|
64537
|
+
return false;
|
|
64538
|
+
};
|
|
64539
|
+
const getAssignedValue = (identifier) => {
|
|
64540
|
+
const assignmentExpression = identifier.parent;
|
|
64541
|
+
return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
|
|
64542
|
+
};
|
|
64543
|
+
const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
|
|
64544
|
+
const initialValue = symbol.initializer;
|
|
64545
|
+
if (!initialValue) return false;
|
|
64546
|
+
const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
|
|
64547
|
+
return guardedWrites.length > 0 && guardedWrites.every((reference) => {
|
|
64548
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64549
|
+
return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
|
|
64550
|
+
});
|
|
64551
|
+
};
|
|
64552
|
+
const isWriteOverwrittenBefore = (symbol, writeIdentifier, guardingIfStatement, readIdentifier, context) => symbol.references.some((reference) => reference.flag !== "read" && reference.identifier !== writeIdentifier && !isDescendantOf(reference.identifier, guardingIfStatement) && isNodeReachableWithinFunction(reference.identifier, context) && isUnconditionalOrStaticallySelected(reference.identifier, context) && getNodeStartIndex(reference.identifier) > getNodeStartIndex(writeIdentifier) && getNodeStartIndex(reference.identifier) < getNodeStartIndex(readIdentifier));
|
|
64553
|
+
const isUnconditionalOrStaticallySelected = (node, context) => {
|
|
64554
|
+
if (context.cfg.isUnconditionalFromEntry(node)) return true;
|
|
64555
|
+
let currentNode = node;
|
|
64556
|
+
let outermostStaticIfStatement = null;
|
|
64557
|
+
let parentNode = currentNode.parent;
|
|
64558
|
+
while (parentNode) {
|
|
64559
|
+
if (isFunctionLike$1(parentNode)) break;
|
|
64560
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
64561
|
+
const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
|
|
64562
|
+
let selectedBranch = null;
|
|
64563
|
+
if (staticResult === true) selectedBranch = parentNode.consequent;
|
|
64564
|
+
if (staticResult === false) selectedBranch = parentNode.alternate;
|
|
64565
|
+
if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
|
|
64566
|
+
outermostStaticIfStatement = parentNode;
|
|
64567
|
+
}
|
|
64568
|
+
currentNode = parentNode;
|
|
64569
|
+
parentNode = currentNode.parent;
|
|
64570
|
+
}
|
|
64571
|
+
return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
|
|
64572
|
+
};
|
|
64573
|
+
const containsExplicitReactRuntimeReference = (node, scopes) => {
|
|
64574
|
+
let hasRuntimeReference = false;
|
|
64575
|
+
walkAst(node, (childNode) => {
|
|
64576
|
+
if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
|
|
64577
|
+
hasRuntimeReference = true;
|
|
64578
|
+
return false;
|
|
64579
|
+
}
|
|
64580
|
+
if (!isNodeOfType(childNode, "CallExpression")) return;
|
|
64581
|
+
const sourceArgument = (childNode.arguments ?? [])[0];
|
|
64582
|
+
if (!isNodeOfType(childNode.callee, "Identifier") || childNode.callee.name !== "require" || !scopes.isGlobalReference(childNode.callee) || !isNodeOfType(sourceArgument, "Literal") || typeof sourceArgument.value !== "string" || !REACT_RUNTIME_MODULE_SOURCES.has(sourceArgument.value)) return;
|
|
64583
|
+
hasRuntimeReference = true;
|
|
64584
|
+
return false;
|
|
64585
|
+
});
|
|
64586
|
+
return hasRuntimeReference;
|
|
64587
|
+
};
|
|
64588
|
+
const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
|
|
64589
|
+
const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
|
|
64590
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
|
|
64591
|
+
const functionSymbol = scopes.symbolFor(bindingIdentifier);
|
|
64592
|
+
if (!functionSymbol) return null;
|
|
64593
|
+
for (const reference of functionSymbol.references) {
|
|
64594
|
+
const callExpression = reference.identifier.parent;
|
|
64595
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
|
|
64596
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
|
|
64597
|
+
if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
|
|
64598
|
+
}
|
|
64599
|
+
return null;
|
|
64600
|
+
};
|
|
64093
64601
|
const evaluateEquality$1 = (operator, left, right) => {
|
|
64094
64602
|
if (operator === "===" || operator === "==") return left === right;
|
|
64095
64603
|
if (operator === "!==" || operator === "!=") return left !== right;
|
|
@@ -64140,6 +64648,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
64140
64648
|
if (leftResult === false && rightResult === false) return false;
|
|
64141
64649
|
return null;
|
|
64142
64650
|
};
|
|
64651
|
+
const areLooselyEqualPrimitiveResults = (left, right) => {
|
|
64652
|
+
if (left.kind === right.kind) return left.value === right.value;
|
|
64653
|
+
if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
|
|
64654
|
+
if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
|
|
64655
|
+
kind: "number",
|
|
64656
|
+
value: left.value ? 1 : 0
|
|
64657
|
+
}, right);
|
|
64658
|
+
if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
|
|
64659
|
+
kind: "number",
|
|
64660
|
+
value: right.value ? 1 : 0
|
|
64661
|
+
});
|
|
64662
|
+
if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
|
|
64663
|
+
if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
|
|
64664
|
+
return false;
|
|
64665
|
+
};
|
|
64666
|
+
const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
|
|
64667
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64668
|
+
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
64669
|
+
if (predicateMatch) return {
|
|
64670
|
+
kind: "boolean",
|
|
64671
|
+
value: predicateMatch[`${runtime}Result`]
|
|
64672
|
+
};
|
|
64673
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
64674
|
+
const value = unwrappedExpression.value;
|
|
64675
|
+
if (value === null) return {
|
|
64676
|
+
kind: "null",
|
|
64677
|
+
value
|
|
64678
|
+
};
|
|
64679
|
+
if (typeof value === "boolean") return {
|
|
64680
|
+
kind: "boolean",
|
|
64681
|
+
value
|
|
64682
|
+
};
|
|
64683
|
+
if (typeof value === "number") return {
|
|
64684
|
+
kind: "number",
|
|
64685
|
+
value
|
|
64686
|
+
};
|
|
64687
|
+
if (typeof value === "string") return {
|
|
64688
|
+
kind: "string",
|
|
64689
|
+
value
|
|
64690
|
+
};
|
|
64691
|
+
return null;
|
|
64692
|
+
}
|
|
64693
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
|
|
64694
|
+
kind: "undefined",
|
|
64695
|
+
value: void 0
|
|
64696
|
+
};
|
|
64697
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64698
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64699
|
+
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
64700
|
+
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64701
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64702
|
+
const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
|
|
64703
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64704
|
+
return result;
|
|
64705
|
+
}
|
|
64706
|
+
if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64707
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64708
|
+
const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
|
|
64709
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64710
|
+
return result;
|
|
64711
|
+
}
|
|
64712
|
+
}
|
|
64713
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
64714
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
64715
|
+
return argumentResult === null ? null : {
|
|
64716
|
+
kind: "boolean",
|
|
64717
|
+
value: !argumentResult
|
|
64718
|
+
};
|
|
64719
|
+
}
|
|
64720
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64721
|
+
const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
|
|
64722
|
+
const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
|
|
64723
|
+
if (!leftResult || !rightResult) return null;
|
|
64724
|
+
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
64725
|
+
const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
|
|
64726
|
+
return {
|
|
64727
|
+
kind: "boolean",
|
|
64728
|
+
value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
|
|
64729
|
+
};
|
|
64730
|
+
}
|
|
64731
|
+
if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
|
|
64732
|
+
const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
|
|
64733
|
+
return {
|
|
64734
|
+
kind: "boolean",
|
|
64735
|
+
value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
|
|
64736
|
+
};
|
|
64737
|
+
}
|
|
64738
|
+
}
|
|
64739
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64740
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
64741
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64742
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
|
|
64743
|
+
const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
64744
|
+
return argumentResult === null ? null : {
|
|
64745
|
+
kind: "boolean",
|
|
64746
|
+
value: argumentResult
|
|
64747
|
+
};
|
|
64748
|
+
}
|
|
64749
|
+
}
|
|
64750
|
+
return null;
|
|
64751
|
+
};
|
|
64143
64752
|
const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
64144
64753
|
const unwrappedExpression = stripParenExpression(expression);
|
|
64145
64754
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -64188,6 +64797,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
|
64188
64797
|
parameterValuesBySymbolId
|
|
64189
64798
|
});
|
|
64190
64799
|
}
|
|
64800
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64801
|
+
const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
|
|
64802
|
+
return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
|
|
64803
|
+
}
|
|
64191
64804
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
64192
64805
|
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
64193
64806
|
return argumentResult === null ? null : !argumentResult;
|
|
@@ -64248,13 +64861,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
|
|
|
64248
64861
|
const rightSymbol = scopes.symbolFor(right);
|
|
64249
64862
|
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
64250
64863
|
}
|
|
64251
|
-
|
|
64252
|
-
|
|
64253
|
-
|
|
64254
|
-
|
|
64255
|
-
|
|
64256
|
-
|
|
64257
|
-
|
|
64864
|
+
const rightEntries = new Map(Object.entries(right));
|
|
64865
|
+
for (const [key, leftValue] of Object.entries(left)) {
|
|
64866
|
+
if (key === "parent") continue;
|
|
64867
|
+
const rightValue = rightEntries.get(key);
|
|
64868
|
+
if (isAstNode(leftValue)) {
|
|
64869
|
+
if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
|
|
64870
|
+
continue;
|
|
64871
|
+
}
|
|
64872
|
+
if (!Array.isArray(leftValue)) continue;
|
|
64873
|
+
if (!Array.isArray(rightValue)) return false;
|
|
64874
|
+
const leftNodes = leftValue.filter(isAstNode);
|
|
64875
|
+
const rightNodes = rightValue.filter(isAstNode);
|
|
64876
|
+
if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
|
|
64258
64877
|
}
|
|
64259
64878
|
return true;
|
|
64260
64879
|
};
|
|
@@ -64268,6 +64887,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
|
64268
64887
|
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
64269
64888
|
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
64270
64889
|
};
|
|
64890
|
+
const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
64891
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64892
|
+
if (matchBrowserPredicate(unwrappedExpression, context)) return true;
|
|
64893
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
|
|
64894
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
|
|
64895
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64896
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64897
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
|
|
64898
|
+
visitedSymbolIds.add(symbol.id);
|
|
64899
|
+
const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
|
|
64900
|
+
const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
|
|
64901
|
+
visitedSymbolIds.delete(symbol.id);
|
|
64902
|
+
return isReflexive;
|
|
64903
|
+
}
|
|
64904
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
|
|
64905
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
|
|
64906
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
|
|
64907
|
+
if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
|
|
64908
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
64909
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64910
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
|
|
64911
|
+
};
|
|
64912
|
+
const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
|
|
64913
|
+
if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
|
|
64914
|
+
if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
|
|
64915
|
+
if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
|
|
64916
|
+
if (isNodeOfType(node, "TryStatement")) return [
|
|
64917
|
+
...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
|
|
64918
|
+
...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
|
|
64919
|
+
...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
|
|
64920
|
+
];
|
|
64921
|
+
if (!isNodeOfType(node, "BlockStatement")) return [];
|
|
64922
|
+
const propertyValues = [];
|
|
64923
|
+
for (const childStatement of node.body) {
|
|
64924
|
+
propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
|
|
64925
|
+
if (statementAlwaysExits$1(childStatement)) break;
|
|
64926
|
+
}
|
|
64927
|
+
return propertyValues;
|
|
64928
|
+
};
|
|
64929
|
+
const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
|
|
64930
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
64931
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
64932
|
+
const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
|
|
64933
|
+
let match = null;
|
|
64934
|
+
for (const propertyValue of propertyValues) {
|
|
64935
|
+
match = matchHydrationConditionInternal(propertyValue, context, state);
|
|
64936
|
+
if (match) break;
|
|
64937
|
+
}
|
|
64938
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
64939
|
+
return match;
|
|
64940
|
+
};
|
|
64271
64941
|
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
64272
64942
|
const unwrappedExpression = stripParenExpression(expression);
|
|
64273
64943
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -64284,14 +64954,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64284
64954
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64285
64955
|
return match;
|
|
64286
64956
|
}
|
|
64957
|
+
if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64958
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64959
|
+
if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
|
|
64960
|
+
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64961
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64962
|
+
return match;
|
|
64963
|
+
}
|
|
64964
|
+
for (const reference of symbol.references) {
|
|
64965
|
+
if (reference.flag === "read") continue;
|
|
64966
|
+
if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
|
|
64967
|
+
const enclosingFunction = findEnclosingFunction$1(reference.identifier);
|
|
64968
|
+
if (!enclosingFunction) continue;
|
|
64969
|
+
for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
|
|
64970
|
+
if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
64971
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
64972
|
+
if (match) {
|
|
64973
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64974
|
+
return match;
|
|
64975
|
+
}
|
|
64976
|
+
}
|
|
64977
|
+
}
|
|
64978
|
+
const readingFunction = findEnclosingFunction$1(unwrappedExpression);
|
|
64979
|
+
for (const reference of symbol.references) {
|
|
64980
|
+
if (reference.flag === "read") continue;
|
|
64981
|
+
const writingFunction = findEnclosingFunction$1(reference.identifier);
|
|
64982
|
+
if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
|
|
64983
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64984
|
+
if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
|
|
64985
|
+
const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
|
|
64986
|
+
if (!isNodeOfType(functionBinding, "Identifier")) continue;
|
|
64987
|
+
const functionSymbol = context.scopes.symbolFor(functionBinding);
|
|
64988
|
+
if (!functionSymbol) continue;
|
|
64989
|
+
for (const functionReference of functionSymbol.references) {
|
|
64990
|
+
const callExpression = functionReference.identifier.parent;
|
|
64991
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
|
|
64992
|
+
for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
|
|
64993
|
+
if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
64994
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
64995
|
+
if (match) {
|
|
64996
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64997
|
+
return match;
|
|
64998
|
+
}
|
|
64999
|
+
}
|
|
65000
|
+
}
|
|
65001
|
+
}
|
|
65002
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65003
|
+
}
|
|
64287
65004
|
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
64288
65005
|
state.visitedSymbolIds.add(symbol.id);
|
|
64289
65006
|
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64290
65007
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64291
65008
|
return match;
|
|
64292
65009
|
}
|
|
65010
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
|
|
65011
|
+
const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
|
|
65012
|
+
allowConstNumericLiteral: true,
|
|
65013
|
+
stringifyNonStringLiterals: true
|
|
65014
|
+
});
|
|
65015
|
+
const object = stripParenExpression(unwrappedExpression.object);
|
|
65016
|
+
if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
|
|
65017
|
+
const callArguments = object.arguments ?? [];
|
|
65018
|
+
if (isReactApiCall(object, "useMemo", context.scopes, {
|
|
65019
|
+
allowGlobalReactNamespace: true,
|
|
65020
|
+
resolveNamedAliases: true
|
|
65021
|
+
})) {
|
|
65022
|
+
const callbackArgument = callArguments[0];
|
|
65023
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
65024
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
65025
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
|
|
65026
|
+
}
|
|
65027
|
+
const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
|
|
65028
|
+
return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
|
|
65029
|
+
}
|
|
64293
65030
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64294
65031
|
const callArguments = unwrappedExpression.arguments ?? [];
|
|
65032
|
+
if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
|
|
65033
|
+
allowGlobalReactNamespace: true,
|
|
65034
|
+
resolveNamedAliases: true
|
|
65035
|
+
})) {
|
|
65036
|
+
const initialState = callArguments[0];
|
|
65037
|
+
if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
|
|
65038
|
+
const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
|
|
65039
|
+
return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
|
|
65040
|
+
}
|
|
64295
65041
|
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
64296
65042
|
allowGlobalReactNamespace: true,
|
|
64297
65043
|
resolveNamedAliases: true
|
|
@@ -64319,6 +65065,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64319
65065
|
});
|
|
64320
65066
|
}
|
|
64321
65067
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
65068
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
65069
|
+
const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
|
|
65070
|
+
if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
|
|
65071
|
+
return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
|
|
65072
|
+
}
|
|
65073
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
65074
|
+
if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
|
|
65075
|
+
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
65076
|
+
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
65077
|
+
const nestedMatch = leftMatch ?? rightMatch;
|
|
65078
|
+
if (!nestedMatch) return null;
|
|
65079
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
|
|
65080
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
|
|
65081
|
+
if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
|
|
65082
|
+
return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
|
|
65083
|
+
}
|
|
64322
65084
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
64323
65085
|
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
64324
65086
|
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
@@ -64335,8 +65097,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
|
|
|
64335
65097
|
const consequentValues = getReturnedValues(statement.consequent);
|
|
64336
65098
|
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
64337
65099
|
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
65100
|
+
if (conditionMatch) {
|
|
65101
|
+
const followingReturnedValues = findFollowingReturnedValues(statement);
|
|
65102
|
+
if ([...new Set([...collectWrittenSymbols(statement.consequent, context.scopes), ...statement.alternate ? collectWrittenSymbols(statement.alternate, context.scopes) : []])].some((symbol) => !doesGuardPreserveInitialSymbolValue(symbol, statement, context.scopes) && followingReturnedValues.some((value) => doesNodeReadSymbol(value, symbol)))) return conditionMatch;
|
|
65103
|
+
}
|
|
64338
65104
|
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
64339
65105
|
}
|
|
65106
|
+
if (isNodeOfType(statement, "TryStatement")) return matchHydrationReturningStatement(statement.block, context, state) ?? (statement.handler ? matchHydrationReturningStatement(statement.handler.body, context, state) : null) ?? (statement.finalizer ? matchHydrationReturningStatement(statement.finalizer, context, state) : null);
|
|
64340
65107
|
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
64341
65108
|
for (const childStatement of statement.body) {
|
|
64342
65109
|
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
@@ -64357,47 +65124,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
|
|
|
64357
65124
|
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
64358
65125
|
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
64359
65126
|
});
|
|
64360
|
-
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
64361
|
-
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
65127
|
+
const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
|
|
65128
|
+
const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
|
|
64362
65129
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
64363
65130
|
const left = stripParenExpression(leftNode);
|
|
64364
65131
|
const right = stripParenExpression(rightNode);
|
|
64365
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65132
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64366
65133
|
if (left.type !== right.type) return false;
|
|
64367
65134
|
if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
|
|
64368
65135
|
if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
|
|
64369
65136
|
if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
|
|
64370
|
-
return areRenderedBranchesEquivalent(left.expression, right.expression);
|
|
65137
|
+
return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
|
|
64371
65138
|
}
|
|
64372
65139
|
if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
|
|
64373
65140
|
if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
|
|
64374
|
-
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
|
|
64375
|
-
return areNodeArraysEquivalent(left.children, right.children);
|
|
65141
|
+
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
|
|
65142
|
+
return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64376
65143
|
}
|
|
64377
|
-
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
|
|
65144
|
+
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64378
65145
|
if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
|
|
64379
65146
|
if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
|
|
64380
|
-
return areRenderedBranchesEquivalent(left.value, right.value);
|
|
65147
|
+
return areRenderedBranchesEquivalent(left.value, right.value, scopes);
|
|
64381
65148
|
}
|
|
64382
|
-
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
|
|
65149
|
+
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
|
|
64383
65150
|
if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
|
|
64384
65151
|
if (left.quasis.length !== right.quasis.length) return false;
|
|
64385
65152
|
if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
|
|
64386
|
-
return areNodeArraysEquivalent(left.expressions, right.expressions);
|
|
65153
|
+
return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
|
|
64387
65154
|
}
|
|
64388
65155
|
return false;
|
|
64389
65156
|
};
|
|
64390
|
-
const
|
|
65157
|
+
const isProvenReactCreateElementCall = (node, scopes) => {
|
|
65158
|
+
if (isReactApiCall(node, "createElement", scopes, {
|
|
65159
|
+
allowGlobalReactNamespace: true,
|
|
65160
|
+
resolveNamedAliases: true
|
|
65161
|
+
})) return true;
|
|
65162
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
65163
|
+
const callee = stripParenExpression(node.callee);
|
|
65164
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
|
|
65165
|
+
const receiver = stripParenExpression(callee.object);
|
|
65166
|
+
const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
|
|
65167
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
65168
|
+
const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
|
|
65169
|
+
return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
|
|
65170
|
+
};
|
|
65171
|
+
const isRenderedValue = (node, scopes) => {
|
|
64391
65172
|
const unwrappedNode = stripParenExpression(node);
|
|
64392
65173
|
if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
|
|
64393
65174
|
if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
|
|
65175
|
+
if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
|
|
64394
65176
|
return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
|
|
64395
65177
|
};
|
|
64396
|
-
const findRenderedValueInAndBranch = (node) => {
|
|
65178
|
+
const findRenderedValueInAndBranch = (node, scopes) => {
|
|
64397
65179
|
const unwrappedNode = stripParenExpression(node);
|
|
64398
|
-
if (
|
|
65180
|
+
if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
|
|
64399
65181
|
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
|
|
64400
|
-
return findRenderedValueInAndBranch(unwrappedNode.right);
|
|
65182
|
+
return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
|
|
64401
65183
|
};
|
|
64402
65184
|
const findEnclosingJsxAttribute = (node) => {
|
|
64403
65185
|
let currentNode = node.parent;
|
|
@@ -64430,6 +65212,11 @@ const getReturnedValues = (statement) => {
|
|
|
64430
65212
|
if (!statement) return [];
|
|
64431
65213
|
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
|
|
64432
65214
|
if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
|
|
65215
|
+
if (isNodeOfType(statement, "TryStatement")) return [
|
|
65216
|
+
...getReturnedValues(statement.block),
|
|
65217
|
+
...getReturnedValues(statement.handler?.body),
|
|
65218
|
+
...getReturnedValues(statement.finalizer)
|
|
65219
|
+
];
|
|
64433
65220
|
if (!isNodeOfType(statement, "BlockStatement")) return [];
|
|
64434
65221
|
const returnedValues = [];
|
|
64435
65222
|
for (const childStatement of statement.body) {
|
|
@@ -64438,6 +65225,127 @@ const getReturnedValues = (statement) => {
|
|
|
64438
65225
|
}
|
|
64439
65226
|
return returnedValues;
|
|
64440
65227
|
};
|
|
65228
|
+
const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
|
|
65229
|
+
const unwrappedNode = stripParenExpression(node);
|
|
65230
|
+
if (isRenderedValue(unwrappedNode, scopes)) return true;
|
|
65231
|
+
if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
|
|
65232
|
+
if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
|
|
65233
|
+
if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
|
|
65234
|
+
const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
|
|
65235
|
+
if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
|
|
65236
|
+
visitedFunctionNodes.add(calledFunction);
|
|
65237
|
+
const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
|
|
65238
|
+
const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
|
|
65239
|
+
visitedFunctionNodes.delete(calledFunction);
|
|
65240
|
+
return isPotentiallyRendered;
|
|
65241
|
+
};
|
|
65242
|
+
const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
|
|
65243
|
+
const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
|
|
65244
|
+
let currentNode = node.parent;
|
|
65245
|
+
while (currentNode && currentNode !== componentOrHookNode) {
|
|
65246
|
+
if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
|
|
65247
|
+
allowGlobalReactNamespace: true,
|
|
65248
|
+
resolveNamedAliases: true
|
|
65249
|
+
}) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
|
|
65250
|
+
const stateBinding = currentNode.parent.id.elements?.[0];
|
|
65251
|
+
return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
|
|
65252
|
+
}
|
|
65253
|
+
if (isFunctionLike$1(currentNode)) return null;
|
|
65254
|
+
currentNode = currentNode.parent;
|
|
65255
|
+
}
|
|
65256
|
+
return null;
|
|
65257
|
+
};
|
|
65258
|
+
const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
|
|
65259
|
+
let currentNode = referenceIdentifier;
|
|
65260
|
+
let parentNode = currentNode.parent;
|
|
65261
|
+
while (parentNode) {
|
|
65262
|
+
if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
|
|
65263
|
+
if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
|
|
65264
|
+
if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
|
|
65265
|
+
currentNode = parentNode;
|
|
65266
|
+
parentNode = currentNode.parent;
|
|
65267
|
+
}
|
|
65268
|
+
return false;
|
|
65269
|
+
};
|
|
65270
|
+
const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
|
|
65271
|
+
const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
|
|
65272
|
+
return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
|
|
65273
|
+
};
|
|
65274
|
+
const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
|
|
65275
|
+
if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
|
|
65276
|
+
const parentNode = node.parent;
|
|
65277
|
+
if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
|
|
65278
|
+
const aliasSymbol = scopes.symbolFor(parentNode.id);
|
|
65279
|
+
if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
|
|
65280
|
+
visitedSymbolIds.add(aliasSymbol.id);
|
|
65281
|
+
const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
|
|
65282
|
+
visitedSymbolIds.delete(aliasSymbol.id);
|
|
65283
|
+
return doesReachRenderedOutput;
|
|
65284
|
+
};
|
|
65285
|
+
const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
|
|
65286
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
|
|
65287
|
+
const consumerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
65288
|
+
if (!consumerSymbol) return false;
|
|
65289
|
+
return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
|
|
65290
|
+
};
|
|
65291
|
+
const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
|
|
65292
|
+
const unwrappedValue = stripParenExpression(returnedValue);
|
|
65293
|
+
if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
|
|
65294
|
+
if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
|
|
65295
|
+
const propertyName = getResolvedStaticPropertyName(property, scopes);
|
|
65296
|
+
return propertyName === null ? [] : [{
|
|
65297
|
+
kind: "property",
|
|
65298
|
+
key: propertyName
|
|
65299
|
+
}];
|
|
65300
|
+
});
|
|
65301
|
+
if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
|
|
65302
|
+
kind: "index",
|
|
65303
|
+
key: String(index)
|
|
65304
|
+
}] : []);
|
|
65305
|
+
return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
|
|
65306
|
+
kind: "direct",
|
|
65307
|
+
key: null
|
|
65308
|
+
}] : [];
|
|
65309
|
+
};
|
|
65310
|
+
const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
|
|
65311
|
+
const callParent = callExpression.parent;
|
|
65312
|
+
if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65313
|
+
if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
|
|
65314
|
+
allowConstNumericLiteral: true,
|
|
65315
|
+
stringifyNonStringLiterals: true
|
|
65316
|
+
}) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65317
|
+
if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
|
|
65318
|
+
if (returnedStatePath.kind === "property" && isNodeOfType(callParent.id, "ObjectPattern")) return callParent.id.properties.some((property) => isNodeOfType(property, "Property") && getResolvedStaticPropertyName(property, scopes) === returnedStatePath.key && doesConsumerBindingReachRenderedOutput(property.value, producerHookNode, scopes));
|
|
65319
|
+
if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
|
|
65320
|
+
const element = callParent.id.elements?.[Number(returnedStatePath.key)];
|
|
65321
|
+
return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
|
|
65322
|
+
}
|
|
65323
|
+
if (!isNodeOfType(callParent.id, "Identifier")) return false;
|
|
65324
|
+
const resultSymbol = scopes.symbolFor(callParent.id);
|
|
65325
|
+
if (!resultSymbol) return false;
|
|
65326
|
+
return resultSymbol.references.some((reference) => {
|
|
65327
|
+
const memberExpression = reference.identifier.parent;
|
|
65328
|
+
return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
|
|
65329
|
+
allowConstNumericLiteral: true,
|
|
65330
|
+
stringifyNonStringLiterals: true
|
|
65331
|
+
}) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
|
|
65332
|
+
});
|
|
65333
|
+
};
|
|
65334
|
+
const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
|
|
65335
|
+
if (!isFunctionLike$1(componentOrHookNode)) return false;
|
|
65336
|
+
const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
|
|
65337
|
+
if (!stateSymbol) return false;
|
|
65338
|
+
const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
|
|
65339
|
+
if (returnedStatePaths.length === 0) return false;
|
|
65340
|
+
const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
|
|
65341
|
+
if (!isNodeOfType(functionBinding, "Identifier")) return false;
|
|
65342
|
+
const functionSymbol = scopes.symbolFor(functionBinding);
|
|
65343
|
+
if (!functionSymbol) return false;
|
|
65344
|
+
return functionSymbol.references.some((functionReference) => {
|
|
65345
|
+
const callExpression = functionReference.identifier.parent;
|
|
65346
|
+
return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
|
|
65347
|
+
});
|
|
65348
|
+
};
|
|
64441
65349
|
const findFollowingReturnedValues = (ifStatement) => {
|
|
64442
65350
|
const parentNode = ifStatement.parent;
|
|
64443
65351
|
if (!isNodeOfType(parentNode, "BlockStatement")) return [];
|
|
@@ -64450,24 +65358,24 @@ const findFollowingReturnedValues = (ifStatement) => {
|
|
|
64450
65358
|
}
|
|
64451
65359
|
return returnedValues;
|
|
64452
65360
|
};
|
|
64453
|
-
const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
|
|
65361
|
+
const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
|
|
64454
65362
|
const left = stripParenExpression(leftExpression);
|
|
64455
65363
|
const right = stripParenExpression(rightExpression);
|
|
64456
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65364
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64457
65365
|
if (left.type !== right.type) return false;
|
|
64458
|
-
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
|
|
64459
|
-
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
64460
|
-
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
65366
|
+
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
|
|
65367
|
+
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
65368
|
+
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
64461
65369
|
return false;
|
|
64462
65370
|
};
|
|
64463
|
-
const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
|
|
65371
|
+
const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
|
|
64464
65372
|
if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
|
|
64465
|
-
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
|
|
64466
|
-
if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
|
|
65373
|
+
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
|
|
65374
|
+
if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test, scopes) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent, scopes) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate, scopes);
|
|
64467
65375
|
if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
|
|
64468
65376
|
const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64469
65377
|
const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64470
|
-
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
|
|
65378
|
+
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
|
|
64471
65379
|
};
|
|
64472
65380
|
const isStructuralRenderedValue = (node) => {
|
|
64473
65381
|
if (!node) return false;
|
|
@@ -64491,19 +65399,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64491
65399
|
if (isTestlikeFilename(context.filename)) return {};
|
|
64492
65400
|
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
64493
65401
|
let fileHasUseClientDirective = false;
|
|
65402
|
+
let fileHasExplicitReactRuntimeReference = false;
|
|
64494
65403
|
let fileIsEmailTemplate = false;
|
|
64495
65404
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
64496
|
-
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
|
|
65405
|
+
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
|
|
64497
65406
|
const conditionMatch = matchHydrationCondition(conditionNode, context);
|
|
64498
65407
|
if (!conditionMatch) return;
|
|
64499
65408
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
64500
65409
|
if (reportedNodes.has(predicateNode)) return;
|
|
64501
|
-
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
64502
|
-
const
|
|
65410
|
+
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
|
|
65411
|
+
const enclosingFunction = findEnclosingFunction$1(conditionNode);
|
|
65412
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
64503
65413
|
if (!componentOrHookNode) return;
|
|
64504
|
-
|
|
64505
|
-
if (
|
|
64506
|
-
if (!
|
|
65414
|
+
const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
|
|
65415
|
+
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
|
|
65416
|
+
if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
|
|
65417
|
+
if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
|
|
64507
65418
|
const attribute = findEnclosingJsxAttribute(conditionNode);
|
|
64508
65419
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
64509
65420
|
}
|
|
@@ -64522,28 +65433,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64522
65433
|
return {
|
|
64523
65434
|
Program(node) {
|
|
64524
65435
|
fileHasUseClientDirective = hasDirective(node, "use client");
|
|
65436
|
+
fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
|
|
64525
65437
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
64526
65438
|
},
|
|
64527
65439
|
ConditionalExpression(node) {
|
|
64528
65440
|
reportHydrationBranch(node.test, node.consequent, node.alternate, true);
|
|
65441
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
65442
|
+
if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
|
|
64529
65443
|
},
|
|
64530
65444
|
LogicalExpression(node) {
|
|
64531
65445
|
if (node.operator !== "&&" && node.operator !== "||") return;
|
|
64532
|
-
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) :
|
|
65446
|
+
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
|
|
64533
65447
|
if (!renderedValue) return;
|
|
64534
65448
|
reportHydrationBranch(node, renderedValue, null, true);
|
|
64535
65449
|
},
|
|
64536
65450
|
IfStatement(node) {
|
|
64537
|
-
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
|
|
65451
|
+
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
|
|
64538
65452
|
const consequentValues = getReturnedValues(node.consequent);
|
|
64539
65453
|
const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
|
|
64540
65454
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
64541
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
64542
|
-
if (!componentOrHookNode) return;
|
|
64543
65455
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
64544
|
-
|
|
65456
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
65457
|
+
if (!componentOrHookNode) return;
|
|
65458
|
+
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
|
|
64545
65459
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
64546
|
-
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
65460
|
+
if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
|
|
64547
65461
|
reportHydrationBranch(node.test, consequentValue, alternateValue, false);
|
|
64548
65462
|
}
|
|
64549
65463
|
}
|
|
@@ -67709,6 +68623,77 @@ const isCancellationGuardTest = (test) => {
|
|
|
67709
68623
|
});
|
|
67710
68624
|
return matches;
|
|
67711
68625
|
};
|
|
68626
|
+
const getReactRefCurrent = (expression, context) => {
|
|
68627
|
+
const stripped = stripParenExpression(expression);
|
|
68628
|
+
if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
|
|
68629
|
+
const receiver = stripParenExpression(stripped.object);
|
|
68630
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
68631
|
+
const binding = findVariableInitializer(receiver, receiver.name);
|
|
68632
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
68633
|
+
return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
|
|
68634
|
+
allowGlobalReactNamespace: true,
|
|
68635
|
+
allowUnboundBareCalls: true
|
|
68636
|
+
}) ? stripped : null;
|
|
68637
|
+
};
|
|
68638
|
+
const getStableOwnershipToken = (expression, context) => {
|
|
68639
|
+
const stripped = stripParenExpression(expression);
|
|
68640
|
+
if (!isNodeOfType(stripped, "Identifier")) return null;
|
|
68641
|
+
const symbol = context.scopes.symbolFor(stripped);
|
|
68642
|
+
const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
68643
|
+
const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
|
|
68644
|
+
return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
|
|
68645
|
+
};
|
|
68646
|
+
const getAsyncOwnershipComparison = (test, context) => {
|
|
68647
|
+
const stripped = stripParenExpression(test);
|
|
68648
|
+
if (!isNodeOfType(stripped, "BinaryExpression")) return null;
|
|
68649
|
+
const leftRef = getReactRefCurrent(stripped.left, context);
|
|
68650
|
+
const rightRef = getReactRefCurrent(stripped.right, context);
|
|
68651
|
+
const leftToken = getStableOwnershipToken(stripped.left, context);
|
|
68652
|
+
const rightToken = getStableOwnershipToken(stripped.right, context);
|
|
68653
|
+
if (stripped.operator === "===" || stripped.operator === "==") {
|
|
68654
|
+
if (leftRef && rightToken) return {
|
|
68655
|
+
refCurrent: leftRef,
|
|
68656
|
+
token: rightToken,
|
|
68657
|
+
mode: "owns",
|
|
68658
|
+
isOrdered: false
|
|
68659
|
+
};
|
|
68660
|
+
if (rightRef && leftToken) return {
|
|
68661
|
+
refCurrent: rightRef,
|
|
68662
|
+
token: leftToken,
|
|
68663
|
+
mode: "owns",
|
|
68664
|
+
isOrdered: false
|
|
68665
|
+
};
|
|
68666
|
+
return null;
|
|
68667
|
+
}
|
|
68668
|
+
if (stripped.operator === "!==" || stripped.operator === "!=") {
|
|
68669
|
+
if (leftRef && rightToken) return {
|
|
68670
|
+
refCurrent: leftRef,
|
|
68671
|
+
token: rightToken,
|
|
68672
|
+
mode: "lost",
|
|
68673
|
+
isOrdered: false
|
|
68674
|
+
};
|
|
68675
|
+
if (rightRef && leftToken) return {
|
|
68676
|
+
refCurrent: rightRef,
|
|
68677
|
+
token: leftToken,
|
|
68678
|
+
mode: "lost",
|
|
68679
|
+
isOrdered: false
|
|
68680
|
+
};
|
|
68681
|
+
return null;
|
|
68682
|
+
}
|
|
68683
|
+
if (stripped.operator === "<=" && leftRef && rightToken) return {
|
|
68684
|
+
refCurrent: leftRef,
|
|
68685
|
+
token: rightToken,
|
|
68686
|
+
mode: "owns",
|
|
68687
|
+
isOrdered: true
|
|
68688
|
+
};
|
|
68689
|
+
if (stripped.operator === ">=" && rightRef && leftToken) return {
|
|
68690
|
+
refCurrent: rightRef,
|
|
68691
|
+
token: leftToken,
|
|
68692
|
+
mode: "owns",
|
|
68693
|
+
isOrdered: true
|
|
68694
|
+
};
|
|
68695
|
+
return null;
|
|
68696
|
+
};
|
|
67712
68697
|
const dedupeCatchPathStates = (states) => {
|
|
67713
68698
|
const statesByKey = /* @__PURE__ */ new Map();
|
|
67714
68699
|
for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
|
|
@@ -68041,7 +69026,221 @@ const isInsideTryFinalizer = (node, tryStatement) => {
|
|
|
68041
69026
|
}
|
|
68042
69027
|
return false;
|
|
68043
69028
|
};
|
|
68044
|
-
const
|
|
69029
|
+
const getDirectBlockEntry = (node, functionNode) => {
|
|
69030
|
+
let entry = node;
|
|
69031
|
+
let cursor = node.parent;
|
|
69032
|
+
while (cursor && cursor !== functionNode) {
|
|
69033
|
+
if (isNodeOfType(cursor, "BlockStatement")) return {
|
|
69034
|
+
block: cursor,
|
|
69035
|
+
entry
|
|
69036
|
+
};
|
|
69037
|
+
entry = cursor;
|
|
69038
|
+
cursor = cursor.parent ?? null;
|
|
69039
|
+
}
|
|
69040
|
+
return null;
|
|
69041
|
+
};
|
|
69042
|
+
const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
|
|
69043
|
+
const claimStart = getNodeStart$1(claimNode);
|
|
69044
|
+
if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
|
|
69045
|
+
const claimEntry = getDirectBlockEntry(claimNode, functionNode);
|
|
69046
|
+
const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
|
|
69047
|
+
if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
|
|
69048
|
+
let claimCursor = claimNode.parent;
|
|
69049
|
+
while (claimCursor && claimCursor !== claimEntry.block) {
|
|
69050
|
+
if (isNodeOfType(claimCursor, "IfStatement") || isNodeOfType(claimCursor, "SwitchCase") || isNodeOfType(claimCursor, "ConditionalExpression") || isNodeOfType(claimCursor, "LogicalExpression") || isNodeOfType(claimCursor, "ForStatement") || isNodeOfType(claimCursor, "ForInStatement") || isNodeOfType(claimCursor, "ForOfStatement") || isNodeOfType(claimCursor, "WhileStatement") || isNodeOfType(claimCursor, "DoWhileStatement")) return false;
|
|
69051
|
+
claimCursor = claimCursor.parent ?? null;
|
|
69052
|
+
}
|
|
69053
|
+
const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
|
|
69054
|
+
const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
|
|
69055
|
+
if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
|
|
69056
|
+
return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
|
|
69057
|
+
};
|
|
69058
|
+
const getOwningFunction = (functionNode) => {
|
|
69059
|
+
let ownerFunction = functionNode;
|
|
69060
|
+
let cursor = functionNode.parent;
|
|
69061
|
+
while (cursor) {
|
|
69062
|
+
if (isFunctionLike$1(cursor)) ownerFunction = cursor;
|
|
69063
|
+
cursor = cursor.parent ?? null;
|
|
69064
|
+
}
|
|
69065
|
+
return ownerFunction;
|
|
69066
|
+
};
|
|
69067
|
+
const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
|
|
69068
|
+
const truthyCall = truthySets[0]?.node;
|
|
69069
|
+
if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
|
|
69070
|
+
const setter = getSetterBooleanValue(truthyCall, context);
|
|
69071
|
+
if (!setter) return false;
|
|
69072
|
+
let effectCallback = writeNode.parent;
|
|
69073
|
+
while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
|
|
69074
|
+
if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
|
|
69075
|
+
if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
|
|
69076
|
+
const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
|
|
69077
|
+
if (!writeEntry) return false;
|
|
69078
|
+
let isPaired = false;
|
|
69079
|
+
walkOwnFunctionScope(effectCallback, (candidate) => {
|
|
69080
|
+
if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
|
|
69081
|
+
const candidateSetter = getSetterBooleanValue(candidate, context);
|
|
69082
|
+
if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
|
|
69083
|
+
const resetEntry = getDirectBlockEntry(candidate, effectCallback);
|
|
69084
|
+
if (!resetEntry || resetEntry.block !== writeEntry.block) return;
|
|
69085
|
+
const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
|
|
69086
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69087
|
+
if (writeIndex === -1 || resetIndex === -1) return;
|
|
69088
|
+
if (resetIndex <= writeIndex) {
|
|
69089
|
+
isPaired = true;
|
|
69090
|
+
return false;
|
|
69091
|
+
}
|
|
69092
|
+
isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
|
|
69093
|
+
return isPaired ? false : void 0;
|
|
69094
|
+
});
|
|
69095
|
+
return isPaired;
|
|
69096
|
+
};
|
|
69097
|
+
const isUnconditionalReturnBranch = (statement) => {
|
|
69098
|
+
if (isNodeOfType(statement, "ReturnStatement")) return true;
|
|
69099
|
+
return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
|
|
69100
|
+
};
|
|
69101
|
+
const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69102
|
+
const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
|
|
69103
|
+
const resetEntry = getDirectBlockEntry(resetNode, functionNode);
|
|
69104
|
+
if (!snapshotEntry || !resetEntry) return null;
|
|
69105
|
+
const claimCandidates = [];
|
|
69106
|
+
const releaseCandidates = [];
|
|
69107
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69108
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
|
|
69109
|
+
const assignedValue = stripParenExpression(candidate.right);
|
|
69110
|
+
if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
|
|
69111
|
+
const candidateKey = serializeReferenceKey({
|
|
69112
|
+
node: candidate.left,
|
|
69113
|
+
scopes: context.scopes
|
|
69114
|
+
});
|
|
69115
|
+
if (!candidateKey) return;
|
|
69116
|
+
if (!assignedValue.value) {
|
|
69117
|
+
if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
|
|
69118
|
+
return;
|
|
69119
|
+
}
|
|
69120
|
+
if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
|
|
69121
|
+
const candidateEntry = getDirectBlockEntry(candidate, functionNode);
|
|
69122
|
+
if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
|
|
69123
|
+
const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
|
|
69124
|
+
const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
|
|
69125
|
+
if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
|
|
69126
|
+
const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
|
|
69127
|
+
if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
|
|
69128
|
+
return serializeReferenceKey({
|
|
69129
|
+
node: stripParenExpression(statement.test),
|
|
69130
|
+
scopes: context.scopes
|
|
69131
|
+
}) === candidateKey;
|
|
69132
|
+
});
|
|
69133
|
+
if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
|
|
69134
|
+
claimCandidates.push(candidate);
|
|
69135
|
+
});
|
|
69136
|
+
const claim = claimCandidates.find((claimCandidate) => {
|
|
69137
|
+
const candidateKey = serializeReferenceKey({
|
|
69138
|
+
node: claimCandidate.left,
|
|
69139
|
+
scopes: context.scopes
|
|
69140
|
+
});
|
|
69141
|
+
return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
|
|
69142
|
+
node: releaseCandidate.left,
|
|
69143
|
+
scopes: context.scopes
|
|
69144
|
+
}) === candidateKey);
|
|
69145
|
+
});
|
|
69146
|
+
if (!claim) return null;
|
|
69147
|
+
const claimKey = serializeReferenceKey({
|
|
69148
|
+
node: claim.left,
|
|
69149
|
+
scopes: context.scopes
|
|
69150
|
+
});
|
|
69151
|
+
const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
|
|
69152
|
+
node: releaseCandidate.left,
|
|
69153
|
+
scopes: context.scopes
|
|
69154
|
+
}) === claimKey);
|
|
69155
|
+
if (!claimKey || !release) return null;
|
|
69156
|
+
const releaseEntry = getDirectBlockEntry(release, functionNode);
|
|
69157
|
+
if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
|
|
69158
|
+
const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
|
|
69159
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69160
|
+
if (releaseIndex === -1 || resetIndex === -1 || !resetEntry.block.body.slice(Math.min(releaseIndex, resetIndex) + 1, Math.max(releaseIndex, resetIndex)).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return null;
|
|
69161
|
+
let didFindUnsafeWrite = false;
|
|
69162
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69163
|
+
if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
|
|
69164
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69165
|
+
if (writeTarget && serializeReferenceKey({
|
|
69166
|
+
node: writeTarget,
|
|
69167
|
+
scopes: context.scopes
|
|
69168
|
+
}) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
|
|
69169
|
+
});
|
|
69170
|
+
return didFindUnsafeWrite ? null : claim;
|
|
69171
|
+
};
|
|
69172
|
+
const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69173
|
+
const refKey = serializeReferenceKey({
|
|
69174
|
+
node: comparison.refCurrent,
|
|
69175
|
+
scopes: context.scopes
|
|
69176
|
+
});
|
|
69177
|
+
const tokenKey = serializeReferenceKey({
|
|
69178
|
+
node: comparison.token,
|
|
69179
|
+
scopes: context.scopes
|
|
69180
|
+
});
|
|
69181
|
+
if (!refKey || !tokenKey) return null;
|
|
69182
|
+
const candidates = [];
|
|
69183
|
+
const tokenSymbol = context.scopes.symbolFor(comparison.token);
|
|
69184
|
+
const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
|
|
69185
|
+
if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
|
|
69186
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
|
|
69187
|
+
node: tokenInitializer.argument,
|
|
69188
|
+
scopes: context.scopes
|
|
69189
|
+
}) === refKey) candidates.push(tokenInitializer);
|
|
69190
|
+
if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
|
|
69191
|
+
node: tokenInitializer,
|
|
69192
|
+
scopes: context.scopes
|
|
69193
|
+
}) === refKey) {
|
|
69194
|
+
const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69195
|
+
if (singleFlightClaim) candidates.push(singleFlightClaim);
|
|
69196
|
+
}
|
|
69197
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
|
|
69198
|
+
const generationKey = serializeReferenceKey({
|
|
69199
|
+
node: tokenInitializer.argument,
|
|
69200
|
+
scopes: context.scopes
|
|
69201
|
+
});
|
|
69202
|
+
if (generationKey && generationKey === refKey) {
|
|
69203
|
+
const ownerFunction = getOwningFunction(functionNode);
|
|
69204
|
+
let didFindOtherGenerationWrite = false;
|
|
69205
|
+
walkAst(ownerFunction, (candidate) => {
|
|
69206
|
+
if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
|
|
69207
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69208
|
+
if (writeTarget && serializeReferenceKey({
|
|
69209
|
+
node: writeTarget,
|
|
69210
|
+
scopes: context.scopes
|
|
69211
|
+
}) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
|
|
69212
|
+
});
|
|
69213
|
+
if (didFindOtherGenerationWrite) return null;
|
|
69214
|
+
}
|
|
69215
|
+
}
|
|
69216
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69217
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
|
|
69218
|
+
if (serializeReferenceKey({
|
|
69219
|
+
node: candidate.left,
|
|
69220
|
+
scopes: context.scopes
|
|
69221
|
+
}) === refKey && serializeReferenceKey({
|
|
69222
|
+
node: candidate.right,
|
|
69223
|
+
scopes: context.scopes
|
|
69224
|
+
}) === tokenKey) candidates.push(candidate);
|
|
69225
|
+
});
|
|
69226
|
+
const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
|
|
69227
|
+
if (!claim) return null;
|
|
69228
|
+
let didFindOtherWrite = false;
|
|
69229
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69230
|
+
if (didFindOtherWrite || candidate === claim) return;
|
|
69231
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69232
|
+
if (writeTarget && serializeReferenceKey({
|
|
69233
|
+
node: writeTarget,
|
|
69234
|
+
scopes: context.scopes
|
|
69235
|
+
}) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
|
|
69236
|
+
});
|
|
69237
|
+
return didFindOtherWrite ? null : claim;
|
|
69238
|
+
};
|
|
69239
|
+
const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69240
|
+
const comparison = getAsyncOwnershipComparison(test, context);
|
|
69241
|
+
return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
|
|
69242
|
+
};
|
|
69243
|
+
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
|
|
68045
69244
|
let didFindOtherWrite = false;
|
|
68046
69245
|
walkAst(effectCallback, (candidate) => {
|
|
68047
69246
|
if (didFindOtherWrite) return false;
|
|
@@ -68049,7 +69248,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
68049
69248
|
if (serializeReferenceKey({
|
|
68050
69249
|
node: candidate.left,
|
|
68051
69250
|
scopes: context.scopes
|
|
68052
|
-
}) === guardKey && !
|
|
69251
|
+
}) === guardKey && !acceptedAssignments.has(candidate)) {
|
|
68053
69252
|
didFindOtherWrite = true;
|
|
68054
69253
|
return false;
|
|
68055
69254
|
}
|
|
@@ -68065,48 +69264,103 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
68065
69264
|
});
|
|
68066
69265
|
return didFindOtherWrite;
|
|
68067
69266
|
};
|
|
68068
|
-
const
|
|
69267
|
+
const collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
|
|
69268
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69269
|
+
for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69270
|
+
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69271
|
+
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69272
|
+
node: cleanupNode.left,
|
|
69273
|
+
scopes: context.scopes
|
|
69274
|
+
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69275
|
+
acceptedAssignments.add(cleanupNode);
|
|
69276
|
+
});
|
|
69277
|
+
if (acceptedAssignments.size === 0) return null;
|
|
69278
|
+
walkOwnFunctionScope(effectCallback, (effectNode) => {
|
|
69279
|
+
const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
|
|
69280
|
+
if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
|
|
69281
|
+
node: effectNode.left,
|
|
69282
|
+
scopes: context.scopes
|
|
69283
|
+
}) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
|
|
69284
|
+
});
|
|
69285
|
+
return acceptedAssignments;
|
|
69286
|
+
};
|
|
69287
|
+
const isEffectCallback = (node, context) => {
|
|
69288
|
+
const callbackRoot = findTransparentExpressionRoot(node);
|
|
69289
|
+
const callbackCall = callbackRoot.parent;
|
|
69290
|
+
return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69291
|
+
allowGlobalReactNamespace: true,
|
|
69292
|
+
allowUnboundBareCalls: true
|
|
69293
|
+
}));
|
|
69294
|
+
};
|
|
69295
|
+
const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
|
|
69296
|
+
const guardKey = serializeReferenceKey({
|
|
69297
|
+
node: guardExpression,
|
|
69298
|
+
scopes: context.scopes
|
|
69299
|
+
});
|
|
69300
|
+
if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69301
|
+
let ownerFunction = functionNode.parent;
|
|
69302
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
69303
|
+
if (!ownerFunction) return false;
|
|
69304
|
+
const effectCallbacks = [];
|
|
69305
|
+
if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
|
|
69306
|
+
walkOwnFunctionScope(ownerFunction, (candidate) => {
|
|
69307
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
69308
|
+
if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69309
|
+
allowGlobalReactNamespace: true,
|
|
69310
|
+
allowUnboundBareCalls: true
|
|
69311
|
+
})) return;
|
|
69312
|
+
const effectCallback = candidate.arguments[0];
|
|
69313
|
+
if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
|
|
69314
|
+
});
|
|
69315
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69316
|
+
for (const effectCallback of effectCallbacks) {
|
|
69317
|
+
const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
|
|
69318
|
+
if (!effectAssignments) continue;
|
|
69319
|
+
for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
|
|
69320
|
+
}
|
|
69321
|
+
return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
|
|
69322
|
+
};
|
|
69323
|
+
const collectLogicalOperands = (expression, operator) => {
|
|
69324
|
+
const stripped = stripParenExpression(expression);
|
|
69325
|
+
if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
|
|
69326
|
+
return [stripped];
|
|
69327
|
+
};
|
|
69328
|
+
const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
|
|
69329
|
+
const positive = [];
|
|
69330
|
+
const negative = [];
|
|
68069
69331
|
let child = resetNode;
|
|
68070
69332
|
let cursor = resetNode.parent;
|
|
68071
|
-
|
|
68072
|
-
|
|
68073
|
-
|
|
68074
|
-
|
|
68075
|
-
|
|
68076
|
-
|
|
68077
|
-
|
|
68078
|
-
|
|
68079
|
-
|
|
68080
|
-
|
|
68081
|
-
|
|
69333
|
+
while (cursor && cursor !== protectingTry) {
|
|
69334
|
+
if (isNodeOfType(cursor, "IfStatement")) {
|
|
69335
|
+
if (cursor.consequent !== child || cursor.alternate !== null) return null;
|
|
69336
|
+
positive.push(...collectLogicalOperands(cursor.test, "&&"));
|
|
69337
|
+
} else if (isNodeOfType(cursor, "LogicalExpression")) {
|
|
69338
|
+
if (cursor.operator !== "&&" || cursor.right !== child) return null;
|
|
69339
|
+
positive.push(...collectLogicalOperands(cursor.left, "&&"));
|
|
69340
|
+
} else if (isNodeOfType(cursor, "BlockStatement")) {
|
|
69341
|
+
const childIndex = cursor.body.findIndex((statement) => statement === child);
|
|
69342
|
+
if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
|
|
69343
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
|
|
69344
|
+
negative.push(...collectLogicalOperands(statement.test, "||"));
|
|
69345
|
+
}
|
|
69346
|
+
} else if (isNodeOfType(cursor, "SwitchCase") || isNodeOfType(cursor, "ConditionalExpression") || isNodeOfType(cursor, "ForStatement") || isNodeOfType(cursor, "ForInStatement") || isNodeOfType(cursor, "ForOfStatement") || isNodeOfType(cursor, "WhileStatement") || isNodeOfType(cursor, "DoWhileStatement")) return null;
|
|
68082
69347
|
child = cursor;
|
|
68083
69348
|
cursor = cursor.parent ?? null;
|
|
68084
69349
|
}
|
|
68085
|
-
|
|
68086
|
-
|
|
68087
|
-
|
|
68088
|
-
|
|
68089
|
-
|
|
68090
|
-
|
|
68091
|
-
|
|
68092
|
-
|
|
68093
|
-
|
|
68094
|
-
|
|
68095
|
-
|
|
68096
|
-
|
|
68097
|
-
|
|
68098
|
-
|
|
68099
|
-
node: cleanupNode.left,
|
|
68100
|
-
scopes: context.scopes
|
|
68101
|
-
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
68102
|
-
acceptedCleanupAssignments.add(cleanupNode);
|
|
68103
|
-
});
|
|
68104
|
-
if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
|
|
68105
|
-
}
|
|
68106
|
-
}
|
|
68107
|
-
cursor = cursor.parent ?? null;
|
|
68108
|
-
}
|
|
68109
|
-
return false;
|
|
69350
|
+
return cursor === protectingTry && positive.length + negative.length > 0 ? {
|
|
69351
|
+
positive,
|
|
69352
|
+
negative
|
|
69353
|
+
} : null;
|
|
69354
|
+
};
|
|
69355
|
+
const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69356
|
+
const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69357
|
+
const stripped = stripParenExpression(expression);
|
|
69358
|
+
if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
|
|
69359
|
+
return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69360
|
+
};
|
|
69361
|
+
const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69362
|
+
const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
|
|
69363
|
+
return Boolean(guards && guards.positive.every((guard) => isPositiveFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)) && guards.negative.every((guard) => isNegativeFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)));
|
|
68110
69364
|
};
|
|
68111
69365
|
const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
|
|
68112
69366
|
let child = awaitNode;
|
|
@@ -68212,7 +69466,13 @@ const analyzeFunction = (functionNode, context) => {
|
|
|
68212
69466
|
const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
|
|
68213
69467
|
const riskyAwaitsWithTruthySet = awaitSites.filter((awaitSite) => rejectingAwaitNodes.has(awaitSite.node) && !exceptionallyProtectedAwaits.has(awaitSite.node) && truthySets.some((truthySet) => truthySet.start < awaitSite.start && !areOnExclusiveBranches(truthySet.node, awaitSite.node, functionNode)));
|
|
68214
69468
|
if (riskyAwaitsWithTruthySet.length === 0) continue;
|
|
68215
|
-
const conditionalExceptionalResets = calls.filter((call) =>
|
|
69469
|
+
const conditionalExceptionalResets = calls.filter((call) => {
|
|
69470
|
+
if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
|
|
69471
|
+
const protectingTry = call.protectingTry;
|
|
69472
|
+
if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
|
|
69473
|
+
const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
|
|
69474
|
+
return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
|
|
69475
|
+
});
|
|
68216
69476
|
for (const reset of conditionalExceptionalResets) {
|
|
68217
69477
|
const catchHandler = reset.protectingTry?.handler;
|
|
68218
69478
|
if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
|
|
@@ -74084,6 +75344,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
74084
75344
|
}
|
|
74085
75345
|
return !isNegated && predicateFunction.body === child;
|
|
74086
75346
|
};
|
|
75347
|
+
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75348
|
+
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75349
|
+
const returnStatement = predicateFunction.body.body[1];
|
|
75350
|
+
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75351
|
+
let negationCount = 0;
|
|
75352
|
+
let expression = matchCall;
|
|
75353
|
+
let parent = expression.parent ?? null;
|
|
75354
|
+
while (parent && parent !== returnStatement) {
|
|
75355
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75356
|
+
negationCount += 1;
|
|
75357
|
+
expression = parent;
|
|
75358
|
+
parent = parent.parent ?? null;
|
|
75359
|
+
continue;
|
|
75360
|
+
}
|
|
75361
|
+
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75362
|
+
expression = parent;
|
|
75363
|
+
parent = parent.parent ?? null;
|
|
75364
|
+
continue;
|
|
75365
|
+
}
|
|
75366
|
+
return false;
|
|
75367
|
+
}
|
|
75368
|
+
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75369
|
+
};
|
|
74087
75370
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
74088
75371
|
const target = stripParenExpression(test);
|
|
74089
75372
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -74124,6 +75407,26 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
74124
75407
|
current = current.object;
|
|
74125
75408
|
}
|
|
74126
75409
|
};
|
|
75410
|
+
const getNormalizedClassNameRoot = (expression) => {
|
|
75411
|
+
const conditional = stripParenExpression(expression);
|
|
75412
|
+
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75413
|
+
const consequent = stripParenExpression(conditional.consequent);
|
|
75414
|
+
const rootIdentifier = getRootIdentifier(consequent);
|
|
75415
|
+
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75416
|
+
const test = stripParenExpression(conditional.test);
|
|
75417
|
+
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75418
|
+
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75419
|
+
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75420
|
+
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75421
|
+
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75422
|
+
const alternate = stripParenExpression(conditional.alternate);
|
|
75423
|
+
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75424
|
+
const fallback = stripParenExpression(alternate.right);
|
|
75425
|
+
const attributeCall = stripParenExpression(alternate.left);
|
|
75426
|
+
if (!isNodeOfType(fallback, "Literal") || fallback.value !== "" || !isNodeOfType(attributeCall, "CallExpression") || !isNodeOfType(attributeCall.callee, "MemberExpression") || getStaticPropertyName(attributeCall.callee) !== "getAttribute" || receiverPathKey(attributeCall.callee.object) !== rootIdentifier.name) return null;
|
|
75427
|
+
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75428
|
+
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75429
|
+
};
|
|
74127
75430
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
74128
75431
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
74129
75432
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -74132,11 +75435,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
74132
75435
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
74133
75436
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
74134
75437
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
74135
|
-
if (resultSymbol?.kind !== "const" || !initializer
|
|
75438
|
+
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75439
|
+
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75440
|
+
const alternate = stripParenExpression(initializer.alternate);
|
|
75441
|
+
const consequent = stripParenExpression(initializer.consequent);
|
|
75442
|
+
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75443
|
+
})() : null;
|
|
75444
|
+
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
74136
75445
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
74137
|
-
const finderCallee = stripParenExpression(
|
|
75446
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
74138
75447
|
if (!isNodeOfType(finderCallee, "Identifier") || !(context.scopes.symbolFor(finderCallee)?.kind === "import" && getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) === "findUpUntil" || finderCallee.name === "findUpUntil" && context.scopes.isGlobalReference(finderCallee))) return false;
|
|
74139
|
-
const predicateArgument =
|
|
75448
|
+
const predicateArgument = finderCall.arguments[1];
|
|
74140
75449
|
if (!predicateArgument) return false;
|
|
74141
75450
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
74142
75451
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -74160,6 +75469,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
74160
75469
|
});
|
|
74161
75470
|
return didProveMatch;
|
|
74162
75471
|
};
|
|
75472
|
+
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75473
|
+
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75474
|
+
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75475
|
+
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75476
|
+
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75477
|
+
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75478
|
+
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75479
|
+
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75480
|
+
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75481
|
+
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75482
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75483
|
+
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75484
|
+
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75485
|
+
const expression = stripParenExpression(test);
|
|
75486
|
+
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75487
|
+
})) return false;
|
|
75488
|
+
const predicateArgument = finderCall.arguments[1];
|
|
75489
|
+
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75490
|
+
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75491
|
+
const predicateParameter = predicateFunction.params[0];
|
|
75492
|
+
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75493
|
+
let didProveNormalizedMatch = false;
|
|
75494
|
+
walkAst(predicateFunction.body, (child) => {
|
|
75495
|
+
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75496
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || getStaticPropertyName(child.callee) !== "match" || !child.arguments[0] || !areRegexPatternsEquivalent(child.arguments[0], assertedPattern, context) || !doesPredicateTruthRequireMatch(child, predicateFunction) && !doesPredicateReturnNormalizedMatch(child, predicateFunction)) return;
|
|
75497
|
+
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75498
|
+
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75499
|
+
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75500
|
+
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75501
|
+
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75502
|
+
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75503
|
+
didProveNormalizedMatch = true;
|
|
75504
|
+
return false;
|
|
75505
|
+
}
|
|
75506
|
+
});
|
|
75507
|
+
return didProveNormalizedMatch;
|
|
75508
|
+
};
|
|
74163
75509
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
74164
75510
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
74165
75511
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -74260,6 +75606,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
74260
75606
|
}
|
|
74261
75607
|
return false;
|
|
74262
75608
|
};
|
|
75609
|
+
const getReceiverRootIdentifier = (node) => {
|
|
75610
|
+
let target = stripParenExpression(node);
|
|
75611
|
+
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75612
|
+
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75613
|
+
};
|
|
75614
|
+
const getReceiverStatePath = (node) => {
|
|
75615
|
+
const target = stripParenExpression(node);
|
|
75616
|
+
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75617
|
+
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75618
|
+
const objectPath = getReceiverStatePath(target.object);
|
|
75619
|
+
if (!objectPath) return null;
|
|
75620
|
+
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75621
|
+
};
|
|
75622
|
+
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75623
|
+
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75624
|
+
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75625
|
+
const receiverPath = getReceiverStatePath(receiver);
|
|
75626
|
+
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75627
|
+
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75628
|
+
let didAddAlias = true;
|
|
75629
|
+
while (didAddAlias) {
|
|
75630
|
+
didAddAlias = false;
|
|
75631
|
+
walkAst(ownerFunction, (child) => {
|
|
75632
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75633
|
+
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75634
|
+
const initializer = stripParenExpression(child.init);
|
|
75635
|
+
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75636
|
+
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75637
|
+
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75638
|
+
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75639
|
+
const initializerPath = getReceiverStatePath(initializer);
|
|
75640
|
+
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75641
|
+
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75642
|
+
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75643
|
+
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75644
|
+
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75645
|
+
didAddAlias = true;
|
|
75646
|
+
}
|
|
75647
|
+
});
|
|
75648
|
+
}
|
|
75649
|
+
let didChangeReceiverState = false;
|
|
75650
|
+
walkAst(ownerFunction, (child) => {
|
|
75651
|
+
if (didChangeReceiverState) return false;
|
|
75652
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75653
|
+
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75654
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
75655
|
+
didChangeReceiverState = true;
|
|
75656
|
+
return false;
|
|
75657
|
+
}
|
|
75658
|
+
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75659
|
+
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75660
|
+
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75661
|
+
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75662
|
+
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75663
|
+
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75664
|
+
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75665
|
+
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75666
|
+
didChangeReceiverState = true;
|
|
75667
|
+
return false;
|
|
75668
|
+
}
|
|
75669
|
+
});
|
|
75670
|
+
return didChangeReceiverState;
|
|
75671
|
+
};
|
|
75672
|
+
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75673
|
+
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75674
|
+
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75675
|
+
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75676
|
+
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75677
|
+
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75678
|
+
if (maximumSymbol?.kind !== "const" || maximumSymbol.references.some((reference) => reference.flag !== "read") || !maximumInitializer || !isNodeOfType(maximumInitializer, "CallExpression") || !isNodeOfType(maximumInitializer.callee, "MemberExpression") || getStaticPropertyName(maximumInitializer.callee) !== "reduce") return false;
|
|
75679
|
+
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75680
|
+
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75681
|
+
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75682
|
+
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75683
|
+
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75684
|
+
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75685
|
+
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75686
|
+
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75687
|
+
const accumulatorParameter = reducerFunction.params[0];
|
|
75688
|
+
const itemParameter = reducerFunction.params[1];
|
|
75689
|
+
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75690
|
+
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75691
|
+
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75692
|
+
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75693
|
+
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75694
|
+
const expression = stripParenExpression(argument);
|
|
75695
|
+
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75696
|
+
});
|
|
75697
|
+
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75698
|
+
const expression = stripParenExpression(argument);
|
|
75699
|
+
const rootIdentifier = getRootIdentifier(expression);
|
|
75700
|
+
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75701
|
+
});
|
|
75702
|
+
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75703
|
+
if (!receiverPathKey(findReceiver)) return false;
|
|
75704
|
+
let ownerFunction = assertion.parent ?? null;
|
|
75705
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75706
|
+
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75707
|
+
const maximumEnd = maximumInitializer.range[1];
|
|
75708
|
+
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75709
|
+
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75710
|
+
const comparison = stripParenExpression(test);
|
|
75711
|
+
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75712
|
+
return [[
|
|
75713
|
+
comparison.left,
|
|
75714
|
+
comparison.right,
|
|
75715
|
+
comparison.operator
|
|
75716
|
+
], [
|
|
75717
|
+
comparison.right,
|
|
75718
|
+
comparison.left,
|
|
75719
|
+
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75720
|
+
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75721
|
+
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75722
|
+
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75723
|
+
});
|
|
75724
|
+
});
|
|
75725
|
+
};
|
|
74263
75726
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
74264
75727
|
if (!value) return false;
|
|
74265
75728
|
const expression = stripParenExpression(value);
|
|
@@ -74283,15 +75746,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
74283
75746
|
};
|
|
74284
75747
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
74285
75748
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
74286
|
-
|
|
75749
|
+
const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
|
|
75750
|
+
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75751
|
+
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
|
|
74287
75752
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
74288
75753
|
if (!receiverSymbol) return false;
|
|
74289
75754
|
const receiverMatches = (candidate) => {
|
|
74290
75755
|
const target = stripParenExpression(candidate);
|
|
74291
75756
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
74292
75757
|
};
|
|
74293
|
-
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74294
|
-
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
74295
75758
|
let child = assertion;
|
|
74296
75759
|
let ancestor = assertion.parent ?? null;
|
|
74297
75760
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -74311,7 +75774,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
74311
75774
|
const populationCall = populationCalls[0];
|
|
74312
75775
|
if (!populationCall) continue;
|
|
74313
75776
|
const populationCallStart = populationCall.range[0];
|
|
74314
|
-
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
|
|
75777
|
+
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) || Boolean(lookupKeySymbol && isNodeOfType(stableLookupKey, "MemberExpression") && subtreeWritesSymbol(ancestor, new Set([lookupKeySymbol.id]), context, void 0, assertion))) continue;
|
|
74315
75778
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
74316
75779
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
74317
75780
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -74421,6 +75884,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74421
75884
|
const findReceiver = callee.object;
|
|
74422
75885
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
74423
75886
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
75887
|
+
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
74424
75888
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
74425
75889
|
}
|
|
74426
75890
|
if (methodName === "match") {
|
|
@@ -74430,6 +75894,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74430
75894
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
74431
75895
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
74432
75896
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
75897
|
+
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
74433
75898
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
74434
75899
|
}
|
|
74435
75900
|
if (methodName === "get") {
|
|
@@ -78368,16 +79833,23 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
|
78368
79833
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
78369
79834
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
78370
79835
|
const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
|
|
78371
|
-
const
|
|
79836
|
+
const isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
78372
79837
|
const strippedExpression = stripParenExpression(expression);
|
|
78373
79838
|
if (isDefinitelyNonThenableValue(strippedExpression)) return true;
|
|
79839
|
+
if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
|
|
79840
|
+
const receiver = stripParenExpression(strippedExpression.callee.object);
|
|
79841
|
+
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
|
|
79842
|
+
const resolvedValue = strippedExpression.arguments[0];
|
|
79843
|
+
return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
|
|
79844
|
+
}
|
|
79845
|
+
}
|
|
78374
79846
|
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
78375
79847
|
if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
|
|
78376
79848
|
const symbol = context.scopes.symbolFor(strippedExpression);
|
|
78377
79849
|
if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
|
|
78378
79850
|
visitedBindingIdentifiers.add(symbol.bindingIdentifier);
|
|
78379
79851
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
78380
|
-
return Boolean(initializer &&
|
|
79852
|
+
return Boolean(initializer && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
|
|
78381
79853
|
};
|
|
78382
79854
|
const isKnownNonRejectingHandler = (argument, context) => {
|
|
78383
79855
|
if (!argument) return false;
|
|
@@ -78392,7 +79864,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
78392
79864
|
canReject = true;
|
|
78393
79865
|
return false;
|
|
78394
79866
|
}
|
|
78395
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !
|
|
79867
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
78396
79868
|
if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
|
|
78397
79869
|
canReject = true;
|
|
78398
79870
|
return false;
|
|
@@ -78441,6 +79913,28 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
|
78441
79913
|
});
|
|
78442
79914
|
return hasPotentiallyThrowingMemberRead;
|
|
78443
79915
|
};
|
|
79916
|
+
const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
|
|
79917
|
+
if (!argument) return false;
|
|
79918
|
+
if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
|
|
79919
|
+
if (!allowTerminalCatchBlock) return false;
|
|
79920
|
+
const candidate = stripParenExpression(argument);
|
|
79921
|
+
const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
|
|
79922
|
+
if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
|
|
79923
|
+
if (!isNodeOfType(handler.body, "BlockStatement")) return false;
|
|
79924
|
+
let doesExplicitlyReject = false;
|
|
79925
|
+
walkOwnFunctionScope(handler, (child) => {
|
|
79926
|
+
if (doesExplicitlyReject) return false;
|
|
79927
|
+
if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
|
|
79928
|
+
doesExplicitlyReject = true;
|
|
79929
|
+
return false;
|
|
79930
|
+
}
|
|
79931
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
79932
|
+
doesExplicitlyReject = true;
|
|
79933
|
+
return false;
|
|
79934
|
+
}
|
|
79935
|
+
});
|
|
79936
|
+
return !doesExplicitlyReject;
|
|
79937
|
+
};
|
|
78444
79938
|
const walkPromiseChain = (chainExpression, context) => {
|
|
78445
79939
|
let cursor = stripParenExpression(chainExpression);
|
|
78446
79940
|
let hasCatch = false;
|
|
@@ -78452,10 +79946,9 @@ const walkPromiseChain = (chainExpression, context) => {
|
|
|
78452
79946
|
while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
|
|
78453
79947
|
const methodName = getStaticPropertyName(cursor.callee);
|
|
78454
79948
|
const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
|
|
78455
|
-
|
|
78456
|
-
if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
|
|
79949
|
+
if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
|
|
78457
79950
|
if (methodName === "then") {
|
|
78458
|
-
if (!didReachTerminalThen &&
|
|
79951
|
+
if (!didReachTerminalThen && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
|
|
78459
79952
|
didReachTerminalThen = true;
|
|
78460
79953
|
sawThen = true;
|
|
78461
79954
|
const callbackArgument = cursor.arguments[0];
|
|
@@ -108771,9 +110264,6 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
108771
110264
|
} })
|
|
108772
110265
|
});
|
|
108773
110266
|
//#endregion
|
|
108774
|
-
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
108775
|
-
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
108776
|
-
//#endregion
|
|
108777
110267
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
108778
110268
|
const rerenderLazyRefInit = defineRule({
|
|
108779
110269
|
id: "rerender-lazy-ref-init",
|
|
@@ -108792,7 +110282,6 @@ const rerenderLazyRefInit = defineRule({
|
|
|
108792
110282
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
108793
110283
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
108794
110284
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
108795
|
-
if (isTrivialBuiltInConstruction(initializer)) return;
|
|
108796
110285
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
108797
110286
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
108798
110287
|
context.report({
|