oxlint-plugin-react-doctor 0.9.2-dev.444e177 → 0.9.2-dev.5dc936e
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 +2187 -232
- 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
|
}
|
|
@@ -29098,6 +29461,11 @@ const jsCacheStorage = defineRule({
|
|
|
29098
29461
|
});
|
|
29099
29462
|
//#endregion
|
|
29100
29463
|
//#region src/plugin/rules/js-performance/js-combine-iterations.ts
|
|
29464
|
+
const SMALL_ARRAY_NON_MUTATING_METHODS = new Set([
|
|
29465
|
+
...CHAINABLE_ITERATION_METHODS,
|
|
29466
|
+
"find",
|
|
29467
|
+
"some"
|
|
29468
|
+
]);
|
|
29101
29469
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
29102
29470
|
const callee = callExpression.callee;
|
|
29103
29471
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
@@ -29209,21 +29577,34 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
29209
29577
|
return false;
|
|
29210
29578
|
};
|
|
29211
29579
|
const isSmallLiteralArray = (node) => {
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29580
|
+
const arrayNode = stripParenExpression(node);
|
|
29581
|
+
if (!isNodeOfType(arrayNode, "ArrayExpression")) return false;
|
|
29582
|
+
const elements = arrayNode.elements ?? [];
|
|
29583
|
+
if (elements.length === 0 || elements.length > 9) return false;
|
|
29215
29584
|
for (const element of elements) {
|
|
29216
29585
|
if (!element) continue;
|
|
29217
29586
|
if (isNodeOfType(element, "SpreadElement")) return false;
|
|
29218
29587
|
}
|
|
29219
29588
|
return true;
|
|
29220
29589
|
};
|
|
29221
|
-
const
|
|
29590
|
+
const isNonMutatingSmallArrayMethodReference = (identifier) => {
|
|
29591
|
+
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
29592
|
+
const memberExpression = identifierRoot.parent;
|
|
29593
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.object !== identifierRoot || !isNodeOfType(memberExpression.property, "Identifier") || !SMALL_ARRAY_NON_MUTATING_METHODS.has(memberExpression.property.name)) return false;
|
|
29594
|
+
const callExpression = memberExpression.parent;
|
|
29595
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberExpression;
|
|
29596
|
+
};
|
|
29597
|
+
const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
29222
29598
|
let cursor = receiverNode;
|
|
29223
29599
|
while (cursor) {
|
|
29224
29600
|
cursor = stripParenExpression(cursor);
|
|
29225
29601
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
29226
|
-
if (isNodeOfType(cursor, "Identifier"))
|
|
29602
|
+
if (isNodeOfType(cursor, "Identifier")) {
|
|
29603
|
+
const symbol = scopes.symbolFor(cursor);
|
|
29604
|
+
if (!symbol?.initializer || !isSmallLiteralArray(symbol.initializer)) return false;
|
|
29605
|
+
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier")) return false;
|
|
29606
|
+
return (symbol.kind === "const" || symbol.kind === "let" || symbol.kind === "var") && symbol.references.every((reference) => reference.flag === "read" && isNonMutatingSmallArrayMethodReference(reference.identifier));
|
|
29607
|
+
}
|
|
29227
29608
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
29228
29609
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
29229
29610
|
const nextCallee = cursor.callee;
|
|
@@ -29232,22 +29613,6 @@ const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
|
29232
29613
|
}
|
|
29233
29614
|
return false;
|
|
29234
29615
|
};
|
|
29235
|
-
const collectSmallConstArrayNames = (programNode) => {
|
|
29236
|
-
const names = /* @__PURE__ */ new Set();
|
|
29237
|
-
const statements = programNode.body ?? [];
|
|
29238
|
-
for (const statement of statements) {
|
|
29239
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
29240
|
-
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
29241
|
-
if (declaration.kind !== "const") continue;
|
|
29242
|
-
for (const declarator of declaration.declarations ?? []) {
|
|
29243
|
-
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
29244
|
-
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
29245
|
-
if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
|
|
29246
|
-
names.add(declarator.id.name);
|
|
29247
|
-
}
|
|
29248
|
-
}
|
|
29249
|
-
return names;
|
|
29250
|
-
};
|
|
29251
29616
|
const collectGeneratorNames = (programNode) => {
|
|
29252
29617
|
const generatorNames = /* @__PURE__ */ new Set();
|
|
29253
29618
|
walkAst(programNode, (child) => {
|
|
@@ -29268,16 +29633,11 @@ const jsCombineIterations = defineRule({
|
|
|
29268
29633
|
create: (context) => {
|
|
29269
29634
|
let programNode = null;
|
|
29270
29635
|
let generatorNamesInFile = null;
|
|
29271
|
-
let smallConstArrayNames = null;
|
|
29272
29636
|
const coveredChainCalls = /* @__PURE__ */ new WeakSet();
|
|
29273
29637
|
const getGeneratorNamesInFile = () => {
|
|
29274
29638
|
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
29275
29639
|
return generatorNamesInFile;
|
|
29276
29640
|
};
|
|
29277
|
-
const getSmallConstArrayNames = () => {
|
|
29278
|
-
smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
|
|
29279
|
-
return smallConstArrayNames;
|
|
29280
|
-
};
|
|
29281
29641
|
return {
|
|
29282
29642
|
Program(node) {
|
|
29283
29643
|
programNode = node;
|
|
@@ -29307,7 +29667,7 @@ const jsCombineIterations = defineRule({
|
|
|
29307
29667
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
29308
29668
|
}
|
|
29309
29669
|
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
29310
|
-
if (isSmallLiteralArrayRootedChain(innerCall.callee.object,
|
|
29670
|
+
if (isSmallLiteralArrayRootedChain(innerCall.callee.object, context.scopes)) return;
|
|
29311
29671
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
29312
29672
|
coveredChainCalls.add(innerCall);
|
|
29313
29673
|
context.report({
|
|
@@ -30534,7 +30894,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
|
|
|
30534
30894
|
"code",
|
|
30535
30895
|
"label",
|
|
30536
30896
|
"slug",
|
|
30537
|
-
"prefix"
|
|
30897
|
+
"prefix",
|
|
30898
|
+
"__html"
|
|
30538
30899
|
]);
|
|
30539
30900
|
const STRING_TYPED_IDENTIFIER_SUFFIXES = [
|
|
30540
30901
|
"Text",
|
|
@@ -30652,13 +31013,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
|
|
|
30652
31013
|
"title"
|
|
30653
31014
|
]);
|
|
30654
31015
|
const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
|
|
31016
|
+
const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
|
|
31017
|
+
"concat",
|
|
31018
|
+
"filter",
|
|
31019
|
+
"flat",
|
|
31020
|
+
"flatMap",
|
|
31021
|
+
"map",
|
|
31022
|
+
"slice",
|
|
31023
|
+
"split"
|
|
31024
|
+
]);
|
|
30655
31025
|
const isLikelyStringReceiver = (receiver) => {
|
|
30656
31026
|
if (!receiver) return false;
|
|
31027
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31028
|
+
if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
|
|
30657
31029
|
if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
|
|
30658
31030
|
if (isNodeOfType(receiver, "TemplateLiteral")) return true;
|
|
30659
31031
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
|
|
30660
31032
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
|
|
30661
31033
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
|
|
31034
|
+
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
31035
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
30663
31036
|
if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
|
|
30664
31037
|
}
|
|
@@ -30676,8 +31049,46 @@ const isLikelyStringReceiver = (receiver) => {
|
|
|
30676
31049
|
}
|
|
30677
31050
|
if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
|
|
30678
31051
|
if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
|
|
31052
|
+
if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
|
|
30679
31053
|
return false;
|
|
30680
31054
|
};
|
|
31055
|
+
const isFreshArrayReceiver = (receiver) => {
|
|
31056
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31057
|
+
if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
|
|
31058
|
+
if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
|
|
31059
|
+
if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
|
|
31060
|
+
if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
|
|
31061
|
+
const sourceReceiver = stripParenExpression(receiver.callee.object);
|
|
31062
|
+
return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
|
|
31063
|
+
};
|
|
31064
|
+
const isSmallRestHelperOmissionList = (node) => {
|
|
31065
|
+
if (!node) return false;
|
|
31066
|
+
const candidate = stripParenExpression(node);
|
|
31067
|
+
if (!isNodeOfType(candidate, "ArrayExpression")) return false;
|
|
31068
|
+
const elements = candidate.elements ?? [];
|
|
31069
|
+
return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
|
|
31070
|
+
};
|
|
31071
|
+
const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
|
|
31072
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
31073
|
+
const enclosingFunction = findEnclosingFunction$1(lookupCall);
|
|
31074
|
+
if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
|
|
31075
|
+
let bindingIdentifier = null;
|
|
31076
|
+
let ancestor = enclosingFunction.parent;
|
|
31077
|
+
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
31078
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
|
|
31079
|
+
bindingIdentifier = ancestor.id;
|
|
31080
|
+
break;
|
|
31081
|
+
}
|
|
31082
|
+
ancestor = ancestor.parent;
|
|
31083
|
+
}
|
|
31084
|
+
if (!bindingIdentifier) return false;
|
|
31085
|
+
const helperSymbol = scopes.symbolFor(bindingIdentifier);
|
|
31086
|
+
if (!helperSymbol || helperSymbol.references.length === 0) return false;
|
|
31087
|
+
return helperSymbol.references.every((reference) => {
|
|
31088
|
+
const callExpression = reference.identifier.parent;
|
|
31089
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
|
|
31090
|
+
});
|
|
31091
|
+
};
|
|
30681
31092
|
const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
|
|
30682
31093
|
"i",
|
|
30683
31094
|
"j",
|
|
@@ -31274,6 +31685,8 @@ const jsSetMapLookups = defineRule({
|
|
|
31274
31685
|
const query = node.arguments[0];
|
|
31275
31686
|
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
31276
31687
|
if (isLikelyStringReceiver(receiver)) return;
|
|
31688
|
+
if (isFreshArrayReceiver(receiver)) return;
|
|
31689
|
+
if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
|
|
31277
31690
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
31278
31691
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
31279
31692
|
if (isSmallFixedListMember(receiver)) return;
|
|
@@ -45187,14 +45600,6 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45187
45600
|
} })
|
|
45188
45601
|
});
|
|
45189
45602
|
//#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
45603
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45199
45604
|
const unwrapNegativeGuardForm = (test) => {
|
|
45200
45605
|
const expression = stripParenExpression(test);
|
|
@@ -46715,6 +47120,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
|
|
|
46715
47120
|
}
|
|
46716
47121
|
return isBindingReassignedOrMutated(referenceNode, indexName);
|
|
46717
47122
|
};
|
|
47123
|
+
const findEnclosingWhileLoop = (node) => {
|
|
47124
|
+
let current = node.parent;
|
|
47125
|
+
while (current) {
|
|
47126
|
+
if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
|
|
47127
|
+
if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
|
|
47128
|
+
current = current.parent;
|
|
47129
|
+
}
|
|
47130
|
+
return null;
|
|
47131
|
+
};
|
|
47132
|
+
const isStaticMemberChain = (expression) => {
|
|
47133
|
+
const candidate = stripParenExpression(expression);
|
|
47134
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
|
|
47135
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
|
|
47136
|
+
};
|
|
47137
|
+
const findLengthBoundCollections = (expression) => {
|
|
47138
|
+
const collections = [];
|
|
47139
|
+
walkAst(expression, (child) => {
|
|
47140
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
|
|
47141
|
+
});
|
|
47142
|
+
return collections;
|
|
47143
|
+
};
|
|
47144
|
+
const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
|
|
47145
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47146
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47147
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47148
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47149
|
+
} });
|
|
47150
|
+
const RELATIONAL_BOUND_OPERATORS = new Set([
|
|
47151
|
+
"<",
|
|
47152
|
+
"<=",
|
|
47153
|
+
">",
|
|
47154
|
+
">="
|
|
47155
|
+
]);
|
|
47156
|
+
const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
|
|
47157
|
+
const readsCounter = (candidate) => {
|
|
47158
|
+
let didReadCounter = false;
|
|
47159
|
+
walkAst(candidate, (child) => {
|
|
47160
|
+
if (didReadCounter) return false;
|
|
47161
|
+
if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47162
|
+
didReadCounter = true;
|
|
47163
|
+
return false;
|
|
47164
|
+
}
|
|
47165
|
+
});
|
|
47166
|
+
return didReadCounter;
|
|
47167
|
+
};
|
|
47168
|
+
const readsLength = (candidate) => {
|
|
47169
|
+
let didReadLength = false;
|
|
47170
|
+
walkAst(candidate, (child) => {
|
|
47171
|
+
if (didReadLength) return false;
|
|
47172
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
47173
|
+
didReadLength = true;
|
|
47174
|
+
return false;
|
|
47175
|
+
}
|
|
47176
|
+
});
|
|
47177
|
+
return didReadLength;
|
|
47178
|
+
};
|
|
47179
|
+
let didFindLengthBound = false;
|
|
47180
|
+
walkAst(expression, (child) => {
|
|
47181
|
+
if (didFindLengthBound) return false;
|
|
47182
|
+
if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
|
|
47183
|
+
if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
|
|
47184
|
+
didFindLengthBound = true;
|
|
47185
|
+
return false;
|
|
47186
|
+
}
|
|
47187
|
+
});
|
|
47188
|
+
return didFindLengthBound;
|
|
47189
|
+
};
|
|
47190
|
+
const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
|
|
47191
|
+
if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
|
|
47192
|
+
const loop = findEnclosingWhileLoop(referenceNode);
|
|
47193
|
+
if (!loop) return false;
|
|
47194
|
+
let doesTestReadCounter = false;
|
|
47195
|
+
walkAst(loop.test, (child) => {
|
|
47196
|
+
if (doesTestReadCounter) return false;
|
|
47197
|
+
if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
|
|
47198
|
+
if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47199
|
+
doesTestReadCounter = true;
|
|
47200
|
+
return false;
|
|
47201
|
+
}
|
|
47202
|
+
});
|
|
47203
|
+
if (!doesTestReadCounter) return false;
|
|
47204
|
+
const lengthBoundCollections = findLengthBoundCollections(loop.test);
|
|
47205
|
+
if (lengthBoundCollections.length === 0) return false;
|
|
47206
|
+
let didFindIndexedCollectionRead = false;
|
|
47207
|
+
walkAst(loop.body, (child) => {
|
|
47208
|
+
if (didFindIndexedCollectionRead) return false;
|
|
47209
|
+
if (isFunctionLike$1(child)) return false;
|
|
47210
|
+
if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
|
|
47211
|
+
if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
|
|
47212
|
+
didFindIndexedCollectionRead = true;
|
|
47213
|
+
return false;
|
|
47214
|
+
}
|
|
47215
|
+
});
|
|
47216
|
+
return didFindIndexedCollectionRead;
|
|
47217
|
+
};
|
|
46718
47218
|
/**
|
|
46719
47219
|
* Resolves whether an identifier is PROVABLY the positional array
|
|
46720
47220
|
* index, by classifying its binding. Per the official rule prompt,
|
|
@@ -46765,6 +47265,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
|
|
|
46765
47265
|
}
|
|
46766
47266
|
const declarator = binding.bindingIdentifier.parent;
|
|
46767
47267
|
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
|
|
47268
|
+
if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
|
|
47269
|
+
iteratorCall: null,
|
|
47270
|
+
bindingFunction: null,
|
|
47271
|
+
indexParameterPosition: null,
|
|
47272
|
+
isDataIndexedLoopCounter: true
|
|
47273
|
+
};
|
|
46768
47274
|
const initializer = stripParenExpression(declarator.init);
|
|
46769
47275
|
if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
|
|
46770
47276
|
if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
|
|
@@ -46799,6 +47305,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
|
|
|
46799
47305
|
const receiver = iteratorCall.callee.object;
|
|
46800
47306
|
return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
|
|
46801
47307
|
};
|
|
47308
|
+
const isReactNamespaceIdentifier = (node) => {
|
|
47309
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47310
|
+
const importBinding = getImportBindingForName(node, node.name);
|
|
47311
|
+
const visibleBinding = findVariableInitializer(node, node.name);
|
|
47312
|
+
if (!importBinding) return node.name === "React" && !visibleBinding;
|
|
47313
|
+
return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
|
|
47314
|
+
};
|
|
47315
|
+
const isReactChildrenObject = (node) => {
|
|
47316
|
+
const candidate = stripParenExpression(node);
|
|
47317
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47318
|
+
const importBinding = getImportBindingForName(candidate, candidate.name);
|
|
47319
|
+
const visibleBinding = findVariableInitializer(candidate, candidate.name);
|
|
47320
|
+
return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
|
|
47321
|
+
}
|
|
47322
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
|
|
47323
|
+
};
|
|
47324
|
+
const isReactChildrenToArrayCall = (node) => {
|
|
47325
|
+
const candidate = stripParenExpression(node);
|
|
47326
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || candidate.callee.computed || !isReactChildrenObject(candidate.callee.object) || !isNodeOfType(candidate.callee.property, "Identifier") || candidate.callee.property.name !== "toArray") return false;
|
|
47327
|
+
const normalizedValueNode = candidate.arguments?.[0];
|
|
47328
|
+
const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
|
|
47329
|
+
if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
|
|
47330
|
+
const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
|
|
47331
|
+
return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
|
|
47332
|
+
};
|
|
47333
|
+
const isSameIdentifier = (first, second) => {
|
|
47334
|
+
const firstIdentifier = stripParenExpression(first);
|
|
47335
|
+
const secondIdentifier = stripParenExpression(second);
|
|
47336
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47337
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47338
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47339
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47340
|
+
};
|
|
47341
|
+
const isReactChildrenArrayNormalization = (node) => {
|
|
47342
|
+
const candidate = stripParenExpression(node);
|
|
47343
|
+
if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
|
|
47344
|
+
const test = stripParenExpression(candidate.test);
|
|
47345
|
+
if (!isNodeOfType(test, "CallExpression") || !isNodeOfType(test.callee, "MemberExpression") || test.callee.computed || !isNodeOfType(test.callee.object, "Identifier") || test.callee.object.name !== "Array" || findVariableInitializer(test.callee.object, "Array") || !isNodeOfType(test.callee.property, "Identifier") || test.callee.property.name !== "isArray") return false;
|
|
47346
|
+
const testedValueNode = test.arguments?.[0];
|
|
47347
|
+
if (!testedValueNode) return false;
|
|
47348
|
+
const testedValue = stripParenExpression(testedValueNode);
|
|
47349
|
+
if (!isNodeOfType(testedValue, "Identifier")) return false;
|
|
47350
|
+
const testedBinding = findVariableInitializer(testedValue, testedValue.name);
|
|
47351
|
+
if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
|
|
47352
|
+
const branchWrapsTestedValue = (branch) => {
|
|
47353
|
+
const unwrappedBranch = stripParenExpression(branch);
|
|
47354
|
+
return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
|
|
47355
|
+
};
|
|
47356
|
+
return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
|
|
47357
|
+
};
|
|
47358
|
+
const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
|
|
47359
|
+
const binding = findVariableInitializer(identifierNode, identifierNode.name);
|
|
47360
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
47361
|
+
if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
|
|
47362
|
+
const program = findProgramRoot(identifierNode);
|
|
47363
|
+
if (!program) return false;
|
|
47364
|
+
let didFindReactChildPush = false;
|
|
47365
|
+
walkAst(program, (child) => {
|
|
47366
|
+
if (didFindReactChildPush) return false;
|
|
47367
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || child.callee.computed || !isNodeOfType(child.callee.object, "Identifier") || child.callee.object.name !== identifierNode.name || !isNodeOfType(child.callee.property, "Identifier") || child.callee.property.name !== "push") return;
|
|
47368
|
+
if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
|
|
47369
|
+
const candidate = stripParenExpression(argument);
|
|
47370
|
+
if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
|
|
47371
|
+
let doesCarryReactChild = false;
|
|
47372
|
+
walkAst(candidate, (argumentChild) => {
|
|
47373
|
+
if (doesCarryReactChild) return false;
|
|
47374
|
+
if (!isNodeOfType(argumentChild, "Identifier")) return;
|
|
47375
|
+
const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
|
|
47376
|
+
const declaration = declarator?.parent;
|
|
47377
|
+
const forOfStatement = declaration?.parent;
|
|
47378
|
+
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
|
|
47379
|
+
doesCarryReactChild = true;
|
|
47380
|
+
return false;
|
|
47381
|
+
}
|
|
47382
|
+
});
|
|
47383
|
+
return doesCarryReactChild;
|
|
47384
|
+
})) {
|
|
47385
|
+
didFindReactChildPush = true;
|
|
47386
|
+
return false;
|
|
47387
|
+
}
|
|
47388
|
+
});
|
|
47389
|
+
return didFindReactChildPush;
|
|
47390
|
+
};
|
|
47391
|
+
const isDynamicReactChildrenExpression = (expression, depth) => {
|
|
47392
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
|
|
47393
|
+
const candidate = stripParenExpression(expression);
|
|
47394
|
+
if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
|
|
47395
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47396
|
+
if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
|
|
47397
|
+
const binding = findVariableInitializer(candidate, candidate.name);
|
|
47398
|
+
return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
|
|
47399
|
+
}
|
|
47400
|
+
if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && !candidate.callee.computed && isNodeOfType(candidate.callee.property, "Identifier") && candidate.callee.property.name === "filter") return isDynamicReactChildrenExpression(candidate.callee.object, depth + 1);
|
|
47401
|
+
return false;
|
|
47402
|
+
};
|
|
46802
47403
|
const resolveKeyTemplateLiteral = (expression) => {
|
|
46803
47404
|
const node = stripParenExpression(expression);
|
|
46804
47405
|
if (isNodeOfType(node, "TemplateLiteral")) return node;
|
|
@@ -46851,28 +47452,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
|
46851
47452
|
}
|
|
46852
47453
|
return referencedItemNames;
|
|
46853
47454
|
};
|
|
46854
|
-
const
|
|
46855
|
-
let didFindLengthRead = false;
|
|
46856
|
-
walkAst(test, (child) => {
|
|
46857
|
-
if (didFindLengthRead) return false;
|
|
46858
|
-
if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
46859
|
-
didFindLengthRead = true;
|
|
46860
|
-
return false;
|
|
46861
|
-
}
|
|
46862
|
-
});
|
|
46863
|
-
return didFindLengthRead;
|
|
46864
|
-
};
|
|
46865
|
-
const isNumericForLoopCounter = (attributeNode, indexName) => {
|
|
47455
|
+
const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
|
|
46866
47456
|
const binding = findVariableInitializer(attributeNode, indexName);
|
|
46867
47457
|
if (!binding) return false;
|
|
46868
47458
|
const declarator = binding.bindingIdentifier.parent;
|
|
46869
47459
|
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
46870
47460
|
const declaration = declarator.parent;
|
|
46871
47461
|
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
47462
|
+
if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
|
|
46872
47463
|
const forStatement = declaration.parent;
|
|
46873
|
-
if (
|
|
46874
|
-
|
|
46875
|
-
return
|
|
47464
|
+
if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
|
|
47465
|
+
const whileLoop = findEnclosingWhileLoop(attributeNode);
|
|
47466
|
+
if (!whileLoop) return false;
|
|
47467
|
+
return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
|
|
46876
47468
|
};
|
|
46877
47469
|
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
46878
47470
|
const findIteratorItemNamesOfBinding = (binding) => {
|
|
@@ -46906,11 +47498,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
|
|
|
46906
47498
|
* observable harm; anything stateful — form controls, media, custom
|
|
46907
47499
|
* components, unknown calls — keeps the diagnostic.
|
|
46908
47500
|
*/
|
|
46909
|
-
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
|
|
47501
|
+
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
|
|
46910
47502
|
const jsxElement = openingElement.parent;
|
|
46911
47503
|
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
46912
47504
|
const children = jsxElement.children ?? [];
|
|
46913
|
-
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
47505
|
+
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
46914
47506
|
return children.some((child) => containsStatefulDescendant(child, {
|
|
46915
47507
|
memberRootNames: itemNames,
|
|
46916
47508
|
allowAnyMemberRead: true,
|
|
@@ -46918,6 +47510,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
|
|
|
46918
47510
|
callCalleeRootNames: itemNames
|
|
46919
47511
|
}));
|
|
46920
47512
|
};
|
|
47513
|
+
const elementHasDirectItemChild = (openingElement, itemNames) => {
|
|
47514
|
+
const jsxElement = openingElement.parent;
|
|
47515
|
+
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47516
|
+
return (jsxElement.children ?? []).some((child) => {
|
|
47517
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
|
|
47518
|
+
const expression = stripParenExpression(child.expression);
|
|
47519
|
+
return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
|
|
47520
|
+
});
|
|
47521
|
+
};
|
|
46921
47522
|
const callbackFiltersRows = (bindingFunction) => {
|
|
46922
47523
|
if (!bindingFunction) return false;
|
|
46923
47524
|
let didFindNullReturn = false;
|
|
@@ -46969,19 +47570,21 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46969
47570
|
const indexUse = findPositionalIndexUse(node.value.expression, 0);
|
|
46970
47571
|
if (!indexUse) return;
|
|
46971
47572
|
const indexName = indexUse.identifier.name;
|
|
46972
|
-
if (
|
|
47573
|
+
if (isNumericPlaceholderLoopCounter(node, indexName)) return;
|
|
46973
47574
|
if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
|
|
46974
47575
|
const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
|
|
46975
47576
|
if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
|
|
46976
|
-
if (hasAriaHiddenAncestor(node)) return;
|
|
47577
|
+
if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
|
|
46977
47578
|
const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
|
|
46978
47579
|
const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
|
|
47580
|
+
const iteratorCallee = indexUse.binding.iteratorCall?.callee;
|
|
47581
|
+
const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
|
|
46979
47582
|
const openingElement = node.parent;
|
|
46980
47583
|
if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
|
|
46981
47584
|
const elementName = openingElement.name;
|
|
46982
47585
|
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
46983
47586
|
if (elementName.name === "Fragment") {
|
|
46984
|
-
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47587
|
+
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46985
47588
|
} else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
|
|
46986
47589
|
if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
|
|
46987
47590
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
@@ -46989,14 +47592,14 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46989
47592
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
46990
47593
|
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
46991
47594
|
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
|
|
46992
|
-
if (!containsStatefulDescendant(jsxElement, {
|
|
47595
|
+
if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
|
|
46993
47596
|
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
|
|
46994
47597
|
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
46995
|
-
})) return;
|
|
47598
|
+
}))) return;
|
|
46996
47599
|
}
|
|
46997
47600
|
}
|
|
46998
47601
|
}
|
|
46999
|
-
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47602
|
+
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
47000
47603
|
}
|
|
47001
47604
|
context.report({
|
|
47002
47605
|
node,
|
|
@@ -55484,9 +56087,13 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
55484
56087
|
return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
|
|
55485
56088
|
};
|
|
55486
56089
|
//#endregion
|
|
56090
|
+
//#region src/plugin/utils/nextjs-page-data-export-names.ts
|
|
56091
|
+
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56092
|
+
//#endregion
|
|
55487
56093
|
//#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
|
|
55488
56094
|
const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
|
|
55489
56095
|
const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
|
|
56096
|
+
const INTERNAL_STATE_NAME_PATTERN = /^(internal|uncontrolled)([A-Z_]|$)/;
|
|
55490
56097
|
const getStateSetterName = (useStateCall) => {
|
|
55491
56098
|
const declarator = useStateCall.parent;
|
|
55492
56099
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -55640,7 +56247,6 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
|
|
|
55640
56247
|
});
|
|
55641
56248
|
return isCommitted;
|
|
55642
56249
|
};
|
|
55643
|
-
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
55644
56250
|
const isNextjsDataFetchingPage = (node) => {
|
|
55645
56251
|
const program = findProgramRoot(node);
|
|
55646
56252
|
if (!program) return false;
|
|
@@ -55684,6 +56290,48 @@ const isInRenderScope = (node, componentFunction) => {
|
|
|
55684
56290
|
}
|
|
55685
56291
|
return true;
|
|
55686
56292
|
};
|
|
56293
|
+
const isReferenceToBinding = (reference, bindingIdentifier, context) => {
|
|
56294
|
+
if (!isNodeOfType(reference, "Identifier")) return false;
|
|
56295
|
+
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
56296
|
+
if (!bindingSymbol) return false;
|
|
56297
|
+
return (context.scopes.referenceFor(reference)?.resolvedSymbol)?.id === bindingSymbol.id;
|
|
56298
|
+
};
|
|
56299
|
+
const isControlledPropFallbackExpression = (expression, stateBinding, isPropName, context) => {
|
|
56300
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
56301
|
+
const consequent = unwrapInitializerSeed(expression.consequent);
|
|
56302
|
+
const alternate = unwrapInitializerSeed(expression.alternate);
|
|
56303
|
+
return isReferenceToBinding(consequent, stateBinding, context) && isPropDerivedArgument(alternate, isPropName) || isPropDerivedArgument(consequent, isPropName) && isReferenceToBinding(alternate, stateBinding, context);
|
|
56304
|
+
}
|
|
56305
|
+
return isNodeOfType(expression, "LogicalExpression") && expression.operator === "??" && isPropDerivedArgument(unwrapInitializerSeed(expression.left), isPropName) && isReferenceToBinding(unwrapInitializerSeed(expression.right), stateBinding, context);
|
|
56306
|
+
};
|
|
56307
|
+
const isUserEditableControlledFallback = (useStateCall, isPropName, context) => {
|
|
56308
|
+
const declarator = useStateCall.parent;
|
|
56309
|
+
if (!isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
56310
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
|
|
56311
|
+
const stateBinding = declarator.id.elements?.[0];
|
|
56312
|
+
const setterBinding = declarator.id.elements?.[1];
|
|
56313
|
+
if (!isNodeOfType(stateBinding, "Identifier") || !isNodeOfType(setterBinding, "Identifier")) return false;
|
|
56314
|
+
if (!INTERNAL_STATE_NAME_PATTERN.test(stateBinding.name)) return false;
|
|
56315
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
56316
|
+
if (!componentFunction) return false;
|
|
56317
|
+
let hasControlledFallback = false;
|
|
56318
|
+
let hasUserEdit = false;
|
|
56319
|
+
walkAst(componentFunction, (child) => {
|
|
56320
|
+
if (child !== componentFunction && isFunctionLike$1(child)) return false;
|
|
56321
|
+
if (!hasControlledFallback && (isNodeOfType(child, "ConditionalExpression") || isNodeOfType(child, "LogicalExpression")) && isControlledPropFallbackExpression(child, stateBinding, isPropName, context)) hasControlledFallback = true;
|
|
56322
|
+
});
|
|
56323
|
+
walkAst(componentFunction, (child) => {
|
|
56324
|
+
if (hasUserEdit) return false;
|
|
56325
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
56326
|
+
if (!isReferenceToBinding(child.callee, setterBinding, context)) return;
|
|
56327
|
+
if (!isHandlerShapedReseed(child, componentFunction)) return;
|
|
56328
|
+
const setterArgument = child.arguments?.[0];
|
|
56329
|
+
if (!setterArgument || isPropDerivedArgument(unwrapInitializerSeed(setterArgument), isPropName)) return;
|
|
56330
|
+
hasUserEdit = true;
|
|
56331
|
+
return false;
|
|
56332
|
+
});
|
|
56333
|
+
return hasControlledFallback && hasUserEdit;
|
|
56334
|
+
};
|
|
55687
56335
|
const getStateValueName = (useStateCall) => {
|
|
55688
56336
|
const declarator = useStateCall.parent;
|
|
55689
56337
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -55748,6 +56396,7 @@ const noDerivedUseState = defineRule({
|
|
|
55748
56396
|
const reportStalePropCopy = (propName) => {
|
|
55749
56397
|
if (isIntentionalSnapshotState(node)) return;
|
|
55750
56398
|
if (hasSessionDismissProp(propStackTracker.getCurrentPropNames())) return;
|
|
56399
|
+
if (isUserEditableControlledFallback(node, propStackTracker.isPropName, context)) return;
|
|
55751
56400
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
55752
56401
|
if (isEffectDrivenResync(node)) return;
|
|
55753
56402
|
if (isNextjsDataFetchingPage(node)) return;
|
|
@@ -63892,6 +64541,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
|
|
|
63892
64541
|
};
|
|
63893
64542
|
//#endregion
|
|
63894
64543
|
//#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
|
|
64544
|
+
const findGuardingIfStatements = (node, functionBoundary) => {
|
|
64545
|
+
const guardingIfStatements = [];
|
|
64546
|
+
let currentNode = node.parent;
|
|
64547
|
+
while (currentNode && currentNode !== functionBoundary) {
|
|
64548
|
+
if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
|
|
64549
|
+
currentNode = currentNode.parent;
|
|
64550
|
+
}
|
|
64551
|
+
return guardingIfStatements;
|
|
64552
|
+
};
|
|
64553
|
+
const doesNodeReadSymbol = (node, symbol) => {
|
|
64554
|
+
let doesReadSymbol = false;
|
|
64555
|
+
walkAst(node, (childNode) => {
|
|
64556
|
+
if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
|
|
64557
|
+
doesReadSymbol = true;
|
|
64558
|
+
return false;
|
|
64559
|
+
}
|
|
64560
|
+
});
|
|
64561
|
+
return doesReadSymbol;
|
|
64562
|
+
};
|
|
64563
|
+
const collectWrittenSymbols = (node, scopes) => {
|
|
64564
|
+
const writtenSymbols = /* @__PURE__ */ new Set();
|
|
64565
|
+
walkAst(node, (childNode) => {
|
|
64566
|
+
if (childNode !== node && isFunctionLike$1(childNode)) return false;
|
|
64567
|
+
if (!isNodeOfType(childNode, "Identifier")) return;
|
|
64568
|
+
const reference = scopes.referenceFor(childNode);
|
|
64569
|
+
if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
|
|
64570
|
+
writtenSymbols.add(reference.resolvedSymbol);
|
|
64571
|
+
});
|
|
64572
|
+
return writtenSymbols;
|
|
64573
|
+
};
|
|
64574
|
+
const isDescendantOf = (node, ancestorNode) => {
|
|
64575
|
+
let currentNode = node.parent;
|
|
64576
|
+
while (currentNode) {
|
|
64577
|
+
if (currentNode === ancestorNode) return true;
|
|
64578
|
+
currentNode = currentNode.parent;
|
|
64579
|
+
}
|
|
64580
|
+
return false;
|
|
64581
|
+
};
|
|
64582
|
+
const getAssignedValue = (identifier) => {
|
|
64583
|
+
const assignmentExpression = identifier.parent;
|
|
64584
|
+
return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
|
|
64585
|
+
};
|
|
64586
|
+
const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
|
|
64587
|
+
const initialValue = symbol.initializer;
|
|
64588
|
+
if (!initialValue) return false;
|
|
64589
|
+
const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
|
|
64590
|
+
return guardedWrites.length > 0 && guardedWrites.every((reference) => {
|
|
64591
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64592
|
+
return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
|
|
64593
|
+
});
|
|
64594
|
+
};
|
|
64595
|
+
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));
|
|
64596
|
+
const isUnconditionalOrStaticallySelected = (node, context) => {
|
|
64597
|
+
if (context.cfg.isUnconditionalFromEntry(node)) return true;
|
|
64598
|
+
let currentNode = node;
|
|
64599
|
+
let outermostStaticIfStatement = null;
|
|
64600
|
+
let parentNode = currentNode.parent;
|
|
64601
|
+
while (parentNode) {
|
|
64602
|
+
if (isFunctionLike$1(parentNode)) break;
|
|
64603
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
64604
|
+
const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
|
|
64605
|
+
let selectedBranch = null;
|
|
64606
|
+
if (staticResult === true) selectedBranch = parentNode.consequent;
|
|
64607
|
+
if (staticResult === false) selectedBranch = parentNode.alternate;
|
|
64608
|
+
if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
|
|
64609
|
+
outermostStaticIfStatement = parentNode;
|
|
64610
|
+
}
|
|
64611
|
+
currentNode = parentNode;
|
|
64612
|
+
parentNode = currentNode.parent;
|
|
64613
|
+
}
|
|
64614
|
+
return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
|
|
64615
|
+
};
|
|
64616
|
+
const containsExplicitReactRuntimeReference = (node, scopes) => {
|
|
64617
|
+
let hasRuntimeReference = false;
|
|
64618
|
+
walkAst(node, (childNode) => {
|
|
64619
|
+
if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
|
|
64620
|
+
hasRuntimeReference = true;
|
|
64621
|
+
return false;
|
|
64622
|
+
}
|
|
64623
|
+
if (!isNodeOfType(childNode, "CallExpression")) return;
|
|
64624
|
+
const sourceArgument = (childNode.arguments ?? [])[0];
|
|
64625
|
+
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;
|
|
64626
|
+
hasRuntimeReference = true;
|
|
64627
|
+
return false;
|
|
64628
|
+
});
|
|
64629
|
+
return hasRuntimeReference;
|
|
64630
|
+
};
|
|
64631
|
+
const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
|
|
64632
|
+
const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
|
|
64633
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
|
|
64634
|
+
const functionSymbol = scopes.symbolFor(bindingIdentifier);
|
|
64635
|
+
if (!functionSymbol) return null;
|
|
64636
|
+
for (const reference of functionSymbol.references) {
|
|
64637
|
+
const callExpression = reference.identifier.parent;
|
|
64638
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
|
|
64639
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
|
|
64640
|
+
if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
|
|
64641
|
+
}
|
|
64642
|
+
return null;
|
|
64643
|
+
};
|
|
63895
64644
|
const evaluateEquality$1 = (operator, left, right) => {
|
|
63896
64645
|
if (operator === "===" || operator === "==") return left === right;
|
|
63897
64646
|
if (operator === "!==" || operator === "!=") return left !== right;
|
|
@@ -63942,6 +64691,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
63942
64691
|
if (leftResult === false && rightResult === false) return false;
|
|
63943
64692
|
return null;
|
|
63944
64693
|
};
|
|
64694
|
+
const areLooselyEqualPrimitiveResults = (left, right) => {
|
|
64695
|
+
if (left.kind === right.kind) return left.value === right.value;
|
|
64696
|
+
if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
|
|
64697
|
+
if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
|
|
64698
|
+
kind: "number",
|
|
64699
|
+
value: left.value ? 1 : 0
|
|
64700
|
+
}, right);
|
|
64701
|
+
if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
|
|
64702
|
+
kind: "number",
|
|
64703
|
+
value: right.value ? 1 : 0
|
|
64704
|
+
});
|
|
64705
|
+
if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
|
|
64706
|
+
if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
|
|
64707
|
+
return false;
|
|
64708
|
+
};
|
|
64709
|
+
const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
|
|
64710
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64711
|
+
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
64712
|
+
if (predicateMatch) return {
|
|
64713
|
+
kind: "boolean",
|
|
64714
|
+
value: predicateMatch[`${runtime}Result`]
|
|
64715
|
+
};
|
|
64716
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
64717
|
+
const value = unwrappedExpression.value;
|
|
64718
|
+
if (value === null) return {
|
|
64719
|
+
kind: "null",
|
|
64720
|
+
value
|
|
64721
|
+
};
|
|
64722
|
+
if (typeof value === "boolean") return {
|
|
64723
|
+
kind: "boolean",
|
|
64724
|
+
value
|
|
64725
|
+
};
|
|
64726
|
+
if (typeof value === "number") return {
|
|
64727
|
+
kind: "number",
|
|
64728
|
+
value
|
|
64729
|
+
};
|
|
64730
|
+
if (typeof value === "string") return {
|
|
64731
|
+
kind: "string",
|
|
64732
|
+
value
|
|
64733
|
+
};
|
|
64734
|
+
return null;
|
|
64735
|
+
}
|
|
64736
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
|
|
64737
|
+
kind: "undefined",
|
|
64738
|
+
value: void 0
|
|
64739
|
+
};
|
|
64740
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64741
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64742
|
+
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
64743
|
+
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64744
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64745
|
+
const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
|
|
64746
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64747
|
+
return result;
|
|
64748
|
+
}
|
|
64749
|
+
if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64750
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64751
|
+
const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
|
|
64752
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64753
|
+
return result;
|
|
64754
|
+
}
|
|
64755
|
+
}
|
|
64756
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
64757
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
64758
|
+
return argumentResult === null ? null : {
|
|
64759
|
+
kind: "boolean",
|
|
64760
|
+
value: !argumentResult
|
|
64761
|
+
};
|
|
64762
|
+
}
|
|
64763
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64764
|
+
const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
|
|
64765
|
+
const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
|
|
64766
|
+
if (!leftResult || !rightResult) return null;
|
|
64767
|
+
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
64768
|
+
const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
|
|
64769
|
+
return {
|
|
64770
|
+
kind: "boolean",
|
|
64771
|
+
value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
|
|
64772
|
+
};
|
|
64773
|
+
}
|
|
64774
|
+
if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
|
|
64775
|
+
const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
|
|
64776
|
+
return {
|
|
64777
|
+
kind: "boolean",
|
|
64778
|
+
value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
|
|
64779
|
+
};
|
|
64780
|
+
}
|
|
64781
|
+
}
|
|
64782
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64783
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
64784
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64785
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
|
|
64786
|
+
const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
64787
|
+
return argumentResult === null ? null : {
|
|
64788
|
+
kind: "boolean",
|
|
64789
|
+
value: argumentResult
|
|
64790
|
+
};
|
|
64791
|
+
}
|
|
64792
|
+
}
|
|
64793
|
+
return null;
|
|
64794
|
+
};
|
|
63945
64795
|
const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
63946
64796
|
const unwrappedExpression = stripParenExpression(expression);
|
|
63947
64797
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -63990,6 +64840,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
|
63990
64840
|
parameterValuesBySymbolId
|
|
63991
64841
|
});
|
|
63992
64842
|
}
|
|
64843
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64844
|
+
const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
|
|
64845
|
+
return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
|
|
64846
|
+
}
|
|
63993
64847
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
63994
64848
|
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
63995
64849
|
return argumentResult === null ? null : !argumentResult;
|
|
@@ -64050,13 +64904,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
|
|
|
64050
64904
|
const rightSymbol = scopes.symbolFor(right);
|
|
64051
64905
|
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
64052
64906
|
}
|
|
64053
|
-
|
|
64054
|
-
|
|
64055
|
-
|
|
64056
|
-
|
|
64057
|
-
|
|
64058
|
-
|
|
64059
|
-
|
|
64907
|
+
const rightEntries = new Map(Object.entries(right));
|
|
64908
|
+
for (const [key, leftValue] of Object.entries(left)) {
|
|
64909
|
+
if (key === "parent") continue;
|
|
64910
|
+
const rightValue = rightEntries.get(key);
|
|
64911
|
+
if (isAstNode(leftValue)) {
|
|
64912
|
+
if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
|
|
64913
|
+
continue;
|
|
64914
|
+
}
|
|
64915
|
+
if (!Array.isArray(leftValue)) continue;
|
|
64916
|
+
if (!Array.isArray(rightValue)) return false;
|
|
64917
|
+
const leftNodes = leftValue.filter(isAstNode);
|
|
64918
|
+
const rightNodes = rightValue.filter(isAstNode);
|
|
64919
|
+
if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
|
|
64060
64920
|
}
|
|
64061
64921
|
return true;
|
|
64062
64922
|
};
|
|
@@ -64070,6 +64930,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
|
64070
64930
|
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
64071
64931
|
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
64072
64932
|
};
|
|
64933
|
+
const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
64934
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64935
|
+
if (matchBrowserPredicate(unwrappedExpression, context)) return true;
|
|
64936
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
|
|
64937
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
|
|
64938
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64939
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64940
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
|
|
64941
|
+
visitedSymbolIds.add(symbol.id);
|
|
64942
|
+
const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
|
|
64943
|
+
const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
|
|
64944
|
+
visitedSymbolIds.delete(symbol.id);
|
|
64945
|
+
return isReflexive;
|
|
64946
|
+
}
|
|
64947
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
|
|
64948
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
|
|
64949
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
|
|
64950
|
+
if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
|
|
64951
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
64952
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64953
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
|
|
64954
|
+
};
|
|
64955
|
+
const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
|
|
64956
|
+
if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
|
|
64957
|
+
if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
|
|
64958
|
+
if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
|
|
64959
|
+
if (isNodeOfType(node, "TryStatement")) return [
|
|
64960
|
+
...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
|
|
64961
|
+
...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
|
|
64962
|
+
...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
|
|
64963
|
+
];
|
|
64964
|
+
if (!isNodeOfType(node, "BlockStatement")) return [];
|
|
64965
|
+
const propertyValues = [];
|
|
64966
|
+
for (const childStatement of node.body) {
|
|
64967
|
+
propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
|
|
64968
|
+
if (statementAlwaysExits$1(childStatement)) break;
|
|
64969
|
+
}
|
|
64970
|
+
return propertyValues;
|
|
64971
|
+
};
|
|
64972
|
+
const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
|
|
64973
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
64974
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
64975
|
+
const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
|
|
64976
|
+
let match = null;
|
|
64977
|
+
for (const propertyValue of propertyValues) {
|
|
64978
|
+
match = matchHydrationConditionInternal(propertyValue, context, state);
|
|
64979
|
+
if (match) break;
|
|
64980
|
+
}
|
|
64981
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
64982
|
+
return match;
|
|
64983
|
+
};
|
|
64073
64984
|
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
64074
64985
|
const unwrappedExpression = stripParenExpression(expression);
|
|
64075
64986
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -64086,14 +64997,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64086
64997
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64087
64998
|
return match;
|
|
64088
64999
|
}
|
|
65000
|
+
if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
65001
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
65002
|
+
if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
|
|
65003
|
+
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
65004
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65005
|
+
return match;
|
|
65006
|
+
}
|
|
65007
|
+
for (const reference of symbol.references) {
|
|
65008
|
+
if (reference.flag === "read") continue;
|
|
65009
|
+
if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
|
|
65010
|
+
const enclosingFunction = findEnclosingFunction$1(reference.identifier);
|
|
65011
|
+
if (!enclosingFunction) continue;
|
|
65012
|
+
for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
|
|
65013
|
+
if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
65014
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
65015
|
+
if (match) {
|
|
65016
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65017
|
+
return match;
|
|
65018
|
+
}
|
|
65019
|
+
}
|
|
65020
|
+
}
|
|
65021
|
+
const readingFunction = findEnclosingFunction$1(unwrappedExpression);
|
|
65022
|
+
for (const reference of symbol.references) {
|
|
65023
|
+
if (reference.flag === "read") continue;
|
|
65024
|
+
const writingFunction = findEnclosingFunction$1(reference.identifier);
|
|
65025
|
+
if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
|
|
65026
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
65027
|
+
if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
|
|
65028
|
+
const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
|
|
65029
|
+
if (!isNodeOfType(functionBinding, "Identifier")) continue;
|
|
65030
|
+
const functionSymbol = context.scopes.symbolFor(functionBinding);
|
|
65031
|
+
if (!functionSymbol) continue;
|
|
65032
|
+
for (const functionReference of functionSymbol.references) {
|
|
65033
|
+
const callExpression = functionReference.identifier.parent;
|
|
65034
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
|
|
65035
|
+
for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
|
|
65036
|
+
if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
65037
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
65038
|
+
if (match) {
|
|
65039
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65040
|
+
return match;
|
|
65041
|
+
}
|
|
65042
|
+
}
|
|
65043
|
+
}
|
|
65044
|
+
}
|
|
65045
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65046
|
+
}
|
|
64089
65047
|
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
64090
65048
|
state.visitedSymbolIds.add(symbol.id);
|
|
64091
65049
|
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64092
65050
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64093
65051
|
return match;
|
|
64094
65052
|
}
|
|
65053
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
|
|
65054
|
+
const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
|
|
65055
|
+
allowConstNumericLiteral: true,
|
|
65056
|
+
stringifyNonStringLiterals: true
|
|
65057
|
+
});
|
|
65058
|
+
const object = stripParenExpression(unwrappedExpression.object);
|
|
65059
|
+
if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
|
|
65060
|
+
const callArguments = object.arguments ?? [];
|
|
65061
|
+
if (isReactApiCall(object, "useMemo", context.scopes, {
|
|
65062
|
+
allowGlobalReactNamespace: true,
|
|
65063
|
+
resolveNamedAliases: true
|
|
65064
|
+
})) {
|
|
65065
|
+
const callbackArgument = callArguments[0];
|
|
65066
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
65067
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
65068
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
|
|
65069
|
+
}
|
|
65070
|
+
const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
|
|
65071
|
+
return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
|
|
65072
|
+
}
|
|
64095
65073
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64096
65074
|
const callArguments = unwrappedExpression.arguments ?? [];
|
|
65075
|
+
if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
|
|
65076
|
+
allowGlobalReactNamespace: true,
|
|
65077
|
+
resolveNamedAliases: true
|
|
65078
|
+
})) {
|
|
65079
|
+
const initialState = callArguments[0];
|
|
65080
|
+
if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
|
|
65081
|
+
const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
|
|
65082
|
+
return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
|
|
65083
|
+
}
|
|
64097
65084
|
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
64098
65085
|
allowGlobalReactNamespace: true,
|
|
64099
65086
|
resolveNamedAliases: true
|
|
@@ -64121,6 +65108,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64121
65108
|
});
|
|
64122
65109
|
}
|
|
64123
65110
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
65111
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
65112
|
+
const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
|
|
65113
|
+
if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
|
|
65114
|
+
return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
|
|
65115
|
+
}
|
|
65116
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
65117
|
+
if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
|
|
65118
|
+
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
65119
|
+
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
65120
|
+
const nestedMatch = leftMatch ?? rightMatch;
|
|
65121
|
+
if (!nestedMatch) return null;
|
|
65122
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
|
|
65123
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
|
|
65124
|
+
if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
|
|
65125
|
+
return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
|
|
65126
|
+
}
|
|
64124
65127
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
64125
65128
|
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
64126
65129
|
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
@@ -64137,8 +65140,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
|
|
|
64137
65140
|
const consequentValues = getReturnedValues(statement.consequent);
|
|
64138
65141
|
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
64139
65142
|
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
65143
|
+
if (conditionMatch) {
|
|
65144
|
+
const followingReturnedValues = findFollowingReturnedValues(statement);
|
|
65145
|
+
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;
|
|
65146
|
+
}
|
|
64140
65147
|
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
64141
65148
|
}
|
|
65149
|
+
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);
|
|
64142
65150
|
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
64143
65151
|
for (const childStatement of statement.body) {
|
|
64144
65152
|
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
@@ -64159,47 +65167,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
|
|
|
64159
65167
|
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
64160
65168
|
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
64161
65169
|
});
|
|
64162
|
-
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
64163
|
-
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
65170
|
+
const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
|
|
65171
|
+
const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
|
|
64164
65172
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
64165
65173
|
const left = stripParenExpression(leftNode);
|
|
64166
65174
|
const right = stripParenExpression(rightNode);
|
|
64167
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65175
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64168
65176
|
if (left.type !== right.type) return false;
|
|
64169
65177
|
if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
|
|
64170
65178
|
if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
|
|
64171
65179
|
if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
|
|
64172
|
-
return areRenderedBranchesEquivalent(left.expression, right.expression);
|
|
65180
|
+
return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
|
|
64173
65181
|
}
|
|
64174
65182
|
if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
|
|
64175
65183
|
if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
|
|
64176
|
-
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
|
|
64177
|
-
return areNodeArraysEquivalent(left.children, right.children);
|
|
65184
|
+
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
|
|
65185
|
+
return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64178
65186
|
}
|
|
64179
|
-
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
|
|
65187
|
+
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64180
65188
|
if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
|
|
64181
65189
|
if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
|
|
64182
|
-
return areRenderedBranchesEquivalent(left.value, right.value);
|
|
65190
|
+
return areRenderedBranchesEquivalent(left.value, right.value, scopes);
|
|
64183
65191
|
}
|
|
64184
|
-
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
|
|
65192
|
+
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
|
|
64185
65193
|
if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
|
|
64186
65194
|
if (left.quasis.length !== right.quasis.length) return false;
|
|
64187
65195
|
if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
|
|
64188
|
-
return areNodeArraysEquivalent(left.expressions, right.expressions);
|
|
65196
|
+
return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
|
|
64189
65197
|
}
|
|
64190
65198
|
return false;
|
|
64191
65199
|
};
|
|
64192
|
-
const
|
|
65200
|
+
const isProvenReactCreateElementCall = (node, scopes) => {
|
|
65201
|
+
if (isReactApiCall(node, "createElement", scopes, {
|
|
65202
|
+
allowGlobalReactNamespace: true,
|
|
65203
|
+
resolveNamedAliases: true
|
|
65204
|
+
})) return true;
|
|
65205
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
65206
|
+
const callee = stripParenExpression(node.callee);
|
|
65207
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
|
|
65208
|
+
const receiver = stripParenExpression(callee.object);
|
|
65209
|
+
const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
|
|
65210
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
65211
|
+
const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
|
|
65212
|
+
return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
|
|
65213
|
+
};
|
|
65214
|
+
const isRenderedValue = (node, scopes) => {
|
|
64193
65215
|
const unwrappedNode = stripParenExpression(node);
|
|
64194
65216
|
if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
|
|
64195
65217
|
if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
|
|
65218
|
+
if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
|
|
64196
65219
|
return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
|
|
64197
65220
|
};
|
|
64198
|
-
const findRenderedValueInAndBranch = (node) => {
|
|
65221
|
+
const findRenderedValueInAndBranch = (node, scopes) => {
|
|
64199
65222
|
const unwrappedNode = stripParenExpression(node);
|
|
64200
|
-
if (
|
|
65223
|
+
if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
|
|
64201
65224
|
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
|
|
64202
|
-
return findRenderedValueInAndBranch(unwrappedNode.right);
|
|
65225
|
+
return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
|
|
64203
65226
|
};
|
|
64204
65227
|
const findEnclosingJsxAttribute = (node) => {
|
|
64205
65228
|
let currentNode = node.parent;
|
|
@@ -64232,6 +65255,11 @@ const getReturnedValues = (statement) => {
|
|
|
64232
65255
|
if (!statement) return [];
|
|
64233
65256
|
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
|
|
64234
65257
|
if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
|
|
65258
|
+
if (isNodeOfType(statement, "TryStatement")) return [
|
|
65259
|
+
...getReturnedValues(statement.block),
|
|
65260
|
+
...getReturnedValues(statement.handler?.body),
|
|
65261
|
+
...getReturnedValues(statement.finalizer)
|
|
65262
|
+
];
|
|
64235
65263
|
if (!isNodeOfType(statement, "BlockStatement")) return [];
|
|
64236
65264
|
const returnedValues = [];
|
|
64237
65265
|
for (const childStatement of statement.body) {
|
|
@@ -64240,6 +65268,127 @@ const getReturnedValues = (statement) => {
|
|
|
64240
65268
|
}
|
|
64241
65269
|
return returnedValues;
|
|
64242
65270
|
};
|
|
65271
|
+
const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
|
|
65272
|
+
const unwrappedNode = stripParenExpression(node);
|
|
65273
|
+
if (isRenderedValue(unwrappedNode, scopes)) return true;
|
|
65274
|
+
if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
|
|
65275
|
+
if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
|
|
65276
|
+
if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
|
|
65277
|
+
const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
|
|
65278
|
+
if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
|
|
65279
|
+
visitedFunctionNodes.add(calledFunction);
|
|
65280
|
+
const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
|
|
65281
|
+
const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
|
|
65282
|
+
visitedFunctionNodes.delete(calledFunction);
|
|
65283
|
+
return isPotentiallyRendered;
|
|
65284
|
+
};
|
|
65285
|
+
const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
|
|
65286
|
+
const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
|
|
65287
|
+
let currentNode = node.parent;
|
|
65288
|
+
while (currentNode && currentNode !== componentOrHookNode) {
|
|
65289
|
+
if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
|
|
65290
|
+
allowGlobalReactNamespace: true,
|
|
65291
|
+
resolveNamedAliases: true
|
|
65292
|
+
}) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
|
|
65293
|
+
const stateBinding = currentNode.parent.id.elements?.[0];
|
|
65294
|
+
return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
|
|
65295
|
+
}
|
|
65296
|
+
if (isFunctionLike$1(currentNode)) return null;
|
|
65297
|
+
currentNode = currentNode.parent;
|
|
65298
|
+
}
|
|
65299
|
+
return null;
|
|
65300
|
+
};
|
|
65301
|
+
const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
|
|
65302
|
+
let currentNode = referenceIdentifier;
|
|
65303
|
+
let parentNode = currentNode.parent;
|
|
65304
|
+
while (parentNode) {
|
|
65305
|
+
if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
|
|
65306
|
+
if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
|
|
65307
|
+
if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
|
|
65308
|
+
currentNode = parentNode;
|
|
65309
|
+
parentNode = currentNode.parent;
|
|
65310
|
+
}
|
|
65311
|
+
return false;
|
|
65312
|
+
};
|
|
65313
|
+
const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
|
|
65314
|
+
const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
|
|
65315
|
+
return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
|
|
65316
|
+
};
|
|
65317
|
+
const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
|
|
65318
|
+
if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
|
|
65319
|
+
const parentNode = node.parent;
|
|
65320
|
+
if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
|
|
65321
|
+
const aliasSymbol = scopes.symbolFor(parentNode.id);
|
|
65322
|
+
if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
|
|
65323
|
+
visitedSymbolIds.add(aliasSymbol.id);
|
|
65324
|
+
const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
|
|
65325
|
+
visitedSymbolIds.delete(aliasSymbol.id);
|
|
65326
|
+
return doesReachRenderedOutput;
|
|
65327
|
+
};
|
|
65328
|
+
const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
|
|
65329
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
|
|
65330
|
+
const consumerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
65331
|
+
if (!consumerSymbol) return false;
|
|
65332
|
+
return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
|
|
65333
|
+
};
|
|
65334
|
+
const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
|
|
65335
|
+
const unwrappedValue = stripParenExpression(returnedValue);
|
|
65336
|
+
if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
|
|
65337
|
+
if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
|
|
65338
|
+
const propertyName = getResolvedStaticPropertyName(property, scopes);
|
|
65339
|
+
return propertyName === null ? [] : [{
|
|
65340
|
+
kind: "property",
|
|
65341
|
+
key: propertyName
|
|
65342
|
+
}];
|
|
65343
|
+
});
|
|
65344
|
+
if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
|
|
65345
|
+
kind: "index",
|
|
65346
|
+
key: String(index)
|
|
65347
|
+
}] : []);
|
|
65348
|
+
return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
|
|
65349
|
+
kind: "direct",
|
|
65350
|
+
key: null
|
|
65351
|
+
}] : [];
|
|
65352
|
+
};
|
|
65353
|
+
const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
|
|
65354
|
+
const callParent = callExpression.parent;
|
|
65355
|
+
if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65356
|
+
if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
|
|
65357
|
+
allowConstNumericLiteral: true,
|
|
65358
|
+
stringifyNonStringLiterals: true
|
|
65359
|
+
}) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65360
|
+
if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
|
|
65361
|
+
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));
|
|
65362
|
+
if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
|
|
65363
|
+
const element = callParent.id.elements?.[Number(returnedStatePath.key)];
|
|
65364
|
+
return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
|
|
65365
|
+
}
|
|
65366
|
+
if (!isNodeOfType(callParent.id, "Identifier")) return false;
|
|
65367
|
+
const resultSymbol = scopes.symbolFor(callParent.id);
|
|
65368
|
+
if (!resultSymbol) return false;
|
|
65369
|
+
return resultSymbol.references.some((reference) => {
|
|
65370
|
+
const memberExpression = reference.identifier.parent;
|
|
65371
|
+
return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
|
|
65372
|
+
allowConstNumericLiteral: true,
|
|
65373
|
+
stringifyNonStringLiterals: true
|
|
65374
|
+
}) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
|
|
65375
|
+
});
|
|
65376
|
+
};
|
|
65377
|
+
const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
|
|
65378
|
+
if (!isFunctionLike$1(componentOrHookNode)) return false;
|
|
65379
|
+
const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
|
|
65380
|
+
if (!stateSymbol) return false;
|
|
65381
|
+
const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
|
|
65382
|
+
if (returnedStatePaths.length === 0) return false;
|
|
65383
|
+
const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
|
|
65384
|
+
if (!isNodeOfType(functionBinding, "Identifier")) return false;
|
|
65385
|
+
const functionSymbol = scopes.symbolFor(functionBinding);
|
|
65386
|
+
if (!functionSymbol) return false;
|
|
65387
|
+
return functionSymbol.references.some((functionReference) => {
|
|
65388
|
+
const callExpression = functionReference.identifier.parent;
|
|
65389
|
+
return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
|
|
65390
|
+
});
|
|
65391
|
+
};
|
|
64243
65392
|
const findFollowingReturnedValues = (ifStatement) => {
|
|
64244
65393
|
const parentNode = ifStatement.parent;
|
|
64245
65394
|
if (!isNodeOfType(parentNode, "BlockStatement")) return [];
|
|
@@ -64252,24 +65401,24 @@ const findFollowingReturnedValues = (ifStatement) => {
|
|
|
64252
65401
|
}
|
|
64253
65402
|
return returnedValues;
|
|
64254
65403
|
};
|
|
64255
|
-
const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
|
|
65404
|
+
const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
|
|
64256
65405
|
const left = stripParenExpression(leftExpression);
|
|
64257
65406
|
const right = stripParenExpression(rightExpression);
|
|
64258
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65407
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64259
65408
|
if (left.type !== right.type) return false;
|
|
64260
|
-
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
|
|
64261
|
-
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
64262
|
-
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
65409
|
+
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
|
|
65410
|
+
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
65411
|
+
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
64263
65412
|
return false;
|
|
64264
65413
|
};
|
|
64265
|
-
const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
|
|
65414
|
+
const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
|
|
64266
65415
|
if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
|
|
64267
|
-
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
|
|
64268
|
-
if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
|
|
65416
|
+
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
|
|
65417
|
+
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);
|
|
64269
65418
|
if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
|
|
64270
65419
|
const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64271
65420
|
const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64272
|
-
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
|
|
65421
|
+
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
|
|
64273
65422
|
};
|
|
64274
65423
|
const isStructuralRenderedValue = (node) => {
|
|
64275
65424
|
if (!node) return false;
|
|
@@ -64293,19 +65442,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64293
65442
|
if (isTestlikeFilename(context.filename)) return {};
|
|
64294
65443
|
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
64295
65444
|
let fileHasUseClientDirective = false;
|
|
65445
|
+
let fileHasExplicitReactRuntimeReference = false;
|
|
64296
65446
|
let fileIsEmailTemplate = false;
|
|
64297
65447
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
64298
|
-
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
|
|
65448
|
+
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
|
|
64299
65449
|
const conditionMatch = matchHydrationCondition(conditionNode, context);
|
|
64300
65450
|
if (!conditionMatch) return;
|
|
64301
65451
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
64302
65452
|
if (reportedNodes.has(predicateNode)) return;
|
|
64303
|
-
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
64304
|
-
const
|
|
65453
|
+
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
|
|
65454
|
+
const enclosingFunction = findEnclosingFunction$1(conditionNode);
|
|
65455
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
64305
65456
|
if (!componentOrHookNode) return;
|
|
64306
|
-
|
|
64307
|
-
if (
|
|
64308
|
-
if (!
|
|
65457
|
+
const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
|
|
65458
|
+
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
|
|
65459
|
+
if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
|
|
65460
|
+
if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
|
|
64309
65461
|
const attribute = findEnclosingJsxAttribute(conditionNode);
|
|
64310
65462
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
64311
65463
|
}
|
|
@@ -64324,28 +65476,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64324
65476
|
return {
|
|
64325
65477
|
Program(node) {
|
|
64326
65478
|
fileHasUseClientDirective = hasDirective(node, "use client");
|
|
65479
|
+
fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
|
|
64327
65480
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
64328
65481
|
},
|
|
64329
65482
|
ConditionalExpression(node) {
|
|
64330
65483
|
reportHydrationBranch(node.test, node.consequent, node.alternate, true);
|
|
65484
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
65485
|
+
if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
|
|
64331
65486
|
},
|
|
64332
65487
|
LogicalExpression(node) {
|
|
64333
65488
|
if (node.operator !== "&&" && node.operator !== "||") return;
|
|
64334
|
-
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) :
|
|
65489
|
+
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
|
|
64335
65490
|
if (!renderedValue) return;
|
|
64336
65491
|
reportHydrationBranch(node, renderedValue, null, true);
|
|
64337
65492
|
},
|
|
64338
65493
|
IfStatement(node) {
|
|
64339
|
-
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
|
|
65494
|
+
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
|
|
64340
65495
|
const consequentValues = getReturnedValues(node.consequent);
|
|
64341
65496
|
const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
|
|
64342
65497
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
64343
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
64344
|
-
if (!componentOrHookNode) return;
|
|
64345
65498
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
64346
|
-
|
|
65499
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
65500
|
+
if (!componentOrHookNode) return;
|
|
65501
|
+
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
|
|
64347
65502
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
64348
|
-
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
65503
|
+
if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
|
|
64349
65504
|
reportHydrationBranch(node.test, consequentValue, alternateValue, false);
|
|
64350
65505
|
}
|
|
64351
65506
|
}
|
|
@@ -66079,6 +67234,124 @@ const isInsideSnapshotHelper = (node) => {
|
|
|
66079
67234
|
}
|
|
66080
67235
|
return false;
|
|
66081
67236
|
};
|
|
67237
|
+
const findEnclosingNextjsPageDataFunction = (node) => {
|
|
67238
|
+
let outermostFunction = null;
|
|
67239
|
+
let cursor = node.parent;
|
|
67240
|
+
while (cursor) {
|
|
67241
|
+
if (isFunctionLike$1(cursor)) outermostFunction = cursor;
|
|
67242
|
+
if (isNodeOfType(cursor, "Program")) {
|
|
67243
|
+
if (!outermostFunction) return null;
|
|
67244
|
+
for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
|
|
67245
|
+
const exportedValue = findExportedValue(cursor, exportName);
|
|
67246
|
+
if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
|
|
67247
|
+
}
|
|
67248
|
+
return null;
|
|
67249
|
+
}
|
|
67250
|
+
cursor = cursor.parent ?? null;
|
|
67251
|
+
}
|
|
67252
|
+
return null;
|
|
67253
|
+
};
|
|
67254
|
+
const findConditionalReturnExpressionRoot = (node) => {
|
|
67255
|
+
let expressionRoot = findTransparentExpressionRoot(node);
|
|
67256
|
+
while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
|
|
67257
|
+
return expressionRoot;
|
|
67258
|
+
};
|
|
67259
|
+
const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
|
|
67260
|
+
const declarator = returnExpression.parent;
|
|
67261
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
|
|
67262
|
+
const bindingSymbol = context.scopes.symbolFor(declarator.id);
|
|
67263
|
+
if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
|
|
67264
|
+
const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
|
|
67265
|
+
const returnStatement = referenceRoot.parent;
|
|
67266
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
|
|
67267
|
+
};
|
|
67268
|
+
const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
|
|
67269
|
+
const isValueForwardedThroughLiteralStructure = (node, structure) => {
|
|
67270
|
+
const strippedNode = stripParenExpression(node);
|
|
67271
|
+
const strippedStructure = stripParenExpression(structure);
|
|
67272
|
+
if (strippedNode === strippedStructure) return true;
|
|
67273
|
+
if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
|
|
67274
|
+
if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
|
|
67275
|
+
if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
|
|
67276
|
+
return strippedStructure.properties.some((property) => {
|
|
67277
|
+
if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
|
|
67278
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
67279
|
+
if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
|
|
67280
|
+
return isSameShorthandPropertyValue(strippedNode, property);
|
|
67281
|
+
});
|
|
67282
|
+
};
|
|
67283
|
+
const isValueForwardedToPropertyValue = (node, property) => {
|
|
67284
|
+
const directValue = findConditionalReturnExpressionRoot(node);
|
|
67285
|
+
if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
|
|
67286
|
+
return isSameShorthandPropertyValue(directValue, property);
|
|
67287
|
+
};
|
|
67288
|
+
const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
|
|
67289
|
+
let cursor = node.parent;
|
|
67290
|
+
while (cursor && cursor !== pageDataFunction) {
|
|
67291
|
+
if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
|
|
67292
|
+
const propertyContainer = cursor.parent;
|
|
67293
|
+
if (!propertyContainer) return false;
|
|
67294
|
+
const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
|
|
67295
|
+
const returnStatement = returnExpression.parent;
|
|
67296
|
+
if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
|
|
67297
|
+
if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
|
|
67298
|
+
if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
|
|
67299
|
+
}
|
|
67300
|
+
cursor = cursor.parent ?? null;
|
|
67301
|
+
}
|
|
67302
|
+
return false;
|
|
67303
|
+
};
|
|
67304
|
+
const isExpressionReturnedByFunction = (node, functionNode) => {
|
|
67305
|
+
const returnExpression = findConditionalReturnExpressionRoot(node);
|
|
67306
|
+
if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
|
|
67307
|
+
const returnStatement = returnExpression.parent;
|
|
67308
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
|
|
67309
|
+
};
|
|
67310
|
+
const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
|
|
67311
|
+
if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
|
|
67312
|
+
const initializer = stripParenExpression(bindingInitializer);
|
|
67313
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
67314
|
+
const callee = stripParenExpression(initializer.callee);
|
|
67315
|
+
return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
|
|
67316
|
+
};
|
|
67317
|
+
const findPageDataResultBinding = (node) => {
|
|
67318
|
+
let cursor = node.parent;
|
|
67319
|
+
while (cursor) {
|
|
67320
|
+
if (isNodeOfType(cursor, "VariableDeclarator")) {
|
|
67321
|
+
if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
|
|
67322
|
+
return null;
|
|
67323
|
+
}
|
|
67324
|
+
cursor = cursor.parent ?? null;
|
|
67325
|
+
}
|
|
67326
|
+
return null;
|
|
67327
|
+
};
|
|
67328
|
+
const isUsedToSerializeNextjsPageProps = (node, context) => {
|
|
67329
|
+
if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
|
|
67330
|
+
const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
|
|
67331
|
+
if (!pageDataFunction) return false;
|
|
67332
|
+
if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
|
|
67333
|
+
const bindingIdentifier = findPageDataResultBinding(node);
|
|
67334
|
+
const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
67335
|
+
if (!bindingSymbol) return false;
|
|
67336
|
+
const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
|
|
67337
|
+
const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
|
|
67338
|
+
let hasPagePropsReference = false;
|
|
67339
|
+
for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
|
|
67340
|
+
if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
|
|
67341
|
+
if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
|
|
67342
|
+
hasPagePropsReference = true;
|
|
67343
|
+
continue;
|
|
67344
|
+
}
|
|
67345
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
67346
|
+
const declarator = referenceRoot.parent;
|
|
67347
|
+
if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
|
|
67348
|
+
const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
|
|
67349
|
+
if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
|
|
67350
|
+
}
|
|
67351
|
+
return false;
|
|
67352
|
+
}
|
|
67353
|
+
return hasPagePropsReference;
|
|
67354
|
+
};
|
|
66082
67355
|
const noJsonParseStringifyClone = defineRule({
|
|
66083
67356
|
id: "no-json-parse-stringify-clone",
|
|
66084
67357
|
title: "JSON parse/stringify deep clone",
|
|
@@ -66096,6 +67369,7 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
66096
67369
|
if (isInsideSnapshotHelper(node)) return;
|
|
66097
67370
|
if (isAssignedToNormalizationBinding(node)) return;
|
|
66098
67371
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
67372
|
+
if (isUsedToSerializeNextjsPageProps(node, context)) return;
|
|
66099
67373
|
context.report({
|
|
66100
67374
|
node,
|
|
66101
67375
|
message: MESSAGE$35
|
|
@@ -67511,6 +68785,77 @@ const isCancellationGuardTest = (test) => {
|
|
|
67511
68785
|
});
|
|
67512
68786
|
return matches;
|
|
67513
68787
|
};
|
|
68788
|
+
const getReactRefCurrent = (expression, context) => {
|
|
68789
|
+
const stripped = stripParenExpression(expression);
|
|
68790
|
+
if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
|
|
68791
|
+
const receiver = stripParenExpression(stripped.object);
|
|
68792
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
68793
|
+
const binding = findVariableInitializer(receiver, receiver.name);
|
|
68794
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
68795
|
+
return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
|
|
68796
|
+
allowGlobalReactNamespace: true,
|
|
68797
|
+
allowUnboundBareCalls: true
|
|
68798
|
+
}) ? stripped : null;
|
|
68799
|
+
};
|
|
68800
|
+
const getStableOwnershipToken = (expression, context) => {
|
|
68801
|
+
const stripped = stripParenExpression(expression);
|
|
68802
|
+
if (!isNodeOfType(stripped, "Identifier")) return null;
|
|
68803
|
+
const symbol = context.scopes.symbolFor(stripped);
|
|
68804
|
+
const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
68805
|
+
const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
|
|
68806
|
+
return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
|
|
68807
|
+
};
|
|
68808
|
+
const getAsyncOwnershipComparison = (test, context) => {
|
|
68809
|
+
const stripped = stripParenExpression(test);
|
|
68810
|
+
if (!isNodeOfType(stripped, "BinaryExpression")) return null;
|
|
68811
|
+
const leftRef = getReactRefCurrent(stripped.left, context);
|
|
68812
|
+
const rightRef = getReactRefCurrent(stripped.right, context);
|
|
68813
|
+
const leftToken = getStableOwnershipToken(stripped.left, context);
|
|
68814
|
+
const rightToken = getStableOwnershipToken(stripped.right, context);
|
|
68815
|
+
if (stripped.operator === "===" || stripped.operator === "==") {
|
|
68816
|
+
if (leftRef && rightToken) return {
|
|
68817
|
+
refCurrent: leftRef,
|
|
68818
|
+
token: rightToken,
|
|
68819
|
+
mode: "owns",
|
|
68820
|
+
isOrdered: false
|
|
68821
|
+
};
|
|
68822
|
+
if (rightRef && leftToken) return {
|
|
68823
|
+
refCurrent: rightRef,
|
|
68824
|
+
token: leftToken,
|
|
68825
|
+
mode: "owns",
|
|
68826
|
+
isOrdered: false
|
|
68827
|
+
};
|
|
68828
|
+
return null;
|
|
68829
|
+
}
|
|
68830
|
+
if (stripped.operator === "!==" || stripped.operator === "!=") {
|
|
68831
|
+
if (leftRef && rightToken) return {
|
|
68832
|
+
refCurrent: leftRef,
|
|
68833
|
+
token: rightToken,
|
|
68834
|
+
mode: "lost",
|
|
68835
|
+
isOrdered: false
|
|
68836
|
+
};
|
|
68837
|
+
if (rightRef && leftToken) return {
|
|
68838
|
+
refCurrent: rightRef,
|
|
68839
|
+
token: leftToken,
|
|
68840
|
+
mode: "lost",
|
|
68841
|
+
isOrdered: false
|
|
68842
|
+
};
|
|
68843
|
+
return null;
|
|
68844
|
+
}
|
|
68845
|
+
if (stripped.operator === "<=" && leftRef && rightToken) return {
|
|
68846
|
+
refCurrent: leftRef,
|
|
68847
|
+
token: rightToken,
|
|
68848
|
+
mode: "owns",
|
|
68849
|
+
isOrdered: true
|
|
68850
|
+
};
|
|
68851
|
+
if (stripped.operator === ">=" && rightRef && leftToken) return {
|
|
68852
|
+
refCurrent: rightRef,
|
|
68853
|
+
token: leftToken,
|
|
68854
|
+
mode: "owns",
|
|
68855
|
+
isOrdered: true
|
|
68856
|
+
};
|
|
68857
|
+
return null;
|
|
68858
|
+
};
|
|
67514
68859
|
const dedupeCatchPathStates = (states) => {
|
|
67515
68860
|
const statesByKey = /* @__PURE__ */ new Map();
|
|
67516
68861
|
for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
|
|
@@ -67843,7 +69188,221 @@ const isInsideTryFinalizer = (node, tryStatement) => {
|
|
|
67843
69188
|
}
|
|
67844
69189
|
return false;
|
|
67845
69190
|
};
|
|
67846
|
-
const
|
|
69191
|
+
const getDirectBlockEntry = (node, functionNode) => {
|
|
69192
|
+
let entry = node;
|
|
69193
|
+
let cursor = node.parent;
|
|
69194
|
+
while (cursor && cursor !== functionNode) {
|
|
69195
|
+
if (isNodeOfType(cursor, "BlockStatement")) return {
|
|
69196
|
+
block: cursor,
|
|
69197
|
+
entry
|
|
69198
|
+
};
|
|
69199
|
+
entry = cursor;
|
|
69200
|
+
cursor = cursor.parent ?? null;
|
|
69201
|
+
}
|
|
69202
|
+
return null;
|
|
69203
|
+
};
|
|
69204
|
+
const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
|
|
69205
|
+
const claimStart = getNodeStart$1(claimNode);
|
|
69206
|
+
if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
|
|
69207
|
+
const claimEntry = getDirectBlockEntry(claimNode, functionNode);
|
|
69208
|
+
const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
|
|
69209
|
+
if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
|
|
69210
|
+
let claimCursor = claimNode.parent;
|
|
69211
|
+
while (claimCursor && claimCursor !== claimEntry.block) {
|
|
69212
|
+
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;
|
|
69213
|
+
claimCursor = claimCursor.parent ?? null;
|
|
69214
|
+
}
|
|
69215
|
+
const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
|
|
69216
|
+
const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
|
|
69217
|
+
if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
|
|
69218
|
+
return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
|
|
69219
|
+
};
|
|
69220
|
+
const getOwningFunction = (functionNode) => {
|
|
69221
|
+
let ownerFunction = functionNode;
|
|
69222
|
+
let cursor = functionNode.parent;
|
|
69223
|
+
while (cursor) {
|
|
69224
|
+
if (isFunctionLike$1(cursor)) ownerFunction = cursor;
|
|
69225
|
+
cursor = cursor.parent ?? null;
|
|
69226
|
+
}
|
|
69227
|
+
return ownerFunction;
|
|
69228
|
+
};
|
|
69229
|
+
const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
|
|
69230
|
+
const truthyCall = truthySets[0]?.node;
|
|
69231
|
+
if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
|
|
69232
|
+
const setter = getSetterBooleanValue(truthyCall, context);
|
|
69233
|
+
if (!setter) return false;
|
|
69234
|
+
let effectCallback = writeNode.parent;
|
|
69235
|
+
while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
|
|
69236
|
+
if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
|
|
69237
|
+
if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
|
|
69238
|
+
const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
|
|
69239
|
+
if (!writeEntry) return false;
|
|
69240
|
+
let isPaired = false;
|
|
69241
|
+
walkOwnFunctionScope(effectCallback, (candidate) => {
|
|
69242
|
+
if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
|
|
69243
|
+
const candidateSetter = getSetterBooleanValue(candidate, context);
|
|
69244
|
+
if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
|
|
69245
|
+
const resetEntry = getDirectBlockEntry(candidate, effectCallback);
|
|
69246
|
+
if (!resetEntry || resetEntry.block !== writeEntry.block) return;
|
|
69247
|
+
const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
|
|
69248
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69249
|
+
if (writeIndex === -1 || resetIndex === -1) return;
|
|
69250
|
+
if (resetIndex <= writeIndex) {
|
|
69251
|
+
isPaired = true;
|
|
69252
|
+
return false;
|
|
69253
|
+
}
|
|
69254
|
+
isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
|
|
69255
|
+
return isPaired ? false : void 0;
|
|
69256
|
+
});
|
|
69257
|
+
return isPaired;
|
|
69258
|
+
};
|
|
69259
|
+
const isUnconditionalReturnBranch = (statement) => {
|
|
69260
|
+
if (isNodeOfType(statement, "ReturnStatement")) return true;
|
|
69261
|
+
return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
|
|
69262
|
+
};
|
|
69263
|
+
const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69264
|
+
const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
|
|
69265
|
+
const resetEntry = getDirectBlockEntry(resetNode, functionNode);
|
|
69266
|
+
if (!snapshotEntry || !resetEntry) return null;
|
|
69267
|
+
const claimCandidates = [];
|
|
69268
|
+
const releaseCandidates = [];
|
|
69269
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69270
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
|
|
69271
|
+
const assignedValue = stripParenExpression(candidate.right);
|
|
69272
|
+
if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
|
|
69273
|
+
const candidateKey = serializeReferenceKey({
|
|
69274
|
+
node: candidate.left,
|
|
69275
|
+
scopes: context.scopes
|
|
69276
|
+
});
|
|
69277
|
+
if (!candidateKey) return;
|
|
69278
|
+
if (!assignedValue.value) {
|
|
69279
|
+
if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
|
|
69280
|
+
return;
|
|
69281
|
+
}
|
|
69282
|
+
if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
|
|
69283
|
+
const candidateEntry = getDirectBlockEntry(candidate, functionNode);
|
|
69284
|
+
if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
|
|
69285
|
+
const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
|
|
69286
|
+
const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
|
|
69287
|
+
if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
|
|
69288
|
+
const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
|
|
69289
|
+
if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
|
|
69290
|
+
return serializeReferenceKey({
|
|
69291
|
+
node: stripParenExpression(statement.test),
|
|
69292
|
+
scopes: context.scopes
|
|
69293
|
+
}) === candidateKey;
|
|
69294
|
+
});
|
|
69295
|
+
if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
|
|
69296
|
+
claimCandidates.push(candidate);
|
|
69297
|
+
});
|
|
69298
|
+
const claim = claimCandidates.find((claimCandidate) => {
|
|
69299
|
+
const candidateKey = serializeReferenceKey({
|
|
69300
|
+
node: claimCandidate.left,
|
|
69301
|
+
scopes: context.scopes
|
|
69302
|
+
});
|
|
69303
|
+
return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
|
|
69304
|
+
node: releaseCandidate.left,
|
|
69305
|
+
scopes: context.scopes
|
|
69306
|
+
}) === candidateKey);
|
|
69307
|
+
});
|
|
69308
|
+
if (!claim) return null;
|
|
69309
|
+
const claimKey = serializeReferenceKey({
|
|
69310
|
+
node: claim.left,
|
|
69311
|
+
scopes: context.scopes
|
|
69312
|
+
});
|
|
69313
|
+
const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
|
|
69314
|
+
node: releaseCandidate.left,
|
|
69315
|
+
scopes: context.scopes
|
|
69316
|
+
}) === claimKey);
|
|
69317
|
+
if (!claimKey || !release) return null;
|
|
69318
|
+
const releaseEntry = getDirectBlockEntry(release, functionNode);
|
|
69319
|
+
if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
|
|
69320
|
+
const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
|
|
69321
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69322
|
+
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;
|
|
69323
|
+
let didFindUnsafeWrite = false;
|
|
69324
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69325
|
+
if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
|
|
69326
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69327
|
+
if (writeTarget && serializeReferenceKey({
|
|
69328
|
+
node: writeTarget,
|
|
69329
|
+
scopes: context.scopes
|
|
69330
|
+
}) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
|
|
69331
|
+
});
|
|
69332
|
+
return didFindUnsafeWrite ? null : claim;
|
|
69333
|
+
};
|
|
69334
|
+
const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69335
|
+
const refKey = serializeReferenceKey({
|
|
69336
|
+
node: comparison.refCurrent,
|
|
69337
|
+
scopes: context.scopes
|
|
69338
|
+
});
|
|
69339
|
+
const tokenKey = serializeReferenceKey({
|
|
69340
|
+
node: comparison.token,
|
|
69341
|
+
scopes: context.scopes
|
|
69342
|
+
});
|
|
69343
|
+
if (!refKey || !tokenKey) return null;
|
|
69344
|
+
const candidates = [];
|
|
69345
|
+
const tokenSymbol = context.scopes.symbolFor(comparison.token);
|
|
69346
|
+
const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
|
|
69347
|
+
if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
|
|
69348
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
|
|
69349
|
+
node: tokenInitializer.argument,
|
|
69350
|
+
scopes: context.scopes
|
|
69351
|
+
}) === refKey) candidates.push(tokenInitializer);
|
|
69352
|
+
if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
|
|
69353
|
+
node: tokenInitializer,
|
|
69354
|
+
scopes: context.scopes
|
|
69355
|
+
}) === refKey) {
|
|
69356
|
+
const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69357
|
+
if (singleFlightClaim) candidates.push(singleFlightClaim);
|
|
69358
|
+
}
|
|
69359
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
|
|
69360
|
+
const generationKey = serializeReferenceKey({
|
|
69361
|
+
node: tokenInitializer.argument,
|
|
69362
|
+
scopes: context.scopes
|
|
69363
|
+
});
|
|
69364
|
+
if (generationKey && generationKey === refKey) {
|
|
69365
|
+
const ownerFunction = getOwningFunction(functionNode);
|
|
69366
|
+
let didFindOtherGenerationWrite = false;
|
|
69367
|
+
walkAst(ownerFunction, (candidate) => {
|
|
69368
|
+
if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
|
|
69369
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69370
|
+
if (writeTarget && serializeReferenceKey({
|
|
69371
|
+
node: writeTarget,
|
|
69372
|
+
scopes: context.scopes
|
|
69373
|
+
}) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
|
|
69374
|
+
});
|
|
69375
|
+
if (didFindOtherGenerationWrite) return null;
|
|
69376
|
+
}
|
|
69377
|
+
}
|
|
69378
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69379
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
|
|
69380
|
+
if (serializeReferenceKey({
|
|
69381
|
+
node: candidate.left,
|
|
69382
|
+
scopes: context.scopes
|
|
69383
|
+
}) === refKey && serializeReferenceKey({
|
|
69384
|
+
node: candidate.right,
|
|
69385
|
+
scopes: context.scopes
|
|
69386
|
+
}) === tokenKey) candidates.push(candidate);
|
|
69387
|
+
});
|
|
69388
|
+
const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
|
|
69389
|
+
if (!claim) return null;
|
|
69390
|
+
let didFindOtherWrite = false;
|
|
69391
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69392
|
+
if (didFindOtherWrite || candidate === claim) return;
|
|
69393
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69394
|
+
if (writeTarget && serializeReferenceKey({
|
|
69395
|
+
node: writeTarget,
|
|
69396
|
+
scopes: context.scopes
|
|
69397
|
+
}) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
|
|
69398
|
+
});
|
|
69399
|
+
return didFindOtherWrite ? null : claim;
|
|
69400
|
+
};
|
|
69401
|
+
const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69402
|
+
const comparison = getAsyncOwnershipComparison(test, context);
|
|
69403
|
+
return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
|
|
69404
|
+
};
|
|
69405
|
+
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
|
|
67847
69406
|
let didFindOtherWrite = false;
|
|
67848
69407
|
walkAst(effectCallback, (candidate) => {
|
|
67849
69408
|
if (didFindOtherWrite) return false;
|
|
@@ -67851,7 +69410,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
67851
69410
|
if (serializeReferenceKey({
|
|
67852
69411
|
node: candidate.left,
|
|
67853
69412
|
scopes: context.scopes
|
|
67854
|
-
}) === guardKey && !
|
|
69413
|
+
}) === guardKey && !acceptedAssignments.has(candidate)) {
|
|
67855
69414
|
didFindOtherWrite = true;
|
|
67856
69415
|
return false;
|
|
67857
69416
|
}
|
|
@@ -67867,48 +69426,103 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
67867
69426
|
});
|
|
67868
69427
|
return didFindOtherWrite;
|
|
67869
69428
|
};
|
|
67870
|
-
const
|
|
69429
|
+
const collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
|
|
69430
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69431
|
+
for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69432
|
+
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69433
|
+
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69434
|
+
node: cleanupNode.left,
|
|
69435
|
+
scopes: context.scopes
|
|
69436
|
+
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69437
|
+
acceptedAssignments.add(cleanupNode);
|
|
69438
|
+
});
|
|
69439
|
+
if (acceptedAssignments.size === 0) return null;
|
|
69440
|
+
walkOwnFunctionScope(effectCallback, (effectNode) => {
|
|
69441
|
+
const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
|
|
69442
|
+
if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
|
|
69443
|
+
node: effectNode.left,
|
|
69444
|
+
scopes: context.scopes
|
|
69445
|
+
}) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
|
|
69446
|
+
});
|
|
69447
|
+
return acceptedAssignments;
|
|
69448
|
+
};
|
|
69449
|
+
const isEffectCallback = (node, context) => {
|
|
69450
|
+
const callbackRoot = findTransparentExpressionRoot(node);
|
|
69451
|
+
const callbackCall = callbackRoot.parent;
|
|
69452
|
+
return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69453
|
+
allowGlobalReactNamespace: true,
|
|
69454
|
+
allowUnboundBareCalls: true
|
|
69455
|
+
}));
|
|
69456
|
+
};
|
|
69457
|
+
const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
|
|
69458
|
+
const guardKey = serializeReferenceKey({
|
|
69459
|
+
node: guardExpression,
|
|
69460
|
+
scopes: context.scopes
|
|
69461
|
+
});
|
|
69462
|
+
if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69463
|
+
let ownerFunction = functionNode.parent;
|
|
69464
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
69465
|
+
if (!ownerFunction) return false;
|
|
69466
|
+
const effectCallbacks = [];
|
|
69467
|
+
if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
|
|
69468
|
+
walkOwnFunctionScope(ownerFunction, (candidate) => {
|
|
69469
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
69470
|
+
if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69471
|
+
allowGlobalReactNamespace: true,
|
|
69472
|
+
allowUnboundBareCalls: true
|
|
69473
|
+
})) return;
|
|
69474
|
+
const effectCallback = candidate.arguments[0];
|
|
69475
|
+
if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
|
|
69476
|
+
});
|
|
69477
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69478
|
+
for (const effectCallback of effectCallbacks) {
|
|
69479
|
+
const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
|
|
69480
|
+
if (!effectAssignments) continue;
|
|
69481
|
+
for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
|
|
69482
|
+
}
|
|
69483
|
+
return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
|
|
69484
|
+
};
|
|
69485
|
+
const collectLogicalOperands = (expression, operator) => {
|
|
69486
|
+
const stripped = stripParenExpression(expression);
|
|
69487
|
+
if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
|
|
69488
|
+
return [stripped];
|
|
69489
|
+
};
|
|
69490
|
+
const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
|
|
69491
|
+
const positive = [];
|
|
69492
|
+
const negative = [];
|
|
67871
69493
|
let child = resetNode;
|
|
67872
69494
|
let cursor = resetNode.parent;
|
|
67873
|
-
|
|
67874
|
-
|
|
67875
|
-
|
|
67876
|
-
|
|
67877
|
-
|
|
67878
|
-
|
|
67879
|
-
|
|
67880
|
-
|
|
67881
|
-
|
|
67882
|
-
|
|
67883
|
-
|
|
69495
|
+
while (cursor && cursor !== protectingTry) {
|
|
69496
|
+
if (isNodeOfType(cursor, "IfStatement")) {
|
|
69497
|
+
if (cursor.consequent !== child || cursor.alternate !== null) return null;
|
|
69498
|
+
positive.push(...collectLogicalOperands(cursor.test, "&&"));
|
|
69499
|
+
} else if (isNodeOfType(cursor, "LogicalExpression")) {
|
|
69500
|
+
if (cursor.operator !== "&&" || cursor.right !== child) return null;
|
|
69501
|
+
positive.push(...collectLogicalOperands(cursor.left, "&&"));
|
|
69502
|
+
} else if (isNodeOfType(cursor, "BlockStatement")) {
|
|
69503
|
+
const childIndex = cursor.body.findIndex((statement) => statement === child);
|
|
69504
|
+
if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
|
|
69505
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
|
|
69506
|
+
negative.push(...collectLogicalOperands(statement.test, "||"));
|
|
69507
|
+
}
|
|
69508
|
+
} 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;
|
|
67884
69509
|
child = cursor;
|
|
67885
69510
|
cursor = cursor.parent ?? null;
|
|
67886
69511
|
}
|
|
67887
|
-
|
|
67888
|
-
|
|
67889
|
-
|
|
67890
|
-
|
|
67891
|
-
|
|
67892
|
-
|
|
67893
|
-
|
|
67894
|
-
|
|
67895
|
-
|
|
67896
|
-
|
|
67897
|
-
|
|
67898
|
-
|
|
67899
|
-
|
|
67900
|
-
|
|
67901
|
-
node: cleanupNode.left,
|
|
67902
|
-
scopes: context.scopes
|
|
67903
|
-
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
67904
|
-
acceptedCleanupAssignments.add(cleanupNode);
|
|
67905
|
-
});
|
|
67906
|
-
if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
|
|
67907
|
-
}
|
|
67908
|
-
}
|
|
67909
|
-
cursor = cursor.parent ?? null;
|
|
67910
|
-
}
|
|
67911
|
-
return false;
|
|
69512
|
+
return cursor === protectingTry && positive.length + negative.length > 0 ? {
|
|
69513
|
+
positive,
|
|
69514
|
+
negative
|
|
69515
|
+
} : null;
|
|
69516
|
+
};
|
|
69517
|
+
const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69518
|
+
const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69519
|
+
const stripped = stripParenExpression(expression);
|
|
69520
|
+
if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
|
|
69521
|
+
return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69522
|
+
};
|
|
69523
|
+
const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69524
|
+
const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
|
|
69525
|
+
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)));
|
|
67912
69526
|
};
|
|
67913
69527
|
const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
|
|
67914
69528
|
let child = awaitNode;
|
|
@@ -68014,7 +69628,13 @@ const analyzeFunction = (functionNode, context) => {
|
|
|
68014
69628
|
const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
|
|
68015
69629
|
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)));
|
|
68016
69630
|
if (riskyAwaitsWithTruthySet.length === 0) continue;
|
|
68017
|
-
const conditionalExceptionalResets = calls.filter((call) =>
|
|
69631
|
+
const conditionalExceptionalResets = calls.filter((call) => {
|
|
69632
|
+
if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
|
|
69633
|
+
const protectingTry = call.protectingTry;
|
|
69634
|
+
if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
|
|
69635
|
+
const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
|
|
69636
|
+
return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
|
|
69637
|
+
});
|
|
68018
69638
|
for (const reset of conditionalExceptionalResets) {
|
|
68019
69639
|
const catchHandler = reset.protectingTry?.handler;
|
|
68020
69640
|
if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
|
|
@@ -73886,6 +75506,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
73886
75506
|
}
|
|
73887
75507
|
return !isNegated && predicateFunction.body === child;
|
|
73888
75508
|
};
|
|
75509
|
+
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75510
|
+
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75511
|
+
const returnStatement = predicateFunction.body.body[1];
|
|
75512
|
+
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75513
|
+
let negationCount = 0;
|
|
75514
|
+
let expression = matchCall;
|
|
75515
|
+
let parent = expression.parent ?? null;
|
|
75516
|
+
while (parent && parent !== returnStatement) {
|
|
75517
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75518
|
+
negationCount += 1;
|
|
75519
|
+
expression = parent;
|
|
75520
|
+
parent = parent.parent ?? null;
|
|
75521
|
+
continue;
|
|
75522
|
+
}
|
|
75523
|
+
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75524
|
+
expression = parent;
|
|
75525
|
+
parent = parent.parent ?? null;
|
|
75526
|
+
continue;
|
|
75527
|
+
}
|
|
75528
|
+
return false;
|
|
75529
|
+
}
|
|
75530
|
+
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75531
|
+
};
|
|
73889
75532
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
73890
75533
|
const target = stripParenExpression(test);
|
|
73891
75534
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -73926,6 +75569,26 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
73926
75569
|
current = current.object;
|
|
73927
75570
|
}
|
|
73928
75571
|
};
|
|
75572
|
+
const getNormalizedClassNameRoot = (expression) => {
|
|
75573
|
+
const conditional = stripParenExpression(expression);
|
|
75574
|
+
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75575
|
+
const consequent = stripParenExpression(conditional.consequent);
|
|
75576
|
+
const rootIdentifier = getRootIdentifier(consequent);
|
|
75577
|
+
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75578
|
+
const test = stripParenExpression(conditional.test);
|
|
75579
|
+
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75580
|
+
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75581
|
+
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75582
|
+
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75583
|
+
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75584
|
+
const alternate = stripParenExpression(conditional.alternate);
|
|
75585
|
+
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75586
|
+
const fallback = stripParenExpression(alternate.right);
|
|
75587
|
+
const attributeCall = stripParenExpression(alternate.left);
|
|
75588
|
+
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;
|
|
75589
|
+
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75590
|
+
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75591
|
+
};
|
|
73929
75592
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
73930
75593
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
73931
75594
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -73934,11 +75597,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73934
75597
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
73935
75598
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
73936
75599
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
73937
|
-
if (resultSymbol?.kind !== "const" || !initializer
|
|
75600
|
+
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75601
|
+
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75602
|
+
const alternate = stripParenExpression(initializer.alternate);
|
|
75603
|
+
const consequent = stripParenExpression(initializer.consequent);
|
|
75604
|
+
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75605
|
+
})() : null;
|
|
75606
|
+
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
73938
75607
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
73939
|
-
const finderCallee = stripParenExpression(
|
|
75608
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
73940
75609
|
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;
|
|
73941
|
-
const predicateArgument =
|
|
75610
|
+
const predicateArgument = finderCall.arguments[1];
|
|
73942
75611
|
if (!predicateArgument) return false;
|
|
73943
75612
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
73944
75613
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -73962,6 +75631,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73962
75631
|
});
|
|
73963
75632
|
return didProveMatch;
|
|
73964
75633
|
};
|
|
75634
|
+
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75635
|
+
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75636
|
+
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75637
|
+
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75638
|
+
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75639
|
+
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75640
|
+
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75641
|
+
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75642
|
+
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75643
|
+
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75644
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75645
|
+
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75646
|
+
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75647
|
+
const expression = stripParenExpression(test);
|
|
75648
|
+
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75649
|
+
})) return false;
|
|
75650
|
+
const predicateArgument = finderCall.arguments[1];
|
|
75651
|
+
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75652
|
+
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75653
|
+
const predicateParameter = predicateFunction.params[0];
|
|
75654
|
+
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75655
|
+
let didProveNormalizedMatch = false;
|
|
75656
|
+
walkAst(predicateFunction.body, (child) => {
|
|
75657
|
+
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75658
|
+
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;
|
|
75659
|
+
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75660
|
+
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75661
|
+
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75662
|
+
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75663
|
+
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75664
|
+
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75665
|
+
didProveNormalizedMatch = true;
|
|
75666
|
+
return false;
|
|
75667
|
+
}
|
|
75668
|
+
});
|
|
75669
|
+
return didProveNormalizedMatch;
|
|
75670
|
+
};
|
|
73965
75671
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
73966
75672
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
73967
75673
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -74062,6 +75768,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
74062
75768
|
}
|
|
74063
75769
|
return false;
|
|
74064
75770
|
};
|
|
75771
|
+
const getReceiverRootIdentifier = (node) => {
|
|
75772
|
+
let target = stripParenExpression(node);
|
|
75773
|
+
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75774
|
+
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75775
|
+
};
|
|
75776
|
+
const getReceiverStatePath = (node) => {
|
|
75777
|
+
const target = stripParenExpression(node);
|
|
75778
|
+
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75779
|
+
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75780
|
+
const objectPath = getReceiverStatePath(target.object);
|
|
75781
|
+
if (!objectPath) return null;
|
|
75782
|
+
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75783
|
+
};
|
|
75784
|
+
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75785
|
+
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75786
|
+
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75787
|
+
const receiverPath = getReceiverStatePath(receiver);
|
|
75788
|
+
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75789
|
+
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75790
|
+
let didAddAlias = true;
|
|
75791
|
+
while (didAddAlias) {
|
|
75792
|
+
didAddAlias = false;
|
|
75793
|
+
walkAst(ownerFunction, (child) => {
|
|
75794
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75795
|
+
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75796
|
+
const initializer = stripParenExpression(child.init);
|
|
75797
|
+
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75798
|
+
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75799
|
+
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75800
|
+
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75801
|
+
const initializerPath = getReceiverStatePath(initializer);
|
|
75802
|
+
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75803
|
+
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75804
|
+
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75805
|
+
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75806
|
+
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75807
|
+
didAddAlias = true;
|
|
75808
|
+
}
|
|
75809
|
+
});
|
|
75810
|
+
}
|
|
75811
|
+
let didChangeReceiverState = false;
|
|
75812
|
+
walkAst(ownerFunction, (child) => {
|
|
75813
|
+
if (didChangeReceiverState) return false;
|
|
75814
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75815
|
+
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75816
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
75817
|
+
didChangeReceiverState = true;
|
|
75818
|
+
return false;
|
|
75819
|
+
}
|
|
75820
|
+
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75821
|
+
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75822
|
+
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75823
|
+
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75824
|
+
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75825
|
+
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75826
|
+
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75827
|
+
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75828
|
+
didChangeReceiverState = true;
|
|
75829
|
+
return false;
|
|
75830
|
+
}
|
|
75831
|
+
});
|
|
75832
|
+
return didChangeReceiverState;
|
|
75833
|
+
};
|
|
75834
|
+
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75835
|
+
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75836
|
+
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75837
|
+
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75838
|
+
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75839
|
+
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75840
|
+
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;
|
|
75841
|
+
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75842
|
+
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75843
|
+
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75844
|
+
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75845
|
+
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75846
|
+
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75847
|
+
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75848
|
+
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75849
|
+
const accumulatorParameter = reducerFunction.params[0];
|
|
75850
|
+
const itemParameter = reducerFunction.params[1];
|
|
75851
|
+
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75852
|
+
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75853
|
+
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75854
|
+
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75855
|
+
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75856
|
+
const expression = stripParenExpression(argument);
|
|
75857
|
+
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75858
|
+
});
|
|
75859
|
+
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75860
|
+
const expression = stripParenExpression(argument);
|
|
75861
|
+
const rootIdentifier = getRootIdentifier(expression);
|
|
75862
|
+
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75863
|
+
});
|
|
75864
|
+
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75865
|
+
if (!receiverPathKey(findReceiver)) return false;
|
|
75866
|
+
let ownerFunction = assertion.parent ?? null;
|
|
75867
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75868
|
+
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75869
|
+
const maximumEnd = maximumInitializer.range[1];
|
|
75870
|
+
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75871
|
+
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75872
|
+
const comparison = stripParenExpression(test);
|
|
75873
|
+
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75874
|
+
return [[
|
|
75875
|
+
comparison.left,
|
|
75876
|
+
comparison.right,
|
|
75877
|
+
comparison.operator
|
|
75878
|
+
], [
|
|
75879
|
+
comparison.right,
|
|
75880
|
+
comparison.left,
|
|
75881
|
+
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75882
|
+
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75883
|
+
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75884
|
+
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75885
|
+
});
|
|
75886
|
+
});
|
|
75887
|
+
};
|
|
74065
75888
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
74066
75889
|
if (!value) return false;
|
|
74067
75890
|
const expression = stripParenExpression(value);
|
|
@@ -74085,15 +75908,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
74085
75908
|
};
|
|
74086
75909
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
74087
75910
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
74088
|
-
|
|
75911
|
+
const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
|
|
75912
|
+
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75913
|
+
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
|
|
74089
75914
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
74090
75915
|
if (!receiverSymbol) return false;
|
|
74091
75916
|
const receiverMatches = (candidate) => {
|
|
74092
75917
|
const target = stripParenExpression(candidate);
|
|
74093
75918
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
74094
75919
|
};
|
|
74095
|
-
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74096
|
-
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
74097
75920
|
let child = assertion;
|
|
74098
75921
|
let ancestor = assertion.parent ?? null;
|
|
74099
75922
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -74113,7 +75936,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
74113
75936
|
const populationCall = populationCalls[0];
|
|
74114
75937
|
if (!populationCall) continue;
|
|
74115
75938
|
const populationCallStart = populationCall.range[0];
|
|
74116
|
-
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
|
|
75939
|
+
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;
|
|
74117
75940
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
74118
75941
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
74119
75942
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -74223,6 +76046,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74223
76046
|
const findReceiver = callee.object;
|
|
74224
76047
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
74225
76048
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
76049
|
+
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
74226
76050
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
74227
76051
|
}
|
|
74228
76052
|
if (methodName === "match") {
|
|
@@ -74232,6 +76056,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74232
76056
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
74233
76057
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
74234
76058
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
76059
|
+
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
74235
76060
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
74236
76061
|
}
|
|
74237
76062
|
if (methodName === "get") {
|
|
@@ -78170,16 +79995,23 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
|
78170
79995
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
78171
79996
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
78172
79997
|
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).";
|
|
78173
|
-
const
|
|
79998
|
+
const isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
78174
79999
|
const strippedExpression = stripParenExpression(expression);
|
|
78175
80000
|
if (isDefinitelyNonThenableValue(strippedExpression)) return true;
|
|
80001
|
+
if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
|
|
80002
|
+
const receiver = stripParenExpression(strippedExpression.callee.object);
|
|
80003
|
+
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
|
|
80004
|
+
const resolvedValue = strippedExpression.arguments[0];
|
|
80005
|
+
return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
|
|
80006
|
+
}
|
|
80007
|
+
}
|
|
78176
80008
|
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
78177
80009
|
if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
|
|
78178
80010
|
const symbol = context.scopes.symbolFor(strippedExpression);
|
|
78179
80011
|
if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
|
|
78180
80012
|
visitedBindingIdentifiers.add(symbol.bindingIdentifier);
|
|
78181
80013
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
78182
|
-
return Boolean(initializer &&
|
|
80014
|
+
return Boolean(initializer && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
|
|
78183
80015
|
};
|
|
78184
80016
|
const isKnownNonRejectingHandler = (argument, context) => {
|
|
78185
80017
|
if (!argument) return false;
|
|
@@ -78194,7 +80026,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
78194
80026
|
canReject = true;
|
|
78195
80027
|
return false;
|
|
78196
80028
|
}
|
|
78197
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !
|
|
80029
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
78198
80030
|
if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
|
|
78199
80031
|
canReject = true;
|
|
78200
80032
|
return false;
|
|
@@ -78243,6 +80075,28 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
|
78243
80075
|
});
|
|
78244
80076
|
return hasPotentiallyThrowingMemberRead;
|
|
78245
80077
|
};
|
|
80078
|
+
const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
|
|
80079
|
+
if (!argument) return false;
|
|
80080
|
+
if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
|
|
80081
|
+
if (!allowTerminalCatchBlock) return false;
|
|
80082
|
+
const candidate = stripParenExpression(argument);
|
|
80083
|
+
const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
|
|
80084
|
+
if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
|
|
80085
|
+
if (!isNodeOfType(handler.body, "BlockStatement")) return false;
|
|
80086
|
+
let doesExplicitlyReject = false;
|
|
80087
|
+
walkOwnFunctionScope(handler, (child) => {
|
|
80088
|
+
if (doesExplicitlyReject) return false;
|
|
80089
|
+
if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
|
|
80090
|
+
doesExplicitlyReject = true;
|
|
80091
|
+
return false;
|
|
80092
|
+
}
|
|
80093
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
80094
|
+
doesExplicitlyReject = true;
|
|
80095
|
+
return false;
|
|
80096
|
+
}
|
|
80097
|
+
});
|
|
80098
|
+
return !doesExplicitlyReject;
|
|
80099
|
+
};
|
|
78246
80100
|
const walkPromiseChain = (chainExpression, context) => {
|
|
78247
80101
|
let cursor = stripParenExpression(chainExpression);
|
|
78248
80102
|
let hasCatch = false;
|
|
@@ -78254,10 +80108,9 @@ const walkPromiseChain = (chainExpression, context) => {
|
|
|
78254
80108
|
while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
|
|
78255
80109
|
const methodName = getStaticPropertyName(cursor.callee);
|
|
78256
80110
|
const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
|
|
78257
|
-
|
|
78258
|
-
if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
|
|
80111
|
+
if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
|
|
78259
80112
|
if (methodName === "then") {
|
|
78260
|
-
if (!didReachTerminalThen &&
|
|
80113
|
+
if (!didReachTerminalThen && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
|
|
78261
80114
|
didReachTerminalThen = true;
|
|
78262
80115
|
sawThen = true;
|
|
78263
80116
|
const callbackArgument = cursor.arguments[0];
|
|
@@ -80584,6 +82437,45 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
80584
82437
|
} })
|
|
80585
82438
|
});
|
|
80586
82439
|
//#endregion
|
|
82440
|
+
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82441
|
+
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82442
|
+
"Math.random",
|
|
82443
|
+
"Date.now",
|
|
82444
|
+
"performance.now",
|
|
82445
|
+
"crypto.randomUUID",
|
|
82446
|
+
"crypto.getRandomValues"
|
|
82447
|
+
]);
|
|
82448
|
+
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82449
|
+
"nanoid",
|
|
82450
|
+
"uuid",
|
|
82451
|
+
"cuid",
|
|
82452
|
+
"ulid",
|
|
82453
|
+
"createId"
|
|
82454
|
+
]);
|
|
82455
|
+
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82456
|
+
const containsNonDeterministicSource = (root) => {
|
|
82457
|
+
let found = false;
|
|
82458
|
+
walkAst(root, (child) => {
|
|
82459
|
+
if (found) return false;
|
|
82460
|
+
if (isFunctionLike$1(child)) return false;
|
|
82461
|
+
if (isZeroArgDateConstruction(child)) {
|
|
82462
|
+
found = true;
|
|
82463
|
+
return false;
|
|
82464
|
+
}
|
|
82465
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82466
|
+
const callee = child.callee;
|
|
82467
|
+
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82468
|
+
found = true;
|
|
82469
|
+
return false;
|
|
82470
|
+
}
|
|
82471
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82472
|
+
found = true;
|
|
82473
|
+
return false;
|
|
82474
|
+
}
|
|
82475
|
+
});
|
|
82476
|
+
return found;
|
|
82477
|
+
};
|
|
82478
|
+
//#endregion
|
|
80587
82479
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
80588
82480
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
80589
82481
|
"DoWhileStatement",
|
|
@@ -80614,45 +82506,75 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
80614
82506
|
};
|
|
80615
82507
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
80616
82508
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
80617
|
-
|
|
82509
|
+
if (!expression) return false;
|
|
82510
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
82511
|
+
const callee = stripParenExpression(expression.callee);
|
|
82512
|
+
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82513
|
+
}
|
|
82514
|
+
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
80618
82515
|
};
|
|
80619
|
-
const
|
|
82516
|
+
const getInitializationValueName = (node, scopes) => {
|
|
80620
82517
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
80621
82518
|
if (!expression) return null;
|
|
80622
|
-
if (isNodeOfType(expression, "NewExpression")) {
|
|
82519
|
+
if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
|
|
80623
82520
|
const callee = stripParenExpression(expression.callee);
|
|
80624
|
-
|
|
82521
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
82522
|
+
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
80625
82523
|
}
|
|
80626
82524
|
return null;
|
|
80627
82525
|
};
|
|
82526
|
+
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82527
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82528
|
+
const typeName = typeNode.typeName;
|
|
82529
|
+
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82530
|
+
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82531
|
+
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82532
|
+
const queriedName = returnTypeArgument.exprName;
|
|
82533
|
+
const expression = stripParenExpression(initializationValue);
|
|
82534
|
+
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82535
|
+
const callee = stripParenExpression(expression.callee);
|
|
82536
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82537
|
+
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82538
|
+
const calleeSymbol = scopes.symbolFor(callee);
|
|
82539
|
+
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82540
|
+
};
|
|
80628
82541
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
80629
82542
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
80630
82543
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
80631
82544
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
80632
82545
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
80633
82546
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82547
|
+
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
80634
82548
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
80635
82549
|
const typeName = typeNode.typeName;
|
|
80636
|
-
|
|
82550
|
+
if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
|
|
82551
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
80637
82552
|
};
|
|
80638
82553
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
80639
82554
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
80640
82555
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
80641
82556
|
const [initialValue] = initializer.arguments ?? [];
|
|
80642
|
-
if (
|
|
82557
|
+
if (initialValue && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
|
|
80643
82558
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
80644
|
-
if (!declaredType
|
|
80645
|
-
|
|
82559
|
+
if (!declaredType) return false;
|
|
82560
|
+
const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
|
|
80646
82561
|
let hasTruthyDomain = false;
|
|
80647
|
-
for (const memberType of
|
|
80648
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
80649
|
-
hasEmptySentinel = true;
|
|
80650
|
-
continue;
|
|
80651
|
-
}
|
|
82562
|
+
for (const memberType of domainTypes) {
|
|
82563
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
|
|
80652
82564
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
80653
82565
|
hasTruthyDomain = true;
|
|
80654
82566
|
}
|
|
80655
|
-
return
|
|
82567
|
+
return hasTruthyDomain;
|
|
82568
|
+
};
|
|
82569
|
+
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82570
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82571
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82572
|
+
const [initialValue] = initializer.arguments ?? [];
|
|
82573
|
+
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82574
|
+
};
|
|
82575
|
+
const refHasDeclaredType = (refSymbol) => {
|
|
82576
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82577
|
+
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
80656
82578
|
};
|
|
80657
82579
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
80658
82580
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -80684,25 +82606,40 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
80684
82606
|
});
|
|
80685
82607
|
return didFindRefCurrent;
|
|
80686
82608
|
};
|
|
80687
|
-
const
|
|
80688
|
-
|
|
80689
|
-
|
|
80690
|
-
|
|
80691
|
-
|
|
80692
|
-
|
|
80693
|
-
|
|
82609
|
+
const isEmptySentinel = (node, scopes) => {
|
|
82610
|
+
const expression = stripParenExpression(node);
|
|
82611
|
+
return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
|
|
82612
|
+
};
|
|
82613
|
+
const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
82614
|
+
let isInputIndependent = true;
|
|
82615
|
+
walkAst(node, (child) => {
|
|
82616
|
+
if (!isInputIndependent) return false;
|
|
82617
|
+
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82618
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
82619
|
+
const symbol = scopes.symbolFor(child);
|
|
82620
|
+
if (!symbol) return;
|
|
82621
|
+
if (symbol.kind === "import") return false;
|
|
82622
|
+
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82623
|
+
isInputIndependent = false;
|
|
82624
|
+
return false;
|
|
80694
82625
|
}
|
|
80695
|
-
if (
|
|
80696
|
-
|
|
80697
|
-
|
|
82626
|
+
if (isOutsideAllFunctions(symbol)) return false;
|
|
82627
|
+
if (symbol.kind === "parameter") {
|
|
82628
|
+
if (symbol.scope.node === renderOwner) isInputIndependent = false;
|
|
82629
|
+
return false;
|
|
80698
82630
|
}
|
|
80699
|
-
if (
|
|
80700
|
-
|
|
82631
|
+
if (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
|
|
82632
|
+
isInputIndependent = false;
|
|
82633
|
+
return false;
|
|
80701
82634
|
}
|
|
82635
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82636
|
+
visitedSymbolIds.add(symbol.id);
|
|
82637
|
+
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82638
|
+
return false;
|
|
80702
82639
|
});
|
|
80703
|
-
return
|
|
82640
|
+
return isInputIndependent;
|
|
80704
82641
|
};
|
|
80705
|
-
const
|
|
82642
|
+
const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
|
|
80706
82643
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
80707
82644
|
let ancestor = node.parent;
|
|
80708
82645
|
while (ancestor && ancestor !== stop) {
|
|
@@ -80732,6 +82669,27 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
80732
82669
|
}
|
|
80733
82670
|
return true;
|
|
80734
82671
|
};
|
|
82672
|
+
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82673
|
+
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82674
|
+
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82675
|
+
let hasCompetingWrite = false;
|
|
82676
|
+
walkAst(renderOwner, (child) => {
|
|
82677
|
+
if (hasCompetingWrite) return false;
|
|
82678
|
+
let writtenExpression = null;
|
|
82679
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82680
|
+
if (child === assignmentExpression) return;
|
|
82681
|
+
writtenExpression = child.left;
|
|
82682
|
+
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82683
|
+
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82684
|
+
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82685
|
+
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82686
|
+
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82687
|
+
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82688
|
+
hasCompetingWrite = true;
|
|
82689
|
+
return false;
|
|
82690
|
+
});
|
|
82691
|
+
return !hasCompetingWrite;
|
|
82692
|
+
};
|
|
80735
82693
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
80736
82694
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
80737
82695
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -80747,16 +82705,17 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
80747
82705
|
});
|
|
80748
82706
|
return !hasCoExecutableWrite;
|
|
80749
82707
|
};
|
|
82708
|
+
const isPredictableGuardedInitialization = (assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, requiresClosedTruthyDomain) => refHasEmptySentinelInitializer(refSymbol, scopes) && isPredictableInitializationValue(assignmentExpression.right, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && (guardedBranch === renderOwner || !hasRepeatedExecutionAncestor(guardedBranch, renderOwner)) && hasNoPriorCoExecutableWrite(assignmentExpression, renderOwner, refSymbol, scopes) && hasNoCoExecutableCompetingWrite(assignmentExpression, renderOwner, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes);
|
|
80750
82709
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
80751
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
80752
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
80753
82710
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
80754
82711
|
if (!renderOwner) return false;
|
|
82712
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82713
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
80755
82714
|
let descendant = assignmentExpression;
|
|
80756
82715
|
let ancestor = descendant.parent;
|
|
80757
82716
|
while (ancestor) {
|
|
80758
82717
|
const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
|
|
80759
|
-
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant &&
|
|
82718
|
+
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
|
|
80760
82719
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
80761
82720
|
"===",
|
|
80762
82721
|
"==",
|
|
@@ -80766,7 +82725,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
80766
82725
|
const { left, right } = test;
|
|
80767
82726
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
80768
82727
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
80769
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
82728
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
|
|
80770
82729
|
}
|
|
80771
82730
|
descendant = ancestor;
|
|
80772
82731
|
ancestor = descendant.parent;
|
|
@@ -108573,9 +110532,6 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
108573
110532
|
} })
|
|
108574
110533
|
});
|
|
108575
110534
|
//#endregion
|
|
108576
|
-
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
108577
|
-
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
108578
|
-
//#endregion
|
|
108579
110535
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
108580
110536
|
const rerenderLazyRefInit = defineRule({
|
|
108581
110537
|
id: "rerender-lazy-ref-init",
|
|
@@ -108594,7 +110550,6 @@ const rerenderLazyRefInit = defineRule({
|
|
|
108594
110550
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
108595
110551
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
108596
110552
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
108597
|
-
if (isTrivialBuiltInConstruction(initializer)) return;
|
|
108598
110553
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
108599
110554
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
108600
110555
|
context.report({
|