oxlint-plugin-react-doctor 0.9.2-dev.3f3197f → 0.9.2-dev.5dc936e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2334 -251
- 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;
|
|
@@ -665,19 +665,6 @@ const TRIVIAL_INITIALIZER_NAMES = new Set([
|
|
|
665
665
|
"parseInt",
|
|
666
666
|
"parseFloat"
|
|
667
667
|
]);
|
|
668
|
-
const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
|
|
669
|
-
"Date",
|
|
670
|
-
"Map",
|
|
671
|
-
"Set",
|
|
672
|
-
"WeakMap",
|
|
673
|
-
"WeakSet",
|
|
674
|
-
"WeakRef",
|
|
675
|
-
"RegExp",
|
|
676
|
-
"Error",
|
|
677
|
-
"URL",
|
|
678
|
-
"URLSearchParams",
|
|
679
|
-
"AbortController"
|
|
680
|
-
]);
|
|
681
668
|
const SETTER_PATTERN = /^set[A-Z]/;
|
|
682
669
|
const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
|
|
683
670
|
const UPPERCASE_PATTERN = /^[A-Z]/;
|
|
@@ -5887,6 +5874,7 @@ const isInlineFunctionExpression = (node) => Boolean(node && (isNodeOfType(node,
|
|
|
5887
5874
|
//#endregion
|
|
5888
5875
|
//#region src/plugin/rules/js-performance/async-await-in-loop.ts
|
|
5889
5876
|
const LOOP_STATEMENT_TYPES$1 = new Set(LOOP_TYPES);
|
|
5877
|
+
const ORDERED_OUTPUT_INSERTION_METHOD_NAMES = new Set(["push", "unshift"]);
|
|
5890
5878
|
const findFirstAwaitOutsideNestedFunctions = (block, skipNestedLoops = false) => {
|
|
5891
5879
|
let firstAwait = null;
|
|
5892
5880
|
walkAst(block, (child) => {
|
|
@@ -5953,7 +5941,151 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
5953
5941
|
});
|
|
5954
5942
|
return isWaitLike;
|
|
5955
5943
|
};
|
|
5956
|
-
const
|
|
5944
|
+
const getRootObjectIdentifierName = (node) => {
|
|
5945
|
+
let current = node;
|
|
5946
|
+
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
5947
|
+
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
5948
|
+
};
|
|
5949
|
+
const isScopeWithinFunction = (candidateScope, functionScope) => {
|
|
5950
|
+
let currentScope = candidateScope;
|
|
5951
|
+
while (currentScope) {
|
|
5952
|
+
if (currentScope === functionScope) return true;
|
|
5953
|
+
currentScope = currentScope.parent;
|
|
5954
|
+
}
|
|
5955
|
+
return false;
|
|
5956
|
+
};
|
|
5957
|
+
const isSymbolDirectlyReturned = (symbol, callerFunction) => Boolean(callerFunction) && symbol.references.some((reference) => {
|
|
5958
|
+
const expressionRoot = findTransparentExpressionRoot(reference.identifier);
|
|
5959
|
+
const parent = expressionRoot.parent;
|
|
5960
|
+
return isNodeOfType(parent, "ReturnStatement") && parent.argument === expressionRoot && findEnclosingFunction$1(parent) === callerFunction;
|
|
5961
|
+
});
|
|
5962
|
+
const collectPatternBindingSymbolIds = (pattern, scopes, target) => {
|
|
5963
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
5964
|
+
const symbol = scopes.symbolFor(pattern);
|
|
5965
|
+
if (symbol) target.add(symbol.id);
|
|
5966
|
+
return;
|
|
5967
|
+
}
|
|
5968
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
5969
|
+
for (const property of pattern.properties ?? []) if (isNodeOfType(property, "Property") && property.value) collectPatternBindingSymbolIds(property.value, scopes, target);
|
|
5970
|
+
else if (isNodeOfType(property, "RestElement") && property.argument) collectPatternBindingSymbolIds(property.argument, scopes, target);
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
5974
|
+
for (const element of pattern.elements ?? []) if (element) collectPatternBindingSymbolIds(element, scopes, target);
|
|
5975
|
+
return;
|
|
5976
|
+
}
|
|
5977
|
+
if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternBindingSymbolIds(pattern.left, scopes, target);
|
|
5978
|
+
};
|
|
5979
|
+
const collectReferencedSymbolIds = (expression, scopes) => {
|
|
5980
|
+
const referencedSymbolIds = /* @__PURE__ */ new Set();
|
|
5981
|
+
walkAst(expression, (child) => {
|
|
5982
|
+
if (child !== expression && isFunctionLike$1(child)) return false;
|
|
5983
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
5984
|
+
const symbol = scopes.symbolFor(child);
|
|
5985
|
+
if (symbol) referencedSymbolIds.add(symbol.id);
|
|
5986
|
+
});
|
|
5987
|
+
return referencedSymbolIds;
|
|
5988
|
+
};
|
|
5989
|
+
const collectAwaitDerivedSymbolIds = (block, scopes) => {
|
|
5990
|
+
const awaitDerivedSymbolIds = /* @__PURE__ */ new Set();
|
|
5991
|
+
const bindingDependencies = [];
|
|
5992
|
+
walkAst(block, (child) => {
|
|
5993
|
+
if (child !== block && isFunctionLike$1(child)) return false;
|
|
5994
|
+
if (isNodeOfType(child, "VariableDeclarator") && child.id && child.init) {
|
|
5995
|
+
const declaredSymbolIds = /* @__PURE__ */ new Set();
|
|
5996
|
+
collectPatternBindingSymbolIds(child.id, scopes, declaredSymbolIds);
|
|
5997
|
+
if (containsDirectAwait(child.init)) for (const symbolId of declaredSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
5998
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.init, scopes);
|
|
5999
|
+
for (const declaredSymbolId of declaredSymbolIds) bindingDependencies.push({
|
|
6000
|
+
declaredSymbolId,
|
|
6001
|
+
referencedSymbolIds
|
|
6002
|
+
});
|
|
6003
|
+
return;
|
|
6004
|
+
}
|
|
6005
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.left) {
|
|
6006
|
+
const assignedSymbolIds = /* @__PURE__ */ new Set();
|
|
6007
|
+
collectPatternBindingSymbolIds(child.left, scopes, assignedSymbolIds);
|
|
6008
|
+
if (containsDirectAwait(child.right)) for (const symbolId of assignedSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6009
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.right, scopes);
|
|
6010
|
+
for (const assignedSymbolId of assignedSymbolIds) bindingDependencies.push({
|
|
6011
|
+
declaredSymbolId: assignedSymbolId,
|
|
6012
|
+
referencedSymbolIds
|
|
6013
|
+
});
|
|
6014
|
+
}
|
|
6015
|
+
});
|
|
6016
|
+
let didGrow = true;
|
|
6017
|
+
while (didGrow) {
|
|
6018
|
+
didGrow = false;
|
|
6019
|
+
for (const { declaredSymbolId, referencedSymbolIds } of bindingDependencies) {
|
|
6020
|
+
if (awaitDerivedSymbolIds.has(declaredSymbolId)) continue;
|
|
6021
|
+
for (const referencedSymbolId of referencedSymbolIds) {
|
|
6022
|
+
if (!awaitDerivedSymbolIds.has(referencedSymbolId)) continue;
|
|
6023
|
+
awaitDerivedSymbolIds.add(declaredSymbolId);
|
|
6024
|
+
didGrow = true;
|
|
6025
|
+
break;
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
return awaitDerivedSymbolIds;
|
|
6030
|
+
};
|
|
6031
|
+
const getSimpleParameterIdentifier = (parameter) => {
|
|
6032
|
+
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
6033
|
+
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
6034
|
+
return null;
|
|
6035
|
+
};
|
|
6036
|
+
const doesAwaitedLocalCallInsertAwaitDerivedOutput = (awaitNode, context) => {
|
|
6037
|
+
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
6038
|
+
const callExpression = awaitNode.argument;
|
|
6039
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
6040
|
+
const localFunction = resolveStaticLocalCallFunction(callExpression, context.scopes);
|
|
6041
|
+
if (!isFunctionLike$1(localFunction)) return false;
|
|
6042
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
6043
|
+
const functionScope = context.scopes.ownScopeFor(localFunction);
|
|
6044
|
+
if (!functionScope) return false;
|
|
6045
|
+
const awaitDerivedSymbolIds = collectAwaitDerivedSymbolIds(localFunction.body, context.scopes);
|
|
6046
|
+
const externallyReachableParameterSymbolIds = /* @__PURE__ */ new Set();
|
|
6047
|
+
for (const [parameterIndex, parameter] of localFunction.params.entries()) {
|
|
6048
|
+
const parameterIdentifier = getSimpleParameterIdentifier(parameter);
|
|
6049
|
+
if (!parameterIdentifier) continue;
|
|
6050
|
+
const argument = callExpression.arguments[parameterIndex];
|
|
6051
|
+
if (!isNodeOfType(argument, "Identifier")) continue;
|
|
6052
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
6053
|
+
const argumentSymbol = context.scopes.symbolFor(argument);
|
|
6054
|
+
if (parameterSymbol && argumentSymbol && (isSymbolDirectlyReturned(argumentSymbol, callerFunction) || argumentSymbol.id === parameterSymbol.id)) externallyReachableParameterSymbolIds.add(parameterSymbol.id);
|
|
6055
|
+
}
|
|
6056
|
+
let doesInsertAwaitDerivedOutput = false;
|
|
6057
|
+
walkAst(localFunction.body, (child) => {
|
|
6058
|
+
if (doesInsertAwaitDerivedOutput) return false;
|
|
6059
|
+
if (child !== localFunction.body && isFunctionLike$1(child)) return false;
|
|
6060
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6061
|
+
const callee = child.callee;
|
|
6062
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !ORDERED_OUTPUT_INSERTION_METHOD_NAMES.has(callee.property.name)) return;
|
|
6063
|
+
let doesMutationConsumeAwaitedValue = false;
|
|
6064
|
+
for (const mutationArgument of child.arguments ?? []) {
|
|
6065
|
+
if (containsDirectAwait(mutationArgument)) {
|
|
6066
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6067
|
+
break;
|
|
6068
|
+
}
|
|
6069
|
+
const referencedSymbolIds = collectReferencedSymbolIds(mutationArgument, context.scopes);
|
|
6070
|
+
for (const referencedSymbolId of referencedSymbolIds) if (awaitDerivedSymbolIds.has(referencedSymbolId)) {
|
|
6071
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6072
|
+
break;
|
|
6073
|
+
}
|
|
6074
|
+
if (doesMutationConsumeAwaitedValue) break;
|
|
6075
|
+
}
|
|
6076
|
+
if (!doesMutationConsumeAwaitedValue) return;
|
|
6077
|
+
let receiverIdentifier = callee.object;
|
|
6078
|
+
while (isNodeOfType(receiverIdentifier, "MemberExpression")) receiverIdentifier = receiverIdentifier.object;
|
|
6079
|
+
if (!isNodeOfType(receiverIdentifier, "Identifier")) return;
|
|
6080
|
+
const receiverSymbol = context.scopes.symbolFor(receiverIdentifier);
|
|
6081
|
+
if (receiverSymbol && (externallyReachableParameterSymbolIds.has(receiverSymbol.id) || !isScopeWithinFunction(receiverSymbol.scope, functionScope) && isSymbolDirectlyReturned(receiverSymbol, callerFunction))) {
|
|
6082
|
+
doesInsertAwaitDerivedOutput = true;
|
|
6083
|
+
return false;
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
return doesInsertAwaitDerivedOutput;
|
|
6087
|
+
};
|
|
6088
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode) || doesAwaitedLocalCallInsertAwaitDerivedOutput(awaitNode, context);
|
|
5957
6089
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
5958
6090
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
5959
6091
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -6099,11 +6231,6 @@ const loopBodyHasAwaitDependentEarlyExit = (block, loopLabelName) => {
|
|
|
6099
6231
|
});
|
|
6100
6232
|
return hasAwaitDependentExit;
|
|
6101
6233
|
};
|
|
6102
|
-
const getRootObjectIdentifierName = (node) => {
|
|
6103
|
-
let current = node;
|
|
6104
|
-
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
6105
|
-
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
6106
|
-
};
|
|
6107
6234
|
const MUTATING_ARRAY_METHOD_NAMES$2 = new Set([
|
|
6108
6235
|
...ARRAY_MUTATION_METHOD_NAMES,
|
|
6109
6236
|
"pop",
|
|
@@ -8675,7 +8802,7 @@ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, r
|
|
|
8675
8802
|
//#endregion
|
|
8676
8803
|
//#region src/plugin/rules/state-and-effects/class-component-missing-component-will-unmount-teardown.ts
|
|
8677
8804
|
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$
|
|
8805
|
+
const GLOBAL_OBJECT_NAMES$4 = new Set([
|
|
8679
8806
|
"window",
|
|
8680
8807
|
"globalThis",
|
|
8681
8808
|
"global",
|
|
@@ -8834,14 +8961,14 @@ const getTimerIdentifierAliasName = (identifier, scopes, visitedSymbolIds = /* @
|
|
|
8834
8961
|
const property = symbol.bindingIdentifier.parent;
|
|
8835
8962
|
const objectPattern = property?.parent;
|
|
8836
8963
|
const source = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
8837
|
-
if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8964
|
+
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
8965
|
return null;
|
|
8839
8966
|
}
|
|
8840
8967
|
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
8841
8968
|
if (isNodeOfType(initializer, "Identifier")) return getTimerIdentifierAliasName(initializer, scopes, visitedSymbolIds);
|
|
8842
8969
|
if (!isNodeOfType(initializer, "MemberExpression")) return null;
|
|
8843
8970
|
const receiver = stripParenExpression(initializer.object);
|
|
8844
|
-
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8971
|
+
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
|
|
8845
8972
|
};
|
|
8846
8973
|
const getTimerCalleeName = (node, scopes) => {
|
|
8847
8974
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
@@ -8849,7 +8976,7 @@ const getTimerCalleeName = (node, scopes) => {
|
|
|
8849
8976
|
if (getBareCalleeName(node) && isNodeOfType(callee, "Identifier")) return getTimerIdentifierAliasName(callee, scopes);
|
|
8850
8977
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
8851
8978
|
const receiver = stripParenExpression(callee.object);
|
|
8852
|
-
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$
|
|
8979
|
+
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$4.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
|
|
8853
8980
|
return getStaticPropertyName(callee);
|
|
8854
8981
|
};
|
|
8855
8982
|
const getClassMemberName$1 = (member) => {
|
|
@@ -10045,6 +10172,12 @@ const FOCUS_FORWARDING_METHOD_NAMES = new Set([
|
|
|
10045
10172
|
"preventDefault",
|
|
10046
10173
|
"stopImmediatePropagation"
|
|
10047
10174
|
]);
|
|
10175
|
+
const DOM_QUERY_METHOD_NAMES = new Set(["getElementById", "querySelector"]);
|
|
10176
|
+
const GLOBAL_OBJECT_NAMES$3 = new Set([
|
|
10177
|
+
"global",
|
|
10178
|
+
"globalThis",
|
|
10179
|
+
"window"
|
|
10180
|
+
]);
|
|
10048
10181
|
const isFocusForwardingCall = (node) => {
|
|
10049
10182
|
if (!node) return false;
|
|
10050
10183
|
const inner = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
@@ -10054,6 +10187,12 @@ const isFocusForwardingCall = (node) => {
|
|
|
10054
10187
|
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
10055
10188
|
return FOCUS_FORWARDING_METHOD_NAMES.has(callee.property.name);
|
|
10056
10189
|
};
|
|
10190
|
+
const isGlobalDocumentExpression = (expression, scopes) => {
|
|
10191
|
+
const candidate = stripParenExpression(expression);
|
|
10192
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate.name === "document" && scopes.isGlobalReference(candidate);
|
|
10193
|
+
if (!isNodeOfType(candidate, "MemberExpression") || !isNodeOfType(candidate.object, "Identifier") || !isNodeOfType(candidate.property, "Identifier") || candidate.property.name !== "document") return false;
|
|
10194
|
+
return GLOBAL_OBJECT_NAMES$3.has(candidate.object.name) && scopes.isGlobalReference(candidate.object);
|
|
10195
|
+
};
|
|
10057
10196
|
const isFocusForwardingFunctionBody = (body) => {
|
|
10058
10197
|
if (!body) return false;
|
|
10059
10198
|
if (isFocusForwardingCall(body)) return true;
|
|
@@ -10073,19 +10212,70 @@ const resolveHandlerFunction$2 = (attribute) => {
|
|
|
10073
10212
|
return resolveHandlerFunctionExpression(attribute.value.expression);
|
|
10074
10213
|
};
|
|
10075
10214
|
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
10076
|
-
let expression = handlerExpression;
|
|
10215
|
+
let expression = stripParenExpression(handlerExpression);
|
|
10077
10216
|
if (isNodeOfType(expression, "Identifier")) {
|
|
10078
10217
|
const binding = findVariableInitializer(expression, expression.name);
|
|
10079
10218
|
if (!binding?.initializer) return null;
|
|
10080
|
-
expression = binding.initializer;
|
|
10219
|
+
expression = stripParenExpression(binding.initializer);
|
|
10081
10220
|
}
|
|
10082
10221
|
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
|
|
10083
10222
|
return null;
|
|
10084
10223
|
};
|
|
10085
|
-
const
|
|
10224
|
+
const isEmptyReturn = (statement) => isNodeOfType(statement, "ReturnStatement") && statement.argument === null;
|
|
10225
|
+
const isClosestEarlyReturn = (statement) => {
|
|
10226
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate) return false;
|
|
10227
|
+
const consequent = statement.consequent;
|
|
10228
|
+
if (!(isNodeOfType(consequent, "BlockStatement") ? consequent.body.length === 1 && isEmptyReturn(consequent.body[0]) : isEmptyReturn(consequent))) return false;
|
|
10229
|
+
const test = stripParenExpression(statement.test);
|
|
10230
|
+
const call = isNodeOfType(test, "ChainExpression") ? test.expression : test;
|
|
10231
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments.length !== 1) return false;
|
|
10232
|
+
const callee = stripParenExpression(call.callee);
|
|
10233
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
|
|
10234
|
+
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";
|
|
10235
|
+
};
|
|
10236
|
+
const isStaticSelectorExpression = (expression) => {
|
|
10237
|
+
const candidate = stripParenExpression(expression);
|
|
10238
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "Literal")) return true;
|
|
10239
|
+
return isNodeOfType(candidate, "TemplateLiteral") && candidate.expressions.every((innerExpression) => isStaticSelectorExpression(innerExpression));
|
|
10240
|
+
};
|
|
10241
|
+
const getDomQueryVariableName = (statement, scopes) => {
|
|
10242
|
+
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const" || statement.declarations.length !== 1) return null;
|
|
10243
|
+
const declaration = statement.declarations[0];
|
|
10244
|
+
if (!declaration || !isNodeOfType(declaration.id, "Identifier") || !declaration.init) return null;
|
|
10245
|
+
const initializer = stripParenExpression(declaration.init);
|
|
10246
|
+
if (!isNodeOfType(initializer, "CallExpression") || initializer.arguments.length !== 1 || isNodeOfType(initializer.arguments[0], "SpreadElement") || !isStaticSelectorExpression(initializer.arguments[0])) return null;
|
|
10247
|
+
const callee = stripParenExpression(initializer.callee);
|
|
10248
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_QUERY_METHOD_NAMES.has(callee.property.name) || !isGlobalDocumentExpression(callee.object, scopes)) return null;
|
|
10249
|
+
return declaration.id.name;
|
|
10250
|
+
};
|
|
10251
|
+
const isFocusCallOnVariable = (statement, variableName) => {
|
|
10252
|
+
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
10253
|
+
const expression = stripParenExpression(statement.expression);
|
|
10254
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
10255
|
+
const callee = stripParenExpression(expression.callee);
|
|
10256
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
10257
|
+
const receiver = stripParenExpression(callee.object);
|
|
10258
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === variableName && isNodeOfType(callee.property, "Identifier") && callee.property.name === "focus" && expression.arguments.length === 0;
|
|
10259
|
+
};
|
|
10260
|
+
const isConditionalFocusForwardingHandler = (attribute, scopes) => {
|
|
10261
|
+
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
10262
|
+
const expression = stripParenExpression(attribute.value.expression);
|
|
10263
|
+
if (!isNodeOfType(expression, "ConditionalExpression")) return false;
|
|
10264
|
+
const consequent = stripParenExpression(expression.consequent);
|
|
10265
|
+
const alternate = stripParenExpression(expression.alternate);
|
|
10266
|
+
const nullishBranch = isNullishExpression$2(consequent) ? consequent : alternate;
|
|
10267
|
+
if (!isNullishExpression$2(nullishBranch) || isNodeOfType(nullishBranch, "Identifier") && !scopes.isGlobalReference(nullishBranch)) return false;
|
|
10268
|
+
const handlerFunction = resolveHandlerFunctionExpression(nullishBranch === consequent ? alternate : consequent);
|
|
10269
|
+
if (!handlerFunction || !isNodeOfType(handlerFunction.body, "BlockStatement")) return false;
|
|
10270
|
+
const [guard, query, focus, ...rest] = handlerFunction.body.body;
|
|
10271
|
+
if (!guard || !query || !focus || rest.length > 0 || !isClosestEarlyReturn(guard)) return false;
|
|
10272
|
+
const queryVariableName = getDomQueryVariableName(query, scopes);
|
|
10273
|
+
return Boolean(queryVariableName && isFocusCallOnVariable(focus, queryVariableName));
|
|
10274
|
+
};
|
|
10275
|
+
const isFocusForwardingHandler = (attribute, scopes) => {
|
|
10276
|
+
if (isConditionalFocusForwardingHandler(attribute, scopes)) return true;
|
|
10086
10277
|
const handlerFunction = resolveHandlerFunction$2(attribute);
|
|
10087
|
-
|
|
10088
|
-
return isFocusForwardingFunctionBody(handlerFunction.body ?? null);
|
|
10278
|
+
return Boolean(handlerFunction && isFocusForwardingFunctionBody(handlerFunction.body ?? null));
|
|
10089
10279
|
};
|
|
10090
10280
|
const COMPOSITE_ITEM_ROLES$1 = new Set([
|
|
10091
10281
|
"option",
|
|
@@ -10155,7 +10345,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
10155
10345
|
const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
|
|
10156
10346
|
if (!onClick && !spreadOnClickExpression) return;
|
|
10157
10347
|
if (onClick && isPureEventBlockerHandler(onClick)) return;
|
|
10158
|
-
if (onClick && isFocusForwardingHandler(onClick)) return;
|
|
10348
|
+
if (onClick && isFocusForwardingHandler(onClick, context.scopes)) return;
|
|
10159
10349
|
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
10160
10350
|
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
10161
10351
|
if (hasCompositeItemRole(node)) return;
|
|
@@ -17809,6 +17999,14 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
17809
17999
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
17810
18000
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
17811
18001
|
//#endregion
|
|
18002
|
+
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
18003
|
+
const isEarlyExitStatement$1 = (statement) => {
|
|
18004
|
+
if (!statement) return false;
|
|
18005
|
+
if (statementAlwaysExits$1(statement)) return true;
|
|
18006
|
+
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
18007
|
+
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
18008
|
+
};
|
|
18009
|
+
//#endregion
|
|
17812
18010
|
//#region src/plugin/utils/is-ast-descendant.ts
|
|
17813
18011
|
/**
|
|
17814
18012
|
* True when `inner` is `outer` itself or any descendant in the AST
|
|
@@ -18027,13 +18225,15 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
|
|
|
18027
18225
|
}
|
|
18028
18226
|
return Boolean(methodName && EAGER_ITERATOR_METHOD_NAMES.has(methodName) && callNode.arguments[0] === callbackArgument && !isProvablyEmptyEagerCollection(callee.object, scopes) && (methodName === "forEach" ? isProvablyEagerForEachCollection(callee.object, scopes) : isProvablyEagerCollection(callee.object, scopes)));
|
|
18029
18227
|
};
|
|
18030
|
-
const
|
|
18031
|
-
const callNode = functionNode.parent;
|
|
18032
|
-
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
18228
|
+
const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
|
|
18033
18229
|
const callee = stripParenExpression(callNode.callee);
|
|
18034
18230
|
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] ===
|
|
18231
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === callbackArgument;
|
|
18232
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === callbackArgument;
|
|
18233
|
+
};
|
|
18234
|
+
const isSynchronousIteratorCallback = (functionNode) => {
|
|
18235
|
+
const callNode = functionNode.parent;
|
|
18236
|
+
return Boolean(isNodeOfType(callNode, "CallExpression") && isSynchronousIteratorCallbackCall(callNode, functionNode));
|
|
18037
18237
|
};
|
|
18038
18238
|
//#endregion
|
|
18039
18239
|
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
@@ -18153,11 +18353,35 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
|
18153
18353
|
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
18154
18354
|
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
18155
18355
|
};
|
|
18356
|
+
const resolveReadOnlyEventListenerOptions = (optionsNode, context) => {
|
|
18357
|
+
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18358
|
+
if (!isNodeOfType(unwrappedOptions, "Identifier")) return resolveStableValue(unwrappedOptions, context);
|
|
18359
|
+
const optionsSymbol = context.scopes.symbolFor(unwrappedOptions);
|
|
18360
|
+
const initializer = optionsSymbol?.initializer ? stripParenExpression(optionsSymbol.initializer) : null;
|
|
18361
|
+
if (!optionsSymbol || !initializer) return resolveStableValue(unwrappedOptions, context);
|
|
18362
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) {
|
|
18363
|
+
if (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression")) return null;
|
|
18364
|
+
return resolveStableValue(unwrappedOptions, context);
|
|
18365
|
+
}
|
|
18366
|
+
if (optionsSymbol.kind !== "const") return null;
|
|
18367
|
+
return optionsSymbol.references.every((reference) => {
|
|
18368
|
+
if (reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) return false;
|
|
18369
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
18370
|
+
const callNode = referenceRoot.parent;
|
|
18371
|
+
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments[2] !== referenceRoot) return false;
|
|
18372
|
+
const callee = stripParenExpression(callNode.callee);
|
|
18373
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
18374
|
+
const methodName = getStaticPropertyKeyName(callee);
|
|
18375
|
+
return methodName === "addEventListener" || methodName === "removeEventListener";
|
|
18376
|
+
}) ? initializer : null;
|
|
18377
|
+
};
|
|
18156
18378
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
18157
|
-
const
|
|
18379
|
+
const stableOptionsNode = optionsNode ? resolveReadOnlyEventListenerOptions(optionsNode, context) : null;
|
|
18380
|
+
if (optionsNode && !stableOptionsNode) return null;
|
|
18381
|
+
const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
|
|
18158
18382
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
18159
|
-
if (!
|
|
18160
|
-
const unwrappedOptions = stripParenExpression(
|
|
18383
|
+
if (!stableOptionsNode) return null;
|
|
18384
|
+
const unwrappedOptions = stripParenExpression(stableOptionsNode);
|
|
18161
18385
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
18162
18386
|
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
18163
18387
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
@@ -18204,12 +18428,8 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
|
|
|
18204
18428
|
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
18205
18429
|
};
|
|
18206
18430
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
while (isNodeOfType(parentNode, "ChainExpression")) {
|
|
18210
|
-
currentNode = parentNode;
|
|
18211
|
-
parentNode = currentNode.parent;
|
|
18212
|
-
}
|
|
18431
|
+
const currentNode = findTransparentExpressionRoot(resourceNode);
|
|
18432
|
+
const parentNode = currentNode.parent;
|
|
18213
18433
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
|
|
18214
18434
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
18215
18435
|
return null;
|
|
@@ -18480,6 +18700,18 @@ const resolveIteratorCollectionKey = (expression, context) => {
|
|
|
18480
18700
|
}
|
|
18481
18701
|
return null;
|
|
18482
18702
|
};
|
|
18703
|
+
const resolveReceiverIteratorCollectionKey = (expression, context) => {
|
|
18704
|
+
if (!expression) return null;
|
|
18705
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
18706
|
+
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
18707
|
+
const collectionExpression = findForOfStatementForIteratorExpression(unwrappedExpression, context)?.right;
|
|
18708
|
+
if (!collectionExpression) return null;
|
|
18709
|
+
const collectionIdentifier = stripParenExpression(collectionExpression);
|
|
18710
|
+
if (!isNodeOfType(collectionIdentifier, "Identifier") || !isPrivatePlainConstIdentifier(collectionIdentifier, context)) return null;
|
|
18711
|
+
const collectionSymbol = context.scopes.symbolFor(collectionIdentifier);
|
|
18712
|
+
const initializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
18713
|
+
return collectionSymbol && isNodeOfType(initializer, "ArrayExpression") && hasOnlyReplayableCollectionReferences(collectionIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${collectionSymbol.id}` : null;
|
|
18714
|
+
};
|
|
18483
18715
|
const isStableLoopReceiver = (expression, context) => {
|
|
18484
18716
|
if (!expression) return false;
|
|
18485
18717
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -19250,6 +19482,28 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
|
|
|
19250
19482
|
});
|
|
19251
19483
|
};
|
|
19252
19484
|
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
19485
|
+
const findCallbackRefReplacementReleaseGuard = (releaseCall, ownerFunction, releaseReceiverKey, registrationReceiverKey, context) => {
|
|
19486
|
+
let descendant = releaseCall;
|
|
19487
|
+
let ancestor = descendant.parent;
|
|
19488
|
+
while (ancestor && ancestor !== ownerFunction) {
|
|
19489
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && ancestor.alternate === null) {
|
|
19490
|
+
const test = stripParenExpression(ancestor.test);
|
|
19491
|
+
if (!isNodeOfType(test, "LogicalExpression") || test.operator !== "&&") return null;
|
|
19492
|
+
const operands = [stripParenExpression(test.left), stripParenExpression(test.right)];
|
|
19493
|
+
const hasLiveReceiverTest = operands.some((operand) => doesTestRequireLiveExpressionKey(operand, releaseReceiverKey, context));
|
|
19494
|
+
const hasDifferentReceiverTest = operands.some((operand) => {
|
|
19495
|
+
if (!isNodeOfType(operand, "BinaryExpression") || operand.operator !== "!==" && operand.operator !== "!=") return false;
|
|
19496
|
+
const leftKey = resolveExpressionKey(operand.left, context);
|
|
19497
|
+
const rightKey = resolveExpressionKey(operand.right, context);
|
|
19498
|
+
return leftKey === releaseReceiverKey && rightKey === registrationReceiverKey || rightKey === releaseReceiverKey && leftKey === registrationReceiverKey;
|
|
19499
|
+
});
|
|
19500
|
+
return hasLiveReceiverTest && hasDifferentReceiverTest ? ancestor : null;
|
|
19501
|
+
}
|
|
19502
|
+
descendant = ancestor;
|
|
19503
|
+
ancestor = descendant.parent;
|
|
19504
|
+
}
|
|
19505
|
+
return null;
|
|
19506
|
+
};
|
|
19253
19507
|
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
19254
19508
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
19255
19509
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
@@ -19270,7 +19524,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
19270
19524
|
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
19271
19525
|
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
|
|
19272
19526
|
});
|
|
19273
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
19527
|
+
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
|
|
19274
19528
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
19275
19529
|
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
19276
19530
|
};
|
|
@@ -19420,14 +19674,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19420
19674
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
19421
19675
|
if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
|
|
19422
19676
|
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
19677
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
19678
|
+
if (usage.registrationVerbName === "addListener" && releaseVerbName === "removeListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) {
|
|
19679
|
+
if (callNode.arguments?.length !== 1) return false;
|
|
19680
|
+
const registrationHandler = resolveStableValue(usage.node.arguments[0], context);
|
|
19681
|
+
if (!isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && !isFunctionLike$1(registrationHandler)) return false;
|
|
19682
|
+
}
|
|
19425
19683
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
19426
19684
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
19427
19685
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
19428
19686
|
}
|
|
19429
19687
|
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
19430
|
-
|
|
19688
|
+
const registrationCallee = isNodeOfType(usage.node, "CallExpression") ? stripParenExpression(usage.node.callee) : null;
|
|
19689
|
+
const registrationReceiverCollectionKey = isNodeOfType(registrationCallee, "MemberExpression") ? resolveReceiverIteratorCollectionKey(registrationCallee.object, context) : null;
|
|
19690
|
+
const releaseReceiverCollectionKeyForPair = resolveReceiverIteratorCollectionKey(callee.object, context);
|
|
19691
|
+
if (!(registrationReceiverCollectionKey !== null && registrationReceiverCollectionKey === releaseReceiverCollectionKeyForPair) && (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey)) return false;
|
|
19431
19692
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
19432
19693
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
19433
19694
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
@@ -19464,7 +19725,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19464
19725
|
const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
|
|
19465
19726
|
const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
19466
19727
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
19467
|
-
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
|
|
19728
|
+
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.handlerKey ?? usage.eventKey : usage.handlerKey;
|
|
19468
19729
|
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
|
|
19469
19730
|
return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
19470
19731
|
}
|
|
@@ -19938,6 +20199,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
19938
20199
|
walkAst(body, (child) => {
|
|
19939
20200
|
if (leak !== null) return false;
|
|
19940
20201
|
if (isFunctionLike$1(child)) return false;
|
|
20202
|
+
if (!isNodeReachableWithinFunction(child, context)) return false;
|
|
19941
20203
|
if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
|
|
19942
20204
|
const socketUsage = {
|
|
19943
20205
|
kind: "socket",
|
|
@@ -20190,6 +20452,164 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
20190
20452
|
const objectParent = objectExpression.parent;
|
|
20191
20453
|
return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
|
|
20192
20454
|
};
|
|
20455
|
+
const readInvocationArgumentValue = (expression, context) => {
|
|
20456
|
+
if (!expression) return {
|
|
20457
|
+
isDefinitelyUndefined: true,
|
|
20458
|
+
truthiness: "falsy"
|
|
20459
|
+
};
|
|
20460
|
+
const target = stripParenExpression(expression);
|
|
20461
|
+
if (isNodeOfType(target, "Literal")) return {
|
|
20462
|
+
isDefinitelyUndefined: false,
|
|
20463
|
+
truthiness: target.value ? "truthy" : "falsy"
|
|
20464
|
+
};
|
|
20465
|
+
if (isNodeOfType(target, "Identifier") && target.name === "undefined" && context.scopes.isGlobalReference(target)) return {
|
|
20466
|
+
isDefinitelyUndefined: true,
|
|
20467
|
+
truthiness: "falsy"
|
|
20468
|
+
};
|
|
20469
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "void") return {
|
|
20470
|
+
isDefinitelyUndefined: true,
|
|
20471
|
+
truthiness: "falsy"
|
|
20472
|
+
};
|
|
20473
|
+
if (isNodeOfType(target, "ArrayExpression") || isNodeOfType(target, "ArrowFunctionExpression") || isNodeOfType(target, "ClassExpression") || isNodeOfType(target, "FunctionExpression") || isNodeOfType(target, "NewExpression") || isNodeOfType(target, "ObjectExpression")) return {
|
|
20474
|
+
isDefinitelyUndefined: false,
|
|
20475
|
+
truthiness: "truthy"
|
|
20476
|
+
};
|
|
20477
|
+
return {
|
|
20478
|
+
isDefinitelyUndefined: false,
|
|
20479
|
+
truthiness: "unknown"
|
|
20480
|
+
};
|
|
20481
|
+
};
|
|
20482
|
+
const readInvocationConditionTruthiness = (expression, parameterValues, context) => {
|
|
20483
|
+
const target = stripParenExpression(expression);
|
|
20484
|
+
const atomicValue = readInvocationArgumentValue(target, context);
|
|
20485
|
+
if (atomicValue.truthiness !== "unknown") return atomicValue.truthiness;
|
|
20486
|
+
if (isNodeOfType(target, "Identifier")) {
|
|
20487
|
+
const symbol = context.scopes.symbolFor(target);
|
|
20488
|
+
return symbol ? parameterValues.get(symbol.id)?.truthiness ?? "unknown" : "unknown";
|
|
20489
|
+
}
|
|
20490
|
+
if (isNodeOfType(target, "UnaryExpression") && target.operator === "!") {
|
|
20491
|
+
const argumentTruthiness = readInvocationConditionTruthiness(target.argument, parameterValues, context);
|
|
20492
|
+
return argumentTruthiness === "truthy" ? "falsy" : argumentTruthiness === "falsy" ? "truthy" : "unknown";
|
|
20493
|
+
}
|
|
20494
|
+
if (isNodeOfType(target, "LogicalExpression")) {
|
|
20495
|
+
const leftTruthiness = readInvocationConditionTruthiness(target.left, parameterValues, context);
|
|
20496
|
+
const rightTruthiness = readInvocationConditionTruthiness(target.right, parameterValues, context);
|
|
20497
|
+
if (target.operator === "&&") {
|
|
20498
|
+
if (leftTruthiness === "falsy" || rightTruthiness === "falsy") return "falsy";
|
|
20499
|
+
return leftTruthiness === "truthy" && rightTruthiness === "truthy" ? "truthy" : "unknown";
|
|
20500
|
+
}
|
|
20501
|
+
if (target.operator === "||") {
|
|
20502
|
+
if (leftTruthiness === "truthy" || rightTruthiness === "truthy") return "truthy";
|
|
20503
|
+
return leftTruthiness === "falsy" && rightTruthiness === "falsy" ? "falsy" : "unknown";
|
|
20504
|
+
}
|
|
20505
|
+
return "unknown";
|
|
20506
|
+
}
|
|
20507
|
+
if (isNodeOfType(target, "ConditionalExpression")) {
|
|
20508
|
+
const testTruthiness = readInvocationConditionTruthiness(target.test, parameterValues, context);
|
|
20509
|
+
if (testTruthiness === "truthy") return readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20510
|
+
if (testTruthiness === "falsy") return readInvocationConditionTruthiness(target.alternate, parameterValues, context);
|
|
20511
|
+
const consequentTruthiness = readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20512
|
+
return consequentTruthiness === readInvocationConditionTruthiness(target.alternate, parameterValues, context) ? consequentTruthiness : "unknown";
|
|
20513
|
+
}
|
|
20514
|
+
if (isNodeOfType(target, "CallExpression") && isNodeOfType(target.callee, "Identifier") && target.callee.name === "Boolean" && context.scopes.isGlobalReference(target.callee) && target.arguments[0] && isAstNode(target.arguments[0])) return readInvocationConditionTruthiness(target.arguments[0], parameterValues, context);
|
|
20515
|
+
return "unknown";
|
|
20516
|
+
};
|
|
20517
|
+
const getInvocationParameterValues = (retainedFunction, invocation, leakNode, context) => {
|
|
20518
|
+
const parameterValues = /* @__PURE__ */ new Map();
|
|
20519
|
+
if (!isFunctionLike$1(retainedFunction) || !invocation.isDirect) return parameterValues;
|
|
20520
|
+
for (const [parameterIndex, parameter] of retainedFunction.params.entries()) {
|
|
20521
|
+
const argument = invocation.call.arguments[parameterIndex];
|
|
20522
|
+
const argumentExpression = argument && isAstNode(argument) ? argument : null;
|
|
20523
|
+
let parameterIdentifier = null;
|
|
20524
|
+
let parameterValue = readInvocationArgumentValue(argumentExpression, context);
|
|
20525
|
+
if (isNodeOfType(parameter, "Identifier")) parameterIdentifier = parameter;
|
|
20526
|
+
else if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) {
|
|
20527
|
+
parameterIdentifier = parameter.left;
|
|
20528
|
+
if (parameterValue.isDefinitelyUndefined) parameterValue = readInvocationArgumentValue(parameter.right, context);
|
|
20529
|
+
} else if (isNodeOfType(parameter, "RestElement") && isNodeOfType(parameter.argument, "Identifier")) {
|
|
20530
|
+
parameterIdentifier = parameter.argument;
|
|
20531
|
+
parameterValue = {
|
|
20532
|
+
isDefinitelyUndefined: false,
|
|
20533
|
+
truthiness: "truthy"
|
|
20534
|
+
};
|
|
20535
|
+
}
|
|
20536
|
+
if (!parameterIdentifier) continue;
|
|
20537
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
20538
|
+
if (!parameterSymbol) continue;
|
|
20539
|
+
const isWrittenBeforeLeak = parameterSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] < leakNode.range[0]);
|
|
20540
|
+
parameterValues.set(parameterSymbol.id, isWrittenBeforeLeak ? {
|
|
20541
|
+
isDefinitelyUndefined: false,
|
|
20542
|
+
truthiness: "unknown"
|
|
20543
|
+
} : parameterValue);
|
|
20544
|
+
}
|
|
20545
|
+
return parameterValues;
|
|
20546
|
+
};
|
|
20547
|
+
const isLeakPathDisabledForInvocation = (retainedFunction, leakNode, invocation, context) => {
|
|
20548
|
+
if (!invocation.isDirect) return false;
|
|
20549
|
+
const parameterValues = getInvocationParameterValues(retainedFunction, invocation, leakNode, context);
|
|
20550
|
+
let child = leakNode;
|
|
20551
|
+
let ancestor = leakNode.parent ?? null;
|
|
20552
|
+
while (ancestor && ancestor !== retainedFunction) {
|
|
20553
|
+
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
20554
|
+
const childIndex = ancestor.body.findIndex((statement) => statement === child);
|
|
20555
|
+
for (const precedingStatement of ancestor.body.slice(0, childIndex)) {
|
|
20556
|
+
if (!isNodeOfType(precedingStatement, "IfStatement") || precedingStatement.alternate || !isEarlyExitStatement$1(precedingStatement.consequent)) continue;
|
|
20557
|
+
if (readInvocationConditionTruthiness(precedingStatement.test, parameterValues, context) === "truthy") return true;
|
|
20558
|
+
}
|
|
20559
|
+
}
|
|
20560
|
+
let requiredTruthiness = null;
|
|
20561
|
+
let condition = null;
|
|
20562
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
20563
|
+
condition = ancestor.test;
|
|
20564
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20565
|
+
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
20566
|
+
condition = ancestor.test;
|
|
20567
|
+
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20568
|
+
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === child && ancestor.operator !== "??") {
|
|
20569
|
+
condition = ancestor.left;
|
|
20570
|
+
requiredTruthiness = ancestor.operator === "&&" ? "truthy" : "falsy";
|
|
20571
|
+
} else if ((isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === child) {
|
|
20572
|
+
condition = ancestor.test;
|
|
20573
|
+
requiredTruthiness = "truthy";
|
|
20574
|
+
} else if (isNodeOfType(ancestor, "ForStatement") && ancestor.body === child && ancestor.test) {
|
|
20575
|
+
condition = ancestor.test;
|
|
20576
|
+
requiredTruthiness = "truthy";
|
|
20577
|
+
}
|
|
20578
|
+
if (condition && requiredTruthiness) {
|
|
20579
|
+
const conditionTruthiness = readInvocationConditionTruthiness(condition, parameterValues, context);
|
|
20580
|
+
if (conditionTruthiness !== "unknown" && conditionTruthiness !== requiredTruthiness) return true;
|
|
20581
|
+
}
|
|
20582
|
+
child = ancestor;
|
|
20583
|
+
ancestor = ancestor.parent ?? null;
|
|
20584
|
+
}
|
|
20585
|
+
return false;
|
|
20586
|
+
};
|
|
20587
|
+
const getEffectRetainedInvocations = (retainedFunction, context) => {
|
|
20588
|
+
if (!isFunctionLike$1(retainedFunction)) return [];
|
|
20589
|
+
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
20590
|
+
if (!componentFunction || !isFunctionLike$1(componentFunction)) return [];
|
|
20591
|
+
const invocations = [];
|
|
20592
|
+
walkAst(componentFunction.body, (child) => {
|
|
20593
|
+
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
20594
|
+
const effectCallback = getEffectCallback(child);
|
|
20595
|
+
if (!effectCallback || !isFunctionLike$1(effectCallback)) return;
|
|
20596
|
+
walkAst(effectCallback.body, (effectChild) => {
|
|
20597
|
+
if (effectChild !== effectCallback.body && isFunctionLike$1(effectChild)) return false;
|
|
20598
|
+
if (!isNodeOfType(effectChild, "CallExpression") || !isNodeReachableWithinFunction(effectChild, context)) return;
|
|
20599
|
+
const isDirectInvocation = resolveRefOwnedCleanupFunction(effectChild.callee, context) === retainedFunction;
|
|
20600
|
+
const isSynchronousIteratorInvocation = effectChild.arguments.some((argument) => isAstNode(argument) && resolveRefOwnedCleanupFunction(argument, context) === retainedFunction && isSynchronousIteratorCallbackCall(effectChild, argument));
|
|
20601
|
+
if (isDirectInvocation) invocations.push({
|
|
20602
|
+
call: effectChild,
|
|
20603
|
+
isDirect: true
|
|
20604
|
+
});
|
|
20605
|
+
if (isSynchronousIteratorInvocation) invocations.push({
|
|
20606
|
+
call: effectChild,
|
|
20607
|
+
isDirect: false
|
|
20608
|
+
});
|
|
20609
|
+
});
|
|
20610
|
+
});
|
|
20611
|
+
return invocations;
|
|
20612
|
+
};
|
|
20193
20613
|
const effectNeedsCleanup = defineRule({
|
|
20194
20614
|
id: "effect-needs-cleanup",
|
|
20195
20615
|
title: "Effect subscription or timer never cleaned up",
|
|
@@ -20200,13 +20620,19 @@ const effectNeedsCleanup = defineRule({
|
|
|
20200
20620
|
const reportRetainedLeak = (retainedFunction) => {
|
|
20201
20621
|
const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
|
|
20202
20622
|
if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
|
|
20623
|
+
const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
|
|
20624
|
+
const isEffectInvoked = effectInvocations.length > 0;
|
|
20203
20625
|
const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
|
|
20204
20626
|
allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
|
|
20205
20627
|
allowReturnedTimerEscape: false,
|
|
20206
20628
|
includeOneShotTimers: true,
|
|
20207
20629
|
requireCallableReturnedResource: true
|
|
20630
|
+
} : isEffectInvoked ? {
|
|
20631
|
+
allowReturnedTimerEscape: false,
|
|
20632
|
+
includeOneShotTimers: true
|
|
20208
20633
|
} : void 0);
|
|
20209
20634
|
if (!leak) return;
|
|
20635
|
+
if (isEffectInvoked && leak.resourceName === "setTimeout" && (!isNodeReachableWithinFunction(leak.node, context) || isFunctionLike$1(retainedFunction) && retainedFunction.params.length > 0 && !context.cfg.isUnconditionalFromEntry(leak.node) && effectInvocations.every((invocation) => isLeakPathDisabledForInvocation(retainedFunction, leak.node, invocation, context)))) return;
|
|
20210
20636
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
|
|
20211
20637
|
context.report({
|
|
20212
20638
|
node: leak.node,
|
|
@@ -24626,14 +25052,14 @@ const getFirstLegendChild = (children, targetNode) => {
|
|
|
24626
25052
|
if (isNodeOfType(child, "JSXExpressionContainer")) {
|
|
24627
25053
|
const potentialLegends = [];
|
|
24628
25054
|
collectPotentialLegends(child.expression, potentialLegends);
|
|
24629
|
-
const containingLegend = potentialLegends.find((legend) => isDescendantOf(targetNode, legend));
|
|
25055
|
+
const containingLegend = potentialLegends.find((legend) => isDescendantOf$1(targetNode, legend));
|
|
24630
25056
|
if (containingLegend) return containingLegend;
|
|
24631
25057
|
if (potentialLegends[0]) return potentialLegends[0];
|
|
24632
25058
|
}
|
|
24633
25059
|
}
|
|
24634
25060
|
return null;
|
|
24635
25061
|
};
|
|
24636
|
-
const isDescendantOf = (node, ancestor) => {
|
|
25062
|
+
const isDescendantOf$1 = (node, ancestor) => {
|
|
24637
25063
|
let current = node.parent;
|
|
24638
25064
|
while (current) {
|
|
24639
25065
|
if (current === ancestor) return true;
|
|
@@ -24658,7 +25084,7 @@ const isDisabledByFieldsetAncestor = (node, context) => {
|
|
|
24658
25084
|
while (ancestor) {
|
|
24659
25085
|
if (isNodeOfType(ancestor, "JSXElement") && resolveJsxElementType(ancestor.openingElement) === "fieldset" && openingElementMayBeDisabled(ancestor.openingElement, context)) {
|
|
24660
25086
|
const firstLegend = getFirstLegendChild(ancestor.children, node);
|
|
24661
|
-
if (!firstLegend || !isDescendantOf(node, firstLegend)) return true;
|
|
25087
|
+
if (!firstLegend || !isDescendantOf$1(node, firstLegend)) return true;
|
|
24662
25088
|
}
|
|
24663
25089
|
ancestor = ancestor.parent;
|
|
24664
25090
|
}
|
|
@@ -29035,6 +29461,11 @@ const jsCacheStorage = defineRule({
|
|
|
29035
29461
|
});
|
|
29036
29462
|
//#endregion
|
|
29037
29463
|
//#region src/plugin/rules/js-performance/js-combine-iterations.ts
|
|
29464
|
+
const SMALL_ARRAY_NON_MUTATING_METHODS = new Set([
|
|
29465
|
+
...CHAINABLE_ITERATION_METHODS,
|
|
29466
|
+
"find",
|
|
29467
|
+
"some"
|
|
29468
|
+
]);
|
|
29038
29469
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
29039
29470
|
const callee = callExpression.callee;
|
|
29040
29471
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
@@ -29146,21 +29577,34 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
29146
29577
|
return false;
|
|
29147
29578
|
};
|
|
29148
29579
|
const isSmallLiteralArray = (node) => {
|
|
29149
|
-
|
|
29150
|
-
|
|
29151
|
-
|
|
29580
|
+
const arrayNode = stripParenExpression(node);
|
|
29581
|
+
if (!isNodeOfType(arrayNode, "ArrayExpression")) return false;
|
|
29582
|
+
const elements = arrayNode.elements ?? [];
|
|
29583
|
+
if (elements.length === 0 || elements.length > 9) return false;
|
|
29152
29584
|
for (const element of elements) {
|
|
29153
29585
|
if (!element) continue;
|
|
29154
29586
|
if (isNodeOfType(element, "SpreadElement")) return false;
|
|
29155
29587
|
}
|
|
29156
29588
|
return true;
|
|
29157
29589
|
};
|
|
29158
|
-
const
|
|
29590
|
+
const isNonMutatingSmallArrayMethodReference = (identifier) => {
|
|
29591
|
+
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
29592
|
+
const memberExpression = identifierRoot.parent;
|
|
29593
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.object !== identifierRoot || !isNodeOfType(memberExpression.property, "Identifier") || !SMALL_ARRAY_NON_MUTATING_METHODS.has(memberExpression.property.name)) return false;
|
|
29594
|
+
const callExpression = memberExpression.parent;
|
|
29595
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberExpression;
|
|
29596
|
+
};
|
|
29597
|
+
const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
29159
29598
|
let cursor = receiverNode;
|
|
29160
29599
|
while (cursor) {
|
|
29161
29600
|
cursor = stripParenExpression(cursor);
|
|
29162
29601
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
29163
|
-
if (isNodeOfType(cursor, "Identifier"))
|
|
29602
|
+
if (isNodeOfType(cursor, "Identifier")) {
|
|
29603
|
+
const symbol = scopes.symbolFor(cursor);
|
|
29604
|
+
if (!symbol?.initializer || !isSmallLiteralArray(symbol.initializer)) return false;
|
|
29605
|
+
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier")) return false;
|
|
29606
|
+
return (symbol.kind === "const" || symbol.kind === "let" || symbol.kind === "var") && symbol.references.every((reference) => reference.flag === "read" && isNonMutatingSmallArrayMethodReference(reference.identifier));
|
|
29607
|
+
}
|
|
29164
29608
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
29165
29609
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
29166
29610
|
const nextCallee = cursor.callee;
|
|
@@ -29169,22 +29613,6 @@ const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
|
29169
29613
|
}
|
|
29170
29614
|
return false;
|
|
29171
29615
|
};
|
|
29172
|
-
const collectSmallConstArrayNames = (programNode) => {
|
|
29173
|
-
const names = /* @__PURE__ */ new Set();
|
|
29174
|
-
const statements = programNode.body ?? [];
|
|
29175
|
-
for (const statement of statements) {
|
|
29176
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
29177
|
-
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
29178
|
-
if (declaration.kind !== "const") continue;
|
|
29179
|
-
for (const declarator of declaration.declarations ?? []) {
|
|
29180
|
-
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
29181
|
-
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
29182
|
-
if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
|
|
29183
|
-
names.add(declarator.id.name);
|
|
29184
|
-
}
|
|
29185
|
-
}
|
|
29186
|
-
return names;
|
|
29187
|
-
};
|
|
29188
29616
|
const collectGeneratorNames = (programNode) => {
|
|
29189
29617
|
const generatorNames = /* @__PURE__ */ new Set();
|
|
29190
29618
|
walkAst(programNode, (child) => {
|
|
@@ -29205,16 +29633,11 @@ const jsCombineIterations = defineRule({
|
|
|
29205
29633
|
create: (context) => {
|
|
29206
29634
|
let programNode = null;
|
|
29207
29635
|
let generatorNamesInFile = null;
|
|
29208
|
-
let smallConstArrayNames = null;
|
|
29209
29636
|
const coveredChainCalls = /* @__PURE__ */ new WeakSet();
|
|
29210
29637
|
const getGeneratorNamesInFile = () => {
|
|
29211
29638
|
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
29212
29639
|
return generatorNamesInFile;
|
|
29213
29640
|
};
|
|
29214
|
-
const getSmallConstArrayNames = () => {
|
|
29215
|
-
smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
|
|
29216
|
-
return smallConstArrayNames;
|
|
29217
|
-
};
|
|
29218
29641
|
return {
|
|
29219
29642
|
Program(node) {
|
|
29220
29643
|
programNode = node;
|
|
@@ -29244,7 +29667,7 @@ const jsCombineIterations = defineRule({
|
|
|
29244
29667
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
29245
29668
|
}
|
|
29246
29669
|
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
29247
|
-
if (isSmallLiteralArrayRootedChain(innerCall.callee.object,
|
|
29670
|
+
if (isSmallLiteralArrayRootedChain(innerCall.callee.object, context.scopes)) return;
|
|
29248
29671
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
29249
29672
|
coveredChainCalls.add(innerCall);
|
|
29250
29673
|
context.report({
|
|
@@ -30471,7 +30894,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
|
|
|
30471
30894
|
"code",
|
|
30472
30895
|
"label",
|
|
30473
30896
|
"slug",
|
|
30474
|
-
"prefix"
|
|
30897
|
+
"prefix",
|
|
30898
|
+
"__html"
|
|
30475
30899
|
]);
|
|
30476
30900
|
const STRING_TYPED_IDENTIFIER_SUFFIXES = [
|
|
30477
30901
|
"Text",
|
|
@@ -30589,13 +31013,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
|
|
|
30589
31013
|
"title"
|
|
30590
31014
|
]);
|
|
30591
31015
|
const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
|
|
31016
|
+
const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
|
|
31017
|
+
"concat",
|
|
31018
|
+
"filter",
|
|
31019
|
+
"flat",
|
|
31020
|
+
"flatMap",
|
|
31021
|
+
"map",
|
|
31022
|
+
"slice",
|
|
31023
|
+
"split"
|
|
31024
|
+
]);
|
|
30592
31025
|
const isLikelyStringReceiver = (receiver) => {
|
|
30593
31026
|
if (!receiver) return false;
|
|
31027
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31028
|
+
if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
|
|
30594
31029
|
if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
|
|
30595
31030
|
if (isNodeOfType(receiver, "TemplateLiteral")) return true;
|
|
30596
31031
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
|
|
30597
31032
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
|
|
30598
31033
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
|
|
31034
|
+
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && (receiver.callee.property.name === "concat" || receiver.callee.property.name === "slice") && isLikelyStringReceiver(receiver.callee.object)) return true;
|
|
30599
31035
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
30600
31036
|
if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
|
|
30601
31037
|
}
|
|
@@ -30613,8 +31049,46 @@ const isLikelyStringReceiver = (receiver) => {
|
|
|
30613
31049
|
}
|
|
30614
31050
|
if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
|
|
30615
31051
|
if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
|
|
31052
|
+
if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
|
|
30616
31053
|
return false;
|
|
30617
31054
|
};
|
|
31055
|
+
const isFreshArrayReceiver = (receiver) => {
|
|
31056
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
31057
|
+
if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
|
|
31058
|
+
if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
|
|
31059
|
+
if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
|
|
31060
|
+
if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
|
|
31061
|
+
const sourceReceiver = stripParenExpression(receiver.callee.object);
|
|
31062
|
+
return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
|
|
31063
|
+
};
|
|
31064
|
+
const isSmallRestHelperOmissionList = (node) => {
|
|
31065
|
+
if (!node) return false;
|
|
31066
|
+
const candidate = stripParenExpression(node);
|
|
31067
|
+
if (!isNodeOfType(candidate, "ArrayExpression")) return false;
|
|
31068
|
+
const elements = candidate.elements ?? [];
|
|
31069
|
+
return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
|
|
31070
|
+
};
|
|
31071
|
+
const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
|
|
31072
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
31073
|
+
const enclosingFunction = findEnclosingFunction$1(lookupCall);
|
|
31074
|
+
if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
|
|
31075
|
+
let bindingIdentifier = null;
|
|
31076
|
+
let ancestor = enclosingFunction.parent;
|
|
31077
|
+
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
31078
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
|
|
31079
|
+
bindingIdentifier = ancestor.id;
|
|
31080
|
+
break;
|
|
31081
|
+
}
|
|
31082
|
+
ancestor = ancestor.parent;
|
|
31083
|
+
}
|
|
31084
|
+
if (!bindingIdentifier) return false;
|
|
31085
|
+
const helperSymbol = scopes.symbolFor(bindingIdentifier);
|
|
31086
|
+
if (!helperSymbol || helperSymbol.references.length === 0) return false;
|
|
31087
|
+
return helperSymbol.references.every((reference) => {
|
|
31088
|
+
const callExpression = reference.identifier.parent;
|
|
31089
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
|
|
31090
|
+
});
|
|
31091
|
+
};
|
|
30618
31092
|
const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
|
|
30619
31093
|
"i",
|
|
30620
31094
|
"j",
|
|
@@ -31211,6 +31685,8 @@ const jsSetMapLookups = defineRule({
|
|
|
31211
31685
|
const query = node.arguments[0];
|
|
31212
31686
|
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
31213
31687
|
if (isLikelyStringReceiver(receiver)) return;
|
|
31688
|
+
if (isFreshArrayReceiver(receiver)) return;
|
|
31689
|
+
if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
|
|
31214
31690
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
31215
31691
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
31216
31692
|
if (isSmallFixedListMember(receiver)) return;
|
|
@@ -45124,14 +45600,6 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45124
45600
|
} })
|
|
45125
45601
|
});
|
|
45126
45602
|
//#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
45603
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45136
45604
|
const unwrapNegativeGuardForm = (test) => {
|
|
45137
45605
|
const expression = stripParenExpression(test);
|
|
@@ -46652,6 +47120,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
|
|
|
46652
47120
|
}
|
|
46653
47121
|
return isBindingReassignedOrMutated(referenceNode, indexName);
|
|
46654
47122
|
};
|
|
47123
|
+
const findEnclosingWhileLoop = (node) => {
|
|
47124
|
+
let current = node.parent;
|
|
47125
|
+
while (current) {
|
|
47126
|
+
if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
|
|
47127
|
+
if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
|
|
47128
|
+
current = current.parent;
|
|
47129
|
+
}
|
|
47130
|
+
return null;
|
|
47131
|
+
};
|
|
47132
|
+
const isStaticMemberChain = (expression) => {
|
|
47133
|
+
const candidate = stripParenExpression(expression);
|
|
47134
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
|
|
47135
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
|
|
47136
|
+
};
|
|
47137
|
+
const findLengthBoundCollections = (expression) => {
|
|
47138
|
+
const collections = [];
|
|
47139
|
+
walkAst(expression, (child) => {
|
|
47140
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
|
|
47141
|
+
});
|
|
47142
|
+
return collections;
|
|
47143
|
+
};
|
|
47144
|
+
const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
|
|
47145
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47146
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47147
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47148
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47149
|
+
} });
|
|
47150
|
+
const RELATIONAL_BOUND_OPERATORS = new Set([
|
|
47151
|
+
"<",
|
|
47152
|
+
"<=",
|
|
47153
|
+
">",
|
|
47154
|
+
">="
|
|
47155
|
+
]);
|
|
47156
|
+
const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
|
|
47157
|
+
const readsCounter = (candidate) => {
|
|
47158
|
+
let didReadCounter = false;
|
|
47159
|
+
walkAst(candidate, (child) => {
|
|
47160
|
+
if (didReadCounter) return false;
|
|
47161
|
+
if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47162
|
+
didReadCounter = true;
|
|
47163
|
+
return false;
|
|
47164
|
+
}
|
|
47165
|
+
});
|
|
47166
|
+
return didReadCounter;
|
|
47167
|
+
};
|
|
47168
|
+
const readsLength = (candidate) => {
|
|
47169
|
+
let didReadLength = false;
|
|
47170
|
+
walkAst(candidate, (child) => {
|
|
47171
|
+
if (didReadLength) return false;
|
|
47172
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
47173
|
+
didReadLength = true;
|
|
47174
|
+
return false;
|
|
47175
|
+
}
|
|
47176
|
+
});
|
|
47177
|
+
return didReadLength;
|
|
47178
|
+
};
|
|
47179
|
+
let didFindLengthBound = false;
|
|
47180
|
+
walkAst(expression, (child) => {
|
|
47181
|
+
if (didFindLengthBound) return false;
|
|
47182
|
+
if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
|
|
47183
|
+
if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
|
|
47184
|
+
didFindLengthBound = true;
|
|
47185
|
+
return false;
|
|
47186
|
+
}
|
|
47187
|
+
});
|
|
47188
|
+
return didFindLengthBound;
|
|
47189
|
+
};
|
|
47190
|
+
const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
|
|
47191
|
+
if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
|
|
47192
|
+
const loop = findEnclosingWhileLoop(referenceNode);
|
|
47193
|
+
if (!loop) return false;
|
|
47194
|
+
let doesTestReadCounter = false;
|
|
47195
|
+
walkAst(loop.test, (child) => {
|
|
47196
|
+
if (doesTestReadCounter) return false;
|
|
47197
|
+
if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
|
|
47198
|
+
if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
47199
|
+
doesTestReadCounter = true;
|
|
47200
|
+
return false;
|
|
47201
|
+
}
|
|
47202
|
+
});
|
|
47203
|
+
if (!doesTestReadCounter) return false;
|
|
47204
|
+
const lengthBoundCollections = findLengthBoundCollections(loop.test);
|
|
47205
|
+
if (lengthBoundCollections.length === 0) return false;
|
|
47206
|
+
let didFindIndexedCollectionRead = false;
|
|
47207
|
+
walkAst(loop.body, (child) => {
|
|
47208
|
+
if (didFindIndexedCollectionRead) return false;
|
|
47209
|
+
if (isFunctionLike$1(child)) return false;
|
|
47210
|
+
if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
|
|
47211
|
+
if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
|
|
47212
|
+
didFindIndexedCollectionRead = true;
|
|
47213
|
+
return false;
|
|
47214
|
+
}
|
|
47215
|
+
});
|
|
47216
|
+
return didFindIndexedCollectionRead;
|
|
47217
|
+
};
|
|
46655
47218
|
/**
|
|
46656
47219
|
* Resolves whether an identifier is PROVABLY the positional array
|
|
46657
47220
|
* index, by classifying its binding. Per the official rule prompt,
|
|
@@ -46702,6 +47265,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
|
|
|
46702
47265
|
}
|
|
46703
47266
|
const declarator = binding.bindingIdentifier.parent;
|
|
46704
47267
|
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
|
|
47268
|
+
if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
|
|
47269
|
+
iteratorCall: null,
|
|
47270
|
+
bindingFunction: null,
|
|
47271
|
+
indexParameterPosition: null,
|
|
47272
|
+
isDataIndexedLoopCounter: true
|
|
47273
|
+
};
|
|
46705
47274
|
const initializer = stripParenExpression(declarator.init);
|
|
46706
47275
|
if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
|
|
46707
47276
|
if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
|
|
@@ -46736,6 +47305,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
|
|
|
46736
47305
|
const receiver = iteratorCall.callee.object;
|
|
46737
47306
|
return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
|
|
46738
47307
|
};
|
|
47308
|
+
const isReactNamespaceIdentifier = (node) => {
|
|
47309
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47310
|
+
const importBinding = getImportBindingForName(node, node.name);
|
|
47311
|
+
const visibleBinding = findVariableInitializer(node, node.name);
|
|
47312
|
+
if (!importBinding) return node.name === "React" && !visibleBinding;
|
|
47313
|
+
return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
|
|
47314
|
+
};
|
|
47315
|
+
const isReactChildrenObject = (node) => {
|
|
47316
|
+
const candidate = stripParenExpression(node);
|
|
47317
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47318
|
+
const importBinding = getImportBindingForName(candidate, candidate.name);
|
|
47319
|
+
const visibleBinding = findVariableInitializer(candidate, candidate.name);
|
|
47320
|
+
return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
|
|
47321
|
+
}
|
|
47322
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
|
|
47323
|
+
};
|
|
47324
|
+
const isReactChildrenToArrayCall = (node) => {
|
|
47325
|
+
const candidate = stripParenExpression(node);
|
|
47326
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || candidate.callee.computed || !isReactChildrenObject(candidate.callee.object) || !isNodeOfType(candidate.callee.property, "Identifier") || candidate.callee.property.name !== "toArray") return false;
|
|
47327
|
+
const normalizedValueNode = candidate.arguments?.[0];
|
|
47328
|
+
const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
|
|
47329
|
+
if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
|
|
47330
|
+
const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
|
|
47331
|
+
return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
|
|
47332
|
+
};
|
|
47333
|
+
const isSameIdentifier = (first, second) => {
|
|
47334
|
+
const firstIdentifier = stripParenExpression(first);
|
|
47335
|
+
const secondIdentifier = stripParenExpression(second);
|
|
47336
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47337
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47338
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47339
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47340
|
+
};
|
|
47341
|
+
const isReactChildrenArrayNormalization = (node) => {
|
|
47342
|
+
const candidate = stripParenExpression(node);
|
|
47343
|
+
if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
|
|
47344
|
+
const test = stripParenExpression(candidate.test);
|
|
47345
|
+
if (!isNodeOfType(test, "CallExpression") || !isNodeOfType(test.callee, "MemberExpression") || test.callee.computed || !isNodeOfType(test.callee.object, "Identifier") || test.callee.object.name !== "Array" || findVariableInitializer(test.callee.object, "Array") || !isNodeOfType(test.callee.property, "Identifier") || test.callee.property.name !== "isArray") return false;
|
|
47346
|
+
const testedValueNode = test.arguments?.[0];
|
|
47347
|
+
if (!testedValueNode) return false;
|
|
47348
|
+
const testedValue = stripParenExpression(testedValueNode);
|
|
47349
|
+
if (!isNodeOfType(testedValue, "Identifier")) return false;
|
|
47350
|
+
const testedBinding = findVariableInitializer(testedValue, testedValue.name);
|
|
47351
|
+
if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
|
|
47352
|
+
const branchWrapsTestedValue = (branch) => {
|
|
47353
|
+
const unwrappedBranch = stripParenExpression(branch);
|
|
47354
|
+
return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
|
|
47355
|
+
};
|
|
47356
|
+
return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
|
|
47357
|
+
};
|
|
47358
|
+
const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
|
|
47359
|
+
const binding = findVariableInitializer(identifierNode, identifierNode.name);
|
|
47360
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
47361
|
+
if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
|
|
47362
|
+
const program = findProgramRoot(identifierNode);
|
|
47363
|
+
if (!program) return false;
|
|
47364
|
+
let didFindReactChildPush = false;
|
|
47365
|
+
walkAst(program, (child) => {
|
|
47366
|
+
if (didFindReactChildPush) return false;
|
|
47367
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || child.callee.computed || !isNodeOfType(child.callee.object, "Identifier") || child.callee.object.name !== identifierNode.name || !isNodeOfType(child.callee.property, "Identifier") || child.callee.property.name !== "push") return;
|
|
47368
|
+
if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
|
|
47369
|
+
const candidate = stripParenExpression(argument);
|
|
47370
|
+
if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
|
|
47371
|
+
let doesCarryReactChild = false;
|
|
47372
|
+
walkAst(candidate, (argumentChild) => {
|
|
47373
|
+
if (doesCarryReactChild) return false;
|
|
47374
|
+
if (!isNodeOfType(argumentChild, "Identifier")) return;
|
|
47375
|
+
const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
|
|
47376
|
+
const declaration = declarator?.parent;
|
|
47377
|
+
const forOfStatement = declaration?.parent;
|
|
47378
|
+
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
|
|
47379
|
+
doesCarryReactChild = true;
|
|
47380
|
+
return false;
|
|
47381
|
+
}
|
|
47382
|
+
});
|
|
47383
|
+
return doesCarryReactChild;
|
|
47384
|
+
})) {
|
|
47385
|
+
didFindReactChildPush = true;
|
|
47386
|
+
return false;
|
|
47387
|
+
}
|
|
47388
|
+
});
|
|
47389
|
+
return didFindReactChildPush;
|
|
47390
|
+
};
|
|
47391
|
+
const isDynamicReactChildrenExpression = (expression, depth) => {
|
|
47392
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
|
|
47393
|
+
const candidate = stripParenExpression(expression);
|
|
47394
|
+
if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
|
|
47395
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47396
|
+
if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
|
|
47397
|
+
const binding = findVariableInitializer(candidate, candidate.name);
|
|
47398
|
+
return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
|
|
47399
|
+
}
|
|
47400
|
+
if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && !candidate.callee.computed && isNodeOfType(candidate.callee.property, "Identifier") && candidate.callee.property.name === "filter") return isDynamicReactChildrenExpression(candidate.callee.object, depth + 1);
|
|
47401
|
+
return false;
|
|
47402
|
+
};
|
|
46739
47403
|
const resolveKeyTemplateLiteral = (expression) => {
|
|
46740
47404
|
const node = stripParenExpression(expression);
|
|
46741
47405
|
if (isNodeOfType(node, "TemplateLiteral")) return node;
|
|
@@ -46788,28 +47452,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
|
46788
47452
|
}
|
|
46789
47453
|
return referencedItemNames;
|
|
46790
47454
|
};
|
|
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) => {
|
|
47455
|
+
const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
|
|
46803
47456
|
const binding = findVariableInitializer(attributeNode, indexName);
|
|
46804
47457
|
if (!binding) return false;
|
|
46805
47458
|
const declarator = binding.bindingIdentifier.parent;
|
|
46806
47459
|
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
46807
47460
|
const declaration = declarator.parent;
|
|
46808
47461
|
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
47462
|
+
if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
|
|
46809
47463
|
const forStatement = declaration.parent;
|
|
46810
|
-
if (
|
|
46811
|
-
|
|
46812
|
-
return
|
|
47464
|
+
if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
|
|
47465
|
+
const whileLoop = findEnclosingWhileLoop(attributeNode);
|
|
47466
|
+
if (!whileLoop) return false;
|
|
47467
|
+
return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
|
|
46813
47468
|
};
|
|
46814
47469
|
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
46815
47470
|
const findIteratorItemNamesOfBinding = (binding) => {
|
|
@@ -46843,11 +47498,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
|
|
|
46843
47498
|
* observable harm; anything stateful — form controls, media, custom
|
|
46844
47499
|
* components, unknown calls — keeps the diagnostic.
|
|
46845
47500
|
*/
|
|
46846
|
-
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
|
|
47501
|
+
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
|
|
46847
47502
|
const jsxElement = openingElement.parent;
|
|
46848
47503
|
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
46849
47504
|
const children = jsxElement.children ?? [];
|
|
46850
|
-
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
47505
|
+
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
46851
47506
|
return children.some((child) => containsStatefulDescendant(child, {
|
|
46852
47507
|
memberRootNames: itemNames,
|
|
46853
47508
|
allowAnyMemberRead: true,
|
|
@@ -46855,6 +47510,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
|
|
|
46855
47510
|
callCalleeRootNames: itemNames
|
|
46856
47511
|
}));
|
|
46857
47512
|
};
|
|
47513
|
+
const elementHasDirectItemChild = (openingElement, itemNames) => {
|
|
47514
|
+
const jsxElement = openingElement.parent;
|
|
47515
|
+
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47516
|
+
return (jsxElement.children ?? []).some((child) => {
|
|
47517
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
|
|
47518
|
+
const expression = stripParenExpression(child.expression);
|
|
47519
|
+
return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
|
|
47520
|
+
});
|
|
47521
|
+
};
|
|
46858
47522
|
const callbackFiltersRows = (bindingFunction) => {
|
|
46859
47523
|
if (!bindingFunction) return false;
|
|
46860
47524
|
let didFindNullReturn = false;
|
|
@@ -46906,19 +47570,21 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46906
47570
|
const indexUse = findPositionalIndexUse(node.value.expression, 0);
|
|
46907
47571
|
if (!indexUse) return;
|
|
46908
47572
|
const indexName = indexUse.identifier.name;
|
|
46909
|
-
if (
|
|
47573
|
+
if (isNumericPlaceholderLoopCounter(node, indexName)) return;
|
|
46910
47574
|
if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
|
|
46911
47575
|
const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
|
|
46912
47576
|
if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
|
|
46913
|
-
if (hasAriaHiddenAncestor(node)) return;
|
|
47577
|
+
if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
|
|
46914
47578
|
const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
|
|
46915
47579
|
const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
|
|
47580
|
+
const iteratorCallee = indexUse.binding.iteratorCall?.callee;
|
|
47581
|
+
const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
|
|
46916
47582
|
const openingElement = node.parent;
|
|
46917
47583
|
if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
|
|
46918
47584
|
const elementName = openingElement.name;
|
|
46919
47585
|
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
46920
47586
|
if (elementName.name === "Fragment") {
|
|
46921
|
-
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47587
|
+
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46922
47588
|
} else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
|
|
46923
47589
|
if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
|
|
46924
47590
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
@@ -46926,14 +47592,14 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46926
47592
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
46927
47593
|
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
46928
47594
|
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
|
|
46929
|
-
if (!containsStatefulDescendant(jsxElement, {
|
|
47595
|
+
if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
|
|
46930
47596
|
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
|
|
46931
47597
|
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
46932
|
-
})) return;
|
|
47598
|
+
}))) return;
|
|
46933
47599
|
}
|
|
46934
47600
|
}
|
|
46935
47601
|
}
|
|
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;
|
|
47602
|
+
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46937
47603
|
}
|
|
46938
47604
|
context.report({
|
|
46939
47605
|
node,
|
|
@@ -55421,9 +56087,13 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
55421
56087
|
return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
|
|
55422
56088
|
};
|
|
55423
56089
|
//#endregion
|
|
56090
|
+
//#region src/plugin/utils/nextjs-page-data-export-names.ts
|
|
56091
|
+
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56092
|
+
//#endregion
|
|
55424
56093
|
//#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
|
|
55425
56094
|
const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
|
|
55426
56095
|
const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
|
|
56096
|
+
const INTERNAL_STATE_NAME_PATTERN = /^(internal|uncontrolled)([A-Z_]|$)/;
|
|
55427
56097
|
const getStateSetterName = (useStateCall) => {
|
|
55428
56098
|
const declarator = useStateCall.parent;
|
|
55429
56099
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -55577,7 +56247,6 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
|
|
|
55577
56247
|
});
|
|
55578
56248
|
return isCommitted;
|
|
55579
56249
|
};
|
|
55580
|
-
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
55581
56250
|
const isNextjsDataFetchingPage = (node) => {
|
|
55582
56251
|
const program = findProgramRoot(node);
|
|
55583
56252
|
if (!program) return false;
|
|
@@ -55621,6 +56290,48 @@ const isInRenderScope = (node, componentFunction) => {
|
|
|
55621
56290
|
}
|
|
55622
56291
|
return true;
|
|
55623
56292
|
};
|
|
56293
|
+
const isReferenceToBinding = (reference, bindingIdentifier, context) => {
|
|
56294
|
+
if (!isNodeOfType(reference, "Identifier")) return false;
|
|
56295
|
+
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
56296
|
+
if (!bindingSymbol) return false;
|
|
56297
|
+
return (context.scopes.referenceFor(reference)?.resolvedSymbol)?.id === bindingSymbol.id;
|
|
56298
|
+
};
|
|
56299
|
+
const isControlledPropFallbackExpression = (expression, stateBinding, isPropName, context) => {
|
|
56300
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
56301
|
+
const consequent = unwrapInitializerSeed(expression.consequent);
|
|
56302
|
+
const alternate = unwrapInitializerSeed(expression.alternate);
|
|
56303
|
+
return isReferenceToBinding(consequent, stateBinding, context) && isPropDerivedArgument(alternate, isPropName) || isPropDerivedArgument(consequent, isPropName) && isReferenceToBinding(alternate, stateBinding, context);
|
|
56304
|
+
}
|
|
56305
|
+
return isNodeOfType(expression, "LogicalExpression") && expression.operator === "??" && isPropDerivedArgument(unwrapInitializerSeed(expression.left), isPropName) && isReferenceToBinding(unwrapInitializerSeed(expression.right), stateBinding, context);
|
|
56306
|
+
};
|
|
56307
|
+
const isUserEditableControlledFallback = (useStateCall, isPropName, context) => {
|
|
56308
|
+
const declarator = useStateCall.parent;
|
|
56309
|
+
if (!isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
56310
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
|
|
56311
|
+
const stateBinding = declarator.id.elements?.[0];
|
|
56312
|
+
const setterBinding = declarator.id.elements?.[1];
|
|
56313
|
+
if (!isNodeOfType(stateBinding, "Identifier") || !isNodeOfType(setterBinding, "Identifier")) return false;
|
|
56314
|
+
if (!INTERNAL_STATE_NAME_PATTERN.test(stateBinding.name)) return false;
|
|
56315
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
56316
|
+
if (!componentFunction) return false;
|
|
56317
|
+
let hasControlledFallback = false;
|
|
56318
|
+
let hasUserEdit = false;
|
|
56319
|
+
walkAst(componentFunction, (child) => {
|
|
56320
|
+
if (child !== componentFunction && isFunctionLike$1(child)) return false;
|
|
56321
|
+
if (!hasControlledFallback && (isNodeOfType(child, "ConditionalExpression") || isNodeOfType(child, "LogicalExpression")) && isControlledPropFallbackExpression(child, stateBinding, isPropName, context)) hasControlledFallback = true;
|
|
56322
|
+
});
|
|
56323
|
+
walkAst(componentFunction, (child) => {
|
|
56324
|
+
if (hasUserEdit) return false;
|
|
56325
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
56326
|
+
if (!isReferenceToBinding(child.callee, setterBinding, context)) return;
|
|
56327
|
+
if (!isHandlerShapedReseed(child, componentFunction)) return;
|
|
56328
|
+
const setterArgument = child.arguments?.[0];
|
|
56329
|
+
if (!setterArgument || isPropDerivedArgument(unwrapInitializerSeed(setterArgument), isPropName)) return;
|
|
56330
|
+
hasUserEdit = true;
|
|
56331
|
+
return false;
|
|
56332
|
+
});
|
|
56333
|
+
return hasControlledFallback && hasUserEdit;
|
|
56334
|
+
};
|
|
55624
56335
|
const getStateValueName = (useStateCall) => {
|
|
55625
56336
|
const declarator = useStateCall.parent;
|
|
55626
56337
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -55685,6 +56396,7 @@ const noDerivedUseState = defineRule({
|
|
|
55685
56396
|
const reportStalePropCopy = (propName) => {
|
|
55686
56397
|
if (isIntentionalSnapshotState(node)) return;
|
|
55687
56398
|
if (hasSessionDismissProp(propStackTracker.getCurrentPropNames())) return;
|
|
56399
|
+
if (isUserEditableControlledFallback(node, propStackTracker.isPropName, context)) return;
|
|
55688
56400
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
55689
56401
|
if (isEffectDrivenResync(node)) return;
|
|
55690
56402
|
if (isNextjsDataFetchingPage(node)) return;
|
|
@@ -63829,6 +64541,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
|
|
|
63829
64541
|
};
|
|
63830
64542
|
//#endregion
|
|
63831
64543
|
//#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
|
|
64544
|
+
const findGuardingIfStatements = (node, functionBoundary) => {
|
|
64545
|
+
const guardingIfStatements = [];
|
|
64546
|
+
let currentNode = node.parent;
|
|
64547
|
+
while (currentNode && currentNode !== functionBoundary) {
|
|
64548
|
+
if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
|
|
64549
|
+
currentNode = currentNode.parent;
|
|
64550
|
+
}
|
|
64551
|
+
return guardingIfStatements;
|
|
64552
|
+
};
|
|
64553
|
+
const doesNodeReadSymbol = (node, symbol) => {
|
|
64554
|
+
let doesReadSymbol = false;
|
|
64555
|
+
walkAst(node, (childNode) => {
|
|
64556
|
+
if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
|
|
64557
|
+
doesReadSymbol = true;
|
|
64558
|
+
return false;
|
|
64559
|
+
}
|
|
64560
|
+
});
|
|
64561
|
+
return doesReadSymbol;
|
|
64562
|
+
};
|
|
64563
|
+
const collectWrittenSymbols = (node, scopes) => {
|
|
64564
|
+
const writtenSymbols = /* @__PURE__ */ new Set();
|
|
64565
|
+
walkAst(node, (childNode) => {
|
|
64566
|
+
if (childNode !== node && isFunctionLike$1(childNode)) return false;
|
|
64567
|
+
if (!isNodeOfType(childNode, "Identifier")) return;
|
|
64568
|
+
const reference = scopes.referenceFor(childNode);
|
|
64569
|
+
if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
|
|
64570
|
+
writtenSymbols.add(reference.resolvedSymbol);
|
|
64571
|
+
});
|
|
64572
|
+
return writtenSymbols;
|
|
64573
|
+
};
|
|
64574
|
+
const isDescendantOf = (node, ancestorNode) => {
|
|
64575
|
+
let currentNode = node.parent;
|
|
64576
|
+
while (currentNode) {
|
|
64577
|
+
if (currentNode === ancestorNode) return true;
|
|
64578
|
+
currentNode = currentNode.parent;
|
|
64579
|
+
}
|
|
64580
|
+
return false;
|
|
64581
|
+
};
|
|
64582
|
+
const getAssignedValue = (identifier) => {
|
|
64583
|
+
const assignmentExpression = identifier.parent;
|
|
64584
|
+
return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
|
|
64585
|
+
};
|
|
64586
|
+
const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
|
|
64587
|
+
const initialValue = symbol.initializer;
|
|
64588
|
+
if (!initialValue) return false;
|
|
64589
|
+
const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
|
|
64590
|
+
return guardedWrites.length > 0 && guardedWrites.every((reference) => {
|
|
64591
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
64592
|
+
return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
|
|
64593
|
+
});
|
|
64594
|
+
};
|
|
64595
|
+
const isWriteOverwrittenBefore = (symbol, writeIdentifier, guardingIfStatement, readIdentifier, context) => symbol.references.some((reference) => reference.flag !== "read" && reference.identifier !== writeIdentifier && !isDescendantOf(reference.identifier, guardingIfStatement) && isNodeReachableWithinFunction(reference.identifier, context) && isUnconditionalOrStaticallySelected(reference.identifier, context) && getNodeStartIndex(reference.identifier) > getNodeStartIndex(writeIdentifier) && getNodeStartIndex(reference.identifier) < getNodeStartIndex(readIdentifier));
|
|
64596
|
+
const isUnconditionalOrStaticallySelected = (node, context) => {
|
|
64597
|
+
if (context.cfg.isUnconditionalFromEntry(node)) return true;
|
|
64598
|
+
let currentNode = node;
|
|
64599
|
+
let outermostStaticIfStatement = null;
|
|
64600
|
+
let parentNode = currentNode.parent;
|
|
64601
|
+
while (parentNode) {
|
|
64602
|
+
if (isFunctionLike$1(parentNode)) break;
|
|
64603
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
64604
|
+
const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
|
|
64605
|
+
let selectedBranch = null;
|
|
64606
|
+
if (staticResult === true) selectedBranch = parentNode.consequent;
|
|
64607
|
+
if (staticResult === false) selectedBranch = parentNode.alternate;
|
|
64608
|
+
if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
|
|
64609
|
+
outermostStaticIfStatement = parentNode;
|
|
64610
|
+
}
|
|
64611
|
+
currentNode = parentNode;
|
|
64612
|
+
parentNode = currentNode.parent;
|
|
64613
|
+
}
|
|
64614
|
+
return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
|
|
64615
|
+
};
|
|
64616
|
+
const containsExplicitReactRuntimeReference = (node, scopes) => {
|
|
64617
|
+
let hasRuntimeReference = false;
|
|
64618
|
+
walkAst(node, (childNode) => {
|
|
64619
|
+
if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
|
|
64620
|
+
hasRuntimeReference = true;
|
|
64621
|
+
return false;
|
|
64622
|
+
}
|
|
64623
|
+
if (!isNodeOfType(childNode, "CallExpression")) return;
|
|
64624
|
+
const sourceArgument = (childNode.arguments ?? [])[0];
|
|
64625
|
+
if (!isNodeOfType(childNode.callee, "Identifier") || childNode.callee.name !== "require" || !scopes.isGlobalReference(childNode.callee) || !isNodeOfType(sourceArgument, "Literal") || typeof sourceArgument.value !== "string" || !REACT_RUNTIME_MODULE_SOURCES.has(sourceArgument.value)) return;
|
|
64626
|
+
hasRuntimeReference = true;
|
|
64627
|
+
return false;
|
|
64628
|
+
});
|
|
64629
|
+
return hasRuntimeReference;
|
|
64630
|
+
};
|
|
64631
|
+
const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
|
|
64632
|
+
const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
|
|
64633
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
|
|
64634
|
+
const functionSymbol = scopes.symbolFor(bindingIdentifier);
|
|
64635
|
+
if (!functionSymbol) return null;
|
|
64636
|
+
for (const reference of functionSymbol.references) {
|
|
64637
|
+
const callExpression = reference.identifier.parent;
|
|
64638
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
|
|
64639
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
|
|
64640
|
+
if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
|
|
64641
|
+
}
|
|
64642
|
+
return null;
|
|
64643
|
+
};
|
|
63832
64644
|
const evaluateEquality$1 = (operator, left, right) => {
|
|
63833
64645
|
if (operator === "===" || operator === "==") return left === right;
|
|
63834
64646
|
if (operator === "!==" || operator === "!=") return left !== right;
|
|
@@ -63879,6 +64691,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
63879
64691
|
if (leftResult === false && rightResult === false) return false;
|
|
63880
64692
|
return null;
|
|
63881
64693
|
};
|
|
64694
|
+
const areLooselyEqualPrimitiveResults = (left, right) => {
|
|
64695
|
+
if (left.kind === right.kind) return left.value === right.value;
|
|
64696
|
+
if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
|
|
64697
|
+
if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
|
|
64698
|
+
kind: "number",
|
|
64699
|
+
value: left.value ? 1 : 0
|
|
64700
|
+
}, right);
|
|
64701
|
+
if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
|
|
64702
|
+
kind: "number",
|
|
64703
|
+
value: right.value ? 1 : 0
|
|
64704
|
+
});
|
|
64705
|
+
if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
|
|
64706
|
+
if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
|
|
64707
|
+
return false;
|
|
64708
|
+
};
|
|
64709
|
+
const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
|
|
64710
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64711
|
+
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
64712
|
+
if (predicateMatch) return {
|
|
64713
|
+
kind: "boolean",
|
|
64714
|
+
value: predicateMatch[`${runtime}Result`]
|
|
64715
|
+
};
|
|
64716
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) {
|
|
64717
|
+
const value = unwrappedExpression.value;
|
|
64718
|
+
if (value === null) return {
|
|
64719
|
+
kind: "null",
|
|
64720
|
+
value
|
|
64721
|
+
};
|
|
64722
|
+
if (typeof value === "boolean") return {
|
|
64723
|
+
kind: "boolean",
|
|
64724
|
+
value
|
|
64725
|
+
};
|
|
64726
|
+
if (typeof value === "number") return {
|
|
64727
|
+
kind: "number",
|
|
64728
|
+
value
|
|
64729
|
+
};
|
|
64730
|
+
if (typeof value === "string") return {
|
|
64731
|
+
kind: "string",
|
|
64732
|
+
value
|
|
64733
|
+
};
|
|
64734
|
+
return null;
|
|
64735
|
+
}
|
|
64736
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
|
|
64737
|
+
kind: "undefined",
|
|
64738
|
+
value: void 0
|
|
64739
|
+
};
|
|
64740
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64741
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64742
|
+
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
64743
|
+
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64744
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64745
|
+
const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
|
|
64746
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64747
|
+
return result;
|
|
64748
|
+
}
|
|
64749
|
+
if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
64750
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
64751
|
+
const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
|
|
64752
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
64753
|
+
return result;
|
|
64754
|
+
}
|
|
64755
|
+
}
|
|
64756
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
64757
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
64758
|
+
return argumentResult === null ? null : {
|
|
64759
|
+
kind: "boolean",
|
|
64760
|
+
value: !argumentResult
|
|
64761
|
+
};
|
|
64762
|
+
}
|
|
64763
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64764
|
+
const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
|
|
64765
|
+
const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
|
|
64766
|
+
if (!leftResult || !rightResult) return null;
|
|
64767
|
+
if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
|
|
64768
|
+
const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
|
|
64769
|
+
return {
|
|
64770
|
+
kind: "boolean",
|
|
64771
|
+
value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
|
|
64772
|
+
};
|
|
64773
|
+
}
|
|
64774
|
+
if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
|
|
64775
|
+
const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
|
|
64776
|
+
return {
|
|
64777
|
+
kind: "boolean",
|
|
64778
|
+
value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
|
|
64779
|
+
};
|
|
64780
|
+
}
|
|
64781
|
+
}
|
|
64782
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64783
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
64784
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64785
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
|
|
64786
|
+
const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
64787
|
+
return argumentResult === null ? null : {
|
|
64788
|
+
kind: "boolean",
|
|
64789
|
+
value: argumentResult
|
|
64790
|
+
};
|
|
64791
|
+
}
|
|
64792
|
+
}
|
|
64793
|
+
return null;
|
|
64794
|
+
};
|
|
63882
64795
|
const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
63883
64796
|
const unwrappedExpression = stripParenExpression(expression);
|
|
63884
64797
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -63927,6 +64840,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
|
63927
64840
|
parameterValuesBySymbolId
|
|
63928
64841
|
});
|
|
63929
64842
|
}
|
|
64843
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
64844
|
+
const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
|
|
64845
|
+
return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
|
|
64846
|
+
}
|
|
63930
64847
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
63931
64848
|
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
63932
64849
|
return argumentResult === null ? null : !argumentResult;
|
|
@@ -63987,13 +64904,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
|
|
|
63987
64904
|
const rightSymbol = scopes.symbolFor(right);
|
|
63988
64905
|
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
63989
64906
|
}
|
|
63990
|
-
|
|
63991
|
-
|
|
63992
|
-
|
|
63993
|
-
|
|
63994
|
-
|
|
63995
|
-
|
|
63996
|
-
|
|
64907
|
+
const rightEntries = new Map(Object.entries(right));
|
|
64908
|
+
for (const [key, leftValue] of Object.entries(left)) {
|
|
64909
|
+
if (key === "parent") continue;
|
|
64910
|
+
const rightValue = rightEntries.get(key);
|
|
64911
|
+
if (isAstNode(leftValue)) {
|
|
64912
|
+
if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
|
|
64913
|
+
continue;
|
|
64914
|
+
}
|
|
64915
|
+
if (!Array.isArray(leftValue)) continue;
|
|
64916
|
+
if (!Array.isArray(rightValue)) return false;
|
|
64917
|
+
const leftNodes = leftValue.filter(isAstNode);
|
|
64918
|
+
const rightNodes = rightValue.filter(isAstNode);
|
|
64919
|
+
if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
|
|
63997
64920
|
}
|
|
63998
64921
|
return true;
|
|
63999
64922
|
};
|
|
@@ -64007,6 +64930,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
|
64007
64930
|
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
64008
64931
|
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
64009
64932
|
};
|
|
64933
|
+
const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
64934
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
64935
|
+
if (matchBrowserPredicate(unwrappedExpression, context)) return true;
|
|
64936
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
|
|
64937
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
|
|
64938
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
64939
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
64940
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
|
|
64941
|
+
visitedSymbolIds.add(symbol.id);
|
|
64942
|
+
const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
|
|
64943
|
+
const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
|
|
64944
|
+
visitedSymbolIds.delete(symbol.id);
|
|
64945
|
+
return isReflexive;
|
|
64946
|
+
}
|
|
64947
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
|
|
64948
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
|
|
64949
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
|
|
64950
|
+
if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
|
|
64951
|
+
if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
|
|
64952
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
64953
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
|
|
64954
|
+
};
|
|
64955
|
+
const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
|
|
64956
|
+
if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
|
|
64957
|
+
if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
|
|
64958
|
+
if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
|
|
64959
|
+
if (isNodeOfType(node, "TryStatement")) return [
|
|
64960
|
+
...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
|
|
64961
|
+
...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
|
|
64962
|
+
...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
|
|
64963
|
+
];
|
|
64964
|
+
if (!isNodeOfType(node, "BlockStatement")) return [];
|
|
64965
|
+
const propertyValues = [];
|
|
64966
|
+
for (const childStatement of node.body) {
|
|
64967
|
+
propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
|
|
64968
|
+
if (statementAlwaysExits$1(childStatement)) break;
|
|
64969
|
+
}
|
|
64970
|
+
return propertyValues;
|
|
64971
|
+
};
|
|
64972
|
+
const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
|
|
64973
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
64974
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
64975
|
+
const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
|
|
64976
|
+
let match = null;
|
|
64977
|
+
for (const propertyValue of propertyValues) {
|
|
64978
|
+
match = matchHydrationConditionInternal(propertyValue, context, state);
|
|
64979
|
+
if (match) break;
|
|
64980
|
+
}
|
|
64981
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
64982
|
+
return match;
|
|
64983
|
+
};
|
|
64010
64984
|
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
64011
64985
|
const unwrappedExpression = stripParenExpression(expression);
|
|
64012
64986
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
@@ -64023,14 +64997,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64023
64997
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64024
64998
|
return match;
|
|
64025
64999
|
}
|
|
65000
|
+
if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
|
|
65001
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
65002
|
+
if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
|
|
65003
|
+
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
65004
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65005
|
+
return match;
|
|
65006
|
+
}
|
|
65007
|
+
for (const reference of symbol.references) {
|
|
65008
|
+
if (reference.flag === "read") continue;
|
|
65009
|
+
if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
|
|
65010
|
+
const enclosingFunction = findEnclosingFunction$1(reference.identifier);
|
|
65011
|
+
if (!enclosingFunction) continue;
|
|
65012
|
+
for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
|
|
65013
|
+
if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
65014
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
65015
|
+
if (match) {
|
|
65016
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65017
|
+
return match;
|
|
65018
|
+
}
|
|
65019
|
+
}
|
|
65020
|
+
}
|
|
65021
|
+
const readingFunction = findEnclosingFunction$1(unwrappedExpression);
|
|
65022
|
+
for (const reference of symbol.references) {
|
|
65023
|
+
if (reference.flag === "read") continue;
|
|
65024
|
+
const writingFunction = findEnclosingFunction$1(reference.identifier);
|
|
65025
|
+
if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
|
|
65026
|
+
const assignedValue = getAssignedValue(reference.identifier);
|
|
65027
|
+
if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
|
|
65028
|
+
const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
|
|
65029
|
+
if (!isNodeOfType(functionBinding, "Identifier")) continue;
|
|
65030
|
+
const functionSymbol = context.scopes.symbolFor(functionBinding);
|
|
65031
|
+
if (!functionSymbol) continue;
|
|
65032
|
+
for (const functionReference of functionSymbol.references) {
|
|
65033
|
+
const callExpression = functionReference.identifier.parent;
|
|
65034
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
|
|
65035
|
+
for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
|
|
65036
|
+
if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
|
|
65037
|
+
const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
|
|
65038
|
+
if (match) {
|
|
65039
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65040
|
+
return match;
|
|
65041
|
+
}
|
|
65042
|
+
}
|
|
65043
|
+
}
|
|
65044
|
+
}
|
|
65045
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
65046
|
+
}
|
|
64026
65047
|
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
64027
65048
|
state.visitedSymbolIds.add(symbol.id);
|
|
64028
65049
|
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
64029
65050
|
state.visitedSymbolIds.delete(symbol.id);
|
|
64030
65051
|
return match;
|
|
64031
65052
|
}
|
|
65053
|
+
if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
|
|
65054
|
+
const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
|
|
65055
|
+
allowConstNumericLiteral: true,
|
|
65056
|
+
stringifyNonStringLiterals: true
|
|
65057
|
+
});
|
|
65058
|
+
const object = stripParenExpression(unwrappedExpression.object);
|
|
65059
|
+
if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
|
|
65060
|
+
const callArguments = object.arguments ?? [];
|
|
65061
|
+
if (isReactApiCall(object, "useMemo", context.scopes, {
|
|
65062
|
+
allowGlobalReactNamespace: true,
|
|
65063
|
+
resolveNamedAliases: true
|
|
65064
|
+
})) {
|
|
65065
|
+
const callbackArgument = callArguments[0];
|
|
65066
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
65067
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
65068
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
|
|
65069
|
+
}
|
|
65070
|
+
const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
|
|
65071
|
+
return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
|
|
65072
|
+
}
|
|
64032
65073
|
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
64033
65074
|
const callArguments = unwrappedExpression.arguments ?? [];
|
|
65075
|
+
if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
|
|
65076
|
+
allowGlobalReactNamespace: true,
|
|
65077
|
+
resolveNamedAliases: true
|
|
65078
|
+
})) {
|
|
65079
|
+
const initialState = callArguments[0];
|
|
65080
|
+
if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
|
|
65081
|
+
const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
|
|
65082
|
+
return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
|
|
65083
|
+
}
|
|
64034
65084
|
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
64035
65085
|
allowGlobalReactNamespace: true,
|
|
64036
65086
|
resolveNamedAliases: true
|
|
@@ -64058,6 +65108,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
|
|
|
64058
65108
|
});
|
|
64059
65109
|
}
|
|
64060
65110
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
65111
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
65112
|
+
const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
|
|
65113
|
+
if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
|
|
65114
|
+
return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
|
|
65115
|
+
}
|
|
65116
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
|
|
65117
|
+
if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
|
|
65118
|
+
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
65119
|
+
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
65120
|
+
const nestedMatch = leftMatch ?? rightMatch;
|
|
65121
|
+
if (!nestedMatch) return null;
|
|
65122
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
|
|
65123
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
|
|
65124
|
+
if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
|
|
65125
|
+
return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
|
|
65126
|
+
}
|
|
64061
65127
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
64062
65128
|
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
64063
65129
|
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
@@ -64074,8 +65140,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
|
|
|
64074
65140
|
const consequentValues = getReturnedValues(statement.consequent);
|
|
64075
65141
|
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
64076
65142
|
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
65143
|
+
if (conditionMatch) {
|
|
65144
|
+
const followingReturnedValues = findFollowingReturnedValues(statement);
|
|
65145
|
+
if ([...new Set([...collectWrittenSymbols(statement.consequent, context.scopes), ...statement.alternate ? collectWrittenSymbols(statement.alternate, context.scopes) : []])].some((symbol) => !doesGuardPreserveInitialSymbolValue(symbol, statement, context.scopes) && followingReturnedValues.some((value) => doesNodeReadSymbol(value, symbol)))) return conditionMatch;
|
|
65146
|
+
}
|
|
64077
65147
|
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
64078
65148
|
}
|
|
65149
|
+
if (isNodeOfType(statement, "TryStatement")) return matchHydrationReturningStatement(statement.block, context, state) ?? (statement.handler ? matchHydrationReturningStatement(statement.handler.body, context, state) : null) ?? (statement.finalizer ? matchHydrationReturningStatement(statement.finalizer, context, state) : null);
|
|
64079
65150
|
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
64080
65151
|
for (const childStatement of statement.body) {
|
|
64081
65152
|
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
@@ -64096,47 +65167,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
|
|
|
64096
65167
|
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
64097
65168
|
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
64098
65169
|
});
|
|
64099
|
-
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
64100
|
-
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
65170
|
+
const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
|
|
65171
|
+
const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
|
|
64101
65172
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
64102
65173
|
const left = stripParenExpression(leftNode);
|
|
64103
65174
|
const right = stripParenExpression(rightNode);
|
|
64104
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65175
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64105
65176
|
if (left.type !== right.type) return false;
|
|
64106
65177
|
if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
|
|
64107
65178
|
if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
|
|
64108
65179
|
if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
|
|
64109
|
-
return areRenderedBranchesEquivalent(left.expression, right.expression);
|
|
65180
|
+
return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
|
|
64110
65181
|
}
|
|
64111
65182
|
if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
|
|
64112
65183
|
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);
|
|
65184
|
+
if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
|
|
65185
|
+
return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64115
65186
|
}
|
|
64116
|
-
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
|
|
65187
|
+
if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
|
|
64117
65188
|
if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
|
|
64118
65189
|
if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
|
|
64119
|
-
return areRenderedBranchesEquivalent(left.value, right.value);
|
|
65190
|
+
return areRenderedBranchesEquivalent(left.value, right.value, scopes);
|
|
64120
65191
|
}
|
|
64121
|
-
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
|
|
65192
|
+
if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
|
|
64122
65193
|
if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
|
|
64123
65194
|
if (left.quasis.length !== right.quasis.length) return false;
|
|
64124
65195
|
if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
|
|
64125
|
-
return areNodeArraysEquivalent(left.expressions, right.expressions);
|
|
65196
|
+
return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
|
|
64126
65197
|
}
|
|
64127
65198
|
return false;
|
|
64128
65199
|
};
|
|
64129
|
-
const
|
|
65200
|
+
const isProvenReactCreateElementCall = (node, scopes) => {
|
|
65201
|
+
if (isReactApiCall(node, "createElement", scopes, {
|
|
65202
|
+
allowGlobalReactNamespace: true,
|
|
65203
|
+
resolveNamedAliases: true
|
|
65204
|
+
})) return true;
|
|
65205
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
65206
|
+
const callee = stripParenExpression(node.callee);
|
|
65207
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
|
|
65208
|
+
const receiver = stripParenExpression(callee.object);
|
|
65209
|
+
const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
|
|
65210
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
65211
|
+
const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
|
|
65212
|
+
return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
|
|
65213
|
+
};
|
|
65214
|
+
const isRenderedValue = (node, scopes) => {
|
|
64130
65215
|
const unwrappedNode = stripParenExpression(node);
|
|
64131
65216
|
if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
|
|
64132
65217
|
if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
|
|
65218
|
+
if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
|
|
64133
65219
|
return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
|
|
64134
65220
|
};
|
|
64135
|
-
const findRenderedValueInAndBranch = (node) => {
|
|
65221
|
+
const findRenderedValueInAndBranch = (node, scopes) => {
|
|
64136
65222
|
const unwrappedNode = stripParenExpression(node);
|
|
64137
|
-
if (
|
|
65223
|
+
if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
|
|
64138
65224
|
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
|
|
64139
|
-
return findRenderedValueInAndBranch(unwrappedNode.right);
|
|
65225
|
+
return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
|
|
64140
65226
|
};
|
|
64141
65227
|
const findEnclosingJsxAttribute = (node) => {
|
|
64142
65228
|
let currentNode = node.parent;
|
|
@@ -64169,6 +65255,11 @@ const getReturnedValues = (statement) => {
|
|
|
64169
65255
|
if (!statement) return [];
|
|
64170
65256
|
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
|
|
64171
65257
|
if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
|
|
65258
|
+
if (isNodeOfType(statement, "TryStatement")) return [
|
|
65259
|
+
...getReturnedValues(statement.block),
|
|
65260
|
+
...getReturnedValues(statement.handler?.body),
|
|
65261
|
+
...getReturnedValues(statement.finalizer)
|
|
65262
|
+
];
|
|
64172
65263
|
if (!isNodeOfType(statement, "BlockStatement")) return [];
|
|
64173
65264
|
const returnedValues = [];
|
|
64174
65265
|
for (const childStatement of statement.body) {
|
|
@@ -64177,6 +65268,127 @@ const getReturnedValues = (statement) => {
|
|
|
64177
65268
|
}
|
|
64178
65269
|
return returnedValues;
|
|
64179
65270
|
};
|
|
65271
|
+
const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
|
|
65272
|
+
const unwrappedNode = stripParenExpression(node);
|
|
65273
|
+
if (isRenderedValue(unwrappedNode, scopes)) return true;
|
|
65274
|
+
if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
|
|
65275
|
+
if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
|
|
65276
|
+
if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
|
|
65277
|
+
const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
|
|
65278
|
+
if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
|
|
65279
|
+
visitedFunctionNodes.add(calledFunction);
|
|
65280
|
+
const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
|
|
65281
|
+
const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
|
|
65282
|
+
visitedFunctionNodes.delete(calledFunction);
|
|
65283
|
+
return isPotentiallyRendered;
|
|
65284
|
+
};
|
|
65285
|
+
const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
|
|
65286
|
+
const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
|
|
65287
|
+
let currentNode = node.parent;
|
|
65288
|
+
while (currentNode && currentNode !== componentOrHookNode) {
|
|
65289
|
+
if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
|
|
65290
|
+
allowGlobalReactNamespace: true,
|
|
65291
|
+
resolveNamedAliases: true
|
|
65292
|
+
}) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
|
|
65293
|
+
const stateBinding = currentNode.parent.id.elements?.[0];
|
|
65294
|
+
return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
|
|
65295
|
+
}
|
|
65296
|
+
if (isFunctionLike$1(currentNode)) return null;
|
|
65297
|
+
currentNode = currentNode.parent;
|
|
65298
|
+
}
|
|
65299
|
+
return null;
|
|
65300
|
+
};
|
|
65301
|
+
const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
|
|
65302
|
+
let currentNode = referenceIdentifier;
|
|
65303
|
+
let parentNode = currentNode.parent;
|
|
65304
|
+
while (parentNode) {
|
|
65305
|
+
if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
|
|
65306
|
+
if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
|
|
65307
|
+
if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
|
|
65308
|
+
currentNode = parentNode;
|
|
65309
|
+
parentNode = currentNode.parent;
|
|
65310
|
+
}
|
|
65311
|
+
return false;
|
|
65312
|
+
};
|
|
65313
|
+
const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
|
|
65314
|
+
const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
|
|
65315
|
+
return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
|
|
65316
|
+
};
|
|
65317
|
+
const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
|
|
65318
|
+
if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
|
|
65319
|
+
const parentNode = node.parent;
|
|
65320
|
+
if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
|
|
65321
|
+
const aliasSymbol = scopes.symbolFor(parentNode.id);
|
|
65322
|
+
if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
|
|
65323
|
+
visitedSymbolIds.add(aliasSymbol.id);
|
|
65324
|
+
const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
|
|
65325
|
+
visitedSymbolIds.delete(aliasSymbol.id);
|
|
65326
|
+
return doesReachRenderedOutput;
|
|
65327
|
+
};
|
|
65328
|
+
const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
|
|
65329
|
+
if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
|
|
65330
|
+
const consumerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
65331
|
+
if (!consumerSymbol) return false;
|
|
65332
|
+
return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
|
|
65333
|
+
};
|
|
65334
|
+
const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
|
|
65335
|
+
const unwrappedValue = stripParenExpression(returnedValue);
|
|
65336
|
+
if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
|
|
65337
|
+
if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
|
|
65338
|
+
const propertyName = getResolvedStaticPropertyName(property, scopes);
|
|
65339
|
+
return propertyName === null ? [] : [{
|
|
65340
|
+
kind: "property",
|
|
65341
|
+
key: propertyName
|
|
65342
|
+
}];
|
|
65343
|
+
});
|
|
65344
|
+
if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
|
|
65345
|
+
kind: "index",
|
|
65346
|
+
key: String(index)
|
|
65347
|
+
}] : []);
|
|
65348
|
+
return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
|
|
65349
|
+
kind: "direct",
|
|
65350
|
+
key: null
|
|
65351
|
+
}] : [];
|
|
65352
|
+
};
|
|
65353
|
+
const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
|
|
65354
|
+
const callParent = callExpression.parent;
|
|
65355
|
+
if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65356
|
+
if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
|
|
65357
|
+
allowConstNumericLiteral: true,
|
|
65358
|
+
stringifyNonStringLiterals: true
|
|
65359
|
+
}) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
|
|
65360
|
+
if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
|
|
65361
|
+
if (returnedStatePath.kind === "property" && isNodeOfType(callParent.id, "ObjectPattern")) return callParent.id.properties.some((property) => isNodeOfType(property, "Property") && getResolvedStaticPropertyName(property, scopes) === returnedStatePath.key && doesConsumerBindingReachRenderedOutput(property.value, producerHookNode, scopes));
|
|
65362
|
+
if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
|
|
65363
|
+
const element = callParent.id.elements?.[Number(returnedStatePath.key)];
|
|
65364
|
+
return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
|
|
65365
|
+
}
|
|
65366
|
+
if (!isNodeOfType(callParent.id, "Identifier")) return false;
|
|
65367
|
+
const resultSymbol = scopes.symbolFor(callParent.id);
|
|
65368
|
+
if (!resultSymbol) return false;
|
|
65369
|
+
return resultSymbol.references.some((reference) => {
|
|
65370
|
+
const memberExpression = reference.identifier.parent;
|
|
65371
|
+
return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
|
|
65372
|
+
allowConstNumericLiteral: true,
|
|
65373
|
+
stringifyNonStringLiterals: true
|
|
65374
|
+
}) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
|
|
65375
|
+
});
|
|
65376
|
+
};
|
|
65377
|
+
const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
|
|
65378
|
+
if (!isFunctionLike$1(componentOrHookNode)) return false;
|
|
65379
|
+
const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
|
|
65380
|
+
if (!stateSymbol) return false;
|
|
65381
|
+
const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
|
|
65382
|
+
if (returnedStatePaths.length === 0) return false;
|
|
65383
|
+
const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
|
|
65384
|
+
if (!isNodeOfType(functionBinding, "Identifier")) return false;
|
|
65385
|
+
const functionSymbol = scopes.symbolFor(functionBinding);
|
|
65386
|
+
if (!functionSymbol) return false;
|
|
65387
|
+
return functionSymbol.references.some((functionReference) => {
|
|
65388
|
+
const callExpression = functionReference.identifier.parent;
|
|
65389
|
+
return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
|
|
65390
|
+
});
|
|
65391
|
+
};
|
|
64180
65392
|
const findFollowingReturnedValues = (ifStatement) => {
|
|
64181
65393
|
const parentNode = ifStatement.parent;
|
|
64182
65394
|
if (!isNodeOfType(parentNode, "BlockStatement")) return [];
|
|
@@ -64189,24 +65401,24 @@ const findFollowingReturnedValues = (ifStatement) => {
|
|
|
64189
65401
|
}
|
|
64190
65402
|
return returnedValues;
|
|
64191
65403
|
};
|
|
64192
|
-
const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
|
|
65404
|
+
const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
|
|
64193
65405
|
const left = stripParenExpression(leftExpression);
|
|
64194
65406
|
const right = stripParenExpression(rightExpression);
|
|
64195
|
-
if (areExpressionsStructurallyEqual(left, right)) return
|
|
65407
|
+
if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
|
|
64196
65408
|
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);
|
|
65409
|
+
if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
|
|
65410
|
+
if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
65411
|
+
if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
|
|
64200
65412
|
return false;
|
|
64201
65413
|
};
|
|
64202
|
-
const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
|
|
65414
|
+
const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
|
|
64203
65415
|
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);
|
|
65416
|
+
if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
|
|
65417
|
+
if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test, scopes) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent, scopes) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate, scopes);
|
|
64206
65418
|
if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
|
|
64207
65419
|
const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64208
65420
|
const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
|
|
64209
|
-
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
|
|
65421
|
+
return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
|
|
64210
65422
|
};
|
|
64211
65423
|
const isStructuralRenderedValue = (node) => {
|
|
64212
65424
|
if (!node) return false;
|
|
@@ -64230,19 +65442,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64230
65442
|
if (isTestlikeFilename(context.filename)) return {};
|
|
64231
65443
|
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
64232
65444
|
let fileHasUseClientDirective = false;
|
|
65445
|
+
let fileHasExplicitReactRuntimeReference = false;
|
|
64233
65446
|
let fileIsEmailTemplate = false;
|
|
64234
65447
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
64235
|
-
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
|
|
65448
|
+
const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
|
|
64236
65449
|
const conditionMatch = matchHydrationCondition(conditionNode, context);
|
|
64237
65450
|
if (!conditionMatch) return;
|
|
64238
65451
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
64239
65452
|
if (reportedNodes.has(predicateNode)) return;
|
|
64240
|
-
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
64241
|
-
const
|
|
65453
|
+
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
|
|
65454
|
+
const enclosingFunction = findEnclosingFunction$1(conditionNode);
|
|
65455
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
64242
65456
|
if (!componentOrHookNode) return;
|
|
64243
|
-
|
|
64244
|
-
if (
|
|
64245
|
-
if (!
|
|
65457
|
+
const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
|
|
65458
|
+
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
|
|
65459
|
+
if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
|
|
65460
|
+
if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
|
|
64246
65461
|
const attribute = findEnclosingJsxAttribute(conditionNode);
|
|
64247
65462
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
64248
65463
|
}
|
|
@@ -64261,28 +65476,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
64261
65476
|
return {
|
|
64262
65477
|
Program(node) {
|
|
64263
65478
|
fileHasUseClientDirective = hasDirective(node, "use client");
|
|
65479
|
+
fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
|
|
64264
65480
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
64265
65481
|
},
|
|
64266
65482
|
ConditionalExpression(node) {
|
|
64267
65483
|
reportHydrationBranch(node.test, node.consequent, node.alternate, true);
|
|
65484
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
65485
|
+
if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
|
|
64268
65486
|
},
|
|
64269
65487
|
LogicalExpression(node) {
|
|
64270
65488
|
if (node.operator !== "&&" && node.operator !== "||") return;
|
|
64271
|
-
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) :
|
|
65489
|
+
const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
|
|
64272
65490
|
if (!renderedValue) return;
|
|
64273
65491
|
reportHydrationBranch(node, renderedValue, null, true);
|
|
64274
65492
|
},
|
|
64275
65493
|
IfStatement(node) {
|
|
64276
|
-
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
|
|
65494
|
+
if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
|
|
64277
65495
|
const consequentValues = getReturnedValues(node.consequent);
|
|
64278
65496
|
const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
|
|
64279
65497
|
if (consequentValues.length === 0 || alternateValues.length === 0) return;
|
|
64280
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
|
|
64281
|
-
if (!componentOrHookNode) return;
|
|
64282
65498
|
const enclosingFunction = findEnclosingFunction$1(node);
|
|
64283
|
-
|
|
65499
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
|
|
65500
|
+
if (!componentOrHookNode) return;
|
|
65501
|
+
if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
|
|
64284
65502
|
for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
|
|
64285
|
-
if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
|
|
65503
|
+
if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
|
|
64286
65504
|
reportHydrationBranch(node.test, consequentValue, alternateValue, false);
|
|
64287
65505
|
}
|
|
64288
65506
|
}
|
|
@@ -66016,6 +67234,124 @@ const isInsideSnapshotHelper = (node) => {
|
|
|
66016
67234
|
}
|
|
66017
67235
|
return false;
|
|
66018
67236
|
};
|
|
67237
|
+
const findEnclosingNextjsPageDataFunction = (node) => {
|
|
67238
|
+
let outermostFunction = null;
|
|
67239
|
+
let cursor = node.parent;
|
|
67240
|
+
while (cursor) {
|
|
67241
|
+
if (isFunctionLike$1(cursor)) outermostFunction = cursor;
|
|
67242
|
+
if (isNodeOfType(cursor, "Program")) {
|
|
67243
|
+
if (!outermostFunction) return null;
|
|
67244
|
+
for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
|
|
67245
|
+
const exportedValue = findExportedValue(cursor, exportName);
|
|
67246
|
+
if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
|
|
67247
|
+
}
|
|
67248
|
+
return null;
|
|
67249
|
+
}
|
|
67250
|
+
cursor = cursor.parent ?? null;
|
|
67251
|
+
}
|
|
67252
|
+
return null;
|
|
67253
|
+
};
|
|
67254
|
+
const findConditionalReturnExpressionRoot = (node) => {
|
|
67255
|
+
let expressionRoot = findTransparentExpressionRoot(node);
|
|
67256
|
+
while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
|
|
67257
|
+
return expressionRoot;
|
|
67258
|
+
};
|
|
67259
|
+
const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
|
|
67260
|
+
const declarator = returnExpression.parent;
|
|
67261
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
|
|
67262
|
+
const bindingSymbol = context.scopes.symbolFor(declarator.id);
|
|
67263
|
+
if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
|
|
67264
|
+
const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
|
|
67265
|
+
const returnStatement = referenceRoot.parent;
|
|
67266
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
|
|
67267
|
+
};
|
|
67268
|
+
const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
|
|
67269
|
+
const isValueForwardedThroughLiteralStructure = (node, structure) => {
|
|
67270
|
+
const strippedNode = stripParenExpression(node);
|
|
67271
|
+
const strippedStructure = stripParenExpression(structure);
|
|
67272
|
+
if (strippedNode === strippedStructure) return true;
|
|
67273
|
+
if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
|
|
67274
|
+
if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
|
|
67275
|
+
if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
|
|
67276
|
+
return strippedStructure.properties.some((property) => {
|
|
67277
|
+
if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
|
|
67278
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
67279
|
+
if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
|
|
67280
|
+
return isSameShorthandPropertyValue(strippedNode, property);
|
|
67281
|
+
});
|
|
67282
|
+
};
|
|
67283
|
+
const isValueForwardedToPropertyValue = (node, property) => {
|
|
67284
|
+
const directValue = findConditionalReturnExpressionRoot(node);
|
|
67285
|
+
if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
|
|
67286
|
+
return isSameShorthandPropertyValue(directValue, property);
|
|
67287
|
+
};
|
|
67288
|
+
const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
|
|
67289
|
+
let cursor = node.parent;
|
|
67290
|
+
while (cursor && cursor !== pageDataFunction) {
|
|
67291
|
+
if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
|
|
67292
|
+
const propertyContainer = cursor.parent;
|
|
67293
|
+
if (!propertyContainer) return false;
|
|
67294
|
+
const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
|
|
67295
|
+
const returnStatement = returnExpression.parent;
|
|
67296
|
+
if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
|
|
67297
|
+
if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
|
|
67298
|
+
if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
|
|
67299
|
+
}
|
|
67300
|
+
cursor = cursor.parent ?? null;
|
|
67301
|
+
}
|
|
67302
|
+
return false;
|
|
67303
|
+
};
|
|
67304
|
+
const isExpressionReturnedByFunction = (node, functionNode) => {
|
|
67305
|
+
const returnExpression = findConditionalReturnExpressionRoot(node);
|
|
67306
|
+
if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
|
|
67307
|
+
const returnStatement = returnExpression.parent;
|
|
67308
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
|
|
67309
|
+
};
|
|
67310
|
+
const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
|
|
67311
|
+
if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
|
|
67312
|
+
const initializer = stripParenExpression(bindingInitializer);
|
|
67313
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
67314
|
+
const callee = stripParenExpression(initializer.callee);
|
|
67315
|
+
return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
|
|
67316
|
+
};
|
|
67317
|
+
const findPageDataResultBinding = (node) => {
|
|
67318
|
+
let cursor = node.parent;
|
|
67319
|
+
while (cursor) {
|
|
67320
|
+
if (isNodeOfType(cursor, "VariableDeclarator")) {
|
|
67321
|
+
if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
|
|
67322
|
+
return null;
|
|
67323
|
+
}
|
|
67324
|
+
cursor = cursor.parent ?? null;
|
|
67325
|
+
}
|
|
67326
|
+
return null;
|
|
67327
|
+
};
|
|
67328
|
+
const isUsedToSerializeNextjsPageProps = (node, context) => {
|
|
67329
|
+
if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
|
|
67330
|
+
const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
|
|
67331
|
+
if (!pageDataFunction) return false;
|
|
67332
|
+
if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
|
|
67333
|
+
const bindingIdentifier = findPageDataResultBinding(node);
|
|
67334
|
+
const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
67335
|
+
if (!bindingSymbol) return false;
|
|
67336
|
+
const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
|
|
67337
|
+
const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
|
|
67338
|
+
let hasPagePropsReference = false;
|
|
67339
|
+
for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
|
|
67340
|
+
if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
|
|
67341
|
+
if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
|
|
67342
|
+
hasPagePropsReference = true;
|
|
67343
|
+
continue;
|
|
67344
|
+
}
|
|
67345
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
67346
|
+
const declarator = referenceRoot.parent;
|
|
67347
|
+
if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
|
|
67348
|
+
const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
|
|
67349
|
+
if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
|
|
67350
|
+
}
|
|
67351
|
+
return false;
|
|
67352
|
+
}
|
|
67353
|
+
return hasPagePropsReference;
|
|
67354
|
+
};
|
|
66019
67355
|
const noJsonParseStringifyClone = defineRule({
|
|
66020
67356
|
id: "no-json-parse-stringify-clone",
|
|
66021
67357
|
title: "JSON parse/stringify deep clone",
|
|
@@ -66033,6 +67369,7 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
66033
67369
|
if (isInsideSnapshotHelper(node)) return;
|
|
66034
67370
|
if (isAssignedToNormalizationBinding(node)) return;
|
|
66035
67371
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
67372
|
+
if (isUsedToSerializeNextjsPageProps(node, context)) return;
|
|
66036
67373
|
context.report({
|
|
66037
67374
|
node,
|
|
66038
67375
|
message: MESSAGE$35
|
|
@@ -67448,6 +68785,77 @@ const isCancellationGuardTest = (test) => {
|
|
|
67448
68785
|
});
|
|
67449
68786
|
return matches;
|
|
67450
68787
|
};
|
|
68788
|
+
const getReactRefCurrent = (expression, context) => {
|
|
68789
|
+
const stripped = stripParenExpression(expression);
|
|
68790
|
+
if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
|
|
68791
|
+
const receiver = stripParenExpression(stripped.object);
|
|
68792
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
68793
|
+
const binding = findVariableInitializer(receiver, receiver.name);
|
|
68794
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
68795
|
+
return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
|
|
68796
|
+
allowGlobalReactNamespace: true,
|
|
68797
|
+
allowUnboundBareCalls: true
|
|
68798
|
+
}) ? stripped : null;
|
|
68799
|
+
};
|
|
68800
|
+
const getStableOwnershipToken = (expression, context) => {
|
|
68801
|
+
const stripped = stripParenExpression(expression);
|
|
68802
|
+
if (!isNodeOfType(stripped, "Identifier")) return null;
|
|
68803
|
+
const symbol = context.scopes.symbolFor(stripped);
|
|
68804
|
+
const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
68805
|
+
const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
|
|
68806
|
+
return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
|
|
68807
|
+
};
|
|
68808
|
+
const getAsyncOwnershipComparison = (test, context) => {
|
|
68809
|
+
const stripped = stripParenExpression(test);
|
|
68810
|
+
if (!isNodeOfType(stripped, "BinaryExpression")) return null;
|
|
68811
|
+
const leftRef = getReactRefCurrent(stripped.left, context);
|
|
68812
|
+
const rightRef = getReactRefCurrent(stripped.right, context);
|
|
68813
|
+
const leftToken = getStableOwnershipToken(stripped.left, context);
|
|
68814
|
+
const rightToken = getStableOwnershipToken(stripped.right, context);
|
|
68815
|
+
if (stripped.operator === "===" || stripped.operator === "==") {
|
|
68816
|
+
if (leftRef && rightToken) return {
|
|
68817
|
+
refCurrent: leftRef,
|
|
68818
|
+
token: rightToken,
|
|
68819
|
+
mode: "owns",
|
|
68820
|
+
isOrdered: false
|
|
68821
|
+
};
|
|
68822
|
+
if (rightRef && leftToken) return {
|
|
68823
|
+
refCurrent: rightRef,
|
|
68824
|
+
token: leftToken,
|
|
68825
|
+
mode: "owns",
|
|
68826
|
+
isOrdered: false
|
|
68827
|
+
};
|
|
68828
|
+
return null;
|
|
68829
|
+
}
|
|
68830
|
+
if (stripped.operator === "!==" || stripped.operator === "!=") {
|
|
68831
|
+
if (leftRef && rightToken) return {
|
|
68832
|
+
refCurrent: leftRef,
|
|
68833
|
+
token: rightToken,
|
|
68834
|
+
mode: "lost",
|
|
68835
|
+
isOrdered: false
|
|
68836
|
+
};
|
|
68837
|
+
if (rightRef && leftToken) return {
|
|
68838
|
+
refCurrent: rightRef,
|
|
68839
|
+
token: leftToken,
|
|
68840
|
+
mode: "lost",
|
|
68841
|
+
isOrdered: false
|
|
68842
|
+
};
|
|
68843
|
+
return null;
|
|
68844
|
+
}
|
|
68845
|
+
if (stripped.operator === "<=" && leftRef && rightToken) return {
|
|
68846
|
+
refCurrent: leftRef,
|
|
68847
|
+
token: rightToken,
|
|
68848
|
+
mode: "owns",
|
|
68849
|
+
isOrdered: true
|
|
68850
|
+
};
|
|
68851
|
+
if (stripped.operator === ">=" && rightRef && leftToken) return {
|
|
68852
|
+
refCurrent: rightRef,
|
|
68853
|
+
token: leftToken,
|
|
68854
|
+
mode: "owns",
|
|
68855
|
+
isOrdered: true
|
|
68856
|
+
};
|
|
68857
|
+
return null;
|
|
68858
|
+
};
|
|
67451
68859
|
const dedupeCatchPathStates = (states) => {
|
|
67452
68860
|
const statesByKey = /* @__PURE__ */ new Map();
|
|
67453
68861
|
for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
|
|
@@ -67780,7 +69188,221 @@ const isInsideTryFinalizer = (node, tryStatement) => {
|
|
|
67780
69188
|
}
|
|
67781
69189
|
return false;
|
|
67782
69190
|
};
|
|
67783
|
-
const
|
|
69191
|
+
const getDirectBlockEntry = (node, functionNode) => {
|
|
69192
|
+
let entry = node;
|
|
69193
|
+
let cursor = node.parent;
|
|
69194
|
+
while (cursor && cursor !== functionNode) {
|
|
69195
|
+
if (isNodeOfType(cursor, "BlockStatement")) return {
|
|
69196
|
+
block: cursor,
|
|
69197
|
+
entry
|
|
69198
|
+
};
|
|
69199
|
+
entry = cursor;
|
|
69200
|
+
cursor = cursor.parent ?? null;
|
|
69201
|
+
}
|
|
69202
|
+
return null;
|
|
69203
|
+
};
|
|
69204
|
+
const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
|
|
69205
|
+
const claimStart = getNodeStart$1(claimNode);
|
|
69206
|
+
if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
|
|
69207
|
+
const claimEntry = getDirectBlockEntry(claimNode, functionNode);
|
|
69208
|
+
const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
|
|
69209
|
+
if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
|
|
69210
|
+
let claimCursor = claimNode.parent;
|
|
69211
|
+
while (claimCursor && claimCursor !== claimEntry.block) {
|
|
69212
|
+
if (isNodeOfType(claimCursor, "IfStatement") || isNodeOfType(claimCursor, "SwitchCase") || isNodeOfType(claimCursor, "ConditionalExpression") || isNodeOfType(claimCursor, "LogicalExpression") || isNodeOfType(claimCursor, "ForStatement") || isNodeOfType(claimCursor, "ForInStatement") || isNodeOfType(claimCursor, "ForOfStatement") || isNodeOfType(claimCursor, "WhileStatement") || isNodeOfType(claimCursor, "DoWhileStatement")) return false;
|
|
69213
|
+
claimCursor = claimCursor.parent ?? null;
|
|
69214
|
+
}
|
|
69215
|
+
const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
|
|
69216
|
+
const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
|
|
69217
|
+
if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
|
|
69218
|
+
return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
|
|
69219
|
+
};
|
|
69220
|
+
const getOwningFunction = (functionNode) => {
|
|
69221
|
+
let ownerFunction = functionNode;
|
|
69222
|
+
let cursor = functionNode.parent;
|
|
69223
|
+
while (cursor) {
|
|
69224
|
+
if (isFunctionLike$1(cursor)) ownerFunction = cursor;
|
|
69225
|
+
cursor = cursor.parent ?? null;
|
|
69226
|
+
}
|
|
69227
|
+
return ownerFunction;
|
|
69228
|
+
};
|
|
69229
|
+
const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
|
|
69230
|
+
const truthyCall = truthySets[0]?.node;
|
|
69231
|
+
if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
|
|
69232
|
+
const setter = getSetterBooleanValue(truthyCall, context);
|
|
69233
|
+
if (!setter) return false;
|
|
69234
|
+
let effectCallback = writeNode.parent;
|
|
69235
|
+
while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
|
|
69236
|
+
if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
|
|
69237
|
+
if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
|
|
69238
|
+
const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
|
|
69239
|
+
if (!writeEntry) return false;
|
|
69240
|
+
let isPaired = false;
|
|
69241
|
+
walkOwnFunctionScope(effectCallback, (candidate) => {
|
|
69242
|
+
if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
|
|
69243
|
+
const candidateSetter = getSetterBooleanValue(candidate, context);
|
|
69244
|
+
if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
|
|
69245
|
+
const resetEntry = getDirectBlockEntry(candidate, effectCallback);
|
|
69246
|
+
if (!resetEntry || resetEntry.block !== writeEntry.block) return;
|
|
69247
|
+
const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
|
|
69248
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69249
|
+
if (writeIndex === -1 || resetIndex === -1) return;
|
|
69250
|
+
if (resetIndex <= writeIndex) {
|
|
69251
|
+
isPaired = true;
|
|
69252
|
+
return false;
|
|
69253
|
+
}
|
|
69254
|
+
isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
|
|
69255
|
+
return isPaired ? false : void 0;
|
|
69256
|
+
});
|
|
69257
|
+
return isPaired;
|
|
69258
|
+
};
|
|
69259
|
+
const isUnconditionalReturnBranch = (statement) => {
|
|
69260
|
+
if (isNodeOfType(statement, "ReturnStatement")) return true;
|
|
69261
|
+
return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
|
|
69262
|
+
};
|
|
69263
|
+
const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69264
|
+
const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
|
|
69265
|
+
const resetEntry = getDirectBlockEntry(resetNode, functionNode);
|
|
69266
|
+
if (!snapshotEntry || !resetEntry) return null;
|
|
69267
|
+
const claimCandidates = [];
|
|
69268
|
+
const releaseCandidates = [];
|
|
69269
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69270
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
|
|
69271
|
+
const assignedValue = stripParenExpression(candidate.right);
|
|
69272
|
+
if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
|
|
69273
|
+
const candidateKey = serializeReferenceKey({
|
|
69274
|
+
node: candidate.left,
|
|
69275
|
+
scopes: context.scopes
|
|
69276
|
+
});
|
|
69277
|
+
if (!candidateKey) return;
|
|
69278
|
+
if (!assignedValue.value) {
|
|
69279
|
+
if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
|
|
69280
|
+
return;
|
|
69281
|
+
}
|
|
69282
|
+
if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
|
|
69283
|
+
const candidateEntry = getDirectBlockEntry(candidate, functionNode);
|
|
69284
|
+
if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
|
|
69285
|
+
const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
|
|
69286
|
+
const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
|
|
69287
|
+
if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
|
|
69288
|
+
const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
|
|
69289
|
+
if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
|
|
69290
|
+
return serializeReferenceKey({
|
|
69291
|
+
node: stripParenExpression(statement.test),
|
|
69292
|
+
scopes: context.scopes
|
|
69293
|
+
}) === candidateKey;
|
|
69294
|
+
});
|
|
69295
|
+
if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
|
|
69296
|
+
claimCandidates.push(candidate);
|
|
69297
|
+
});
|
|
69298
|
+
const claim = claimCandidates.find((claimCandidate) => {
|
|
69299
|
+
const candidateKey = serializeReferenceKey({
|
|
69300
|
+
node: claimCandidate.left,
|
|
69301
|
+
scopes: context.scopes
|
|
69302
|
+
});
|
|
69303
|
+
return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
|
|
69304
|
+
node: releaseCandidate.left,
|
|
69305
|
+
scopes: context.scopes
|
|
69306
|
+
}) === candidateKey);
|
|
69307
|
+
});
|
|
69308
|
+
if (!claim) return null;
|
|
69309
|
+
const claimKey = serializeReferenceKey({
|
|
69310
|
+
node: claim.left,
|
|
69311
|
+
scopes: context.scopes
|
|
69312
|
+
});
|
|
69313
|
+
const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
|
|
69314
|
+
node: releaseCandidate.left,
|
|
69315
|
+
scopes: context.scopes
|
|
69316
|
+
}) === claimKey);
|
|
69317
|
+
if (!claimKey || !release) return null;
|
|
69318
|
+
const releaseEntry = getDirectBlockEntry(release, functionNode);
|
|
69319
|
+
if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
|
|
69320
|
+
const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
|
|
69321
|
+
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69322
|
+
if (releaseIndex === -1 || resetIndex === -1 || !resetEntry.block.body.slice(Math.min(releaseIndex, resetIndex) + 1, Math.max(releaseIndex, resetIndex)).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return null;
|
|
69323
|
+
let didFindUnsafeWrite = false;
|
|
69324
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69325
|
+
if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
|
|
69326
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69327
|
+
if (writeTarget && serializeReferenceKey({
|
|
69328
|
+
node: writeTarget,
|
|
69329
|
+
scopes: context.scopes
|
|
69330
|
+
}) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
|
|
69331
|
+
});
|
|
69332
|
+
return didFindUnsafeWrite ? null : claim;
|
|
69333
|
+
};
|
|
69334
|
+
const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69335
|
+
const refKey = serializeReferenceKey({
|
|
69336
|
+
node: comparison.refCurrent,
|
|
69337
|
+
scopes: context.scopes
|
|
69338
|
+
});
|
|
69339
|
+
const tokenKey = serializeReferenceKey({
|
|
69340
|
+
node: comparison.token,
|
|
69341
|
+
scopes: context.scopes
|
|
69342
|
+
});
|
|
69343
|
+
if (!refKey || !tokenKey) return null;
|
|
69344
|
+
const candidates = [];
|
|
69345
|
+
const tokenSymbol = context.scopes.symbolFor(comparison.token);
|
|
69346
|
+
const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
|
|
69347
|
+
if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
|
|
69348
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
|
|
69349
|
+
node: tokenInitializer.argument,
|
|
69350
|
+
scopes: context.scopes
|
|
69351
|
+
}) === refKey) candidates.push(tokenInitializer);
|
|
69352
|
+
if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
|
|
69353
|
+
node: tokenInitializer,
|
|
69354
|
+
scopes: context.scopes
|
|
69355
|
+
}) === refKey) {
|
|
69356
|
+
const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69357
|
+
if (singleFlightClaim) candidates.push(singleFlightClaim);
|
|
69358
|
+
}
|
|
69359
|
+
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
|
|
69360
|
+
const generationKey = serializeReferenceKey({
|
|
69361
|
+
node: tokenInitializer.argument,
|
|
69362
|
+
scopes: context.scopes
|
|
69363
|
+
});
|
|
69364
|
+
if (generationKey && generationKey === refKey) {
|
|
69365
|
+
const ownerFunction = getOwningFunction(functionNode);
|
|
69366
|
+
let didFindOtherGenerationWrite = false;
|
|
69367
|
+
walkAst(ownerFunction, (candidate) => {
|
|
69368
|
+
if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
|
|
69369
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69370
|
+
if (writeTarget && serializeReferenceKey({
|
|
69371
|
+
node: writeTarget,
|
|
69372
|
+
scopes: context.scopes
|
|
69373
|
+
}) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
|
|
69374
|
+
});
|
|
69375
|
+
if (didFindOtherGenerationWrite) return null;
|
|
69376
|
+
}
|
|
69377
|
+
}
|
|
69378
|
+
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69379
|
+
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
|
|
69380
|
+
if (serializeReferenceKey({
|
|
69381
|
+
node: candidate.left,
|
|
69382
|
+
scopes: context.scopes
|
|
69383
|
+
}) === refKey && serializeReferenceKey({
|
|
69384
|
+
node: candidate.right,
|
|
69385
|
+
scopes: context.scopes
|
|
69386
|
+
}) === tokenKey) candidates.push(candidate);
|
|
69387
|
+
});
|
|
69388
|
+
const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
|
|
69389
|
+
if (!claim) return null;
|
|
69390
|
+
let didFindOtherWrite = false;
|
|
69391
|
+
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69392
|
+
if (didFindOtherWrite || candidate === claim) return;
|
|
69393
|
+
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69394
|
+
if (writeTarget && serializeReferenceKey({
|
|
69395
|
+
node: writeTarget,
|
|
69396
|
+
scopes: context.scopes
|
|
69397
|
+
}) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
|
|
69398
|
+
});
|
|
69399
|
+
return didFindOtherWrite ? null : claim;
|
|
69400
|
+
};
|
|
69401
|
+
const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69402
|
+
const comparison = getAsyncOwnershipComparison(test, context);
|
|
69403
|
+
return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
|
|
69404
|
+
};
|
|
69405
|
+
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
|
|
67784
69406
|
let didFindOtherWrite = false;
|
|
67785
69407
|
walkAst(effectCallback, (candidate) => {
|
|
67786
69408
|
if (didFindOtherWrite) return false;
|
|
@@ -67788,7 +69410,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
67788
69410
|
if (serializeReferenceKey({
|
|
67789
69411
|
node: candidate.left,
|
|
67790
69412
|
scopes: context.scopes
|
|
67791
|
-
}) === guardKey && !
|
|
69413
|
+
}) === guardKey && !acceptedAssignments.has(candidate)) {
|
|
67792
69414
|
didFindOtherWrite = true;
|
|
67793
69415
|
return false;
|
|
67794
69416
|
}
|
|
@@ -67804,48 +69426,103 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
67804
69426
|
});
|
|
67805
69427
|
return didFindOtherWrite;
|
|
67806
69428
|
};
|
|
67807
|
-
const
|
|
69429
|
+
const collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
|
|
69430
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69431
|
+
for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69432
|
+
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69433
|
+
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69434
|
+
node: cleanupNode.left,
|
|
69435
|
+
scopes: context.scopes
|
|
69436
|
+
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69437
|
+
acceptedAssignments.add(cleanupNode);
|
|
69438
|
+
});
|
|
69439
|
+
if (acceptedAssignments.size === 0) return null;
|
|
69440
|
+
walkOwnFunctionScope(effectCallback, (effectNode) => {
|
|
69441
|
+
const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
|
|
69442
|
+
if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
|
|
69443
|
+
node: effectNode.left,
|
|
69444
|
+
scopes: context.scopes
|
|
69445
|
+
}) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
|
|
69446
|
+
});
|
|
69447
|
+
return acceptedAssignments;
|
|
69448
|
+
};
|
|
69449
|
+
const isEffectCallback = (node, context) => {
|
|
69450
|
+
const callbackRoot = findTransparentExpressionRoot(node);
|
|
69451
|
+
const callbackCall = callbackRoot.parent;
|
|
69452
|
+
return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69453
|
+
allowGlobalReactNamespace: true,
|
|
69454
|
+
allowUnboundBareCalls: true
|
|
69455
|
+
}));
|
|
69456
|
+
};
|
|
69457
|
+
const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
|
|
69458
|
+
const guardKey = serializeReferenceKey({
|
|
69459
|
+
node: guardExpression,
|
|
69460
|
+
scopes: context.scopes
|
|
69461
|
+
});
|
|
69462
|
+
if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69463
|
+
let ownerFunction = functionNode.parent;
|
|
69464
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
69465
|
+
if (!ownerFunction) return false;
|
|
69466
|
+
const effectCallbacks = [];
|
|
69467
|
+
if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
|
|
69468
|
+
walkOwnFunctionScope(ownerFunction, (candidate) => {
|
|
69469
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
69470
|
+
if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69471
|
+
allowGlobalReactNamespace: true,
|
|
69472
|
+
allowUnboundBareCalls: true
|
|
69473
|
+
})) return;
|
|
69474
|
+
const effectCallback = candidate.arguments[0];
|
|
69475
|
+
if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
|
|
69476
|
+
});
|
|
69477
|
+
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69478
|
+
for (const effectCallback of effectCallbacks) {
|
|
69479
|
+
const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
|
|
69480
|
+
if (!effectAssignments) continue;
|
|
69481
|
+
for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
|
|
69482
|
+
}
|
|
69483
|
+
return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
|
|
69484
|
+
};
|
|
69485
|
+
const collectLogicalOperands = (expression, operator) => {
|
|
69486
|
+
const stripped = stripParenExpression(expression);
|
|
69487
|
+
if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
|
|
69488
|
+
return [stripped];
|
|
69489
|
+
};
|
|
69490
|
+
const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
|
|
69491
|
+
const positive = [];
|
|
69492
|
+
const negative = [];
|
|
67808
69493
|
let child = resetNode;
|
|
67809
69494
|
let cursor = resetNode.parent;
|
|
67810
|
-
|
|
67811
|
-
|
|
67812
|
-
|
|
67813
|
-
|
|
67814
|
-
|
|
67815
|
-
|
|
67816
|
-
|
|
67817
|
-
|
|
67818
|
-
|
|
67819
|
-
|
|
67820
|
-
|
|
69495
|
+
while (cursor && cursor !== protectingTry) {
|
|
69496
|
+
if (isNodeOfType(cursor, "IfStatement")) {
|
|
69497
|
+
if (cursor.consequent !== child || cursor.alternate !== null) return null;
|
|
69498
|
+
positive.push(...collectLogicalOperands(cursor.test, "&&"));
|
|
69499
|
+
} else if (isNodeOfType(cursor, "LogicalExpression")) {
|
|
69500
|
+
if (cursor.operator !== "&&" || cursor.right !== child) return null;
|
|
69501
|
+
positive.push(...collectLogicalOperands(cursor.left, "&&"));
|
|
69502
|
+
} else if (isNodeOfType(cursor, "BlockStatement")) {
|
|
69503
|
+
const childIndex = cursor.body.findIndex((statement) => statement === child);
|
|
69504
|
+
if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
|
|
69505
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
|
|
69506
|
+
negative.push(...collectLogicalOperands(statement.test, "||"));
|
|
69507
|
+
}
|
|
69508
|
+
} else if (isNodeOfType(cursor, "SwitchCase") || isNodeOfType(cursor, "ConditionalExpression") || isNodeOfType(cursor, "ForStatement") || isNodeOfType(cursor, "ForInStatement") || isNodeOfType(cursor, "ForOfStatement") || isNodeOfType(cursor, "WhileStatement") || isNodeOfType(cursor, "DoWhileStatement")) return null;
|
|
67821
69509
|
child = cursor;
|
|
67822
69510
|
cursor = cursor.parent ?? null;
|
|
67823
69511
|
}
|
|
67824
|
-
|
|
67825
|
-
|
|
67826
|
-
|
|
67827
|
-
|
|
67828
|
-
|
|
67829
|
-
|
|
67830
|
-
|
|
67831
|
-
|
|
67832
|
-
|
|
67833
|
-
|
|
67834
|
-
|
|
67835
|
-
|
|
67836
|
-
|
|
67837
|
-
|
|
67838
|
-
node: cleanupNode.left,
|
|
67839
|
-
scopes: context.scopes
|
|
67840
|
-
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
67841
|
-
acceptedCleanupAssignments.add(cleanupNode);
|
|
67842
|
-
});
|
|
67843
|
-
if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
|
|
67844
|
-
}
|
|
67845
|
-
}
|
|
67846
|
-
cursor = cursor.parent ?? null;
|
|
67847
|
-
}
|
|
67848
|
-
return false;
|
|
69512
|
+
return cursor === protectingTry && positive.length + negative.length > 0 ? {
|
|
69513
|
+
positive,
|
|
69514
|
+
negative
|
|
69515
|
+
} : null;
|
|
69516
|
+
};
|
|
69517
|
+
const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69518
|
+
const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69519
|
+
const stripped = stripParenExpression(expression);
|
|
69520
|
+
if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
|
|
69521
|
+
return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69522
|
+
};
|
|
69523
|
+
const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
|
|
69524
|
+
const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
|
|
69525
|
+
return Boolean(guards && guards.positive.every((guard) => isPositiveFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)) && guards.negative.every((guard) => isNegativeFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)));
|
|
67849
69526
|
};
|
|
67850
69527
|
const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
|
|
67851
69528
|
let child = awaitNode;
|
|
@@ -67951,7 +69628,13 @@ const analyzeFunction = (functionNode, context) => {
|
|
|
67951
69628
|
const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
|
|
67952
69629
|
const riskyAwaitsWithTruthySet = awaitSites.filter((awaitSite) => rejectingAwaitNodes.has(awaitSite.node) && !exceptionallyProtectedAwaits.has(awaitSite.node) && truthySets.some((truthySet) => truthySet.start < awaitSite.start && !areOnExclusiveBranches(truthySet.node, awaitSite.node, functionNode)));
|
|
67953
69630
|
if (riskyAwaitsWithTruthySet.length === 0) continue;
|
|
67954
|
-
const conditionalExceptionalResets = calls.filter((call) =>
|
|
69631
|
+
const conditionalExceptionalResets = calls.filter((call) => {
|
|
69632
|
+
if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
|
|
69633
|
+
const protectingTry = call.protectingTry;
|
|
69634
|
+
if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
|
|
69635
|
+
const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
|
|
69636
|
+
return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
|
|
69637
|
+
});
|
|
67955
69638
|
for (const reset of conditionalExceptionalResets) {
|
|
67956
69639
|
const catchHandler = reset.protectingTry?.handler;
|
|
67957
69640
|
if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
|
|
@@ -73823,6 +75506,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
73823
75506
|
}
|
|
73824
75507
|
return !isNegated && predicateFunction.body === child;
|
|
73825
75508
|
};
|
|
75509
|
+
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75510
|
+
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75511
|
+
const returnStatement = predicateFunction.body.body[1];
|
|
75512
|
+
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75513
|
+
let negationCount = 0;
|
|
75514
|
+
let expression = matchCall;
|
|
75515
|
+
let parent = expression.parent ?? null;
|
|
75516
|
+
while (parent && parent !== returnStatement) {
|
|
75517
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75518
|
+
negationCount += 1;
|
|
75519
|
+
expression = parent;
|
|
75520
|
+
parent = parent.parent ?? null;
|
|
75521
|
+
continue;
|
|
75522
|
+
}
|
|
75523
|
+
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75524
|
+
expression = parent;
|
|
75525
|
+
parent = parent.parent ?? null;
|
|
75526
|
+
continue;
|
|
75527
|
+
}
|
|
75528
|
+
return false;
|
|
75529
|
+
}
|
|
75530
|
+
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75531
|
+
};
|
|
73826
75532
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
73827
75533
|
const target = stripParenExpression(test);
|
|
73828
75534
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -73863,6 +75569,26 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
73863
75569
|
current = current.object;
|
|
73864
75570
|
}
|
|
73865
75571
|
};
|
|
75572
|
+
const getNormalizedClassNameRoot = (expression) => {
|
|
75573
|
+
const conditional = stripParenExpression(expression);
|
|
75574
|
+
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75575
|
+
const consequent = stripParenExpression(conditional.consequent);
|
|
75576
|
+
const rootIdentifier = getRootIdentifier(consequent);
|
|
75577
|
+
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75578
|
+
const test = stripParenExpression(conditional.test);
|
|
75579
|
+
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75580
|
+
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75581
|
+
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75582
|
+
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75583
|
+
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75584
|
+
const alternate = stripParenExpression(conditional.alternate);
|
|
75585
|
+
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75586
|
+
const fallback = stripParenExpression(alternate.right);
|
|
75587
|
+
const attributeCall = stripParenExpression(alternate.left);
|
|
75588
|
+
if (!isNodeOfType(fallback, "Literal") || fallback.value !== "" || !isNodeOfType(attributeCall, "CallExpression") || !isNodeOfType(attributeCall.callee, "MemberExpression") || getStaticPropertyName(attributeCall.callee) !== "getAttribute" || receiverPathKey(attributeCall.callee.object) !== rootIdentifier.name) return null;
|
|
75589
|
+
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75590
|
+
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75591
|
+
};
|
|
73866
75592
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
73867
75593
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
73868
75594
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -73871,11 +75597,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73871
75597
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
73872
75598
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
73873
75599
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
73874
|
-
if (resultSymbol?.kind !== "const" || !initializer
|
|
75600
|
+
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75601
|
+
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75602
|
+
const alternate = stripParenExpression(initializer.alternate);
|
|
75603
|
+
const consequent = stripParenExpression(initializer.consequent);
|
|
75604
|
+
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75605
|
+
})() : null;
|
|
75606
|
+
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
73875
75607
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
73876
|
-
const finderCallee = stripParenExpression(
|
|
75608
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
73877
75609
|
if (!isNodeOfType(finderCallee, "Identifier") || !(context.scopes.symbolFor(finderCallee)?.kind === "import" && getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) === "findUpUntil" || finderCallee.name === "findUpUntil" && context.scopes.isGlobalReference(finderCallee))) return false;
|
|
73878
|
-
const predicateArgument =
|
|
75610
|
+
const predicateArgument = finderCall.arguments[1];
|
|
73879
75611
|
if (!predicateArgument) return false;
|
|
73880
75612
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
73881
75613
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -73899,6 +75631,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
73899
75631
|
});
|
|
73900
75632
|
return didProveMatch;
|
|
73901
75633
|
};
|
|
75634
|
+
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75635
|
+
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75636
|
+
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75637
|
+
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75638
|
+
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75639
|
+
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75640
|
+
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75641
|
+
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75642
|
+
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75643
|
+
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75644
|
+
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75645
|
+
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75646
|
+
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75647
|
+
const expression = stripParenExpression(test);
|
|
75648
|
+
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75649
|
+
})) return false;
|
|
75650
|
+
const predicateArgument = finderCall.arguments[1];
|
|
75651
|
+
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75652
|
+
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75653
|
+
const predicateParameter = predicateFunction.params[0];
|
|
75654
|
+
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75655
|
+
let didProveNormalizedMatch = false;
|
|
75656
|
+
walkAst(predicateFunction.body, (child) => {
|
|
75657
|
+
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75658
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || getStaticPropertyName(child.callee) !== "match" || !child.arguments[0] || !areRegexPatternsEquivalent(child.arguments[0], assertedPattern, context) || !doesPredicateTruthRequireMatch(child, predicateFunction) && !doesPredicateReturnNormalizedMatch(child, predicateFunction)) return;
|
|
75659
|
+
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75660
|
+
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75661
|
+
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75662
|
+
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75663
|
+
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75664
|
+
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75665
|
+
didProveNormalizedMatch = true;
|
|
75666
|
+
return false;
|
|
75667
|
+
}
|
|
75668
|
+
});
|
|
75669
|
+
return didProveNormalizedMatch;
|
|
75670
|
+
};
|
|
73902
75671
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
73903
75672
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
73904
75673
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -73999,6 +75768,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
73999
75768
|
}
|
|
74000
75769
|
return false;
|
|
74001
75770
|
};
|
|
75771
|
+
const getReceiverRootIdentifier = (node) => {
|
|
75772
|
+
let target = stripParenExpression(node);
|
|
75773
|
+
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75774
|
+
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75775
|
+
};
|
|
75776
|
+
const getReceiverStatePath = (node) => {
|
|
75777
|
+
const target = stripParenExpression(node);
|
|
75778
|
+
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75779
|
+
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75780
|
+
const objectPath = getReceiverStatePath(target.object);
|
|
75781
|
+
if (!objectPath) return null;
|
|
75782
|
+
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75783
|
+
};
|
|
75784
|
+
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75785
|
+
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75786
|
+
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75787
|
+
const receiverPath = getReceiverStatePath(receiver);
|
|
75788
|
+
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75789
|
+
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75790
|
+
let didAddAlias = true;
|
|
75791
|
+
while (didAddAlias) {
|
|
75792
|
+
didAddAlias = false;
|
|
75793
|
+
walkAst(ownerFunction, (child) => {
|
|
75794
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75795
|
+
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75796
|
+
const initializer = stripParenExpression(child.init);
|
|
75797
|
+
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75798
|
+
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75799
|
+
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75800
|
+
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75801
|
+
const initializerPath = getReceiverStatePath(initializer);
|
|
75802
|
+
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75803
|
+
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75804
|
+
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75805
|
+
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75806
|
+
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75807
|
+
didAddAlias = true;
|
|
75808
|
+
}
|
|
75809
|
+
});
|
|
75810
|
+
}
|
|
75811
|
+
let didChangeReceiverState = false;
|
|
75812
|
+
walkAst(ownerFunction, (child) => {
|
|
75813
|
+
if (didChangeReceiverState) return false;
|
|
75814
|
+
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75815
|
+
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75816
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
75817
|
+
didChangeReceiverState = true;
|
|
75818
|
+
return false;
|
|
75819
|
+
}
|
|
75820
|
+
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75821
|
+
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75822
|
+
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75823
|
+
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75824
|
+
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75825
|
+
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75826
|
+
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75827
|
+
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75828
|
+
didChangeReceiverState = true;
|
|
75829
|
+
return false;
|
|
75830
|
+
}
|
|
75831
|
+
});
|
|
75832
|
+
return didChangeReceiverState;
|
|
75833
|
+
};
|
|
75834
|
+
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75835
|
+
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75836
|
+
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75837
|
+
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75838
|
+
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75839
|
+
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75840
|
+
if (maximumSymbol?.kind !== "const" || maximumSymbol.references.some((reference) => reference.flag !== "read") || !maximumInitializer || !isNodeOfType(maximumInitializer, "CallExpression") || !isNodeOfType(maximumInitializer.callee, "MemberExpression") || getStaticPropertyName(maximumInitializer.callee) !== "reduce") return false;
|
|
75841
|
+
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75842
|
+
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75843
|
+
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75844
|
+
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75845
|
+
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75846
|
+
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75847
|
+
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75848
|
+
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75849
|
+
const accumulatorParameter = reducerFunction.params[0];
|
|
75850
|
+
const itemParameter = reducerFunction.params[1];
|
|
75851
|
+
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75852
|
+
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75853
|
+
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75854
|
+
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75855
|
+
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75856
|
+
const expression = stripParenExpression(argument);
|
|
75857
|
+
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75858
|
+
});
|
|
75859
|
+
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75860
|
+
const expression = stripParenExpression(argument);
|
|
75861
|
+
const rootIdentifier = getRootIdentifier(expression);
|
|
75862
|
+
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75863
|
+
});
|
|
75864
|
+
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75865
|
+
if (!receiverPathKey(findReceiver)) return false;
|
|
75866
|
+
let ownerFunction = assertion.parent ?? null;
|
|
75867
|
+
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75868
|
+
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75869
|
+
const maximumEnd = maximumInitializer.range[1];
|
|
75870
|
+
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75871
|
+
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75872
|
+
const comparison = stripParenExpression(test);
|
|
75873
|
+
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75874
|
+
return [[
|
|
75875
|
+
comparison.left,
|
|
75876
|
+
comparison.right,
|
|
75877
|
+
comparison.operator
|
|
75878
|
+
], [
|
|
75879
|
+
comparison.right,
|
|
75880
|
+
comparison.left,
|
|
75881
|
+
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75882
|
+
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75883
|
+
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75884
|
+
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75885
|
+
});
|
|
75886
|
+
});
|
|
75887
|
+
};
|
|
74002
75888
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
74003
75889
|
if (!value) return false;
|
|
74004
75890
|
const expression = stripParenExpression(value);
|
|
@@ -74022,15 +75908,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
74022
75908
|
};
|
|
74023
75909
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
74024
75910
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
74025
|
-
|
|
75911
|
+
const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
|
|
75912
|
+
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75913
|
+
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
|
|
74026
75914
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
74027
75915
|
if (!receiverSymbol) return false;
|
|
74028
75916
|
const receiverMatches = (candidate) => {
|
|
74029
75917
|
const target = stripParenExpression(candidate);
|
|
74030
75918
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
74031
75919
|
};
|
|
74032
|
-
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74033
|
-
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
74034
75920
|
let child = assertion;
|
|
74035
75921
|
let ancestor = assertion.parent ?? null;
|
|
74036
75922
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -74050,7 +75936,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
74050
75936
|
const populationCall = populationCalls[0];
|
|
74051
75937
|
if (!populationCall) continue;
|
|
74052
75938
|
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;
|
|
75939
|
+
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) || Boolean(lookupKeySymbol && isNodeOfType(stableLookupKey, "MemberExpression") && subtreeWritesSymbol(ancestor, new Set([lookupKeySymbol.id]), context, void 0, assertion))) continue;
|
|
74054
75940
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
74055
75941
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
74056
75942
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -74160,6 +76046,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74160
76046
|
const findReceiver = callee.object;
|
|
74161
76047
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
74162
76048
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
76049
|
+
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
74163
76050
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
74164
76051
|
}
|
|
74165
76052
|
if (methodName === "match") {
|
|
@@ -74169,6 +76056,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
74169
76056
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
74170
76057
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
74171
76058
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
76059
|
+
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
74172
76060
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
74173
76061
|
}
|
|
74174
76062
|
if (methodName === "get") {
|
|
@@ -78107,16 +79995,23 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
|
78107
79995
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
78108
79996
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
78109
79997
|
const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
|
|
78110
|
-
const
|
|
79998
|
+
const isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
78111
79999
|
const strippedExpression = stripParenExpression(expression);
|
|
78112
80000
|
if (isDefinitelyNonThenableValue(strippedExpression)) return true;
|
|
80001
|
+
if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
|
|
80002
|
+
const receiver = stripParenExpression(strippedExpression.callee.object);
|
|
80003
|
+
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
|
|
80004
|
+
const resolvedValue = strippedExpression.arguments[0];
|
|
80005
|
+
return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
|
|
80006
|
+
}
|
|
80007
|
+
}
|
|
78113
80008
|
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
78114
80009
|
if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
|
|
78115
80010
|
const symbol = context.scopes.symbolFor(strippedExpression);
|
|
78116
80011
|
if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
|
|
78117
80012
|
visitedBindingIdentifiers.add(symbol.bindingIdentifier);
|
|
78118
80013
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
78119
|
-
return Boolean(initializer &&
|
|
80014
|
+
return Boolean(initializer && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
|
|
78120
80015
|
};
|
|
78121
80016
|
const isKnownNonRejectingHandler = (argument, context) => {
|
|
78122
80017
|
if (!argument) return false;
|
|
@@ -78131,7 +80026,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
78131
80026
|
canReject = true;
|
|
78132
80027
|
return false;
|
|
78133
80028
|
}
|
|
78134
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !
|
|
80029
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
78135
80030
|
if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
|
|
78136
80031
|
canReject = true;
|
|
78137
80032
|
return false;
|
|
@@ -78180,6 +80075,28 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
|
78180
80075
|
});
|
|
78181
80076
|
return hasPotentiallyThrowingMemberRead;
|
|
78182
80077
|
};
|
|
80078
|
+
const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
|
|
80079
|
+
if (!argument) return false;
|
|
80080
|
+
if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
|
|
80081
|
+
if (!allowTerminalCatchBlock) return false;
|
|
80082
|
+
const candidate = stripParenExpression(argument);
|
|
80083
|
+
const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
|
|
80084
|
+
if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
|
|
80085
|
+
if (!isNodeOfType(handler.body, "BlockStatement")) return false;
|
|
80086
|
+
let doesExplicitlyReject = false;
|
|
80087
|
+
walkOwnFunctionScope(handler, (child) => {
|
|
80088
|
+
if (doesExplicitlyReject) return false;
|
|
80089
|
+
if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
|
|
80090
|
+
doesExplicitlyReject = true;
|
|
80091
|
+
return false;
|
|
80092
|
+
}
|
|
80093
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
80094
|
+
doesExplicitlyReject = true;
|
|
80095
|
+
return false;
|
|
80096
|
+
}
|
|
80097
|
+
});
|
|
80098
|
+
return !doesExplicitlyReject;
|
|
80099
|
+
};
|
|
78183
80100
|
const walkPromiseChain = (chainExpression, context) => {
|
|
78184
80101
|
let cursor = stripParenExpression(chainExpression);
|
|
78185
80102
|
let hasCatch = false;
|
|
@@ -78191,10 +80108,9 @@ const walkPromiseChain = (chainExpression, context) => {
|
|
|
78191
80108
|
while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
|
|
78192
80109
|
const methodName = getStaticPropertyName(cursor.callee);
|
|
78193
80110
|
const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
|
|
78194
|
-
|
|
78195
|
-
if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
|
|
80111
|
+
if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
|
|
78196
80112
|
if (methodName === "then") {
|
|
78197
|
-
if (!didReachTerminalThen &&
|
|
80113
|
+
if (!didReachTerminalThen && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
|
|
78198
80114
|
didReachTerminalThen = true;
|
|
78199
80115
|
sawThen = true;
|
|
78200
80116
|
const callbackArgument = cursor.arguments[0];
|
|
@@ -80521,6 +82437,45 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
80521
82437
|
} })
|
|
80522
82438
|
});
|
|
80523
82439
|
//#endregion
|
|
82440
|
+
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82441
|
+
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82442
|
+
"Math.random",
|
|
82443
|
+
"Date.now",
|
|
82444
|
+
"performance.now",
|
|
82445
|
+
"crypto.randomUUID",
|
|
82446
|
+
"crypto.getRandomValues"
|
|
82447
|
+
]);
|
|
82448
|
+
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82449
|
+
"nanoid",
|
|
82450
|
+
"uuid",
|
|
82451
|
+
"cuid",
|
|
82452
|
+
"ulid",
|
|
82453
|
+
"createId"
|
|
82454
|
+
]);
|
|
82455
|
+
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82456
|
+
const containsNonDeterministicSource = (root) => {
|
|
82457
|
+
let found = false;
|
|
82458
|
+
walkAst(root, (child) => {
|
|
82459
|
+
if (found) return false;
|
|
82460
|
+
if (isFunctionLike$1(child)) return false;
|
|
82461
|
+
if (isZeroArgDateConstruction(child)) {
|
|
82462
|
+
found = true;
|
|
82463
|
+
return false;
|
|
82464
|
+
}
|
|
82465
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82466
|
+
const callee = child.callee;
|
|
82467
|
+
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82468
|
+
found = true;
|
|
82469
|
+
return false;
|
|
82470
|
+
}
|
|
82471
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82472
|
+
found = true;
|
|
82473
|
+
return false;
|
|
82474
|
+
}
|
|
82475
|
+
});
|
|
82476
|
+
return found;
|
|
82477
|
+
};
|
|
82478
|
+
//#endregion
|
|
80524
82479
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
80525
82480
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
80526
82481
|
"DoWhileStatement",
|
|
@@ -80551,45 +82506,75 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
80551
82506
|
};
|
|
80552
82507
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
80553
82508
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
80554
|
-
|
|
82509
|
+
if (!expression) return false;
|
|
82510
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
82511
|
+
const callee = stripParenExpression(expression.callee);
|
|
82512
|
+
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82513
|
+
}
|
|
82514
|
+
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
80555
82515
|
};
|
|
80556
|
-
const
|
|
82516
|
+
const getInitializationValueName = (node, scopes) => {
|
|
80557
82517
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
80558
82518
|
if (!expression) return null;
|
|
80559
|
-
if (isNodeOfType(expression, "NewExpression")) {
|
|
82519
|
+
if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
|
|
80560
82520
|
const callee = stripParenExpression(expression.callee);
|
|
80561
|
-
|
|
82521
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
82522
|
+
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
80562
82523
|
}
|
|
80563
82524
|
return null;
|
|
80564
82525
|
};
|
|
82526
|
+
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82527
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82528
|
+
const typeName = typeNode.typeName;
|
|
82529
|
+
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82530
|
+
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82531
|
+
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82532
|
+
const queriedName = returnTypeArgument.exprName;
|
|
82533
|
+
const expression = stripParenExpression(initializationValue);
|
|
82534
|
+
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82535
|
+
const callee = stripParenExpression(expression.callee);
|
|
82536
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82537
|
+
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82538
|
+
const calleeSymbol = scopes.symbolFor(callee);
|
|
82539
|
+
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82540
|
+
};
|
|
80565
82541
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
80566
82542
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
80567
82543
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
80568
82544
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
80569
82545
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
80570
82546
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82547
|
+
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
80571
82548
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
80572
82549
|
const typeName = typeNode.typeName;
|
|
80573
|
-
|
|
82550
|
+
if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
|
|
82551
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
80574
82552
|
};
|
|
80575
82553
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
80576
82554
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
80577
82555
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
80578
82556
|
const [initialValue] = initializer.arguments ?? [];
|
|
80579
|
-
if (
|
|
82557
|
+
if (initialValue && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
|
|
80580
82558
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
80581
|
-
if (!declaredType
|
|
80582
|
-
|
|
82559
|
+
if (!declaredType) return false;
|
|
82560
|
+
const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
|
|
80583
82561
|
let hasTruthyDomain = false;
|
|
80584
|
-
for (const memberType of
|
|
80585
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
80586
|
-
hasEmptySentinel = true;
|
|
80587
|
-
continue;
|
|
80588
|
-
}
|
|
82562
|
+
for (const memberType of domainTypes) {
|
|
82563
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
|
|
80589
82564
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
80590
82565
|
hasTruthyDomain = true;
|
|
80591
82566
|
}
|
|
80592
|
-
return
|
|
82567
|
+
return hasTruthyDomain;
|
|
82568
|
+
};
|
|
82569
|
+
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82570
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82571
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82572
|
+
const [initialValue] = initializer.arguments ?? [];
|
|
82573
|
+
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82574
|
+
};
|
|
82575
|
+
const refHasDeclaredType = (refSymbol) => {
|
|
82576
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82577
|
+
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
80593
82578
|
};
|
|
80594
82579
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
80595
82580
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -80621,25 +82606,40 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
80621
82606
|
});
|
|
80622
82607
|
return didFindRefCurrent;
|
|
80623
82608
|
};
|
|
80624
|
-
const
|
|
80625
|
-
|
|
80626
|
-
|
|
80627
|
-
|
|
80628
|
-
|
|
80629
|
-
|
|
80630
|
-
|
|
82609
|
+
const isEmptySentinel = (node, scopes) => {
|
|
82610
|
+
const expression = stripParenExpression(node);
|
|
82611
|
+
return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
|
|
82612
|
+
};
|
|
82613
|
+
const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
82614
|
+
let isInputIndependent = true;
|
|
82615
|
+
walkAst(node, (child) => {
|
|
82616
|
+
if (!isInputIndependent) return false;
|
|
82617
|
+
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82618
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
82619
|
+
const symbol = scopes.symbolFor(child);
|
|
82620
|
+
if (!symbol) return;
|
|
82621
|
+
if (symbol.kind === "import") return false;
|
|
82622
|
+
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82623
|
+
isInputIndependent = false;
|
|
82624
|
+
return false;
|
|
80631
82625
|
}
|
|
80632
|
-
if (
|
|
80633
|
-
|
|
80634
|
-
|
|
82626
|
+
if (isOutsideAllFunctions(symbol)) return false;
|
|
82627
|
+
if (symbol.kind === "parameter") {
|
|
82628
|
+
if (symbol.scope.node === renderOwner) isInputIndependent = false;
|
|
82629
|
+
return false;
|
|
80635
82630
|
}
|
|
80636
|
-
if (
|
|
80637
|
-
|
|
82631
|
+
if (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
|
|
82632
|
+
isInputIndependent = false;
|
|
82633
|
+
return false;
|
|
80638
82634
|
}
|
|
82635
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82636
|
+
visitedSymbolIds.add(symbol.id);
|
|
82637
|
+
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82638
|
+
return false;
|
|
80639
82639
|
});
|
|
80640
|
-
return
|
|
82640
|
+
return isInputIndependent;
|
|
80641
82641
|
};
|
|
80642
|
-
const
|
|
82642
|
+
const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
|
|
80643
82643
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
80644
82644
|
let ancestor = node.parent;
|
|
80645
82645
|
while (ancestor && ancestor !== stop) {
|
|
@@ -80669,6 +82669,27 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
80669
82669
|
}
|
|
80670
82670
|
return true;
|
|
80671
82671
|
};
|
|
82672
|
+
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82673
|
+
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82674
|
+
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82675
|
+
let hasCompetingWrite = false;
|
|
82676
|
+
walkAst(renderOwner, (child) => {
|
|
82677
|
+
if (hasCompetingWrite) return false;
|
|
82678
|
+
let writtenExpression = null;
|
|
82679
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82680
|
+
if (child === assignmentExpression) return;
|
|
82681
|
+
writtenExpression = child.left;
|
|
82682
|
+
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82683
|
+
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82684
|
+
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82685
|
+
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82686
|
+
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82687
|
+
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82688
|
+
hasCompetingWrite = true;
|
|
82689
|
+
return false;
|
|
82690
|
+
});
|
|
82691
|
+
return !hasCompetingWrite;
|
|
82692
|
+
};
|
|
80672
82693
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
80673
82694
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
80674
82695
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -80684,16 +82705,17 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
80684
82705
|
});
|
|
80685
82706
|
return !hasCoExecutableWrite;
|
|
80686
82707
|
};
|
|
82708
|
+
const isPredictableGuardedInitialization = (assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, requiresClosedTruthyDomain) => refHasEmptySentinelInitializer(refSymbol, scopes) && isPredictableInitializationValue(assignmentExpression.right, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && (guardedBranch === renderOwner || !hasRepeatedExecutionAncestor(guardedBranch, renderOwner)) && hasNoPriorCoExecutableWrite(assignmentExpression, renderOwner, refSymbol, scopes) && hasNoCoExecutableCompetingWrite(assignmentExpression, renderOwner, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes);
|
|
80687
82709
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
80688
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
80689
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
80690
82710
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
80691
82711
|
if (!renderOwner) return false;
|
|
82712
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82713
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
80692
82714
|
let descendant = assignmentExpression;
|
|
80693
82715
|
let ancestor = descendant.parent;
|
|
80694
82716
|
while (ancestor) {
|
|
80695
82717
|
const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
|
|
80696
|
-
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant &&
|
|
82718
|
+
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
|
|
80697
82719
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
80698
82720
|
"===",
|
|
80699
82721
|
"==",
|
|
@@ -80703,7 +82725,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
80703
82725
|
const { left, right } = test;
|
|
80704
82726
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
80705
82727
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
80706
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
82728
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
|
|
80707
82729
|
}
|
|
80708
82730
|
descendant = ancestor;
|
|
80709
82731
|
ancestor = descendant.parent;
|
|
@@ -88308,6 +90330,11 @@ const noUndersizedIconButton = defineRule({
|
|
|
88308
90330
|
//#region src/plugin/rules/correctness/no-unescaped-dynamic-string-in-regexp.ts
|
|
88309
90331
|
const TEST_CONTEXT_FILE_PATTERN = /(\.test\.|\.spec\.|__tests__|(^|\/)(test|tests|e2e|cypress|playwright)\/)/;
|
|
88310
90332
|
const SEARCH_TERM_NAME_PATTERN = /search|query|highlight|filter|term(?!in)|keyword/i;
|
|
90333
|
+
const REGEX_SOURCE_WORDS = [
|
|
90334
|
+
"pattern",
|
|
90335
|
+
"regex",
|
|
90336
|
+
"regexp"
|
|
90337
|
+
];
|
|
88311
90338
|
const ESCAPE_HELPER_NAME_PATTERN = /escape.*reg|safe.*reg/i;
|
|
88312
90339
|
const SANITIZED_NAME_PATTERN = /escap|sanitiz/i;
|
|
88313
90340
|
const INITIALIZER_RESOLUTION_HOPS = 2;
|
|
@@ -88461,14 +90488,70 @@ const isTypePositionIdentifier = (identifier) => {
|
|
|
88461
90488
|
}
|
|
88462
90489
|
return false;
|
|
88463
90490
|
};
|
|
88464
|
-
const
|
|
88465
|
-
const
|
|
90491
|
+
const identifierNameHasPathSegmentSemantics = (identifierName) => {
|
|
90492
|
+
const identifierWords = identifierName.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_]+/).map((word) => word.toLowerCase());
|
|
90493
|
+
if (identifierWords.some((word) => REGEX_SOURCE_WORDS.includes(word))) return false;
|
|
90494
|
+
return identifierWords.some((word) => [
|
|
90495
|
+
"path",
|
|
90496
|
+
"folder",
|
|
90497
|
+
"directory",
|
|
90498
|
+
"root"
|
|
90499
|
+
].includes(word)) || identifierWords.includes("top") && identifierWords.includes("level");
|
|
90500
|
+
};
|
|
90501
|
+
const flattenPatternParts = (expression) => {
|
|
90502
|
+
const inner = stripParenExpression(expression);
|
|
90503
|
+
const staticValue = literalStringValue(inner);
|
|
90504
|
+
if (staticValue !== null) return [staticValue];
|
|
90505
|
+
if (isNodeOfType(inner, "Identifier")) return [inner];
|
|
90506
|
+
if (isNodeOfType(inner, "TemplateLiteral")) {
|
|
90507
|
+
const parts = [];
|
|
90508
|
+
for (let index = 0; index < inner.quasis.length; index += 1) {
|
|
90509
|
+
const quasi = inner.quasis[index];
|
|
90510
|
+
if (quasi) parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
90511
|
+
const templateExpression = inner.expressions[index];
|
|
90512
|
+
if (templateExpression) parts.push(stripParenExpression(templateExpression));
|
|
90513
|
+
}
|
|
90514
|
+
return parts;
|
|
90515
|
+
}
|
|
90516
|
+
if (isNodeOfType(inner, "BinaryExpression") && inner.operator === "+") {
|
|
90517
|
+
const leftParts = flattenPatternParts(inner.left);
|
|
90518
|
+
const rightParts = flattenPatternParts(inner.right);
|
|
90519
|
+
return leftParts && rightParts ? [...leftParts, ...rightParts] : null;
|
|
90520
|
+
}
|
|
90521
|
+
if (isNodeOfType(inner, "CallExpression") && isNodeOfType(inner.callee, "MemberExpression") && getStaticPropertyName(inner.callee) === "concat") {
|
|
90522
|
+
const receiverParts = flattenPatternParts(inner.callee.object);
|
|
90523
|
+
if (!receiverParts) return null;
|
|
90524
|
+
const parts = [...receiverParts];
|
|
90525
|
+
for (const argument of inner.arguments) {
|
|
90526
|
+
if (isNodeOfType(argument, "SpreadElement")) return null;
|
|
90527
|
+
const argumentParts = flattenPatternParts(argument);
|
|
90528
|
+
if (!argumentParts) return null;
|
|
90529
|
+
parts.push(...argumentParts);
|
|
90530
|
+
}
|
|
90531
|
+
return parts;
|
|
90532
|
+
}
|
|
90533
|
+
return null;
|
|
90534
|
+
};
|
|
90535
|
+
const isIdentifierAnAnchoredPathSegment = (argument, identifier) => {
|
|
90536
|
+
if (!identifierNameHasPathSegmentSemantics(identifier.name)) return false;
|
|
90537
|
+
const parts = flattenPatternParts(argument);
|
|
90538
|
+
if (!parts) return false;
|
|
90539
|
+
const identifierPartIndex = parts.findIndex((part) => part === identifier);
|
|
90540
|
+
if (identifierPartIndex < 0) return false;
|
|
90541
|
+
const precedingParts = parts.slice(0, identifierPartIndex);
|
|
90542
|
+
if (precedingParts.some((part) => typeof part !== "string")) return false;
|
|
90543
|
+
const staticPrefix = precedingParts.join("");
|
|
90544
|
+
const followingPart = parts[identifierPartIndex + 1];
|
|
90545
|
+
return staticPrefix === "^" && typeof followingPart === "string" && followingPart.startsWith("/");
|
|
90546
|
+
};
|
|
90547
|
+
const collectRawDynamicLiteralIdentifiers = (argument) => {
|
|
90548
|
+
const rawDynamicLiteralIdentifiers = [];
|
|
88466
90549
|
walkAst(argument, (child) => {
|
|
88467
90550
|
if (isEscapingCall(child) || isRegexSourceAccess(child)) return false;
|
|
88468
90551
|
if (isLiteralReturningGetterCall(child)) return false;
|
|
88469
|
-
if (isNodeOfType(child, "Identifier") && SEARCH_TERM_NAME_PATTERN.test(child.name) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child))
|
|
90552
|
+
if (isNodeOfType(child, "Identifier") && (SEARCH_TERM_NAME_PATTERN.test(child.name) || isIdentifierAnAnchoredPathSegment(argument, child)) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawDynamicLiteralIdentifiers.push(child);
|
|
88470
90553
|
});
|
|
88471
|
-
return
|
|
90554
|
+
return rawDynamicLiteralIdentifiers;
|
|
88472
90555
|
};
|
|
88473
90556
|
const collectLeafIdentifiers = (node) => {
|
|
88474
90557
|
const leafIdentifiers = [];
|
|
@@ -88479,8 +90562,11 @@ const collectLeafIdentifiers = (node) => {
|
|
|
88479
90562
|
return leafIdentifiers;
|
|
88480
90563
|
};
|
|
88481
90564
|
const compositeInitializerResolvesEscaped = (strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames) => {
|
|
90565
|
+
if (isNodeOfType(strippedInitializer, "ConditionalExpression")) return [strippedInitializer.consequent, strippedInitializer.alternate].every((branch) => initializerLooksEscaped(branch, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
90566
|
+
if (isNodeOfType(strippedInitializer, "BinaryExpression") || isNodeOfType(strippedInitializer, "LogicalExpression")) return [strippedInitializer.left, strippedInitializer.right].every((operand) => initializerLooksEscaped(operand, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
90567
|
+
if (isNodeOfType(strippedInitializer, "TemplateLiteral")) return strippedInitializer.expressions.every((expression) => initializerLooksEscaped(expression, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
88482
90568
|
let didResolveAnyLeafEscaped = false;
|
|
88483
|
-
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
90569
|
+
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
88484
90570
|
else if (SEARCH_TERM_NAME_PATTERN.test(leafIdentifier.name)) return false;
|
|
88485
90571
|
return didResolveAnyLeafEscaped;
|
|
88486
90572
|
};
|
|
@@ -88492,7 +90578,7 @@ const initializerLooksEscaped = (initializer, remainingHops, scopes, regexpObjec
|
|
|
88492
90578
|
if (isNodeOfType(strippedInitializer, "CallExpression") && (isEscapingCall(strippedInitializer) || calleeBindingBodyEscapes(strippedInitializer))) return true;
|
|
88493
90579
|
if (remainingHops > 0) {
|
|
88494
90580
|
if (isNodeOfType(strippedInitializer, "Identifier")) return identifierResolvesToEscapedValue(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88495
|
-
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops
|
|
90581
|
+
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88496
90582
|
}
|
|
88497
90583
|
return false;
|
|
88498
90584
|
};
|
|
@@ -88684,7 +90770,7 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88684
90770
|
severity: "warn",
|
|
88685
90771
|
category: "Correctness",
|
|
88686
90772
|
tags: ["test-noise"],
|
|
88687
|
-
recommendation: "A search
|
|
90773
|
+
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
90774
|
create: (context) => {
|
|
88689
90775
|
if (TEST_CONTEXT_FILE_PATTERN.test(context.filename ?? "")) return {};
|
|
88690
90776
|
let regexpObjectIndex = null;
|
|
@@ -88701,10 +90787,10 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88701
90787
|
regexpObjectIndex = buildRegExpObjectIndex(programRoot, context.scopes);
|
|
88702
90788
|
}
|
|
88703
90789
|
const currentRegExpObjectIndex = regexpObjectIndex;
|
|
88704
|
-
if (!
|
|
90790
|
+
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
90791
|
context.report({
|
|
88706
90792
|
node,
|
|
88707
|
-
message: "This builds a `RegExp` from a dynamic
|
|
90793
|
+
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
90794
|
});
|
|
88709
90795
|
};
|
|
88710
90796
|
return {
|
|
@@ -108446,9 +110532,6 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
108446
110532
|
} })
|
|
108447
110533
|
});
|
|
108448
110534
|
//#endregion
|
|
108449
|
-
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
108450
|
-
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
108451
|
-
//#endregion
|
|
108452
110535
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
108453
110536
|
const rerenderLazyRefInit = defineRule({
|
|
108454
110537
|
id: "rerender-lazy-ref-init",
|
|
@@ -108467,7 +110550,6 @@ const rerenderLazyRefInit = defineRule({
|
|
|
108467
110550
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
108468
110551
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
108469
110552
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
108470
|
-
if (isTrivialBuiltInConstruction(initializer)) return;
|
|
108471
110553
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
108472
110554
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
108473
110555
|
context.report({
|
|
@@ -140049,6 +142131,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
140049
142131
|
//#region src/plugin/utils/capability.ts
|
|
140050
142132
|
const FRAMEWORK_TOKENS = [
|
|
140051
142133
|
"nextjs",
|
|
142134
|
+
"astro",
|
|
140052
142135
|
"vite",
|
|
140053
142136
|
"cra",
|
|
140054
142137
|
"remix",
|