oxlint-plugin-react-doctor 0.9.2-dev.b31fd85 → 0.9.2-dev.c6bdd2d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1586 -128
- package/package.json +2 -2
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;
|
|
@@ -5887,6 +5887,7 @@ const isInlineFunctionExpression = (node) => Boolean(node && (isNodeOfType(node,
|
|
|
5887
5887
|
//#endregion
|
|
5888
5888
|
//#region src/plugin/rules/js-performance/async-await-in-loop.ts
|
|
5889
5889
|
const LOOP_STATEMENT_TYPES$1 = new Set(LOOP_TYPES);
|
|
5890
|
+
const ORDERED_OUTPUT_INSERTION_METHOD_NAMES = new Set(["push", "unshift"]);
|
|
5890
5891
|
const findFirstAwaitOutsideNestedFunctions = (block, skipNestedLoops = false) => {
|
|
5891
5892
|
let firstAwait = null;
|
|
5892
5893
|
walkAst(block, (child) => {
|
|
@@ -5953,7 +5954,151 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
5953
5954
|
});
|
|
5954
5955
|
return isWaitLike;
|
|
5955
5956
|
};
|
|
5956
|
-
const
|
|
5957
|
+
const getRootObjectIdentifierName = (node) => {
|
|
5958
|
+
let current = node;
|
|
5959
|
+
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
5960
|
+
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
5961
|
+
};
|
|
5962
|
+
const isScopeWithinFunction = (candidateScope, functionScope) => {
|
|
5963
|
+
let currentScope = candidateScope;
|
|
5964
|
+
while (currentScope) {
|
|
5965
|
+
if (currentScope === functionScope) return true;
|
|
5966
|
+
currentScope = currentScope.parent;
|
|
5967
|
+
}
|
|
5968
|
+
return false;
|
|
5969
|
+
};
|
|
5970
|
+
const isSymbolDirectlyReturned = (symbol, callerFunction) => Boolean(callerFunction) && symbol.references.some((reference) => {
|
|
5971
|
+
const expressionRoot = findTransparentExpressionRoot(reference.identifier);
|
|
5972
|
+
const parent = expressionRoot.parent;
|
|
5973
|
+
return isNodeOfType(parent, "ReturnStatement") && parent.argument === expressionRoot && findEnclosingFunction$1(parent) === callerFunction;
|
|
5974
|
+
});
|
|
5975
|
+
const collectPatternBindingSymbolIds = (pattern, scopes, target) => {
|
|
5976
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
5977
|
+
const symbol = scopes.symbolFor(pattern);
|
|
5978
|
+
if (symbol) target.add(symbol.id);
|
|
5979
|
+
return;
|
|
5980
|
+
}
|
|
5981
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
5982
|
+
for (const property of pattern.properties ?? []) if (isNodeOfType(property, "Property") && property.value) collectPatternBindingSymbolIds(property.value, scopes, target);
|
|
5983
|
+
else if (isNodeOfType(property, "RestElement") && property.argument) collectPatternBindingSymbolIds(property.argument, scopes, target);
|
|
5984
|
+
return;
|
|
5985
|
+
}
|
|
5986
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
5987
|
+
for (const element of pattern.elements ?? []) if (element) collectPatternBindingSymbolIds(element, scopes, target);
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternBindingSymbolIds(pattern.left, scopes, target);
|
|
5991
|
+
};
|
|
5992
|
+
const collectReferencedSymbolIds = (expression, scopes) => {
|
|
5993
|
+
const referencedSymbolIds = /* @__PURE__ */ new Set();
|
|
5994
|
+
walkAst(expression, (child) => {
|
|
5995
|
+
if (child !== expression && isFunctionLike$1(child)) return false;
|
|
5996
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
5997
|
+
const symbol = scopes.symbolFor(child);
|
|
5998
|
+
if (symbol) referencedSymbolIds.add(symbol.id);
|
|
5999
|
+
});
|
|
6000
|
+
return referencedSymbolIds;
|
|
6001
|
+
};
|
|
6002
|
+
const collectAwaitDerivedSymbolIds = (block, scopes) => {
|
|
6003
|
+
const awaitDerivedSymbolIds = /* @__PURE__ */ new Set();
|
|
6004
|
+
const bindingDependencies = [];
|
|
6005
|
+
walkAst(block, (child) => {
|
|
6006
|
+
if (child !== block && isFunctionLike$1(child)) return false;
|
|
6007
|
+
if (isNodeOfType(child, "VariableDeclarator") && child.id && child.init) {
|
|
6008
|
+
const declaredSymbolIds = /* @__PURE__ */ new Set();
|
|
6009
|
+
collectPatternBindingSymbolIds(child.id, scopes, declaredSymbolIds);
|
|
6010
|
+
if (containsDirectAwait(child.init)) for (const symbolId of declaredSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6011
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.init, scopes);
|
|
6012
|
+
for (const declaredSymbolId of declaredSymbolIds) bindingDependencies.push({
|
|
6013
|
+
declaredSymbolId,
|
|
6014
|
+
referencedSymbolIds
|
|
6015
|
+
});
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.left) {
|
|
6019
|
+
const assignedSymbolIds = /* @__PURE__ */ new Set();
|
|
6020
|
+
collectPatternBindingSymbolIds(child.left, scopes, assignedSymbolIds);
|
|
6021
|
+
if (containsDirectAwait(child.right)) for (const symbolId of assignedSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6022
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.right, scopes);
|
|
6023
|
+
for (const assignedSymbolId of assignedSymbolIds) bindingDependencies.push({
|
|
6024
|
+
declaredSymbolId: assignedSymbolId,
|
|
6025
|
+
referencedSymbolIds
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
});
|
|
6029
|
+
let didGrow = true;
|
|
6030
|
+
while (didGrow) {
|
|
6031
|
+
didGrow = false;
|
|
6032
|
+
for (const { declaredSymbolId, referencedSymbolIds } of bindingDependencies) {
|
|
6033
|
+
if (awaitDerivedSymbolIds.has(declaredSymbolId)) continue;
|
|
6034
|
+
for (const referencedSymbolId of referencedSymbolIds) {
|
|
6035
|
+
if (!awaitDerivedSymbolIds.has(referencedSymbolId)) continue;
|
|
6036
|
+
awaitDerivedSymbolIds.add(declaredSymbolId);
|
|
6037
|
+
didGrow = true;
|
|
6038
|
+
break;
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
return awaitDerivedSymbolIds;
|
|
6043
|
+
};
|
|
6044
|
+
const getSimpleParameterIdentifier = (parameter) => {
|
|
6045
|
+
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
6046
|
+
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
6047
|
+
return null;
|
|
6048
|
+
};
|
|
6049
|
+
const doesAwaitedLocalCallInsertAwaitDerivedOutput = (awaitNode, context) => {
|
|
6050
|
+
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
6051
|
+
const callExpression = awaitNode.argument;
|
|
6052
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
6053
|
+
const localFunction = resolveStaticLocalCallFunction(callExpression, context.scopes);
|
|
6054
|
+
if (!isFunctionLike$1(localFunction)) return false;
|
|
6055
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
6056
|
+
const functionScope = context.scopes.ownScopeFor(localFunction);
|
|
6057
|
+
if (!functionScope) return false;
|
|
6058
|
+
const awaitDerivedSymbolIds = collectAwaitDerivedSymbolIds(localFunction.body, context.scopes);
|
|
6059
|
+
const externallyReachableParameterSymbolIds = /* @__PURE__ */ new Set();
|
|
6060
|
+
for (const [parameterIndex, parameter] of localFunction.params.entries()) {
|
|
6061
|
+
const parameterIdentifier = getSimpleParameterIdentifier(parameter);
|
|
6062
|
+
if (!parameterIdentifier) continue;
|
|
6063
|
+
const argument = callExpression.arguments[parameterIndex];
|
|
6064
|
+
if (!isNodeOfType(argument, "Identifier")) continue;
|
|
6065
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
6066
|
+
const argumentSymbol = context.scopes.symbolFor(argument);
|
|
6067
|
+
if (parameterSymbol && argumentSymbol && (isSymbolDirectlyReturned(argumentSymbol, callerFunction) || argumentSymbol.id === parameterSymbol.id)) externallyReachableParameterSymbolIds.add(parameterSymbol.id);
|
|
6068
|
+
}
|
|
6069
|
+
let doesInsertAwaitDerivedOutput = false;
|
|
6070
|
+
walkAst(localFunction.body, (child) => {
|
|
6071
|
+
if (doesInsertAwaitDerivedOutput) return false;
|
|
6072
|
+
if (child !== localFunction.body && isFunctionLike$1(child)) return false;
|
|
6073
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6074
|
+
const callee = child.callee;
|
|
6075
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !ORDERED_OUTPUT_INSERTION_METHOD_NAMES.has(callee.property.name)) return;
|
|
6076
|
+
let doesMutationConsumeAwaitedValue = false;
|
|
6077
|
+
for (const mutationArgument of child.arguments ?? []) {
|
|
6078
|
+
if (containsDirectAwait(mutationArgument)) {
|
|
6079
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6080
|
+
break;
|
|
6081
|
+
}
|
|
6082
|
+
const referencedSymbolIds = collectReferencedSymbolIds(mutationArgument, context.scopes);
|
|
6083
|
+
for (const referencedSymbolId of referencedSymbolIds) if (awaitDerivedSymbolIds.has(referencedSymbolId)) {
|
|
6084
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6085
|
+
break;
|
|
6086
|
+
}
|
|
6087
|
+
if (doesMutationConsumeAwaitedValue) break;
|
|
6088
|
+
}
|
|
6089
|
+
if (!doesMutationConsumeAwaitedValue) return;
|
|
6090
|
+
let receiverIdentifier = callee.object;
|
|
6091
|
+
while (isNodeOfType(receiverIdentifier, "MemberExpression")) receiverIdentifier = receiverIdentifier.object;
|
|
6092
|
+
if (!isNodeOfType(receiverIdentifier, "Identifier")) return;
|
|
6093
|
+
const receiverSymbol = context.scopes.symbolFor(receiverIdentifier);
|
|
6094
|
+
if (receiverSymbol && (externallyReachableParameterSymbolIds.has(receiverSymbol.id) || !isScopeWithinFunction(receiverSymbol.scope, functionScope) && isSymbolDirectlyReturned(receiverSymbol, callerFunction))) {
|
|
6095
|
+
doesInsertAwaitDerivedOutput = true;
|
|
6096
|
+
return false;
|
|
6097
|
+
}
|
|
6098
|
+
});
|
|
6099
|
+
return doesInsertAwaitDerivedOutput;
|
|
6100
|
+
};
|
|
6101
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode) || doesAwaitedLocalCallInsertAwaitDerivedOutput(awaitNode, context);
|
|
5957
6102
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
5958
6103
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
5959
6104
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -6099,11 +6244,6 @@ const loopBodyHasAwaitDependentEarlyExit = (block, loopLabelName) => {
|
|
|
6099
6244
|
});
|
|
6100
6245
|
return hasAwaitDependentExit;
|
|
6101
6246
|
};
|
|
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
6247
|
const MUTATING_ARRAY_METHOD_NAMES$2 = new Set([
|
|
6108
6248
|
...ARRAY_MUTATION_METHOD_NAMES,
|
|
6109
6249
|
"pop",
|
|
@@ -8675,7 +8815,7 @@ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, r
|
|
|
8675
8815
|
//#endregion
|
|
8676
8816
|
//#region src/plugin/rules/state-and-effects/class-component-missing-component-will-unmount-teardown.ts
|
|
8677
8817
|
const MESSAGE$89 = "This class registers a listener or timer during mount without a matching teardown on every unmount path, so it can keep firing after the component unmounts; release it in `componentWillUnmount`.";
|
|
8678
|
-
const GLOBAL_OBJECT_NAMES$
|
|
8818
|
+
const GLOBAL_OBJECT_NAMES$4 = new Set([
|
|
8679
8819
|
"window",
|
|
8680
8820
|
"globalThis",
|
|
8681
8821
|
"global",
|
|
@@ -8834,14 +8974,14 @@ const getTimerIdentifierAliasName = (identifier, scopes, visitedSymbolIds = /* @
|
|
|
8834
8974
|
const property = symbol.bindingIdentifier.parent;
|
|
8835
8975
|
const objectPattern = property?.parent;
|
|
8836
8976
|
const source = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
8837
|
-
if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8977
|
+
if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(source.name) && scopes.isGlobalReference(source)) return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
8838
8978
|
return null;
|
|
8839
8979
|
}
|
|
8840
8980
|
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
8841
8981
|
if (isNodeOfType(initializer, "Identifier")) return getTimerIdentifierAliasName(initializer, scopes, visitedSymbolIds);
|
|
8842
8982
|
if (!isNodeOfType(initializer, "MemberExpression")) return null;
|
|
8843
8983
|
const receiver = stripParenExpression(initializer.object);
|
|
8844
|
-
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8984
|
+
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
|
|
8845
8985
|
};
|
|
8846
8986
|
const getTimerCalleeName = (node, scopes) => {
|
|
8847
8987
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
@@ -8849,7 +8989,7 @@ const getTimerCalleeName = (node, scopes) => {
|
|
|
8849
8989
|
if (getBareCalleeName(node) && isNodeOfType(callee, "Identifier")) return getTimerIdentifierAliasName(callee, scopes);
|
|
8850
8990
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
8851
8991
|
const receiver = stripParenExpression(callee.object);
|
|
8852
|
-
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$
|
|
8992
|
+
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$4.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
|
|
8853
8993
|
return getStaticPropertyName(callee);
|
|
8854
8994
|
};
|
|
8855
8995
|
const getClassMemberName$1 = (member) => {
|
|
@@ -10045,6 +10185,12 @@ const FOCUS_FORWARDING_METHOD_NAMES = new Set([
|
|
|
10045
10185
|
"preventDefault",
|
|
10046
10186
|
"stopImmediatePropagation"
|
|
10047
10187
|
]);
|
|
10188
|
+
const DOM_QUERY_METHOD_NAMES = new Set(["getElementById", "querySelector"]);
|
|
10189
|
+
const GLOBAL_OBJECT_NAMES$3 = new Set([
|
|
10190
|
+
"global",
|
|
10191
|
+
"globalThis",
|
|
10192
|
+
"window"
|
|
10193
|
+
]);
|
|
10048
10194
|
const isFocusForwardingCall = (node) => {
|
|
10049
10195
|
if (!node) return false;
|
|
10050
10196
|
const inner = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
@@ -10054,6 +10200,12 @@ const isFocusForwardingCall = (node) => {
|
|
|
10054
10200
|
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
10055
10201
|
return FOCUS_FORWARDING_METHOD_NAMES.has(callee.property.name);
|
|
10056
10202
|
};
|
|
10203
|
+
const isGlobalDocumentExpression = (expression, scopes) => {
|
|
10204
|
+
const candidate = stripParenExpression(expression);
|
|
10205
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate.name === "document" && scopes.isGlobalReference(candidate);
|
|
10206
|
+
if (!isNodeOfType(candidate, "MemberExpression") || !isNodeOfType(candidate.object, "Identifier") || !isNodeOfType(candidate.property, "Identifier") || candidate.property.name !== "document") return false;
|
|
10207
|
+
return GLOBAL_OBJECT_NAMES$3.has(candidate.object.name) && scopes.isGlobalReference(candidate.object);
|
|
10208
|
+
};
|
|
10057
10209
|
const isFocusForwardingFunctionBody = (body) => {
|
|
10058
10210
|
if (!body) return false;
|
|
10059
10211
|
if (isFocusForwardingCall(body)) return true;
|
|
@@ -10073,19 +10225,70 @@ const resolveHandlerFunction$2 = (attribute) => {
|
|
|
10073
10225
|
return resolveHandlerFunctionExpression(attribute.value.expression);
|
|
10074
10226
|
};
|
|
10075
10227
|
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
10076
|
-
let expression = handlerExpression;
|
|
10228
|
+
let expression = stripParenExpression(handlerExpression);
|
|
10077
10229
|
if (isNodeOfType(expression, "Identifier")) {
|
|
10078
10230
|
const binding = findVariableInitializer(expression, expression.name);
|
|
10079
10231
|
if (!binding?.initializer) return null;
|
|
10080
|
-
expression = binding.initializer;
|
|
10232
|
+
expression = stripParenExpression(binding.initializer);
|
|
10081
10233
|
}
|
|
10082
10234
|
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
|
|
10083
10235
|
return null;
|
|
10084
10236
|
};
|
|
10085
|
-
const
|
|
10237
|
+
const isEmptyReturn = (statement) => isNodeOfType(statement, "ReturnStatement") && statement.argument === null;
|
|
10238
|
+
const isClosestEarlyReturn = (statement) => {
|
|
10239
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate) return false;
|
|
10240
|
+
const consequent = statement.consequent;
|
|
10241
|
+
if (!(isNodeOfType(consequent, "BlockStatement") ? consequent.body.length === 1 && isEmptyReturn(consequent.body[0]) : isEmptyReturn(consequent))) return false;
|
|
10242
|
+
const test = stripParenExpression(statement.test);
|
|
10243
|
+
const call = isNodeOfType(test, "ChainExpression") ? test.expression : test;
|
|
10244
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments.length !== 1) return false;
|
|
10245
|
+
const callee = stripParenExpression(call.callee);
|
|
10246
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
|
|
10247
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "closest" && isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.object, "Identifier") && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "target" && isNodeOfType(call.arguments[0], "Literal") && typeof call.arguments[0].value === "string";
|
|
10248
|
+
};
|
|
10249
|
+
const isStaticSelectorExpression = (expression) => {
|
|
10250
|
+
const candidate = stripParenExpression(expression);
|
|
10251
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "Literal")) return true;
|
|
10252
|
+
return isNodeOfType(candidate, "TemplateLiteral") && candidate.expressions.every((innerExpression) => isStaticSelectorExpression(innerExpression));
|
|
10253
|
+
};
|
|
10254
|
+
const getDomQueryVariableName = (statement, scopes) => {
|
|
10255
|
+
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const" || statement.declarations.length !== 1) return null;
|
|
10256
|
+
const declaration = statement.declarations[0];
|
|
10257
|
+
if (!declaration || !isNodeOfType(declaration.id, "Identifier") || !declaration.init) return null;
|
|
10258
|
+
const initializer = stripParenExpression(declaration.init);
|
|
10259
|
+
if (!isNodeOfType(initializer, "CallExpression") || initializer.arguments.length !== 1 || isNodeOfType(initializer.arguments[0], "SpreadElement") || !isStaticSelectorExpression(initializer.arguments[0])) return null;
|
|
10260
|
+
const callee = stripParenExpression(initializer.callee);
|
|
10261
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_QUERY_METHOD_NAMES.has(callee.property.name) || !isGlobalDocumentExpression(callee.object, scopes)) return null;
|
|
10262
|
+
return declaration.id.name;
|
|
10263
|
+
};
|
|
10264
|
+
const isFocusCallOnVariable = (statement, variableName) => {
|
|
10265
|
+
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
10266
|
+
const expression = stripParenExpression(statement.expression);
|
|
10267
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
10268
|
+
const callee = stripParenExpression(expression.callee);
|
|
10269
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
10270
|
+
const receiver = stripParenExpression(callee.object);
|
|
10271
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === variableName && isNodeOfType(callee.property, "Identifier") && callee.property.name === "focus" && expression.arguments.length === 0;
|
|
10272
|
+
};
|
|
10273
|
+
const isConditionalFocusForwardingHandler = (attribute, scopes) => {
|
|
10274
|
+
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
10275
|
+
const expression = stripParenExpression(attribute.value.expression);
|
|
10276
|
+
if (!isNodeOfType(expression, "ConditionalExpression")) return false;
|
|
10277
|
+
const consequent = stripParenExpression(expression.consequent);
|
|
10278
|
+
const alternate = stripParenExpression(expression.alternate);
|
|
10279
|
+
const nullishBranch = isNullishExpression$2(consequent) ? consequent : alternate;
|
|
10280
|
+
if (!isNullishExpression$2(nullishBranch) || isNodeOfType(nullishBranch, "Identifier") && !scopes.isGlobalReference(nullishBranch)) return false;
|
|
10281
|
+
const handlerFunction = resolveHandlerFunctionExpression(nullishBranch === consequent ? alternate : consequent);
|
|
10282
|
+
if (!handlerFunction || !isNodeOfType(handlerFunction.body, "BlockStatement")) return false;
|
|
10283
|
+
const [guard, query, focus, ...rest] = handlerFunction.body.body;
|
|
10284
|
+
if (!guard || !query || !focus || rest.length > 0 || !isClosestEarlyReturn(guard)) return false;
|
|
10285
|
+
const queryVariableName = getDomQueryVariableName(query, scopes);
|
|
10286
|
+
return Boolean(queryVariableName && isFocusCallOnVariable(focus, queryVariableName));
|
|
10287
|
+
};
|
|
10288
|
+
const isFocusForwardingHandler = (attribute, scopes) => {
|
|
10289
|
+
if (isConditionalFocusForwardingHandler(attribute, scopes)) return true;
|
|
10086
10290
|
const handlerFunction = resolveHandlerFunction$2(attribute);
|
|
10087
|
-
|
|
10088
|
-
return isFocusForwardingFunctionBody(handlerFunction.body ?? null);
|
|
10291
|
+
return Boolean(handlerFunction && isFocusForwardingFunctionBody(handlerFunction.body ?? null));
|
|
10089
10292
|
};
|
|
10090
10293
|
const COMPOSITE_ITEM_ROLES$1 = new Set([
|
|
10091
10294
|
"option",
|
|
@@ -10155,7 +10358,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
10155
10358
|
const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
|
|
10156
10359
|
if (!onClick && !spreadOnClickExpression) return;
|
|
10157
10360
|
if (onClick && isPureEventBlockerHandler(onClick)) return;
|
|
10158
|
-
if (onClick && isFocusForwardingHandler(onClick)) return;
|
|
10361
|
+
if (onClick && isFocusForwardingHandler(onClick, context.scopes)) return;
|
|
10159
10362
|
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
10160
10363
|
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
10161
10364
|
if (hasCompositeItemRole(node)) return;
|
|
@@ -17809,6 +18012,14 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
17809
18012
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
17810
18013
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
17811
18014
|
//#endregion
|
|
18015
|
+
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
18016
|
+
const isEarlyExitStatement$1 = (statement) => {
|
|
18017
|
+
if (!statement) return false;
|
|
18018
|
+
if (statementAlwaysExits$1(statement)) return true;
|
|
18019
|
+
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
18020
|
+
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
18021
|
+
};
|
|
18022
|
+
//#endregion
|
|
17812
18023
|
//#region src/plugin/utils/is-ast-descendant.ts
|
|
17813
18024
|
/**
|
|
17814
18025
|
* True when `inner` is `outer` itself or any descendant in the AST
|
|
@@ -18027,13 +18238,15 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
|
|
|
18027
18238
|
}
|
|
18028
18239
|
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)));
|
|
18029
18240
|
};
|
|
18030
|
-
const
|
|
18031
|
-
const callNode = functionNode.parent;
|
|
18032
|
-
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
18241
|
+
const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
|
|
18033
18242
|
const callee = stripParenExpression(callNode.callee);
|
|
18034
18243
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
18035
|
-
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] ===
|
|
18036
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] ===
|
|
18244
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === callbackArgument;
|
|
18245
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === callbackArgument;
|
|
18246
|
+
};
|
|
18247
|
+
const isSynchronousIteratorCallback = (functionNode) => {
|
|
18248
|
+
const callNode = functionNode.parent;
|
|
18249
|
+
return Boolean(isNodeOfType(callNode, "CallExpression") && isSynchronousIteratorCallbackCall(callNode, functionNode));
|
|
18037
18250
|
};
|
|
18038
18251
|
//#endregion
|
|
18039
18252
|
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
@@ -18153,11 +18366,35 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
|
18153
18366
|
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
18154
18367
|
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
18155
18368
|
};
|
|
18369
|
+
const resolveReadOnlyEventListenerOptions = (optionsNode, context) => {
|
|
18370
|
+
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18371
|
+
if (!isNodeOfType(unwrappedOptions, "Identifier")) return resolveStableValue(unwrappedOptions, context);
|
|
18372
|
+
const optionsSymbol = context.scopes.symbolFor(unwrappedOptions);
|
|
18373
|
+
const initializer = optionsSymbol?.initializer ? stripParenExpression(optionsSymbol.initializer) : null;
|
|
18374
|
+
if (!optionsSymbol || !initializer) return resolveStableValue(unwrappedOptions, context);
|
|
18375
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) {
|
|
18376
|
+
if (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression")) return null;
|
|
18377
|
+
return resolveStableValue(unwrappedOptions, context);
|
|
18378
|
+
}
|
|
18379
|
+
if (optionsSymbol.kind !== "const") return null;
|
|
18380
|
+
return optionsSymbol.references.every((reference) => {
|
|
18381
|
+
if (reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) return false;
|
|
18382
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
18383
|
+
const callNode = referenceRoot.parent;
|
|
18384
|
+
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments[2] !== referenceRoot) return false;
|
|
18385
|
+
const callee = stripParenExpression(callNode.callee);
|
|
18386
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
18387
|
+
const methodName = getStaticPropertyKeyName(callee);
|
|
18388
|
+
return methodName === "addEventListener" || methodName === "removeEventListener";
|
|
18389
|
+
}) ? initializer : null;
|
|
18390
|
+
};
|
|
18156
18391
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
18157
|
-
const
|
|
18392
|
+
const stableOptionsNode = optionsNode ? resolveReadOnlyEventListenerOptions(optionsNode, context) : null;
|
|
18393
|
+
if (optionsNode && !stableOptionsNode) return null;
|
|
18394
|
+
const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
|
|
18158
18395
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
18159
|
-
if (!
|
|
18160
|
-
const unwrappedOptions = stripParenExpression(
|
|
18396
|
+
if (!stableOptionsNode) return null;
|
|
18397
|
+
const unwrappedOptions = stripParenExpression(stableOptionsNode);
|
|
18161
18398
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
18162
18399
|
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
18163
18400
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
@@ -18204,12 +18441,8 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
|
|
|
18204
18441
|
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
18205
18442
|
};
|
|
18206
18443
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
while (isNodeOfType(parentNode, "ChainExpression")) {
|
|
18210
|
-
currentNode = parentNode;
|
|
18211
|
-
parentNode = currentNode.parent;
|
|
18212
|
-
}
|
|
18444
|
+
const currentNode = findTransparentExpressionRoot(resourceNode);
|
|
18445
|
+
const parentNode = currentNode.parent;
|
|
18213
18446
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
|
|
18214
18447
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
18215
18448
|
return null;
|
|
@@ -18480,6 +18713,18 @@ const resolveIteratorCollectionKey = (expression, context) => {
|
|
|
18480
18713
|
}
|
|
18481
18714
|
return null;
|
|
18482
18715
|
};
|
|
18716
|
+
const resolveReceiverIteratorCollectionKey = (expression, context) => {
|
|
18717
|
+
if (!expression) return null;
|
|
18718
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
18719
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
18720
|
+
const collectionExpression = findForOfStatementForIteratorExpression(unwrappedExpression, context)?.right;
|
|
18721
|
+
if (!collectionExpression) return null;
|
|
18722
|
+
const collectionIdentifier = stripParenExpression(collectionExpression);
|
|
18723
|
+
if (!isNodeOfType(collectionIdentifier, "Identifier") || !isPrivatePlainConstIdentifier(collectionIdentifier, context)) return null;
|
|
18724
|
+
const collectionSymbol = context.scopes.symbolFor(collectionIdentifier);
|
|
18725
|
+
const initializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
18726
|
+
return collectionSymbol && isNodeOfType(initializer, "ArrayExpression") && hasOnlyReplayableCollectionReferences(collectionIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${collectionSymbol.id}` : null;
|
|
18727
|
+
};
|
|
18483
18728
|
const isStableLoopReceiver = (expression, context) => {
|
|
18484
18729
|
if (!expression) return false;
|
|
18485
18730
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -19250,6 +19495,28 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
|
|
|
19250
19495
|
});
|
|
19251
19496
|
};
|
|
19252
19497
|
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
19498
|
+
const findCallbackRefReplacementReleaseGuard = (releaseCall, ownerFunction, releaseReceiverKey, registrationReceiverKey, context) => {
|
|
19499
|
+
let descendant = releaseCall;
|
|
19500
|
+
let ancestor = descendant.parent;
|
|
19501
|
+
while (ancestor && ancestor !== ownerFunction) {
|
|
19502
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && ancestor.alternate === null) {
|
|
19503
|
+
const test = stripParenExpression(ancestor.test);
|
|
19504
|
+
if (!isNodeOfType(test, "LogicalExpression") || test.operator !== "&&") return null;
|
|
19505
|
+
const operands = [stripParenExpression(test.left), stripParenExpression(test.right)];
|
|
19506
|
+
const hasLiveReceiverTest = operands.some((operand) => doesTestRequireLiveExpressionKey(operand, releaseReceiverKey, context));
|
|
19507
|
+
const hasDifferentReceiverTest = operands.some((operand) => {
|
|
19508
|
+
if (!isNodeOfType(operand, "BinaryExpression") || operand.operator !== "!==" && operand.operator !== "!=") return false;
|
|
19509
|
+
const leftKey = resolveExpressionKey(operand.left, context);
|
|
19510
|
+
const rightKey = resolveExpressionKey(operand.right, context);
|
|
19511
|
+
return leftKey === releaseReceiverKey && rightKey === registrationReceiverKey || rightKey === releaseReceiverKey && leftKey === registrationReceiverKey;
|
|
19512
|
+
});
|
|
19513
|
+
return hasLiveReceiverTest && hasDifferentReceiverTest ? ancestor : null;
|
|
19514
|
+
}
|
|
19515
|
+
descendant = ancestor;
|
|
19516
|
+
ancestor = descendant.parent;
|
|
19517
|
+
}
|
|
19518
|
+
return null;
|
|
19519
|
+
};
|
|
19253
19520
|
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
19254
19521
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
19255
19522
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
@@ -19270,7 +19537,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
19270
19537
|
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
19271
19538
|
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);
|
|
19272
19539
|
});
|
|
19273
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
19540
|
+
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
|
|
19274
19541
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
19275
19542
|
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
19276
19543
|
};
|
|
@@ -19420,14 +19687,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19420
19687
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
19421
19688
|
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;
|
|
19422
19689
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
19423
|
-
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;
|
|
19424
19690
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
19691
|
+
if (usage.registrationVerbName === "addListener" && releaseVerbName === "removeListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) {
|
|
19692
|
+
if (callNode.arguments?.length !== 1) return false;
|
|
19693
|
+
const registrationHandler = resolveStableValue(usage.node.arguments[0], context);
|
|
19694
|
+
if (!isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && !isFunctionLike$1(registrationHandler)) return false;
|
|
19695
|
+
}
|
|
19425
19696
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
19426
19697
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
19427
19698
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
19428
19699
|
}
|
|
19429
19700
|
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
19430
|
-
|
|
19701
|
+
const registrationCallee = isNodeOfType(usage.node, "CallExpression") ? stripParenExpression(usage.node.callee) : null;
|
|
19702
|
+
const registrationReceiverCollectionKey = isNodeOfType(registrationCallee, "MemberExpression") ? resolveReceiverIteratorCollectionKey(registrationCallee.object, context) : null;
|
|
19703
|
+
const releaseReceiverCollectionKeyForPair = resolveReceiverIteratorCollectionKey(callee.object, context);
|
|
19704
|
+
if (!(registrationReceiverCollectionKey !== null && registrationReceiverCollectionKey === releaseReceiverCollectionKeyForPair) && (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey)) return false;
|
|
19431
19705
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
19432
19706
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
19433
19707
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
@@ -19464,7 +19738,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19464
19738
|
const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
|
|
19465
19739
|
const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
19466
19740
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
19467
|
-
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
|
|
19741
|
+
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.handlerKey ?? usage.eventKey : usage.handlerKey;
|
|
19468
19742
|
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
|
|
19469
19743
|
return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
19470
19744
|
}
|
|
@@ -19938,6 +20212,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
19938
20212
|
walkAst(body, (child) => {
|
|
19939
20213
|
if (leak !== null) return false;
|
|
19940
20214
|
if (isFunctionLike$1(child)) return false;
|
|
20215
|
+
if (!isNodeReachableWithinFunction(child, context)) return false;
|
|
19941
20216
|
if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
|
|
19942
20217
|
const socketUsage = {
|
|
19943
20218
|
kind: "socket",
|
|
@@ -20190,6 +20465,164 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
20190
20465
|
const objectParent = objectExpression.parent;
|
|
20191
20466
|
return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
|
|
20192
20467
|
};
|
|
20468
|
+
const readInvocationArgumentValue = (expression, context) => {
|
|
20469
|
+
if (!expression) return {
|
|
20470
|
+
isDefinitelyUndefined: true,
|
|
20471
|
+
truthiness: "falsy"
|
|
20472
|
+
};
|
|
20473
|
+
const target = stripParenExpression(expression);
|
|
20474
|
+
if (isNodeOfType(target, "Literal")) return {
|
|
20475
|
+
isDefinitelyUndefined: false,
|
|
20476
|
+
truthiness: target.value ? "truthy" : "falsy"
|
|
20477
|
+
};
|
|
20478
|
+
if (isNodeOfType(target, "Identifier") && target.name === "undefined" && context.scopes.isGlobalReference(target)) return {
|
|
20479
|
+
isDefinitelyUndefined: true,
|
|
20480
|
+
truthiness: "falsy"
|
|
20481
|
+
};
|
|
20482
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "void") return {
|
|
20483
|
+
isDefinitelyUndefined: true,
|
|
20484
|
+
truthiness: "falsy"
|
|
20485
|
+
};
|
|
20486
|
+
if (isNodeOfType(target, "ArrayExpression") || isNodeOfType(target, "ArrowFunctionExpression") || isNodeOfType(target, "ClassExpression") || isNodeOfType(target, "FunctionExpression") || isNodeOfType(target, "NewExpression") || isNodeOfType(target, "ObjectExpression")) return {
|
|
20487
|
+
isDefinitelyUndefined: false,
|
|
20488
|
+
truthiness: "truthy"
|
|
20489
|
+
};
|
|
20490
|
+
return {
|
|
20491
|
+
isDefinitelyUndefined: false,
|
|
20492
|
+
truthiness: "unknown"
|
|
20493
|
+
};
|
|
20494
|
+
};
|
|
20495
|
+
const readInvocationConditionTruthiness = (expression, parameterValues, context) => {
|
|
20496
|
+
const target = stripParenExpression(expression);
|
|
20497
|
+
const atomicValue = readInvocationArgumentValue(target, context);
|
|
20498
|
+
if (atomicValue.truthiness !== "unknown") return atomicValue.truthiness;
|
|
20499
|
+
if (isNodeOfType(target, "Identifier")) {
|
|
20500
|
+
const symbol = context.scopes.symbolFor(target);
|
|
20501
|
+
return symbol ? parameterValues.get(symbol.id)?.truthiness ?? "unknown" : "unknown";
|
|
20502
|
+
}
|
|
20503
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "!") {
|
|
20504
|
+
const argumentTruthiness = readInvocationConditionTruthiness(target.argument, parameterValues, context);
|
|
20505
|
+
return argumentTruthiness === "truthy" ? "falsy" : argumentTruthiness === "falsy" ? "truthy" : "unknown";
|
|
20506
|
+
}
|
|
20507
|
+
if (isNodeOfType(target, "LogicalExpression")) {
|
|
20508
|
+
const leftTruthiness = readInvocationConditionTruthiness(target.left, parameterValues, context);
|
|
20509
|
+
const rightTruthiness = readInvocationConditionTruthiness(target.right, parameterValues, context);
|
|
20510
|
+
if (target.operator === "&&") {
|
|
20511
|
+
if (leftTruthiness === "falsy" || rightTruthiness === "falsy") return "falsy";
|
|
20512
|
+
return leftTruthiness === "truthy" && rightTruthiness === "truthy" ? "truthy" : "unknown";
|
|
20513
|
+
}
|
|
20514
|
+
if (target.operator === "||") {
|
|
20515
|
+
if (leftTruthiness === "truthy" || rightTruthiness === "truthy") return "truthy";
|
|
20516
|
+
return leftTruthiness === "falsy" && rightTruthiness === "falsy" ? "falsy" : "unknown";
|
|
20517
|
+
}
|
|
20518
|
+
return "unknown";
|
|
20519
|
+
}
|
|
20520
|
+
if (isNodeOfType(target, "ConditionalExpression")) {
|
|
20521
|
+
const testTruthiness = readInvocationConditionTruthiness(target.test, parameterValues, context);
|
|
20522
|
+
if (testTruthiness === "truthy") return readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20523
|
+
if (testTruthiness === "falsy") return readInvocationConditionTruthiness(target.alternate, parameterValues, context);
|
|
20524
|
+
const consequentTruthiness = readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20525
|
+
return consequentTruthiness === readInvocationConditionTruthiness(target.alternate, parameterValues, context) ? consequentTruthiness : "unknown";
|
|
20526
|
+
}
|
|
20527
|
+
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);
|
|
20528
|
+
return "unknown";
|
|
20529
|
+
};
|
|
20530
|
+
const getInvocationParameterValues = (retainedFunction, invocation, leakNode, context) => {
|
|
20531
|
+
const parameterValues = /* @__PURE__ */ new Map();
|
|
20532
|
+
if (!isFunctionLike$1(retainedFunction) || !invocation.isDirect) return parameterValues;
|
|
20533
|
+
for (const [parameterIndex, parameter] of retainedFunction.params.entries()) {
|
|
20534
|
+
const argument = invocation.call.arguments[parameterIndex];
|
|
20535
|
+
const argumentExpression = argument && isAstNode(argument) ? argument : null;
|
|
20536
|
+
let parameterIdentifier = null;
|
|
20537
|
+
let parameterValue = readInvocationArgumentValue(argumentExpression, context);
|
|
20538
|
+
if (isNodeOfType(parameter, "Identifier")) parameterIdentifier = parameter;
|
|
20539
|
+
else if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) {
|
|
20540
|
+
parameterIdentifier = parameter.left;
|
|
20541
|
+
if (parameterValue.isDefinitelyUndefined) parameterValue = readInvocationArgumentValue(parameter.right, context);
|
|
20542
|
+
} else if (isNodeOfType(parameter, "RestElement") && isNodeOfType(parameter.argument, "Identifier")) {
|
|
20543
|
+
parameterIdentifier = parameter.argument;
|
|
20544
|
+
parameterValue = {
|
|
20545
|
+
isDefinitelyUndefined: false,
|
|
20546
|
+
truthiness: "truthy"
|
|
20547
|
+
};
|
|
20548
|
+
}
|
|
20549
|
+
if (!parameterIdentifier) continue;
|
|
20550
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
20551
|
+
if (!parameterSymbol) continue;
|
|
20552
|
+
const isWrittenBeforeLeak = parameterSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] < leakNode.range[0]);
|
|
20553
|
+
parameterValues.set(parameterSymbol.id, isWrittenBeforeLeak ? {
|
|
20554
|
+
isDefinitelyUndefined: false,
|
|
20555
|
+
truthiness: "unknown"
|
|
20556
|
+
} : parameterValue);
|
|
20557
|
+
}
|
|
20558
|
+
return parameterValues;
|
|
20559
|
+
};
|
|
20560
|
+
const isLeakPathDisabledForInvocation = (retainedFunction, leakNode, invocation, context) => {
|
|
20561
|
+
if (!invocation.isDirect) return false;
|
|
20562
|
+
const parameterValues = getInvocationParameterValues(retainedFunction, invocation, leakNode, context);
|
|
20563
|
+
let child = leakNode;
|
|
20564
|
+
let ancestor = leakNode.parent ?? null;
|
|
20565
|
+
while (ancestor && ancestor !== retainedFunction) {
|
|
20566
|
+
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
20567
|
+
const childIndex = ancestor.body.findIndex((statement) => statement === child);
|
|
20568
|
+
for (const precedingStatement of ancestor.body.slice(0, childIndex)) {
|
|
20569
|
+
if (!isNodeOfType(precedingStatement, "IfStatement") || precedingStatement.alternate || !isEarlyExitStatement$1(precedingStatement.consequent)) continue;
|
|
20570
|
+
if (readInvocationConditionTruthiness(precedingStatement.test, parameterValues, context) === "truthy") return true;
|
|
20571
|
+
}
|
|
20572
|
+
}
|
|
20573
|
+
let requiredTruthiness = null;
|
|
20574
|
+
let condition = null;
|
|
20575
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
20576
|
+
condition = ancestor.test;
|
|
20577
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20578
|
+
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
20579
|
+
condition = ancestor.test;
|
|
20580
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20581
|
+
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === child && ancestor.operator !== "??") {
|
|
20582
|
+
condition = ancestor.left;
|
|
20583
|
+
requiredTruthiness = ancestor.operator === "&&" ? "truthy" : "falsy";
|
|
20584
|
+
} else if ((isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === child) {
|
|
20585
|
+
condition = ancestor.test;
|
|
20586
|
+
requiredTruthiness = "truthy";
|
|
20587
|
+
} else if (isNodeOfType(ancestor, "ForStatement") && ancestor.body === child && ancestor.test) {
|
|
20588
|
+
condition = ancestor.test;
|
|
20589
|
+
requiredTruthiness = "truthy";
|
|
20590
|
+
}
|
|
20591
|
+
if (condition && requiredTruthiness) {
|
|
20592
|
+
const conditionTruthiness = readInvocationConditionTruthiness(condition, parameterValues, context);
|
|
20593
|
+
if (conditionTruthiness !== "unknown" && conditionTruthiness !== requiredTruthiness) return true;
|
|
20594
|
+
}
|
|
20595
|
+
child = ancestor;
|
|
20596
|
+
ancestor = ancestor.parent ?? null;
|
|
20597
|
+
}
|
|
20598
|
+
return false;
|
|
20599
|
+
};
|
|
20600
|
+
const getEffectRetainedInvocations = (retainedFunction, context) => {
|
|
20601
|
+
if (!isFunctionLike$1(retainedFunction)) return [];
|
|
20602
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
20603
|
+
if (!componentFunction || !isFunctionLike$1(componentFunction)) return [];
|
|
20604
|
+
const invocations = [];
|
|
20605
|
+
walkAst(componentFunction.body, (child) => {
|
|
20606
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
20607
|
+
const effectCallback = getEffectCallback(child);
|
|
20608
|
+
if (!effectCallback || !isFunctionLike$1(effectCallback)) return;
|
|
20609
|
+
walkAst(effectCallback.body, (effectChild) => {
|
|
20610
|
+
if (effectChild !== effectCallback.body && isFunctionLike$1(effectChild)) return false;
|
|
20611
|
+
if (!isNodeOfType(effectChild, "CallExpression") || !isNodeReachableWithinFunction(effectChild, context)) return;
|
|
20612
|
+
const isDirectInvocation = resolveRefOwnedCleanupFunction(effectChild.callee, context) === retainedFunction;
|
|
20613
|
+
const isSynchronousIteratorInvocation = effectChild.arguments.some((argument) => isAstNode(argument) && resolveRefOwnedCleanupFunction(argument, context) === retainedFunction && isSynchronousIteratorCallbackCall(effectChild, argument));
|
|
20614
|
+
if (isDirectInvocation) invocations.push({
|
|
20615
|
+
call: effectChild,
|
|
20616
|
+
isDirect: true
|
|
20617
|
+
});
|
|
20618
|
+
if (isSynchronousIteratorInvocation) invocations.push({
|
|
20619
|
+
call: effectChild,
|
|
20620
|
+
isDirect: false
|
|
20621
|
+
});
|
|
20622
|
+
});
|
|
20623
|
+
});
|
|
20624
|
+
return invocations;
|
|
20625
|
+
};
|
|
20193
20626
|
const effectNeedsCleanup = defineRule({
|
|
20194
20627
|
id: "effect-needs-cleanup",
|
|
20195
20628
|
title: "Effect subscription or timer never cleaned up",
|
|
@@ -20200,13 +20633,19 @@ const effectNeedsCleanup = defineRule({
|
|
|
20200
20633
|
const reportRetainedLeak = (retainedFunction) => {
|
|
20201
20634
|
const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
|
|
20202
20635
|
if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
|
|
20636
|
+
const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
|
|
20637
|
+
const isEffectInvoked = effectInvocations.length > 0;
|
|
20203
20638
|
const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
|
|
20204
20639
|
allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
|
|
20205
20640
|
allowReturnedTimerEscape: false,
|
|
20206
20641
|
includeOneShotTimers: true,
|
|
20207
20642
|
requireCallableReturnedResource: true
|
|
20643
|
+
} : isEffectInvoked ? {
|
|
20644
|
+
allowReturnedTimerEscape: false,
|
|
20645
|
+
includeOneShotTimers: true
|
|
20208
20646
|
} : void 0);
|
|
20209
20647
|
if (!leak) return;
|
|
20648
|
+
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;
|
|
20210
20649
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
|
|
20211
20650
|
context.report({
|
|
20212
20651
|
node: leak.node,
|
|
@@ -24626,14 +25065,14 @@ const getFirstLegendChild = (children, targetNode) => {
|
|
|
24626
25065
|
if (isNodeOfType(child, "JSXExpressionContainer")) {
|
|
24627
25066
|
const potentialLegends = [];
|
|
24628
25067
|
collectPotentialLegends(child.expression, potentialLegends);
|
|
24629
|
-
const containingLegend = potentialLegends.find((legend) => isDescendantOf(targetNode, legend));
|
|
25068
|
+
const containingLegend = potentialLegends.find((legend) => isDescendantOf$1(targetNode, legend));
|
|
24630
25069
|
if (containingLegend) return containingLegend;
|
|
24631
25070
|
if (potentialLegends[0]) return potentialLegends[0];
|
|
24632
25071
|
}
|
|
24633
25072
|
}
|
|
24634
25073
|
return null;
|
|
24635
25074
|
};
|
|
24636
|
-
const isDescendantOf = (node, ancestor) => {
|
|
25075
|
+
const isDescendantOf$1 = (node, ancestor) => {
|
|
24637
25076
|
let current = node.parent;
|
|
24638
25077
|
while (current) {
|
|
24639
25078
|
if (current === ancestor) return true;
|
|
@@ -24658,7 +25097,7 @@ const isDisabledByFieldsetAncestor = (node, context) => {
|
|
|
24658
25097
|
while (ancestor) {
|
|
24659
25098
|
if (isNodeOfType(ancestor, "JSXElement") && resolveJsxElementType(ancestor.openingElement) === "fieldset" && openingElementMayBeDisabled(ancestor.openingElement, context)) {
|
|
24660
25099
|
const firstLegend = getFirstLegendChild(ancestor.children, node);
|
|
24661
|
-
if (!firstLegend || !isDescendantOf(node, firstLegend)) return true;
|
|
25100
|
+
if (!firstLegend || !isDescendantOf$1(node, firstLegend)) return true;
|
|
24662
25101
|
}
|
|
24663
25102
|
ancestor = ancestor.parent;
|
|
24664
25103
|
}
|
|
@@ -30471,7 +30910,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
|
|
|
30471
30910
|
"code",
|
|
30472
30911
|
"label",
|
|
30473
30912
|
"slug",
|
|
30474
|
-
"prefix"
|
|
30913
|
+
"prefix",
|
|
30914
|
+
"__html"
|
|
30475
30915
|
]);
|
|
30476
30916
|
const STRING_TYPED_IDENTIFIER_SUFFIXES = [
|
|
30477
30917
|
"Text",
|
|
@@ -30589,13 +31029,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
|
|
|
30589
31029
|
"title"
|
|
30590
31030
|
]);
|
|
30591
31031
|
const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
|
|
31032
|
+
const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
|
|
31033
|
+
"concat",
|
|
31034
|
+
"filter",
|
|
31035
|
+
"flat",
|
|
31036
|
+
"flatMap",
|
|
31037
|
+
"map",
|
|
31038
|
+
"slice",
|
|
31039
|
+
"split"
|
|
31040
|
+
]);
|
|
30592
31041
|
const isLikelyStringReceiver = (receiver) => {
|
|
30593
31042
|
if (!receiver) return false;
|
|
31043
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31044
|
+
if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
|
|
30594
31045
|
if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
|
|
30595
31046
|
if (isNodeOfType(receiver, "TemplateLiteral")) return true;
|
|
30596
31047
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
|
|
30597
31048
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
|
|
30598
31049
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
|
|
31050
|
+
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;
|
|
30599
31051
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
30600
31052
|
if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
|
|
30601
31053
|
}
|
|
@@ -30613,8 +31065,46 @@ const isLikelyStringReceiver = (receiver) => {
|
|
|
30613
31065
|
}
|
|
30614
31066
|
if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
|
|
30615
31067
|
if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
|
|
31068
|
+
if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
|
|
30616
31069
|
return false;
|
|
30617
31070
|
};
|
|
31071
|
+
const isFreshArrayReceiver = (receiver) => {
|
|
31072
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31073
|
+
if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
|
|
31074
|
+
if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
|
|
31075
|
+
if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
|
|
31076
|
+
if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
|
|
31077
|
+
const sourceReceiver = stripParenExpression(receiver.callee.object);
|
|
31078
|
+
return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
|
|
31079
|
+
};
|
|
31080
|
+
const isSmallRestHelperOmissionList = (node) => {
|
|
31081
|
+
if (!node) return false;
|
|
31082
|
+
const candidate = stripParenExpression(node);
|
|
31083
|
+
if (!isNodeOfType(candidate, "ArrayExpression")) return false;
|
|
31084
|
+
const elements = candidate.elements ?? [];
|
|
31085
|
+
return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
|
|
31086
|
+
};
|
|
31087
|
+
const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
|
|
31088
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
31089
|
+
const enclosingFunction = findEnclosingFunction$1(lookupCall);
|
|
31090
|
+
if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
|
|
31091
|
+
let bindingIdentifier = null;
|
|
31092
|
+
let ancestor = enclosingFunction.parent;
|
|
31093
|
+
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
31094
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
|
|
31095
|
+
bindingIdentifier = ancestor.id;
|
|
31096
|
+
break;
|
|
31097
|
+
}
|
|
31098
|
+
ancestor = ancestor.parent;
|
|
31099
|
+
}
|
|
31100
|
+
if (!bindingIdentifier) return false;
|
|
31101
|
+
const helperSymbol = scopes.symbolFor(bindingIdentifier);
|
|
31102
|
+
if (!helperSymbol || helperSymbol.references.length === 0) return false;
|
|
31103
|
+
return helperSymbol.references.every((reference) => {
|
|
31104
|
+
const callExpression = reference.identifier.parent;
|
|
31105
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
|
|
31106
|
+
});
|
|
31107
|
+
};
|
|
30618
31108
|
const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
|
|
30619
31109
|
"i",
|
|
30620
31110
|
"j",
|
|
@@ -31211,6 +31701,8 @@ const jsSetMapLookups = defineRule({
|
|
|
31211
31701
|
const query = node.arguments[0];
|
|
31212
31702
|
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
31213
31703
|
if (isLikelyStringReceiver(receiver)) return;
|
|
31704
|
+
if (isFreshArrayReceiver(receiver)) return;
|
|
31705
|
+
if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
|
|
31214
31706
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
31215
31707
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
31216
31708
|
if (isSmallFixedListMember(receiver)) return;
|
|
@@ -45124,14 +45616,6 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45124
45616
|
} })
|
|
45125
45617
|
});
|
|
45126
45618
|
//#endregion
|
|
45127
|
-
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
45128
|
-
const isEarlyExitStatement$1 = (statement) => {
|
|
45129
|
-
if (!statement) return false;
|
|
45130
|
-
if (statementAlwaysExits$1(statement)) return true;
|
|
45131
|
-
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
45132
|
-
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
45133
|
-
};
|
|
45134
|
-
//#endregion
|
|
45135
45619
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45136
45620
|
const unwrapNegativeGuardForm = (test) => {
|
|
45137
45621
|
const expression = stripParenExpression(test);
|
|
@@ -46652,6 +47136,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
|
|
|
46652
47136
|
}
|
|
46653
47137
|
return isBindingReassignedOrMutated(referenceNode, indexName);
|
|
46654
47138
|
};
|
|
47139
|
+
const findEnclosingWhileLoop = (node) => {
|
|
47140
|
+
let current = node.parent;
|
|
47141
|
+
while (current) {
|
|
47142
|
+
if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
|
|
47143
|
+
if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
|
|
47144
|
+
current = current.parent;
|
|
47145
|
+
}
|
|
47146
|
+
return null;
|
|
47147
|
+
};
|
|
47148
|
+
const isStaticMemberChain = (expression) => {
|
|
47149
|
+
const candidate = stripParenExpression(expression);
|
|
47150
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
|
|
47151
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
|
|
47152
|
+
};
|
|
47153
|
+
const findLengthBoundCollections = (expression) => {
|
|
47154
|
+
const collections = [];
|
|
47155
|
+
walkAst(expression, (child) => {
|
|
47156
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
|
|
47157
|
+
});
|
|
47158
|
+
return collections;
|
|
47159
|
+
};
|
|
47160
|
+
const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
|
|
47161
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47162
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47163
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47164
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47165
|
+
} });
|
|
47166
|
+
const RELATIONAL_BOUND_OPERATORS = new Set([
|
|
47167
|
+
"<",
|
|
47168
|
+
"<=",
|
|
47169
|
+
">",
|
|
47170
|
+
">="
|
|
47171
|
+
]);
|
|
47172
|
+
const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
|
|
47173
|
+
const readsCounter = (candidate) => {
|
|
47174
|
+
let didReadCounter = false;
|
|
47175
|
+
walkAst(candidate, (child) => {
|
|
47176
|
+
if (didReadCounter) return false;
|
|
47177
|
+
if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47178
|
+
didReadCounter = true;
|
|
47179
|
+
return false;
|
|
47180
|
+
}
|
|
47181
|
+
});
|
|
47182
|
+
return didReadCounter;
|
|
47183
|
+
};
|
|
47184
|
+
const readsLength = (candidate) => {
|
|
47185
|
+
let didReadLength = false;
|
|
47186
|
+
walkAst(candidate, (child) => {
|
|
47187
|
+
if (didReadLength) return false;
|
|
47188
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
47189
|
+
didReadLength = true;
|
|
47190
|
+
return false;
|
|
47191
|
+
}
|
|
47192
|
+
});
|
|
47193
|
+
return didReadLength;
|
|
47194
|
+
};
|
|
47195
|
+
let didFindLengthBound = false;
|
|
47196
|
+
walkAst(expression, (child) => {
|
|
47197
|
+
if (didFindLengthBound) return false;
|
|
47198
|
+
if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
|
|
47199
|
+
if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
|
|
47200
|
+
didFindLengthBound = true;
|
|
47201
|
+
return false;
|
|
47202
|
+
}
|
|
47203
|
+
});
|
|
47204
|
+
return didFindLengthBound;
|
|
47205
|
+
};
|
|
47206
|
+
const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
|
|
47207
|
+
if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
|
|
47208
|
+
const loop = findEnclosingWhileLoop(referenceNode);
|
|
47209
|
+
if (!loop) return false;
|
|
47210
|
+
let doesTestReadCounter = false;
|
|
47211
|
+
walkAst(loop.test, (child) => {
|
|
47212
|
+
if (doesTestReadCounter) return false;
|
|
47213
|
+
if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
|
|
47214
|
+
if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47215
|
+
doesTestReadCounter = true;
|
|
47216
|
+
return false;
|
|
47217
|
+
}
|
|
47218
|
+
});
|
|
47219
|
+
if (!doesTestReadCounter) return false;
|
|
47220
|
+
const lengthBoundCollections = findLengthBoundCollections(loop.test);
|
|
47221
|
+
if (lengthBoundCollections.length === 0) return false;
|
|
47222
|
+
let didFindIndexedCollectionRead = false;
|
|
47223
|
+
walkAst(loop.body, (child) => {
|
|
47224
|
+
if (didFindIndexedCollectionRead) return false;
|
|
47225
|
+
if (isFunctionLike$1(child)) return false;
|
|
47226
|
+
if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
|
|
47227
|
+
if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
|
|
47228
|
+
didFindIndexedCollectionRead = true;
|
|
47229
|
+
return false;
|
|
47230
|
+
}
|
|
47231
|
+
});
|
|
47232
|
+
return didFindIndexedCollectionRead;
|
|
47233
|
+
};
|
|
46655
47234
|
/**
|
|
46656
47235
|
* Resolves whether an identifier is PROVABLY the positional array
|
|
46657
47236
|
* index, by classifying its binding. Per the official rule prompt,
|
|
@@ -46702,6 +47281,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
|
|
|
46702
47281
|
}
|
|
46703
47282
|
const declarator = binding.bindingIdentifier.parent;
|
|
46704
47283
|
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
|
|
47284
|
+
if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
|
|
47285
|
+
iteratorCall: null,
|
|
47286
|
+
bindingFunction: null,
|
|
47287
|
+
indexParameterPosition: null,
|
|
47288
|
+
isDataIndexedLoopCounter: true
|
|
47289
|
+
};
|
|
46705
47290
|
const initializer = stripParenExpression(declarator.init);
|
|
46706
47291
|
if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
|
|
46707
47292
|
if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
|
|
@@ -46736,6 +47321,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
|
|
|
46736
47321
|
const receiver = iteratorCall.callee.object;
|
|
46737
47322
|
return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
|
|
46738
47323
|
};
|
|
47324
|
+
const isReactNamespaceIdentifier = (node) => {
|
|
47325
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47326
|
+
const importBinding = getImportBindingForName(node, node.name);
|
|
47327
|
+
const visibleBinding = findVariableInitializer(node, node.name);
|
|
47328
|
+
if (!importBinding) return node.name === "React" && !visibleBinding;
|
|
47329
|
+
return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
|
|
47330
|
+
};
|
|
47331
|
+
const isReactChildrenObject = (node) => {
|
|
47332
|
+
const candidate = stripParenExpression(node);
|
|
47333
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47334
|
+
const importBinding = getImportBindingForName(candidate, candidate.name);
|
|
47335
|
+
const visibleBinding = findVariableInitializer(candidate, candidate.name);
|
|
47336
|
+
return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
|
|
47337
|
+
}
|
|
47338
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
|
|
47339
|
+
};
|
|
47340
|
+
const isReactChildrenToArrayCall = (node) => {
|
|
47341
|
+
const candidate = stripParenExpression(node);
|
|
47342
|
+
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;
|
|
47343
|
+
const normalizedValueNode = candidate.arguments?.[0];
|
|
47344
|
+
const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
|
|
47345
|
+
if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
|
|
47346
|
+
const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
|
|
47347
|
+
return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
|
|
47348
|
+
};
|
|
47349
|
+
const isSameIdentifier = (first, second) => {
|
|
47350
|
+
const firstIdentifier = stripParenExpression(first);
|
|
47351
|
+
const secondIdentifier = stripParenExpression(second);
|
|
47352
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47353
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47354
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47355
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47356
|
+
};
|
|
47357
|
+
const isReactChildrenArrayNormalization = (node) => {
|
|
47358
|
+
const candidate = stripParenExpression(node);
|
|
47359
|
+
if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
|
|
47360
|
+
const test = stripParenExpression(candidate.test);
|
|
47361
|
+
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;
|
|
47362
|
+
const testedValueNode = test.arguments?.[0];
|
|
47363
|
+
if (!testedValueNode) return false;
|
|
47364
|
+
const testedValue = stripParenExpression(testedValueNode);
|
|
47365
|
+
if (!isNodeOfType(testedValue, "Identifier")) return false;
|
|
47366
|
+
const testedBinding = findVariableInitializer(testedValue, testedValue.name);
|
|
47367
|
+
if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
|
|
47368
|
+
const branchWrapsTestedValue = (branch) => {
|
|
47369
|
+
const unwrappedBranch = stripParenExpression(branch);
|
|
47370
|
+
return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
|
|
47371
|
+
};
|
|
47372
|
+
return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
|
|
47373
|
+
};
|
|
47374
|
+
const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
|
|
47375
|
+
const binding = findVariableInitializer(identifierNode, identifierNode.name);
|
|
47376
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
47377
|
+
if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
|
|
47378
|
+
const program = findProgramRoot(identifierNode);
|
|
47379
|
+
if (!program) return false;
|
|
47380
|
+
let didFindReactChildPush = false;
|
|
47381
|
+
walkAst(program, (child) => {
|
|
47382
|
+
if (didFindReactChildPush) return false;
|
|
47383
|
+
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;
|
|
47384
|
+
if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
|
|
47385
|
+
const candidate = stripParenExpression(argument);
|
|
47386
|
+
if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
|
|
47387
|
+
let doesCarryReactChild = false;
|
|
47388
|
+
walkAst(candidate, (argumentChild) => {
|
|
47389
|
+
if (doesCarryReactChild) return false;
|
|
47390
|
+
if (!isNodeOfType(argumentChild, "Identifier")) return;
|
|
47391
|
+
const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
|
|
47392
|
+
const declaration = declarator?.parent;
|
|
47393
|
+
const forOfStatement = declaration?.parent;
|
|
47394
|
+
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
|
|
47395
|
+
doesCarryReactChild = true;
|
|
47396
|
+
return false;
|
|
47397
|
+
}
|
|
47398
|
+
});
|
|
47399
|
+
return doesCarryReactChild;
|
|
47400
|
+
})) {
|
|
47401
|
+
didFindReactChildPush = true;
|
|
47402
|
+
return false;
|
|
47403
|
+
}
|
|
47404
|
+
});
|
|
47405
|
+
return didFindReactChildPush;
|
|
47406
|
+
};
|
|
47407
|
+
const isDynamicReactChildrenExpression = (expression, depth) => {
|
|
47408
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
|
|
47409
|
+
const candidate = stripParenExpression(expression);
|
|
47410
|
+
if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
|
|
47411
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47412
|
+
if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
|
|
47413
|
+
const binding = findVariableInitializer(candidate, candidate.name);
|
|
47414
|
+
return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
|
|
47415
|
+
}
|
|
47416
|
+
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);
|
|
47417
|
+
return false;
|
|
47418
|
+
};
|
|
46739
47419
|
const resolveKeyTemplateLiteral = (expression) => {
|
|
46740
47420
|
const node = stripParenExpression(expression);
|
|
46741
47421
|
if (isNodeOfType(node, "TemplateLiteral")) return node;
|
|
@@ -46788,28 +47468,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
|
46788
47468
|
}
|
|
46789
47469
|
return referencedItemNames;
|
|
46790
47470
|
};
|
|
46791
|
-
const
|
|
46792
|
-
let didFindLengthRead = false;
|
|
46793
|
-
walkAst(test, (child) => {
|
|
46794
|
-
if (didFindLengthRead) return false;
|
|
46795
|
-
if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
46796
|
-
didFindLengthRead = true;
|
|
46797
|
-
return false;
|
|
46798
|
-
}
|
|
46799
|
-
});
|
|
46800
|
-
return didFindLengthRead;
|
|
46801
|
-
};
|
|
46802
|
-
const isNumericForLoopCounter = (attributeNode, indexName) => {
|
|
47471
|
+
const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
|
|
46803
47472
|
const binding = findVariableInitializer(attributeNode, indexName);
|
|
46804
47473
|
if (!binding) return false;
|
|
46805
47474
|
const declarator = binding.bindingIdentifier.parent;
|
|
46806
47475
|
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
46807
47476
|
const declaration = declarator.parent;
|
|
46808
47477
|
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
47478
|
+
if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
|
|
46809
47479
|
const forStatement = declaration.parent;
|
|
46810
|
-
if (
|
|
46811
|
-
|
|
46812
|
-
return
|
|
47480
|
+
if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
|
|
47481
|
+
const whileLoop = findEnclosingWhileLoop(attributeNode);
|
|
47482
|
+
if (!whileLoop) return false;
|
|
47483
|
+
return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
|
|
46813
47484
|
};
|
|
46814
47485
|
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
46815
47486
|
const findIteratorItemNamesOfBinding = (binding) => {
|
|
@@ -46843,11 +47514,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
|
|
|
46843
47514
|
* observable harm; anything stateful — form controls, media, custom
|
|
46844
47515
|
* components, unknown calls — keeps the diagnostic.
|
|
46845
47516
|
*/
|
|
46846
|
-
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
|
|
47517
|
+
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
|
|
46847
47518
|
const jsxElement = openingElement.parent;
|
|
46848
47519
|
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
46849
47520
|
const children = jsxElement.children ?? [];
|
|
46850
|
-
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
47521
|
+
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
46851
47522
|
return children.some((child) => containsStatefulDescendant(child, {
|
|
46852
47523
|
memberRootNames: itemNames,
|
|
46853
47524
|
allowAnyMemberRead: true,
|
|
@@ -46855,6 +47526,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
|
|
|
46855
47526
|
callCalleeRootNames: itemNames
|
|
46856
47527
|
}));
|
|
46857
47528
|
};
|
|
47529
|
+
const elementHasDirectItemChild = (openingElement, itemNames) => {
|
|
47530
|
+
const jsxElement = openingElement.parent;
|
|
47531
|
+
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47532
|
+
return (jsxElement.children ?? []).some((child) => {
|
|
47533
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
|
|
47534
|
+
const expression = stripParenExpression(child.expression);
|
|
47535
|
+
return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
|
|
47536
|
+
});
|
|
47537
|
+
};
|
|
46858
47538
|
const callbackFiltersRows = (bindingFunction) => {
|
|
46859
47539
|
if (!bindingFunction) return false;
|
|
46860
47540
|
let didFindNullReturn = false;
|
|
@@ -46906,19 +47586,21 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46906
47586
|
const indexUse = findPositionalIndexUse(node.value.expression, 0);
|
|
46907
47587
|
if (!indexUse) return;
|
|
46908
47588
|
const indexName = indexUse.identifier.name;
|
|
46909
|
-
if (
|
|
47589
|
+
if (isNumericPlaceholderLoopCounter(node, indexName)) return;
|
|
46910
47590
|
if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
|
|
46911
47591
|
const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
|
|
46912
47592
|
if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
|
|
46913
|
-
if (hasAriaHiddenAncestor(node)) return;
|
|
47593
|
+
if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
|
|
46914
47594
|
const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
|
|
46915
47595
|
const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
|
|
47596
|
+
const iteratorCallee = indexUse.binding.iteratorCall?.callee;
|
|
47597
|
+
const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
|
|
46916
47598
|
const openingElement = node.parent;
|
|
46917
47599
|
if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
|
|
46918
47600
|
const elementName = openingElement.name;
|
|
46919
47601
|
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
46920
47602
|
if (elementName.name === "Fragment") {
|
|
46921
|
-
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47603
|
+
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46922
47604
|
} else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
|
|
46923
47605
|
if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
|
|
46924
47606
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
@@ -46926,14 +47608,14 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46926
47608
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
46927
47609
|
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
46928
47610
|
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
|
|
46929
|
-
if (!containsStatefulDescendant(jsxElement, {
|
|
47611
|
+
if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
|
|
46930
47612
|
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
|
|
46931
47613
|
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
46932
|
-
})) return;
|
|
47614
|
+
}))) return;
|
|
46933
47615
|
}
|
|
46934
47616
|
}
|
|
46935
47617
|
}
|
|
46936
|
-
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;
|
|
47618
|
+
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;
|
|
46937
47619
|
}
|
|
46938
47620
|
context.report({
|
|
46939
47621
|
node,
|
|
@@ -63829,6 +64511,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
|
|
|
63829
64511
|
};
|
|
63830
64512
|
//#endregion
|
|
63831
64513
|
//#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
|
|
64514
|
+
const findGuardingIfStatements = (node, functionBoundary) => {
|
|
64515
|
+
const guardingIfStatements = [];
|
|
64516
|
+
let currentNode = node.parent;
|
|
64517
|
+
while (currentNode && currentNode !== functionBoundary) {
|
|
64518
|
+
if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
|
|
64519
|
+
currentNode = currentNode.parent;
|
|
64520
|
+
}
|
|
64521
|
+
return guardingIfStatements;
|
|
64522
|
+
};
|
|
64523
|
+
const doesNodeReadSymbol = (node, symbol) => {
|
|
64524
|
+
let doesReadSymbol = false;
|
|
64525
|
+
walkAst(node, (childNode) => {
|
|
64526
|
+
if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
|
|
64527
|
+
doesReadSymbol = true;
|
|
64528
|
+
return false;
|
|
64529
|
+
}
|
|
64530
|
+
});
|
|
64531
|
+
return doesReadSymbol;
|
|
64532
|
+
};
|
|
64533
|
+
const collectWrittenSymbols = (node, scopes) => {
|
|
64534
|
+
const writtenSymbols = /* @__PURE__ */ new Set();
|
|
64535
|
+
walkAst(node, (childNode) => {
|
|
64536
|
+
if (childNode !== node && isFunctionLike$1(childNode)) return false;
|
|
64537
|
+
if (!isNodeOfType(childNode, "Identifier")) return;
|
|
64538
|
+
const reference = scopes.referenceFor(childNode);
|
|
64539
|
+
if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
|
|
64540
|
+
writtenSymbols.add(reference.resolvedSymbol);
|
|
64541
|
+
});
|
|
64542
|
+
return writtenSymbols;
|
|
64543
|
+
};
|
|
64544
|
+
const isDescendantOf = (node, ancestorNode) => {
|
|
64545
|
+
let currentNode = node.parent;
|
|
64546
|
+
while (currentNode) {
|
|
64547
|
+
if (currentNode === ancestorNode) return true;
|
|
64548
|
+
currentNode = currentNode.parent;
|
|
64549
|
+
}
|
|
64550
|
+
return false;
|
|
64551
|
+
};
|
|
64552
|
+
const getAssignedValue = (identifier) => {
|
|
64553
|
+
const assignmentExpression = identifier.parent;
|
|
64554
|
+
return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
|
|
64555
|
+
};
|
|
64556
|
+
const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
|
|
64557
|
+
const initialValue = symbol.initializer;
|
|
64558
|
+
if (!initialValue) return false;
|
|
64559
|
+
const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
|
|
64560
|
+
return guardedWrites.length > 0 && guardedWrites.every((reference) => {
|
|
64561
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64562
|
+
return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
|
|
64563
|
+
});
|
|
64564
|
+
};
|
|
64565
|
+
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));
|
|
64566
|
+
const isUnconditionalOrStaticallySelected = (node, context) => {
|
|
64567
|
+
if (context.cfg.isUnconditionalFromEntry(node)) return true;
|
|
64568
|
+
let currentNode = node;
|
|
64569
|
+
let outermostStaticIfStatement = null;
|
|
64570
|
+
let parentNode = currentNode.parent;
|
|
64571
|
+
while (parentNode) {
|
|
64572
|
+
if (isFunctionLike$1(parentNode)) break;
|
|
64573
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
64574
|
+
const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
|
|
64575
|
+
let selectedBranch = null;
|
|
64576
|
+
if (staticResult === true) selectedBranch = parentNode.consequent;
|
|
64577
|
+
if (staticResult === false) selectedBranch = parentNode.alternate;
|
|
64578
|
+
if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
|
|
64579
|
+
outermostStaticIfStatement = parentNode;
|
|
64580
|
+
}
|
|
64581
|
+
currentNode = parentNode;
|
|
64582
|
+
parentNode = currentNode.parent;
|
|
64583
|
+
}
|
|
64584
|
+
return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
|
|
64585
|
+
};
|
|
64586
|
+
const containsExplicitReactRuntimeReference = (node, scopes) => {
|
|
64587
|
+
let hasRuntimeReference = false;
|
|
64588
|
+
walkAst(node, (childNode) => {
|
|
64589
|
+
if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
|
|
64590
|
+
hasRuntimeReference = true;
|
|
64591
|
+
return false;
|
|
64592
|
+
}
|
|
64593
|
+
if (!isNodeOfType(childNode, "CallExpression")) return;
|
|
64594
|
+
const sourceArgument = (childNode.arguments ?? [])[0];
|
|
64595
|
+
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;
|
|
64596
|
+
hasRuntimeReference = true;
|
|
64597
|
+
return false;
|
|
64598
|
+
});
|
|
64599
|
+
return hasRuntimeReference;
|
|
64600
|
+
};
|
|
64601
|
+
const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
|
|
64602
|
+
const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
|
|
64603
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
|
|
64604
|
+
const functionSymbol = scopes.symbolFor(bindingIdentifier);
|
|
64605
|
+
if (!functionSymbol) return null;
|
|
64606
|
+
for (const reference of functionSymbol.references) {
|
|
64607
|
+
const callExpression = reference.identifier.parent;
|
|
64608
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
|
|
64609
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
|
|
64610
|
+
if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
|
|
64611
|
+
}
|
|
64612
|
+
return null;
|
|
64613
|
+
};
|
|
63832
64614
|
const evaluateEquality$1 = (operator, left, right) => {
|
|
63833
64615
|
if (operator === "===" || operator === "==") return left === right;
|
|
63834
64616
|
if (operator === "!==" || operator === "!=") return left !== right;
|
|
@@ -63879,6 +64661,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
63879
64661
|
if (leftResult === false && rightResult === false) return false;
|
|
63880
64662
|
return null;
|
|
63881
64663
|
};
|
|
64664
|
+
const areLooselyEqualPrimitiveResults = (left, right) => {
|
|
64665
|
+
if (left.kind === right.kind) return left.value === right.value;
|
|
64666
|
+
if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
|
|
64667
|
+
if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
|
|
64668
|
+
kind: "number",
|
|
64669
|
+
value: left.value ? 1 : 0
|
|
64670
|
+
}, right);
|
|
64671
|
+
if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
|
|
64672
|
+
kind: "number",
|
|
64673
|
+
value: right.value ? 1 : 0
|
|
64674
|
+
});
|
|
64675
|
+
if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
|
|
64676
|
+
if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
|
|
64677
|
+
return false;
|
|
64678
|
+
};
|
|
64679
|
+
const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
|
|
64680
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64681
|
+
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
64682
|
+
if (predicateMatch) return {
|
|
64683
|
+
kind: "boolean",
|
|
64684
|
+
value: predicateMatch[`${runtime}Result`]
|
|
64685
|
+
};
|
|
64686
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
64687
|
+
const value = unwrappedExpression.value;
|
|
64688
|
+
if (value === null) return {
|
|
64689
|
+
kind: "null",
|
|
64690
|
+
value
|
|
64691
|
+
};
|
|
64692
|
+
if (typeof value === "boolean") return {
|
|
64693
|
+
kind: "boolean",
|
|
64694
|
+
value
|
|
64695
|
+
};
|
|
64696
|
+
if (typeof value === "number") return {
|
|
64697
|
+
kind: "number",
|
|
64698
|
+
value
|
|
64699
|
+
};
|
|
64700
|
+
if (typeof value === "string") return {
|
|
64701
|
+
kind: "string",
|
|
64702
|
+
value
|
|
64703
|
+
};
|
|
64704
|
+
return null;
|
|
64705
|
+
}
|
|
64706
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
|
|
64707
|
+
kind: "undefined",
|
|
64708
|
+
value: void 0
|
|
64709
|
+
};
|
|
64710
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64711
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64712
|
+
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
64713
|
+
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64714
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64715
|
+
const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
|
|
64716
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64717
|
+
return result;
|
|
64718
|
+
}
|
|
64719
|
+
if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64720
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64721
|
+
const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
|
|
64722
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64723
|
+
return result;
|
|
64724
|
+
}
|
|
64725
|
+
}
|
|
64726
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
64727
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
64728
|
+
return argumentResult === null ? null : {
|
|
64729
|
+
kind: "boolean",
|
|
64730
|
+
value: !argumentResult
|
|
64731
|
+
};
|
|
64732
|
+
}
|
|
64733
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64734
|
+
const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
|
|
64735
|
+
const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
|
|
64736
|
+
if (!leftResult || !rightResult) return null;
|
|
64737
|
+
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
64738
|
+
const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
|
|
64739
|
+
return {
|
|
64740
|
+
kind: "boolean",
|
|
64741
|
+
value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
|
|
64742
|
+
};
|
|
64743
|
+
}
|
|
64744
|
+
if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
|
|
64745
|
+
const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
|
|
64746
|
+
return {
|
|
64747
|
+
kind: "boolean",
|
|
64748
|
+
value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
|
|
64749
|
+
};
|
|
64750
|
+
}
|
|
64751
|
+
}
|
|
64752
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64753
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
64754
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64755
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
|
|
64756
|
+
const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
64757
|
+
return argumentResult === null ? null : {
|
|
64758
|
+
kind: "boolean",
|
|
64759
|
+
value: argumentResult
|
|
64760
|
+
};
|
|
64761
|
+
}
|
|
64762
|
+
}
|
|
64763
|
+
return null;
|
|
64764
|
+
};
|
|
63882
64765
|
const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
63883
64766
|
const unwrappedExpression = stripParenExpression(expression);
|
|
63884
64767
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -63927,6 +64810,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
|
63927
64810
|
parameterValuesBySymbolId
|
|
63928
64811
|
});
|
|
63929
64812
|
}
|
|
64813
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64814
|
+
const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
|
|
64815
|
+
return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
|
|
64816
|
+
}
|
|
63930
64817
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
63931
64818
|
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
63932
64819
|
return argumentResult === null ? null : !argumentResult;
|
|
@@ -63987,13 +64874,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
|
|
|
63987
64874
|
const rightSymbol = scopes.symbolFor(right);
|
|
63988
64875
|
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
63989
64876
|
}
|
|
63990
|
-
|
|
63991
|
-
|
|
63992
|
-
|
|
63993
|
-
|
|
63994
|
-
|
|
63995
|
-
|
|
63996
|
-
|
|
64877
|
+
const rightEntries = new Map(Object.entries(right));
|
|
64878
|
+
for (const [key, leftValue] of Object.entries(left)) {
|
|
64879
|
+
if (key === "parent") continue;
|
|
64880
|
+
const rightValue = rightEntries.get(key);
|
|
64881
|
+
if (isAstNode(leftValue)) {
|
|
64882
|
+
if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
|
|
64883
|
+
continue;
|
|
64884
|
+
}
|
|
64885
|
+
if (!Array.isArray(leftValue)) continue;
|
|
64886
|
+
if (!Array.isArray(rightValue)) return false;
|
|
64887
|
+
const leftNodes = leftValue.filter(isAstNode);
|
|
64888
|
+
const rightNodes = rightValue.filter(isAstNode);
|
|
64889
|
+
if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
|
|
63997
64890
|
}
|
|
63998
64891
|
return true;
|
|
63999
64892
|
};
|
|
@@ -64007,6 +64900,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
|
64007
64900
|
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
64008
64901
|
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
64009
64902
|
};
|
|
64903
|
+
const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
64904
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64905
|
+
if (matchBrowserPredicate(unwrappedExpression, context)) return true;
|
|
64906
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
|
|
64907
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
|
|
64908
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64909
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64910
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
|
|
64911
|
+
visitedSymbolIds.add(symbol.id);
|
|
64912
|
+
const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
|
|
64913
|
+
const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
|
|
64914
|
+
visitedSymbolIds.delete(symbol.id);
|
|
64915
|
+
return isReflexive;
|
|
64916
|
+
}
|
|
64917
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
|
|
64918
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
|
|
64919
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
|
|
64920
|
+
if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
|
|
64921
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
64922
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64923
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
|
|
64924
|
+
};
|
|
64925
|
+
const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
|
|
64926
|
+
if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
|
|
64927
|
+
if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
|
|
64928
|
+
if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
|
|
64929
|
+
if (isNodeOfType(node, "TryStatement")) return [
|
|
64930
|
+
...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
|
|
64931
|
+
...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
|
|
64932
|
+
...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
|
|
64933
|
+
];
|
|
64934
|
+
if (!isNodeOfType(node, "BlockStatement")) return [];
|
|
64935
|
+
const propertyValues = [];
|
|
64936
|
+
for (const childStatement of node.body) {
|
|
64937
|
+
propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
|
|
64938
|
+
if (statementAlwaysExits$1(childStatement)) break;
|
|
64939
|
+
}
|
|
64940
|
+
return propertyValues;
|
|
64941
|
+
};
|
|
64942
|
+
const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
|
|
64943
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
64944
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
64945
|
+
const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
|
|
64946
|
+
let match = null;
|
|
64947
|
+
for (const propertyValue of propertyValues) {
|
|
64948
|
+
match = matchHydrationConditionInternal(propertyValue, context, state);
|
|
64949
|
+
if (match) break;
|
|
64950
|
+
}
|
|
64951
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
64952
|
+
return match;
|
|
64953
|
+
};
|
|
64010
64954
|
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
64011
64955
|
const unwrappedExpression = stripParenExpression(expression);
|
|
64012
64956
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -64023,14 +64967,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64023
64967
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64024
64968
|
return match;
|
|
64025
64969
|
}
|
|
64970
|
+
if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64971
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64972
|
+
if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
|
|
64973
|
+
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64974
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64975
|
+
return match;
|
|
64976
|
+
}
|
|
64977
|
+
for (const reference of symbol.references) {
|
|
64978
|
+
if (reference.flag === "read") continue;
|
|
64979
|
+
if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
|
|
64980
|
+
const enclosingFunction = findEnclosingFunction$1(reference.identifier);
|
|
64981
|
+
if (!enclosingFunction) continue;
|
|
64982
|
+
for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
|
|
64983
|
+
if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
64984
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
64985
|
+
if (match) {
|
|
64986
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64987
|
+
return match;
|
|
64988
|
+
}
|
|
64989
|
+
}
|
|
64990
|
+
}
|
|
64991
|
+
const readingFunction = findEnclosingFunction$1(unwrappedExpression);
|
|
64992
|
+
for (const reference of symbol.references) {
|
|
64993
|
+
if (reference.flag === "read") continue;
|
|
64994
|
+
const writingFunction = findEnclosingFunction$1(reference.identifier);
|
|
64995
|
+
if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
|
|
64996
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64997
|
+
if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
|
|
64998
|
+
const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
|
|
64999
|
+
if (!isNodeOfType(functionBinding, "Identifier")) continue;
|
|
65000
|
+
const functionSymbol = context.scopes.symbolFor(functionBinding);
|
|
65001
|
+
if (!functionSymbol) continue;
|
|
65002
|
+
for (const functionReference of functionSymbol.references) {
|
|
65003
|
+
const callExpression = functionReference.identifier.parent;
|
|
65004
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
|
|
65005
|
+
for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
|
|
65006
|
+
if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
65007
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
65008
|
+
if (match) {
|
|
65009
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65010
|
+
return match;
|
|
65011
|
+
}
|
|
65012
|
+
}
|
|
65013
|
+
}
|
|
65014
|
+
}
|
|
65015
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65016
|
+
}
|
|
64026
65017
|
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
64027
65018
|
state.visitedSymbolIds.add(symbol.id);
|
|
64028
65019
|
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64029
65020
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64030
65021
|
return match;
|
|
64031
65022
|
}
|
|
65023
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
|
|
65024
|
+
const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
|
|
65025
|
+
allowConstNumericLiteral: true,
|
|
65026
|
+
stringifyNonStringLiterals: true
|
|
65027
|
+
});
|
|
65028
|
+
const object = stripParenExpression(unwrappedExpression.object);
|
|
65029
|
+
if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
|
|
65030
|
+
const callArguments = object.arguments ?? [];
|
|
65031
|
+
if (isReactApiCall(object, "useMemo", context.scopes, {
|
|
65032
|
+
allowGlobalReactNamespace: true,
|
|
65033
|
+
resolveNamedAliases: true
|
|
65034
|
+
})) {
|
|
65035
|
+
const callbackArgument = callArguments[0];
|
|
65036
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
65037
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
65038
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
|
|
65039
|
+
}
|
|
65040
|
+
const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
|
|
65041
|
+
return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
|
|
65042
|
+
}
|
|
64032
65043
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64033
65044
|
const callArguments = unwrappedExpression.arguments ?? [];
|
|
65045
|
+
if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
|
|
65046
|
+
allowGlobalReactNamespace: true,
|
|
65047
|
+
resolveNamedAliases: true
|
|
65048
|
+
})) {
|
|
65049
|
+
const initialState = callArguments[0];
|
|
65050
|
+
if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
|
|
65051
|
+
const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
|
|
65052
|
+
return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
|
|
65053
|
+
}
|
|
64034
65054
|
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
64035
65055
|
allowGlobalReactNamespace: true,
|
|
64036
65056
|
resolveNamedAliases: true
|
|
@@ -64058,6 +65078,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64058
65078
|
});
|
|
64059
65079
|
}
|
|
64060
65080
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
65081
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
65082
|
+
const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
|
|
65083
|
+
if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
|
|
65084
|
+
return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
|
|
65085
|
+
}
|
|
65086
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
65087
|
+
if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
|
|
65088
|
+
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
65089
|
+
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
65090
|
+
const nestedMatch = leftMatch ?? rightMatch;
|
|
65091
|
+
if (!nestedMatch) return null;
|
|
65092
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
|
|
65093
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
|
|
65094
|
+
if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
|
|
65095
|
+
return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
|
|
65096
|
+
}
|
|
64061
65097
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
64062
65098
|
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
64063
65099
|
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
@@ -64074,8 +65110,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
|
|
|
64074
65110
|
const consequentValues = getReturnedValues(statement.consequent);
|
|
64075
65111
|
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
64076
65112
|
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
65113
|
+
if (conditionMatch) {
|
|
65114
|
+
const followingReturnedValues = findFollowingReturnedValues(statement);
|
|
65115
|
+
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;
|
|
65116
|
+
}
|
|
64077
65117
|
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
64078
65118
|
}
|
|
65119
|
+
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);
|
|
64079
65120
|
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
64080
65121
|
for (const childStatement of statement.body) {
|
|
64081
65122
|
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
@@ -64096,47 +65137,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
|
|
|
64096
65137
|
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
64097
65138
|
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
64098
65139
|
});
|
|
64099
|
-
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
64100
|
-
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
65140
|
+
const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
|
|
65141
|
+
const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
|
|
64101
65142
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
64102
65143
|
const left = stripParenExpression(leftNode);
|
|
64103
65144
|
const right = stripParenExpression(rightNode);
|
|
64104
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65145
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64105
65146
|
if (left.type !== right.type) return false;
|
|
64106
65147
|
if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
|
|
64107
65148
|
if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
|
|
64108
65149
|
if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
|
|
64109
|
-
return areRenderedBranchesEquivalent(left.expression, right.expression);
|
|
65150
|
+
return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
|
|
64110
65151
|
}
|
|
64111
65152
|
if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
|
|
64112
65153
|
if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
|
|
64113
|
-
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
|
|
64114
|
-
return areNodeArraysEquivalent(left.children, right.children);
|
|
65154
|
+
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
|
|
65155
|
+
return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64115
65156
|
}
|
|
64116
|
-
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
|
|
65157
|
+
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64117
65158
|
if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
|
|
64118
65159
|
if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
|
|
64119
|
-
return areRenderedBranchesEquivalent(left.value, right.value);
|
|
65160
|
+
return areRenderedBranchesEquivalent(left.value, right.value, scopes);
|
|
64120
65161
|
}
|
|
64121
|
-
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
|
|
65162
|
+
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
|
|
64122
65163
|
if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
|
|
64123
65164
|
if (left.quasis.length !== right.quasis.length) return false;
|
|
64124
65165
|
if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
|
|
64125
|
-
return areNodeArraysEquivalent(left.expressions, right.expressions);
|
|
65166
|
+
return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
|
|
64126
65167
|
}
|
|
64127
65168
|
return false;
|
|
64128
65169
|
};
|
|
64129
|
-
const
|
|
65170
|
+
const isProvenReactCreateElementCall = (node, scopes) => {
|
|
65171
|
+
if (isReactApiCall(node, "createElement", scopes, {
|
|
65172
|
+
allowGlobalReactNamespace: true,
|
|
65173
|
+
resolveNamedAliases: true
|
|
65174
|
+
})) return true;
|
|
65175
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
65176
|
+
const callee = stripParenExpression(node.callee);
|
|
65177
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
|
|
65178
|
+
const receiver = stripParenExpression(callee.object);
|
|
65179
|
+
const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
|
|
65180
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
65181
|
+
const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
|
|
65182
|
+
return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
|
|
65183
|
+
};
|
|
65184
|
+
const isRenderedValue = (node, scopes) => {
|
|
64130
65185
|
const unwrappedNode = stripParenExpression(node);
|
|
64131
65186
|
if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
|
|
64132
65187
|
if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
|
|
65188
|
+
if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
|
|
64133
65189
|
return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
|
|
64134
65190
|
};
|
|
64135
|
-
const findRenderedValueInAndBranch = (node) => {
|
|
65191
|
+
const findRenderedValueInAndBranch = (node, scopes) => {
|
|
64136
65192
|
const unwrappedNode = stripParenExpression(node);
|
|
64137
|
-
if (
|
|
65193
|
+
if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
|
|
64138
65194
|
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
|
|
64139
|
-
return findRenderedValueInAndBranch(unwrappedNode.right);
|
|
65195
|
+
return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
|
|
64140
65196
|
};
|
|
64141
65197
|
const findEnclosingJsxAttribute = (node) => {
|
|
64142
65198
|
let currentNode = node.parent;
|
|
@@ -64169,6 +65225,11 @@ const getReturnedValues = (statement) => {
|
|
|
64169
65225
|
if (!statement) return [];
|
|
64170
65226
|
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
|
|
64171
65227
|
if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
|
|
65228
|
+
if (isNodeOfType(statement, "TryStatement")) return [
|
|
65229
|
+
...getReturnedValues(statement.block),
|
|
65230
|
+
...getReturnedValues(statement.handler?.body),
|
|
65231
|
+
...getReturnedValues(statement.finalizer)
|
|
65232
|
+
];
|
|
64172
65233
|
if (!isNodeOfType(statement, "BlockStatement")) return [];
|
|
64173
65234
|
const returnedValues = [];
|
|
64174
65235
|
for (const childStatement of statement.body) {
|
|
@@ -64177,6 +65238,127 @@ const getReturnedValues = (statement) => {
|
|
|
64177
65238
|
}
|
|
64178
65239
|
return returnedValues;
|
|
64179
65240
|
};
|
|
65241
|
+
const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
|
|
65242
|
+
const unwrappedNode = stripParenExpression(node);
|
|
65243
|
+
if (isRenderedValue(unwrappedNode, scopes)) return true;
|
|
65244
|
+
if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
|
|
65245
|
+
if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
|
|
65246
|
+
if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
|
|
65247
|
+
const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
|
|
65248
|
+
if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
|
|
65249
|
+
visitedFunctionNodes.add(calledFunction);
|
|
65250
|
+
const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
|
|
65251
|
+
const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
|
|
65252
|
+
visitedFunctionNodes.delete(calledFunction);
|
|
65253
|
+
return isPotentiallyRendered;
|
|
65254
|
+
};
|
|
65255
|
+
const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
|
|
65256
|
+
const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
|
|
65257
|
+
let currentNode = node.parent;
|
|
65258
|
+
while (currentNode && currentNode !== componentOrHookNode) {
|
|
65259
|
+
if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
|
|
65260
|
+
allowGlobalReactNamespace: true,
|
|
65261
|
+
resolveNamedAliases: true
|
|
65262
|
+
}) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
|
|
65263
|
+
const stateBinding = currentNode.parent.id.elements?.[0];
|
|
65264
|
+
return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
|
|
65265
|
+
}
|
|
65266
|
+
if (isFunctionLike$1(currentNode)) return null;
|
|
65267
|
+
currentNode = currentNode.parent;
|
|
65268
|
+
}
|
|
65269
|
+
return null;
|
|
65270
|
+
};
|
|
65271
|
+
const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
|
|
65272
|
+
let currentNode = referenceIdentifier;
|
|
65273
|
+
let parentNode = currentNode.parent;
|
|
65274
|
+
while (parentNode) {
|
|
65275
|
+
if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
|
|
65276
|
+
if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
|
|
65277
|
+
if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
|
|
65278
|
+
currentNode = parentNode;
|
|
65279
|
+
parentNode = currentNode.parent;
|
|
65280
|
+
}
|
|
65281
|
+
return false;
|
|
65282
|
+
};
|
|
65283
|
+
const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
|
|
65284
|
+
const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
|
|
65285
|
+
return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
|
|
65286
|
+
};
|
|
65287
|
+
const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
|
|
65288
|
+
if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
|
|
65289
|
+
const parentNode = node.parent;
|
|
65290
|
+
if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
|
|
65291
|
+
const aliasSymbol = scopes.symbolFor(parentNode.id);
|
|
65292
|
+
if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
|
|
65293
|
+
visitedSymbolIds.add(aliasSymbol.id);
|
|
65294
|
+
const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
|
|
65295
|
+
visitedSymbolIds.delete(aliasSymbol.id);
|
|
65296
|
+
return doesReachRenderedOutput;
|
|
65297
|
+
};
|
|
65298
|
+
const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
|
|
65299
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
|
|
65300
|
+
const consumerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
65301
|
+
if (!consumerSymbol) return false;
|
|
65302
|
+
return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
|
|
65303
|
+
};
|
|
65304
|
+
const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
|
|
65305
|
+
const unwrappedValue = stripParenExpression(returnedValue);
|
|
65306
|
+
if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
|
|
65307
|
+
if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
|
|
65308
|
+
const propertyName = getResolvedStaticPropertyName(property, scopes);
|
|
65309
|
+
return propertyName === null ? [] : [{
|
|
65310
|
+
kind: "property",
|
|
65311
|
+
key: propertyName
|
|
65312
|
+
}];
|
|
65313
|
+
});
|
|
65314
|
+
if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
|
|
65315
|
+
kind: "index",
|
|
65316
|
+
key: String(index)
|
|
65317
|
+
}] : []);
|
|
65318
|
+
return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
|
|
65319
|
+
kind: "direct",
|
|
65320
|
+
key: null
|
|
65321
|
+
}] : [];
|
|
65322
|
+
};
|
|
65323
|
+
const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
|
|
65324
|
+
const callParent = callExpression.parent;
|
|
65325
|
+
if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65326
|
+
if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
|
|
65327
|
+
allowConstNumericLiteral: true,
|
|
65328
|
+
stringifyNonStringLiterals: true
|
|
65329
|
+
}) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65330
|
+
if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
|
|
65331
|
+
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));
|
|
65332
|
+
if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
|
|
65333
|
+
const element = callParent.id.elements?.[Number(returnedStatePath.key)];
|
|
65334
|
+
return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
|
|
65335
|
+
}
|
|
65336
|
+
if (!isNodeOfType(callParent.id, "Identifier")) return false;
|
|
65337
|
+
const resultSymbol = scopes.symbolFor(callParent.id);
|
|
65338
|
+
if (!resultSymbol) return false;
|
|
65339
|
+
return resultSymbol.references.some((reference) => {
|
|
65340
|
+
const memberExpression = reference.identifier.parent;
|
|
65341
|
+
return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
|
|
65342
|
+
allowConstNumericLiteral: true,
|
|
65343
|
+
stringifyNonStringLiterals: true
|
|
65344
|
+
}) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
|
|
65345
|
+
});
|
|
65346
|
+
};
|
|
65347
|
+
const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
|
|
65348
|
+
if (!isFunctionLike$1(componentOrHookNode)) return false;
|
|
65349
|
+
const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
|
|
65350
|
+
if (!stateSymbol) return false;
|
|
65351
|
+
const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
|
|
65352
|
+
if (returnedStatePaths.length === 0) return false;
|
|
65353
|
+
const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
|
|
65354
|
+
if (!isNodeOfType(functionBinding, "Identifier")) return false;
|
|
65355
|
+
const functionSymbol = scopes.symbolFor(functionBinding);
|
|
65356
|
+
if (!functionSymbol) return false;
|
|
65357
|
+
return functionSymbol.references.some((functionReference) => {
|
|
65358
|
+
const callExpression = functionReference.identifier.parent;
|
|
65359
|
+
return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
|
|
65360
|
+
});
|
|
65361
|
+
};
|
|
64180
65362
|
const findFollowingReturnedValues = (ifStatement) => {
|
|
64181
65363
|
const parentNode = ifStatement.parent;
|
|
64182
65364
|
if (!isNodeOfType(parentNode, "BlockStatement")) return [];
|
|
@@ -64189,24 +65371,24 @@ const findFollowingReturnedValues = (ifStatement) => {
|
|
|
64189
65371
|
}
|
|
64190
65372
|
return returnedValues;
|
|
64191
65373
|
};
|
|
64192
|
-
const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
|
|
65374
|
+
const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
|
|
64193
65375
|
const left = stripParenExpression(leftExpression);
|
|
64194
65376
|
const right = stripParenExpression(rightExpression);
|
|
64195
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65377
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64196
65378
|
if (left.type !== right.type) return false;
|
|
64197
|
-
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
|
|
64198
|
-
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
64199
|
-
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
|
|
65379
|
+
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
|
|
65380
|
+
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
65381
|
+
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
64200
65382
|
return false;
|
|
64201
65383
|
};
|
|
64202
|
-
const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
|
|
65384
|
+
const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
|
|
64203
65385
|
if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
|
|
64204
|
-
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
|
|
64205
|
-
if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
|
|
65386
|
+
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
|
|
65387
|
+
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);
|
|
64206
65388
|
if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
|
|
64207
65389
|
const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64208
65390
|
const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64209
|
-
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
|
|
65391
|
+
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
|
|
64210
65392
|
};
|
|
64211
65393
|
const isStructuralRenderedValue = (node) => {
|
|
64212
65394
|
if (!node) return false;
|
|
@@ -64230,19 +65412,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64230
65412
|
if (isTestlikeFilename(context.filename)) return {};
|
|
64231
65413
|
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
64232
65414
|
let fileHasUseClientDirective = false;
|
|
65415
|
+
let fileHasExplicitReactRuntimeReference = false;
|
|
64233
65416
|
let fileIsEmailTemplate = false;
|
|
64234
65417
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
64235
|
-
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
|
|
65418
|
+
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
|
|
64236
65419
|
const conditionMatch = matchHydrationCondition(conditionNode, context);
|
|
64237
65420
|
if (!conditionMatch) return;
|
|
64238
65421
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
64239
65422
|
if (reportedNodes.has(predicateNode)) return;
|
|
64240
|
-
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
64241
|
-
const
|
|
65423
|
+
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
|
|
65424
|
+
const enclosingFunction = findEnclosingFunction$1(conditionNode);
|
|
65425
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
64242
65426
|
if (!componentOrHookNode) return;
|
|
64243
|
-
|
|
64244
|
-
if (
|
|
64245
|
-
if (!
|
|
65427
|
+
const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
|
|
65428
|
+
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
|
|
65429
|
+
if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
|
|
65430
|
+
if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
|
|
64246
65431
|
const attribute = findEnclosingJsxAttribute(conditionNode);
|
|
64247
65432
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
64248
65433
|
}
|
|
@@ -64261,28 +65446,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64261
65446
|
return {
|
|
64262
65447
|
Program(node) {
|
|
64263
65448
|
fileHasUseClientDirective = hasDirective(node, "use client");
|
|
65449
|
+
fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
|
|
64264
65450
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
64265
65451
|
},
|
|
64266
65452
|
ConditionalExpression(node) {
|
|
64267
65453
|
reportHydrationBranch(node.test, node.consequent, node.alternate, true);
|
|
65454
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
65455
|
+
if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
|
|
64268
65456
|
},
|
|
64269
65457
|
LogicalExpression(node) {
|
|
64270
65458
|
if (node.operator !== "&&" && node.operator !== "||") return;
|
|
64271
|
-
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) :
|
|
65459
|
+
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
|
|
64272
65460
|
if (!renderedValue) return;
|
|
64273
65461
|
reportHydrationBranch(node, renderedValue, null, true);
|
|
64274
65462
|
},
|
|
64275
65463
|
IfStatement(node) {
|
|
64276
|
-
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
|
|
65464
|
+
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
|
|
64277
65465
|
const consequentValues = getReturnedValues(node.consequent);
|
|
64278
65466
|
const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
|
|
64279
65467
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
64280
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
64281
|
-
if (!componentOrHookNode) return;
|
|
64282
65468
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
64283
|
-
|
|
65469
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
65470
|
+
if (!componentOrHookNode) return;
|
|
65471
|
+
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
|
|
64284
65472
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
64285
|
-
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
65473
|
+
if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
|
|
64286
65474
|
reportHydrationBranch(node.test, consequentValue, alternateValue, false);
|
|
64287
65475
|
}
|
|
64288
65476
|
}
|
|
@@ -73823,6 +75011,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
73823
75011
|
}
|
|
73824
75012
|
return !isNegated && predicateFunction.body === child;
|
|
73825
75013
|
};
|
|
75014
|
+
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75015
|
+
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75016
|
+
const returnStatement = predicateFunction.body.body[1];
|
|
75017
|
+
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75018
|
+
let negationCount = 0;
|
|
75019
|
+
let expression = matchCall;
|
|
75020
|
+
let parent = expression.parent ?? null;
|
|
75021
|
+
while (parent && parent !== returnStatement) {
|
|
75022
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75023
|
+
negationCount += 1;
|
|
75024
|
+
expression = parent;
|
|
75025
|
+
parent = parent.parent ?? null;
|
|
75026
|
+
continue;
|
|
75027
|
+
}
|
|
75028
|
+
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75029
|
+
expression = parent;
|
|
75030
|
+
parent = parent.parent ?? null;
|
|
75031
|
+
continue;
|
|
75032
|
+
}
|
|
75033
|
+
return false;
|
|
75034
|
+
}
|
|
75035
|
+
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75036
|
+
};
|
|
73826
75037
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
73827
75038
|
const target = stripParenExpression(test);
|
|
73828
75039
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -73863,6 +75074,26 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
73863
75074
|
current = current.object;
|
|
73864
75075
|
}
|
|
73865
75076
|
};
|
|
75077
|
+
const getNormalizedClassNameRoot = (expression) => {
|
|
75078
|
+
const conditional = stripParenExpression(expression);
|
|
75079
|
+
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75080
|
+
const consequent = stripParenExpression(conditional.consequent);
|
|
75081
|
+
const rootIdentifier = getRootIdentifier(consequent);
|
|
75082
|
+
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75083
|
+
const test = stripParenExpression(conditional.test);
|
|
75084
|
+
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75085
|
+
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75086
|
+
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75087
|
+
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75088
|
+
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75089
|
+
const alternate = stripParenExpression(conditional.alternate);
|
|
75090
|
+
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75091
|
+
const fallback = stripParenExpression(alternate.right);
|
|
75092
|
+
const attributeCall = stripParenExpression(alternate.left);
|
|
75093
|
+
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;
|
|
75094
|
+
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75095
|
+
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75096
|
+
};
|
|
73866
75097
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
73867
75098
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
73868
75099
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -73871,11 +75102,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73871
75102
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
73872
75103
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
73873
75104
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
73874
|
-
if (resultSymbol?.kind !== "const" || !initializer
|
|
75105
|
+
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75106
|
+
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75107
|
+
const alternate = stripParenExpression(initializer.alternate);
|
|
75108
|
+
const consequent = stripParenExpression(initializer.consequent);
|
|
75109
|
+
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75110
|
+
})() : null;
|
|
75111
|
+
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
73875
75112
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
73876
|
-
const finderCallee = stripParenExpression(
|
|
75113
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
73877
75114
|
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;
|
|
73878
|
-
const predicateArgument =
|
|
75115
|
+
const predicateArgument = finderCall.arguments[1];
|
|
73879
75116
|
if (!predicateArgument) return false;
|
|
73880
75117
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
73881
75118
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -73899,6 +75136,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73899
75136
|
});
|
|
73900
75137
|
return didProveMatch;
|
|
73901
75138
|
};
|
|
75139
|
+
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75140
|
+
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75141
|
+
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75142
|
+
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75143
|
+
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75144
|
+
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75145
|
+
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75146
|
+
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75147
|
+
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75148
|
+
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75149
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75150
|
+
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75151
|
+
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75152
|
+
const expression = stripParenExpression(test);
|
|
75153
|
+
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75154
|
+
})) return false;
|
|
75155
|
+
const predicateArgument = finderCall.arguments[1];
|
|
75156
|
+
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75157
|
+
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75158
|
+
const predicateParameter = predicateFunction.params[0];
|
|
75159
|
+
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75160
|
+
let didProveNormalizedMatch = false;
|
|
75161
|
+
walkAst(predicateFunction.body, (child) => {
|
|
75162
|
+
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75163
|
+
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;
|
|
75164
|
+
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75165
|
+
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75166
|
+
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75167
|
+
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75168
|
+
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75169
|
+
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75170
|
+
didProveNormalizedMatch = true;
|
|
75171
|
+
return false;
|
|
75172
|
+
}
|
|
75173
|
+
});
|
|
75174
|
+
return didProveNormalizedMatch;
|
|
75175
|
+
};
|
|
73902
75176
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
73903
75177
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
73904
75178
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -73999,6 +75273,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
73999
75273
|
}
|
|
74000
75274
|
return false;
|
|
74001
75275
|
};
|
|
75276
|
+
const getReceiverRootIdentifier = (node) => {
|
|
75277
|
+
let target = stripParenExpression(node);
|
|
75278
|
+
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75279
|
+
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75280
|
+
};
|
|
75281
|
+
const getReceiverStatePath = (node) => {
|
|
75282
|
+
const target = stripParenExpression(node);
|
|
75283
|
+
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75284
|
+
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75285
|
+
const objectPath = getReceiverStatePath(target.object);
|
|
75286
|
+
if (!objectPath) return null;
|
|
75287
|
+
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75288
|
+
};
|
|
75289
|
+
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75290
|
+
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75291
|
+
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75292
|
+
const receiverPath = getReceiverStatePath(receiver);
|
|
75293
|
+
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75294
|
+
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75295
|
+
let didAddAlias = true;
|
|
75296
|
+
while (didAddAlias) {
|
|
75297
|
+
didAddAlias = false;
|
|
75298
|
+
walkAst(ownerFunction, (child) => {
|
|
75299
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75300
|
+
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75301
|
+
const initializer = stripParenExpression(child.init);
|
|
75302
|
+
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75303
|
+
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75304
|
+
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75305
|
+
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75306
|
+
const initializerPath = getReceiverStatePath(initializer);
|
|
75307
|
+
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75308
|
+
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75309
|
+
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75310
|
+
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75311
|
+
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75312
|
+
didAddAlias = true;
|
|
75313
|
+
}
|
|
75314
|
+
});
|
|
75315
|
+
}
|
|
75316
|
+
let didChangeReceiverState = false;
|
|
75317
|
+
walkAst(ownerFunction, (child) => {
|
|
75318
|
+
if (didChangeReceiverState) return false;
|
|
75319
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75320
|
+
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75321
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
75322
|
+
didChangeReceiverState = true;
|
|
75323
|
+
return false;
|
|
75324
|
+
}
|
|
75325
|
+
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75326
|
+
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75327
|
+
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75328
|
+
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75329
|
+
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75330
|
+
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75331
|
+
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75332
|
+
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75333
|
+
didChangeReceiverState = true;
|
|
75334
|
+
return false;
|
|
75335
|
+
}
|
|
75336
|
+
});
|
|
75337
|
+
return didChangeReceiverState;
|
|
75338
|
+
};
|
|
75339
|
+
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75340
|
+
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75341
|
+
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75342
|
+
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75343
|
+
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75344
|
+
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75345
|
+
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;
|
|
75346
|
+
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75347
|
+
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75348
|
+
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75349
|
+
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75350
|
+
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75351
|
+
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75352
|
+
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75353
|
+
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75354
|
+
const accumulatorParameter = reducerFunction.params[0];
|
|
75355
|
+
const itemParameter = reducerFunction.params[1];
|
|
75356
|
+
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75357
|
+
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75358
|
+
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75359
|
+
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75360
|
+
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75361
|
+
const expression = stripParenExpression(argument);
|
|
75362
|
+
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75363
|
+
});
|
|
75364
|
+
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75365
|
+
const expression = stripParenExpression(argument);
|
|
75366
|
+
const rootIdentifier = getRootIdentifier(expression);
|
|
75367
|
+
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75368
|
+
});
|
|
75369
|
+
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75370
|
+
if (!receiverPathKey(findReceiver)) return false;
|
|
75371
|
+
let ownerFunction = assertion.parent ?? null;
|
|
75372
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75373
|
+
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75374
|
+
const maximumEnd = maximumInitializer.range[1];
|
|
75375
|
+
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75376
|
+
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75377
|
+
const comparison = stripParenExpression(test);
|
|
75378
|
+
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75379
|
+
return [[
|
|
75380
|
+
comparison.left,
|
|
75381
|
+
comparison.right,
|
|
75382
|
+
comparison.operator
|
|
75383
|
+
], [
|
|
75384
|
+
comparison.right,
|
|
75385
|
+
comparison.left,
|
|
75386
|
+
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75387
|
+
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75388
|
+
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75389
|
+
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75390
|
+
});
|
|
75391
|
+
});
|
|
75392
|
+
};
|
|
74002
75393
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
74003
75394
|
if (!value) return false;
|
|
74004
75395
|
const expression = stripParenExpression(value);
|
|
@@ -74022,15 +75413,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
74022
75413
|
};
|
|
74023
75414
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
74024
75415
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
74025
|
-
|
|
75416
|
+
const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
|
|
75417
|
+
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75418
|
+
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
|
|
74026
75419
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
74027
75420
|
if (!receiverSymbol) return false;
|
|
74028
75421
|
const receiverMatches = (candidate) => {
|
|
74029
75422
|
const target = stripParenExpression(candidate);
|
|
74030
75423
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
74031
75424
|
};
|
|
74032
|
-
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74033
|
-
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
74034
75425
|
let child = assertion;
|
|
74035
75426
|
let ancestor = assertion.parent ?? null;
|
|
74036
75427
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -74050,7 +75441,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
74050
75441
|
const populationCall = populationCalls[0];
|
|
74051
75442
|
if (!populationCall) continue;
|
|
74052
75443
|
const populationCallStart = populationCall.range[0];
|
|
74053
|
-
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
|
|
75444
|
+
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;
|
|
74054
75445
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
74055
75446
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
74056
75447
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -74160,6 +75551,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74160
75551
|
const findReceiver = callee.object;
|
|
74161
75552
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
74162
75553
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
75554
|
+
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
74163
75555
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
74164
75556
|
}
|
|
74165
75557
|
if (methodName === "match") {
|
|
@@ -74169,6 +75561,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74169
75561
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
74170
75562
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
74171
75563
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
75564
|
+
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
74172
75565
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
74173
75566
|
}
|
|
74174
75567
|
if (methodName === "get") {
|
|
@@ -88308,6 +89701,11 @@ const noUndersizedIconButton = defineRule({
|
|
|
88308
89701
|
//#region src/plugin/rules/correctness/no-unescaped-dynamic-string-in-regexp.ts
|
|
88309
89702
|
const TEST_CONTEXT_FILE_PATTERN = /(\.test\.|\.spec\.|__tests__|(^|\/)(test|tests|e2e|cypress|playwright)\/)/;
|
|
88310
89703
|
const SEARCH_TERM_NAME_PATTERN = /search|query|highlight|filter|term(?!in)|keyword/i;
|
|
89704
|
+
const REGEX_SOURCE_WORDS = [
|
|
89705
|
+
"pattern",
|
|
89706
|
+
"regex",
|
|
89707
|
+
"regexp"
|
|
89708
|
+
];
|
|
88311
89709
|
const ESCAPE_HELPER_NAME_PATTERN = /escape.*reg|safe.*reg/i;
|
|
88312
89710
|
const SANITIZED_NAME_PATTERN = /escap|sanitiz/i;
|
|
88313
89711
|
const INITIALIZER_RESOLUTION_HOPS = 2;
|
|
@@ -88461,14 +89859,70 @@ const isTypePositionIdentifier = (identifier) => {
|
|
|
88461
89859
|
}
|
|
88462
89860
|
return false;
|
|
88463
89861
|
};
|
|
88464
|
-
const
|
|
88465
|
-
const
|
|
89862
|
+
const identifierNameHasPathSegmentSemantics = (identifierName) => {
|
|
89863
|
+
const identifierWords = identifierName.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_]+/).map((word) => word.toLowerCase());
|
|
89864
|
+
if (identifierWords.some((word) => REGEX_SOURCE_WORDS.includes(word))) return false;
|
|
89865
|
+
return identifierWords.some((word) => [
|
|
89866
|
+
"path",
|
|
89867
|
+
"folder",
|
|
89868
|
+
"directory",
|
|
89869
|
+
"root"
|
|
89870
|
+
].includes(word)) || identifierWords.includes("top") && identifierWords.includes("level");
|
|
89871
|
+
};
|
|
89872
|
+
const flattenPatternParts = (expression) => {
|
|
89873
|
+
const inner = stripParenExpression(expression);
|
|
89874
|
+
const staticValue = literalStringValue(inner);
|
|
89875
|
+
if (staticValue !== null) return [staticValue];
|
|
89876
|
+
if (isNodeOfType(inner, "Identifier")) return [inner];
|
|
89877
|
+
if (isNodeOfType(inner, "TemplateLiteral")) {
|
|
89878
|
+
const parts = [];
|
|
89879
|
+
for (let index = 0; index < inner.quasis.length; index += 1) {
|
|
89880
|
+
const quasi = inner.quasis[index];
|
|
89881
|
+
if (quasi) parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
89882
|
+
const templateExpression = inner.expressions[index];
|
|
89883
|
+
if (templateExpression) parts.push(stripParenExpression(templateExpression));
|
|
89884
|
+
}
|
|
89885
|
+
return parts;
|
|
89886
|
+
}
|
|
89887
|
+
if (isNodeOfType(inner, "BinaryExpression") && inner.operator === "+") {
|
|
89888
|
+
const leftParts = flattenPatternParts(inner.left);
|
|
89889
|
+
const rightParts = flattenPatternParts(inner.right);
|
|
89890
|
+
return leftParts && rightParts ? [...leftParts, ...rightParts] : null;
|
|
89891
|
+
}
|
|
89892
|
+
if (isNodeOfType(inner, "CallExpression") && isNodeOfType(inner.callee, "MemberExpression") && getStaticPropertyName(inner.callee) === "concat") {
|
|
89893
|
+
const receiverParts = flattenPatternParts(inner.callee.object);
|
|
89894
|
+
if (!receiverParts) return null;
|
|
89895
|
+
const parts = [...receiverParts];
|
|
89896
|
+
for (const argument of inner.arguments) {
|
|
89897
|
+
if (isNodeOfType(argument, "SpreadElement")) return null;
|
|
89898
|
+
const argumentParts = flattenPatternParts(argument);
|
|
89899
|
+
if (!argumentParts) return null;
|
|
89900
|
+
parts.push(...argumentParts);
|
|
89901
|
+
}
|
|
89902
|
+
return parts;
|
|
89903
|
+
}
|
|
89904
|
+
return null;
|
|
89905
|
+
};
|
|
89906
|
+
const isIdentifierAnAnchoredPathSegment = (argument, identifier) => {
|
|
89907
|
+
if (!identifierNameHasPathSegmentSemantics(identifier.name)) return false;
|
|
89908
|
+
const parts = flattenPatternParts(argument);
|
|
89909
|
+
if (!parts) return false;
|
|
89910
|
+
const identifierPartIndex = parts.findIndex((part) => part === identifier);
|
|
89911
|
+
if (identifierPartIndex < 0) return false;
|
|
89912
|
+
const precedingParts = parts.slice(0, identifierPartIndex);
|
|
89913
|
+
if (precedingParts.some((part) => typeof part !== "string")) return false;
|
|
89914
|
+
const staticPrefix = precedingParts.join("");
|
|
89915
|
+
const followingPart = parts[identifierPartIndex + 1];
|
|
89916
|
+
return staticPrefix === "^" && typeof followingPart === "string" && followingPart.startsWith("/");
|
|
89917
|
+
};
|
|
89918
|
+
const collectRawDynamicLiteralIdentifiers = (argument) => {
|
|
89919
|
+
const rawDynamicLiteralIdentifiers = [];
|
|
88466
89920
|
walkAst(argument, (child) => {
|
|
88467
89921
|
if (isEscapingCall(child) || isRegexSourceAccess(child)) return false;
|
|
88468
89922
|
if (isLiteralReturningGetterCall(child)) return false;
|
|
88469
|
-
if (isNodeOfType(child, "Identifier") && SEARCH_TERM_NAME_PATTERN.test(child.name) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child))
|
|
89923
|
+
if (isNodeOfType(child, "Identifier") && (SEARCH_TERM_NAME_PATTERN.test(child.name) || isIdentifierAnAnchoredPathSegment(argument, child)) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawDynamicLiteralIdentifiers.push(child);
|
|
88470
89924
|
});
|
|
88471
|
-
return
|
|
89925
|
+
return rawDynamicLiteralIdentifiers;
|
|
88472
89926
|
};
|
|
88473
89927
|
const collectLeafIdentifiers = (node) => {
|
|
88474
89928
|
const leafIdentifiers = [];
|
|
@@ -88479,8 +89933,11 @@ const collectLeafIdentifiers = (node) => {
|
|
|
88479
89933
|
return leafIdentifiers;
|
|
88480
89934
|
};
|
|
88481
89935
|
const compositeInitializerResolvesEscaped = (strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames) => {
|
|
89936
|
+
if (isNodeOfType(strippedInitializer, "ConditionalExpression")) return [strippedInitializer.consequent, strippedInitializer.alternate].every((branch) => initializerLooksEscaped(branch, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
89937
|
+
if (isNodeOfType(strippedInitializer, "BinaryExpression") || isNodeOfType(strippedInitializer, "LogicalExpression")) return [strippedInitializer.left, strippedInitializer.right].every((operand) => initializerLooksEscaped(operand, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
89938
|
+
if (isNodeOfType(strippedInitializer, "TemplateLiteral")) return strippedInitializer.expressions.every((expression) => initializerLooksEscaped(expression, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
88482
89939
|
let didResolveAnyLeafEscaped = false;
|
|
88483
|
-
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
89940
|
+
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
88484
89941
|
else if (SEARCH_TERM_NAME_PATTERN.test(leafIdentifier.name)) return false;
|
|
88485
89942
|
return didResolveAnyLeafEscaped;
|
|
88486
89943
|
};
|
|
@@ -88492,7 +89949,7 @@ const initializerLooksEscaped = (initializer, remainingHops, scopes, regexpObjec
|
|
|
88492
89949
|
if (isNodeOfType(strippedInitializer, "CallExpression") && (isEscapingCall(strippedInitializer) || calleeBindingBodyEscapes(strippedInitializer))) return true;
|
|
88493
89950
|
if (remainingHops > 0) {
|
|
88494
89951
|
if (isNodeOfType(strippedInitializer, "Identifier")) return identifierResolvesToEscapedValue(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88495
|
-
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops
|
|
89952
|
+
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88496
89953
|
}
|
|
88497
89954
|
return false;
|
|
88498
89955
|
};
|
|
@@ -88684,7 +90141,7 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88684
90141
|
severity: "warn",
|
|
88685
90142
|
category: "Correctness",
|
|
88686
90143
|
tags: ["test-noise"],
|
|
88687
|
-
recommendation: "A search
|
|
90144
|
+
recommendation: "A dynamic literal string such as a search term or path segment dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so values containing `.` or `(` over-match or throw. Escape the value with an `escapeRegExp` helper before constructing the pattern.",
|
|
88688
90145
|
create: (context) => {
|
|
88689
90146
|
if (TEST_CONTEXT_FILE_PATTERN.test(context.filename ?? "")) return {};
|
|
88690
90147
|
let regexpObjectIndex = null;
|
|
@@ -88701,10 +90158,10 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88701
90158
|
regexpObjectIndex = buildRegExpObjectIndex(programRoot, context.scopes);
|
|
88702
90159
|
}
|
|
88703
90160
|
const currentRegExpObjectIndex = regexpObjectIndex;
|
|
88704
|
-
if (!
|
|
90161
|
+
if (!collectRawDynamicLiteralIdentifiers(firstArgument).some((identifier) => !identifierResolvesToEscapedValue(identifier, INITIALIZER_RESOLUTION_HOPS, context.scopes, currentRegExpObjectIndex.regexpObjectSymbolIds, currentRegExpObjectIndex.globalRegExpObjectNames) && !isShapeTestedByDominatingGuard(node, identifier, context.scopes) && !isParameterFedOnlyMetacharacterFreeLiterals(identifier, context.scopes))) return;
|
|
88705
90162
|
context.report({
|
|
88706
90163
|
node,
|
|
88707
|
-
message: "This builds a `RegExp` from a dynamic
|
|
90164
|
+
message: "This builds a `RegExp` from a dynamic literal string without escaping it, so regex metacharacters in the value act as operators and over-match or throw. Escape the value with an `escapeRegExp` helper first."
|
|
88708
90165
|
});
|
|
88709
90166
|
};
|
|
88710
90167
|
return {
|
|
@@ -140049,6 +141506,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
140049
141506
|
//#region src/plugin/utils/capability.ts
|
|
140050
141507
|
const FRAMEWORK_TOKENS = [
|
|
140051
141508
|
"nextjs",
|
|
141509
|
+
"astro",
|
|
140052
141510
|
"vite",
|
|
140053
141511
|
"cra",
|
|
140054
141512
|
"remix",
|