oxlint-plugin-react-doctor 0.9.2-dev.c126684 → 0.9.2-dev.c6bdd2d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +123 -704
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -665,6 +665,19 @@ 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
|
+
]);
|
|
668
681
|
const SETTER_PATTERN = /^set[A-Z]/;
|
|
669
682
|
const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
|
|
670
683
|
const UPPERCASE_PATTERN = /^[A-Z]/;
|
|
@@ -29461,11 +29474,6 @@ const jsCacheStorage = defineRule({
|
|
|
29461
29474
|
});
|
|
29462
29475
|
//#endregion
|
|
29463
29476
|
//#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
|
-
]);
|
|
29469
29477
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
29470
29478
|
const callee = callExpression.callee;
|
|
29471
29479
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
@@ -29577,34 +29585,21 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
29577
29585
|
return false;
|
|
29578
29586
|
};
|
|
29579
29587
|
const isSmallLiteralArray = (node) => {
|
|
29580
|
-
|
|
29581
|
-
|
|
29582
|
-
|
|
29583
|
-
if (elements.length === 0 || elements.length > 9) return false;
|
|
29588
|
+
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
29589
|
+
const elements = node.elements ?? [];
|
|
29590
|
+
if (elements.length === 0 || elements.length > 8) return false;
|
|
29584
29591
|
for (const element of elements) {
|
|
29585
29592
|
if (!element) continue;
|
|
29586
29593
|
if (isNodeOfType(element, "SpreadElement")) return false;
|
|
29587
29594
|
}
|
|
29588
29595
|
return true;
|
|
29589
29596
|
};
|
|
29590
|
-
const
|
|
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) => {
|
|
29597
|
+
const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
29598
29598
|
let cursor = receiverNode;
|
|
29599
29599
|
while (cursor) {
|
|
29600
29600
|
cursor = stripParenExpression(cursor);
|
|
29601
29601
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
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
|
-
}
|
|
29602
|
+
if (isNodeOfType(cursor, "Identifier")) return smallConstArrayNames.has(cursor.name);
|
|
29608
29603
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
29609
29604
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
29610
29605
|
const nextCallee = cursor.callee;
|
|
@@ -29613,6 +29608,22 @@ const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
|
29613
29608
|
}
|
|
29614
29609
|
return false;
|
|
29615
29610
|
};
|
|
29611
|
+
const collectSmallConstArrayNames = (programNode) => {
|
|
29612
|
+
const names = /* @__PURE__ */ new Set();
|
|
29613
|
+
const statements = programNode.body ?? [];
|
|
29614
|
+
for (const statement of statements) {
|
|
29615
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
29616
|
+
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
29617
|
+
if (declaration.kind !== "const") continue;
|
|
29618
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
29619
|
+
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
29620
|
+
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
29621
|
+
if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
|
|
29622
|
+
names.add(declarator.id.name);
|
|
29623
|
+
}
|
|
29624
|
+
}
|
|
29625
|
+
return names;
|
|
29626
|
+
};
|
|
29616
29627
|
const collectGeneratorNames = (programNode) => {
|
|
29617
29628
|
const generatorNames = /* @__PURE__ */ new Set();
|
|
29618
29629
|
walkAst(programNode, (child) => {
|
|
@@ -29633,11 +29644,16 @@ const jsCombineIterations = defineRule({
|
|
|
29633
29644
|
create: (context) => {
|
|
29634
29645
|
let programNode = null;
|
|
29635
29646
|
let generatorNamesInFile = null;
|
|
29647
|
+
let smallConstArrayNames = null;
|
|
29636
29648
|
const coveredChainCalls = /* @__PURE__ */ new WeakSet();
|
|
29637
29649
|
const getGeneratorNamesInFile = () => {
|
|
29638
29650
|
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
29639
29651
|
return generatorNamesInFile;
|
|
29640
29652
|
};
|
|
29653
|
+
const getSmallConstArrayNames = () => {
|
|
29654
|
+
smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
|
|
29655
|
+
return smallConstArrayNames;
|
|
29656
|
+
};
|
|
29641
29657
|
return {
|
|
29642
29658
|
Program(node) {
|
|
29643
29659
|
programNode = node;
|
|
@@ -29667,7 +29683,7 @@ const jsCombineIterations = defineRule({
|
|
|
29667
29683
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
29668
29684
|
}
|
|
29669
29685
|
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
29670
|
-
if (isSmallLiteralArrayRootedChain(innerCall.callee.object,
|
|
29686
|
+
if (isSmallLiteralArrayRootedChain(innerCall.callee.object, getSmallConstArrayNames())) return;
|
|
29671
29687
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
29672
29688
|
coveredChainCalls.add(innerCall);
|
|
29673
29689
|
context.report({
|
|
@@ -56087,9 +56103,6 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
56087
56103
|
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);
|
|
56088
56104
|
};
|
|
56089
56105
|
//#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
|
|
56093
56106
|
//#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
|
|
56094
56107
|
const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
|
|
56095
56108
|
const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
|
|
@@ -56246,6 +56259,7 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
|
|
|
56246
56259
|
});
|
|
56247
56260
|
return isCommitted;
|
|
56248
56261
|
};
|
|
56262
|
+
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56249
56263
|
const isNextjsDataFetchingPage = (node) => {
|
|
56250
56264
|
const program = findProgramRoot(node);
|
|
56251
56265
|
if (!program) return false;
|
|
@@ -67190,124 +67204,6 @@ const isInsideSnapshotHelper = (node) => {
|
|
|
67190
67204
|
}
|
|
67191
67205
|
return false;
|
|
67192
67206
|
};
|
|
67193
|
-
const findEnclosingNextjsPageDataFunction = (node) => {
|
|
67194
|
-
let outermostFunction = null;
|
|
67195
|
-
let cursor = node.parent;
|
|
67196
|
-
while (cursor) {
|
|
67197
|
-
if (isFunctionLike$1(cursor)) outermostFunction = cursor;
|
|
67198
|
-
if (isNodeOfType(cursor, "Program")) {
|
|
67199
|
-
if (!outermostFunction) return null;
|
|
67200
|
-
for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
|
|
67201
|
-
const exportedValue = findExportedValue(cursor, exportName);
|
|
67202
|
-
if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
|
|
67203
|
-
}
|
|
67204
|
-
return null;
|
|
67205
|
-
}
|
|
67206
|
-
cursor = cursor.parent ?? null;
|
|
67207
|
-
}
|
|
67208
|
-
return null;
|
|
67209
|
-
};
|
|
67210
|
-
const findConditionalReturnExpressionRoot = (node) => {
|
|
67211
|
-
let expressionRoot = findTransparentExpressionRoot(node);
|
|
67212
|
-
while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
|
|
67213
|
-
return expressionRoot;
|
|
67214
|
-
};
|
|
67215
|
-
const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
|
|
67216
|
-
const declarator = returnExpression.parent;
|
|
67217
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
|
|
67218
|
-
const bindingSymbol = context.scopes.symbolFor(declarator.id);
|
|
67219
|
-
if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
|
|
67220
|
-
const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
|
|
67221
|
-
const returnStatement = referenceRoot.parent;
|
|
67222
|
-
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
|
|
67223
|
-
};
|
|
67224
|
-
const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
|
|
67225
|
-
const isValueForwardedThroughLiteralStructure = (node, structure) => {
|
|
67226
|
-
const strippedNode = stripParenExpression(node);
|
|
67227
|
-
const strippedStructure = stripParenExpression(structure);
|
|
67228
|
-
if (strippedNode === strippedStructure) return true;
|
|
67229
|
-
if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
|
|
67230
|
-
if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
|
|
67231
|
-
if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
|
|
67232
|
-
return strippedStructure.properties.some((property) => {
|
|
67233
|
-
if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
|
|
67234
|
-
if (!isNodeOfType(property, "Property")) return false;
|
|
67235
|
-
if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
|
|
67236
|
-
return isSameShorthandPropertyValue(strippedNode, property);
|
|
67237
|
-
});
|
|
67238
|
-
};
|
|
67239
|
-
const isValueForwardedToPropertyValue = (node, property) => {
|
|
67240
|
-
const directValue = findConditionalReturnExpressionRoot(node);
|
|
67241
|
-
if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
|
|
67242
|
-
return isSameShorthandPropertyValue(directValue, property);
|
|
67243
|
-
};
|
|
67244
|
-
const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
|
|
67245
|
-
let cursor = node.parent;
|
|
67246
|
-
while (cursor && cursor !== pageDataFunction) {
|
|
67247
|
-
if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
|
|
67248
|
-
const propertyContainer = cursor.parent;
|
|
67249
|
-
if (!propertyContainer) return false;
|
|
67250
|
-
const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
|
|
67251
|
-
const returnStatement = returnExpression.parent;
|
|
67252
|
-
if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
|
|
67253
|
-
if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
|
|
67254
|
-
if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
|
|
67255
|
-
}
|
|
67256
|
-
cursor = cursor.parent ?? null;
|
|
67257
|
-
}
|
|
67258
|
-
return false;
|
|
67259
|
-
};
|
|
67260
|
-
const isExpressionReturnedByFunction = (node, functionNode) => {
|
|
67261
|
-
const returnExpression = findConditionalReturnExpressionRoot(node);
|
|
67262
|
-
if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
|
|
67263
|
-
const returnStatement = returnExpression.parent;
|
|
67264
|
-
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
|
|
67265
|
-
};
|
|
67266
|
-
const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
|
|
67267
|
-
if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
|
|
67268
|
-
const initializer = stripParenExpression(bindingInitializer);
|
|
67269
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
67270
|
-
const callee = stripParenExpression(initializer.callee);
|
|
67271
|
-
return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
|
|
67272
|
-
};
|
|
67273
|
-
const findPageDataResultBinding = (node) => {
|
|
67274
|
-
let cursor = node.parent;
|
|
67275
|
-
while (cursor) {
|
|
67276
|
-
if (isNodeOfType(cursor, "VariableDeclarator")) {
|
|
67277
|
-
if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
|
|
67278
|
-
return null;
|
|
67279
|
-
}
|
|
67280
|
-
cursor = cursor.parent ?? null;
|
|
67281
|
-
}
|
|
67282
|
-
return null;
|
|
67283
|
-
};
|
|
67284
|
-
const isUsedToSerializeNextjsPageProps = (node, context) => {
|
|
67285
|
-
if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
|
|
67286
|
-
const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
|
|
67287
|
-
if (!pageDataFunction) return false;
|
|
67288
|
-
if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
|
|
67289
|
-
const bindingIdentifier = findPageDataResultBinding(node);
|
|
67290
|
-
const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
67291
|
-
if (!bindingSymbol) return false;
|
|
67292
|
-
const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
|
|
67293
|
-
const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
|
|
67294
|
-
let hasPagePropsReference = false;
|
|
67295
|
-
for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
|
|
67296
|
-
if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
|
|
67297
|
-
if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
|
|
67298
|
-
hasPagePropsReference = true;
|
|
67299
|
-
continue;
|
|
67300
|
-
}
|
|
67301
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
67302
|
-
const declarator = referenceRoot.parent;
|
|
67303
|
-
if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
|
|
67304
|
-
const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
|
|
67305
|
-
if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
|
|
67306
|
-
}
|
|
67307
|
-
return false;
|
|
67308
|
-
}
|
|
67309
|
-
return hasPagePropsReference;
|
|
67310
|
-
};
|
|
67311
67207
|
const noJsonParseStringifyClone = defineRule({
|
|
67312
67208
|
id: "no-json-parse-stringify-clone",
|
|
67313
67209
|
title: "JSON parse/stringify deep clone",
|
|
@@ -67325,7 +67221,6 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
67325
67221
|
if (isInsideSnapshotHelper(node)) return;
|
|
67326
67222
|
if (isAssignedToNormalizationBinding(node)) return;
|
|
67327
67223
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
67328
|
-
if (isUsedToSerializeNextjsPageProps(node, context)) return;
|
|
67329
67224
|
context.report({
|
|
67330
67225
|
node,
|
|
67331
67226
|
message: MESSAGE$35
|
|
@@ -68741,77 +68636,6 @@ const isCancellationGuardTest = (test) => {
|
|
|
68741
68636
|
});
|
|
68742
68637
|
return matches;
|
|
68743
68638
|
};
|
|
68744
|
-
const getReactRefCurrent = (expression, context) => {
|
|
68745
|
-
const stripped = stripParenExpression(expression);
|
|
68746
|
-
if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
|
|
68747
|
-
const receiver = stripParenExpression(stripped.object);
|
|
68748
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
68749
|
-
const binding = findVariableInitializer(receiver, receiver.name);
|
|
68750
|
-
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
68751
|
-
return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
|
|
68752
|
-
allowGlobalReactNamespace: true,
|
|
68753
|
-
allowUnboundBareCalls: true
|
|
68754
|
-
}) ? stripped : null;
|
|
68755
|
-
};
|
|
68756
|
-
const getStableOwnershipToken = (expression, context) => {
|
|
68757
|
-
const stripped = stripParenExpression(expression);
|
|
68758
|
-
if (!isNodeOfType(stripped, "Identifier")) return null;
|
|
68759
|
-
const symbol = context.scopes.symbolFor(stripped);
|
|
68760
|
-
const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
68761
|
-
const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
|
|
68762
|
-
return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
|
|
68763
|
-
};
|
|
68764
|
-
const getAsyncOwnershipComparison = (test, context) => {
|
|
68765
|
-
const stripped = stripParenExpression(test);
|
|
68766
|
-
if (!isNodeOfType(stripped, "BinaryExpression")) return null;
|
|
68767
|
-
const leftRef = getReactRefCurrent(stripped.left, context);
|
|
68768
|
-
const rightRef = getReactRefCurrent(stripped.right, context);
|
|
68769
|
-
const leftToken = getStableOwnershipToken(stripped.left, context);
|
|
68770
|
-
const rightToken = getStableOwnershipToken(stripped.right, context);
|
|
68771
|
-
if (stripped.operator === "===" || stripped.operator === "==") {
|
|
68772
|
-
if (leftRef && rightToken) return {
|
|
68773
|
-
refCurrent: leftRef,
|
|
68774
|
-
token: rightToken,
|
|
68775
|
-
mode: "owns",
|
|
68776
|
-
isOrdered: false
|
|
68777
|
-
};
|
|
68778
|
-
if (rightRef && leftToken) return {
|
|
68779
|
-
refCurrent: rightRef,
|
|
68780
|
-
token: leftToken,
|
|
68781
|
-
mode: "owns",
|
|
68782
|
-
isOrdered: false
|
|
68783
|
-
};
|
|
68784
|
-
return null;
|
|
68785
|
-
}
|
|
68786
|
-
if (stripped.operator === "!==" || stripped.operator === "!=") {
|
|
68787
|
-
if (leftRef && rightToken) return {
|
|
68788
|
-
refCurrent: leftRef,
|
|
68789
|
-
token: rightToken,
|
|
68790
|
-
mode: "lost",
|
|
68791
|
-
isOrdered: false
|
|
68792
|
-
};
|
|
68793
|
-
if (rightRef && leftToken) return {
|
|
68794
|
-
refCurrent: rightRef,
|
|
68795
|
-
token: leftToken,
|
|
68796
|
-
mode: "lost",
|
|
68797
|
-
isOrdered: false
|
|
68798
|
-
};
|
|
68799
|
-
return null;
|
|
68800
|
-
}
|
|
68801
|
-
if (stripped.operator === "<=" && leftRef && rightToken) return {
|
|
68802
|
-
refCurrent: leftRef,
|
|
68803
|
-
token: rightToken,
|
|
68804
|
-
mode: "owns",
|
|
68805
|
-
isOrdered: true
|
|
68806
|
-
};
|
|
68807
|
-
if (stripped.operator === ">=" && rightRef && leftToken) return {
|
|
68808
|
-
refCurrent: rightRef,
|
|
68809
|
-
token: leftToken,
|
|
68810
|
-
mode: "owns",
|
|
68811
|
-
isOrdered: true
|
|
68812
|
-
};
|
|
68813
|
-
return null;
|
|
68814
|
-
};
|
|
68815
68639
|
const dedupeCatchPathStates = (states) => {
|
|
68816
68640
|
const statesByKey = /* @__PURE__ */ new Map();
|
|
68817
68641
|
for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
|
|
@@ -69144,221 +68968,7 @@ const isInsideTryFinalizer = (node, tryStatement) => {
|
|
|
69144
68968
|
}
|
|
69145
68969
|
return false;
|
|
69146
68970
|
};
|
|
69147
|
-
const
|
|
69148
|
-
let entry = node;
|
|
69149
|
-
let cursor = node.parent;
|
|
69150
|
-
while (cursor && cursor !== functionNode) {
|
|
69151
|
-
if (isNodeOfType(cursor, "BlockStatement")) return {
|
|
69152
|
-
block: cursor,
|
|
69153
|
-
entry
|
|
69154
|
-
};
|
|
69155
|
-
entry = cursor;
|
|
69156
|
-
cursor = cursor.parent ?? null;
|
|
69157
|
-
}
|
|
69158
|
-
return null;
|
|
69159
|
-
};
|
|
69160
|
-
const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
|
|
69161
|
-
const claimStart = getNodeStart$1(claimNode);
|
|
69162
|
-
if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
|
|
69163
|
-
const claimEntry = getDirectBlockEntry(claimNode, functionNode);
|
|
69164
|
-
const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
|
|
69165
|
-
if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
|
|
69166
|
-
let claimCursor = claimNode.parent;
|
|
69167
|
-
while (claimCursor && claimCursor !== claimEntry.block) {
|
|
69168
|
-
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;
|
|
69169
|
-
claimCursor = claimCursor.parent ?? null;
|
|
69170
|
-
}
|
|
69171
|
-
const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
|
|
69172
|
-
const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
|
|
69173
|
-
if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
|
|
69174
|
-
return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
|
|
69175
|
-
};
|
|
69176
|
-
const getOwningFunction = (functionNode) => {
|
|
69177
|
-
let ownerFunction = functionNode;
|
|
69178
|
-
let cursor = functionNode.parent;
|
|
69179
|
-
while (cursor) {
|
|
69180
|
-
if (isFunctionLike$1(cursor)) ownerFunction = cursor;
|
|
69181
|
-
cursor = cursor.parent ?? null;
|
|
69182
|
-
}
|
|
69183
|
-
return ownerFunction;
|
|
69184
|
-
};
|
|
69185
|
-
const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
|
|
69186
|
-
const truthyCall = truthySets[0]?.node;
|
|
69187
|
-
if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
|
|
69188
|
-
const setter = getSetterBooleanValue(truthyCall, context);
|
|
69189
|
-
if (!setter) return false;
|
|
69190
|
-
let effectCallback = writeNode.parent;
|
|
69191
|
-
while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
|
|
69192
|
-
if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
|
|
69193
|
-
if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
|
|
69194
|
-
const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
|
|
69195
|
-
if (!writeEntry) return false;
|
|
69196
|
-
let isPaired = false;
|
|
69197
|
-
walkOwnFunctionScope(effectCallback, (candidate) => {
|
|
69198
|
-
if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
|
|
69199
|
-
const candidateSetter = getSetterBooleanValue(candidate, context);
|
|
69200
|
-
if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
|
|
69201
|
-
const resetEntry = getDirectBlockEntry(candidate, effectCallback);
|
|
69202
|
-
if (!resetEntry || resetEntry.block !== writeEntry.block) return;
|
|
69203
|
-
const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
|
|
69204
|
-
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69205
|
-
if (writeIndex === -1 || resetIndex === -1) return;
|
|
69206
|
-
if (resetIndex <= writeIndex) {
|
|
69207
|
-
isPaired = true;
|
|
69208
|
-
return false;
|
|
69209
|
-
}
|
|
69210
|
-
isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
|
|
69211
|
-
return isPaired ? false : void 0;
|
|
69212
|
-
});
|
|
69213
|
-
return isPaired;
|
|
69214
|
-
};
|
|
69215
|
-
const isUnconditionalReturnBranch = (statement) => {
|
|
69216
|
-
if (isNodeOfType(statement, "ReturnStatement")) return true;
|
|
69217
|
-
return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
|
|
69218
|
-
};
|
|
69219
|
-
const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69220
|
-
const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
|
|
69221
|
-
const resetEntry = getDirectBlockEntry(resetNode, functionNode);
|
|
69222
|
-
if (!snapshotEntry || !resetEntry) return null;
|
|
69223
|
-
const claimCandidates = [];
|
|
69224
|
-
const releaseCandidates = [];
|
|
69225
|
-
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69226
|
-
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
|
|
69227
|
-
const assignedValue = stripParenExpression(candidate.right);
|
|
69228
|
-
if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
|
|
69229
|
-
const candidateKey = serializeReferenceKey({
|
|
69230
|
-
node: candidate.left,
|
|
69231
|
-
scopes: context.scopes
|
|
69232
|
-
});
|
|
69233
|
-
if (!candidateKey) return;
|
|
69234
|
-
if (!assignedValue.value) {
|
|
69235
|
-
if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
|
|
69236
|
-
return;
|
|
69237
|
-
}
|
|
69238
|
-
if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
|
|
69239
|
-
const candidateEntry = getDirectBlockEntry(candidate, functionNode);
|
|
69240
|
-
if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
|
|
69241
|
-
const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
|
|
69242
|
-
const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
|
|
69243
|
-
if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
|
|
69244
|
-
const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
|
|
69245
|
-
if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
|
|
69246
|
-
return serializeReferenceKey({
|
|
69247
|
-
node: stripParenExpression(statement.test),
|
|
69248
|
-
scopes: context.scopes
|
|
69249
|
-
}) === candidateKey;
|
|
69250
|
-
});
|
|
69251
|
-
if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
|
|
69252
|
-
claimCandidates.push(candidate);
|
|
69253
|
-
});
|
|
69254
|
-
const claim = claimCandidates.find((claimCandidate) => {
|
|
69255
|
-
const candidateKey = serializeReferenceKey({
|
|
69256
|
-
node: claimCandidate.left,
|
|
69257
|
-
scopes: context.scopes
|
|
69258
|
-
});
|
|
69259
|
-
return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
|
|
69260
|
-
node: releaseCandidate.left,
|
|
69261
|
-
scopes: context.scopes
|
|
69262
|
-
}) === candidateKey);
|
|
69263
|
-
});
|
|
69264
|
-
if (!claim) return null;
|
|
69265
|
-
const claimKey = serializeReferenceKey({
|
|
69266
|
-
node: claim.left,
|
|
69267
|
-
scopes: context.scopes
|
|
69268
|
-
});
|
|
69269
|
-
const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
|
|
69270
|
-
node: releaseCandidate.left,
|
|
69271
|
-
scopes: context.scopes
|
|
69272
|
-
}) === claimKey);
|
|
69273
|
-
if (!claimKey || !release) return null;
|
|
69274
|
-
const releaseEntry = getDirectBlockEntry(release, functionNode);
|
|
69275
|
-
if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
|
|
69276
|
-
const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
|
|
69277
|
-
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69278
|
-
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;
|
|
69279
|
-
let didFindUnsafeWrite = false;
|
|
69280
|
-
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69281
|
-
if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
|
|
69282
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69283
|
-
if (writeTarget && serializeReferenceKey({
|
|
69284
|
-
node: writeTarget,
|
|
69285
|
-
scopes: context.scopes
|
|
69286
|
-
}) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
|
|
69287
|
-
});
|
|
69288
|
-
return didFindUnsafeWrite ? null : claim;
|
|
69289
|
-
};
|
|
69290
|
-
const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69291
|
-
const refKey = serializeReferenceKey({
|
|
69292
|
-
node: comparison.refCurrent,
|
|
69293
|
-
scopes: context.scopes
|
|
69294
|
-
});
|
|
69295
|
-
const tokenKey = serializeReferenceKey({
|
|
69296
|
-
node: comparison.token,
|
|
69297
|
-
scopes: context.scopes
|
|
69298
|
-
});
|
|
69299
|
-
if (!refKey || !tokenKey) return null;
|
|
69300
|
-
const candidates = [];
|
|
69301
|
-
const tokenSymbol = context.scopes.symbolFor(comparison.token);
|
|
69302
|
-
const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
|
|
69303
|
-
if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
|
|
69304
|
-
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
|
|
69305
|
-
node: tokenInitializer.argument,
|
|
69306
|
-
scopes: context.scopes
|
|
69307
|
-
}) === refKey) candidates.push(tokenInitializer);
|
|
69308
|
-
if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
|
|
69309
|
-
node: tokenInitializer,
|
|
69310
|
-
scopes: context.scopes
|
|
69311
|
-
}) === refKey) {
|
|
69312
|
-
const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69313
|
-
if (singleFlightClaim) candidates.push(singleFlightClaim);
|
|
69314
|
-
}
|
|
69315
|
-
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
|
|
69316
|
-
const generationKey = serializeReferenceKey({
|
|
69317
|
-
node: tokenInitializer.argument,
|
|
69318
|
-
scopes: context.scopes
|
|
69319
|
-
});
|
|
69320
|
-
if (generationKey && generationKey === refKey) {
|
|
69321
|
-
const ownerFunction = getOwningFunction(functionNode);
|
|
69322
|
-
let didFindOtherGenerationWrite = false;
|
|
69323
|
-
walkAst(ownerFunction, (candidate) => {
|
|
69324
|
-
if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
|
|
69325
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69326
|
-
if (writeTarget && serializeReferenceKey({
|
|
69327
|
-
node: writeTarget,
|
|
69328
|
-
scopes: context.scopes
|
|
69329
|
-
}) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
|
|
69330
|
-
});
|
|
69331
|
-
if (didFindOtherGenerationWrite) return null;
|
|
69332
|
-
}
|
|
69333
|
-
}
|
|
69334
|
-
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69335
|
-
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
|
|
69336
|
-
if (serializeReferenceKey({
|
|
69337
|
-
node: candidate.left,
|
|
69338
|
-
scopes: context.scopes
|
|
69339
|
-
}) === refKey && serializeReferenceKey({
|
|
69340
|
-
node: candidate.right,
|
|
69341
|
-
scopes: context.scopes
|
|
69342
|
-
}) === tokenKey) candidates.push(candidate);
|
|
69343
|
-
});
|
|
69344
|
-
const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
|
|
69345
|
-
if (!claim) return null;
|
|
69346
|
-
let didFindOtherWrite = false;
|
|
69347
|
-
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69348
|
-
if (didFindOtherWrite || candidate === claim) return;
|
|
69349
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69350
|
-
if (writeTarget && serializeReferenceKey({
|
|
69351
|
-
node: writeTarget,
|
|
69352
|
-
scopes: context.scopes
|
|
69353
|
-
}) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
|
|
69354
|
-
});
|
|
69355
|
-
return didFindOtherWrite ? null : claim;
|
|
69356
|
-
};
|
|
69357
|
-
const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69358
|
-
const comparison = getAsyncOwnershipComparison(test, context);
|
|
69359
|
-
return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
|
|
69360
|
-
};
|
|
69361
|
-
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
|
|
68971
|
+
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedCleanupAssignments, context) => {
|
|
69362
68972
|
let didFindOtherWrite = false;
|
|
69363
68973
|
walkAst(effectCallback, (candidate) => {
|
|
69364
68974
|
if (didFindOtherWrite) return false;
|
|
@@ -69366,7 +68976,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
69366
68976
|
if (serializeReferenceKey({
|
|
69367
68977
|
node: candidate.left,
|
|
69368
68978
|
scopes: context.scopes
|
|
69369
|
-
}) === guardKey && !
|
|
68979
|
+
}) === guardKey && !acceptedCleanupAssignments.has(candidate)) {
|
|
69370
68980
|
didFindOtherWrite = true;
|
|
69371
68981
|
return false;
|
|
69372
68982
|
}
|
|
@@ -69382,103 +68992,48 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
69382
68992
|
});
|
|
69383
68993
|
return didFindOtherWrite;
|
|
69384
68994
|
};
|
|
69385
|
-
const
|
|
69386
|
-
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69387
|
-
for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69388
|
-
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69389
|
-
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69390
|
-
node: cleanupNode.left,
|
|
69391
|
-
scopes: context.scopes
|
|
69392
|
-
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69393
|
-
acceptedAssignments.add(cleanupNode);
|
|
69394
|
-
});
|
|
69395
|
-
if (acceptedAssignments.size === 0) return null;
|
|
69396
|
-
walkOwnFunctionScope(effectCallback, (effectNode) => {
|
|
69397
|
-
const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
|
|
69398
|
-
if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
|
|
69399
|
-
node: effectNode.left,
|
|
69400
|
-
scopes: context.scopes
|
|
69401
|
-
}) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
|
|
69402
|
-
});
|
|
69403
|
-
return acceptedAssignments;
|
|
69404
|
-
};
|
|
69405
|
-
const isEffectCallback = (node, context) => {
|
|
69406
|
-
const callbackRoot = findTransparentExpressionRoot(node);
|
|
69407
|
-
const callbackCall = callbackRoot.parent;
|
|
69408
|
-
return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69409
|
-
allowGlobalReactNamespace: true,
|
|
69410
|
-
allowUnboundBareCalls: true
|
|
69411
|
-
}));
|
|
69412
|
-
};
|
|
69413
|
-
const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
|
|
69414
|
-
const guardKey = serializeReferenceKey({
|
|
69415
|
-
node: guardExpression,
|
|
69416
|
-
scopes: context.scopes
|
|
69417
|
-
});
|
|
69418
|
-
if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69419
|
-
let ownerFunction = functionNode.parent;
|
|
69420
|
-
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
69421
|
-
if (!ownerFunction) return false;
|
|
69422
|
-
const effectCallbacks = [];
|
|
69423
|
-
if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
|
|
69424
|
-
walkOwnFunctionScope(ownerFunction, (candidate) => {
|
|
69425
|
-
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
69426
|
-
if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69427
|
-
allowGlobalReactNamespace: true,
|
|
69428
|
-
allowUnboundBareCalls: true
|
|
69429
|
-
})) return;
|
|
69430
|
-
const effectCallback = candidate.arguments[0];
|
|
69431
|
-
if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
|
|
69432
|
-
});
|
|
69433
|
-
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69434
|
-
for (const effectCallback of effectCallbacks) {
|
|
69435
|
-
const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
|
|
69436
|
-
if (!effectAssignments) continue;
|
|
69437
|
-
for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
|
|
69438
|
-
}
|
|
69439
|
-
return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
|
|
69440
|
-
};
|
|
69441
|
-
const collectLogicalOperands = (expression, operator) => {
|
|
69442
|
-
const stripped = stripParenExpression(expression);
|
|
69443
|
-
if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
|
|
69444
|
-
return [stripped];
|
|
69445
|
-
};
|
|
69446
|
-
const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
|
|
69447
|
-
const positive = [];
|
|
69448
|
-
const negative = [];
|
|
68995
|
+
const isResetGuardedByCleanupBackedLifecycle = (resetNode, functionNode, context) => {
|
|
69449
68996
|
let child = resetNode;
|
|
69450
68997
|
let cursor = resetNode.parent;
|
|
69451
|
-
|
|
69452
|
-
|
|
69453
|
-
|
|
69454
|
-
|
|
69455
|
-
|
|
69456
|
-
|
|
69457
|
-
|
|
69458
|
-
|
|
69459
|
-
|
|
69460
|
-
|
|
69461
|
-
|
|
69462
|
-
negative.push(...collectLogicalOperands(statement.test, "||"));
|
|
69463
|
-
}
|
|
69464
|
-
} 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;
|
|
68998
|
+
let guardKey = null;
|
|
68999
|
+
let guardExpression = null;
|
|
69000
|
+
while (cursor && cursor !== functionNode) {
|
|
69001
|
+
if (isNodeOfType(cursor, "IfStatement") && cursor.consequent === child && cursor.alternate === null) {
|
|
69002
|
+
guardExpression = cursor.test;
|
|
69003
|
+
guardKey = serializeReferenceKey({
|
|
69004
|
+
node: cursor.test,
|
|
69005
|
+
scopes: context.scopes
|
|
69006
|
+
});
|
|
69007
|
+
break;
|
|
69008
|
+
}
|
|
69465
69009
|
child = cursor;
|
|
69466
69010
|
cursor = cursor.parent ?? null;
|
|
69467
69011
|
}
|
|
69468
|
-
|
|
69469
|
-
|
|
69470
|
-
|
|
69471
|
-
|
|
69472
|
-
|
|
69473
|
-
const
|
|
69474
|
-
|
|
69475
|
-
|
|
69476
|
-
|
|
69477
|
-
|
|
69478
|
-
|
|
69479
|
-
const
|
|
69480
|
-
|
|
69481
|
-
|
|
69012
|
+
if (!guardKey || !guardExpression || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69013
|
+
cursor = functionNode.parent;
|
|
69014
|
+
while (cursor) {
|
|
69015
|
+
if (isFunctionLike$1(cursor)) {
|
|
69016
|
+
const callbackRoot = findTransparentExpressionRoot(cursor);
|
|
69017
|
+
const callbackCall = callbackRoot.parent;
|
|
69018
|
+
if (Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69019
|
+
allowGlobalReactNamespace: true,
|
|
69020
|
+
allowUnboundBareCalls: true
|
|
69021
|
+
}))) {
|
|
69022
|
+
const acceptedCleanupAssignments = /* @__PURE__ */ new Set();
|
|
69023
|
+
for (const cleanupFunction of collectReturnedCleanupFunctions(cursor, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69024
|
+
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69025
|
+
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69026
|
+
node: cleanupNode.left,
|
|
69027
|
+
scopes: context.scopes
|
|
69028
|
+
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69029
|
+
acceptedCleanupAssignments.add(cleanupNode);
|
|
69030
|
+
});
|
|
69031
|
+
if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
|
|
69032
|
+
}
|
|
69033
|
+
}
|
|
69034
|
+
cursor = cursor.parent ?? null;
|
|
69035
|
+
}
|
|
69036
|
+
return false;
|
|
69482
69037
|
};
|
|
69483
69038
|
const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
|
|
69484
69039
|
let child = awaitNode;
|
|
@@ -69584,13 +69139,7 @@ const analyzeFunction = (functionNode, context) => {
|
|
|
69584
69139
|
const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
|
|
69585
69140
|
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)));
|
|
69586
69141
|
if (riskyAwaitsWithTruthySet.length === 0) continue;
|
|
69587
|
-
const conditionalExceptionalResets = calls.filter((call) =>
|
|
69588
|
-
if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
|
|
69589
|
-
const protectingTry = call.protectingTry;
|
|
69590
|
-
if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
|
|
69591
|
-
const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
|
|
69592
|
-
return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
|
|
69593
|
-
});
|
|
69142
|
+
const conditionalExceptionalResets = calls.filter((call) => !call.value && call.context !== "plain" && !call.isUnconditional && call.protectingTry !== null && !(isInsideTryFinalizer(call.node, call.protectingTry) && isResetGuardedByCleanupBackedLifecycle(call.node, functionNode, context)));
|
|
69594
69143
|
for (const reset of conditionalExceptionalResets) {
|
|
69595
69144
|
const catchHandler = reset.protectingTry?.handler;
|
|
69596
69145
|
if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
|
|
@@ -79951,23 +79500,16 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
|
79951
79500
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
79952
79501
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
79953
79502
|
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).";
|
|
79954
|
-
const
|
|
79503
|
+
const isKnownNonThenableHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
79955
79504
|
const strippedExpression = stripParenExpression(expression);
|
|
79956
79505
|
if (isDefinitelyNonThenableValue(strippedExpression)) return true;
|
|
79957
|
-
if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
|
|
79958
|
-
const receiver = stripParenExpression(strippedExpression.callee.object);
|
|
79959
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
|
|
79960
|
-
const resolvedValue = strippedExpression.arguments[0];
|
|
79961
|
-
return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
|
|
79962
|
-
}
|
|
79963
|
-
}
|
|
79964
79506
|
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
79965
79507
|
if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
|
|
79966
79508
|
const symbol = context.scopes.symbolFor(strippedExpression);
|
|
79967
79509
|
if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
|
|
79968
79510
|
visitedBindingIdentifiers.add(symbol.bindingIdentifier);
|
|
79969
79511
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
79970
|
-
return Boolean(initializer &&
|
|
79512
|
+
return Boolean(initializer && isKnownNonThenableHandlerReturn(initializer, context, visitedBindingIdentifiers));
|
|
79971
79513
|
};
|
|
79972
79514
|
const isKnownNonRejectingHandler = (argument, context) => {
|
|
79973
79515
|
if (!argument) return false;
|
|
@@ -79982,7 +79524,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
79982
79524
|
canReject = true;
|
|
79983
79525
|
return false;
|
|
79984
79526
|
}
|
|
79985
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !
|
|
79527
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonThenableHandlerReturn(child.argument, context)) {
|
|
79986
79528
|
if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
|
|
79987
79529
|
canReject = true;
|
|
79988
79530
|
return false;
|
|
@@ -80031,28 +79573,6 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
|
80031
79573
|
});
|
|
80032
79574
|
return hasPotentiallyThrowingMemberRead;
|
|
80033
79575
|
};
|
|
80034
|
-
const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
|
|
80035
|
-
if (!argument) return false;
|
|
80036
|
-
if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
|
|
80037
|
-
if (!allowTerminalCatchBlock) return false;
|
|
80038
|
-
const candidate = stripParenExpression(argument);
|
|
80039
|
-
const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
|
|
80040
|
-
if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
|
|
80041
|
-
if (!isNodeOfType(handler.body, "BlockStatement")) return false;
|
|
80042
|
-
let doesExplicitlyReject = false;
|
|
80043
|
-
walkOwnFunctionScope(handler, (child) => {
|
|
80044
|
-
if (doesExplicitlyReject) return false;
|
|
80045
|
-
if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
|
|
80046
|
-
doesExplicitlyReject = true;
|
|
80047
|
-
return false;
|
|
80048
|
-
}
|
|
80049
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
80050
|
-
doesExplicitlyReject = true;
|
|
80051
|
-
return false;
|
|
80052
|
-
}
|
|
80053
|
-
});
|
|
80054
|
-
return !doesExplicitlyReject;
|
|
80055
|
-
};
|
|
80056
79576
|
const walkPromiseChain = (chainExpression, context) => {
|
|
80057
79577
|
let cursor = stripParenExpression(chainExpression);
|
|
80058
79578
|
let hasCatch = false;
|
|
@@ -80064,9 +79584,10 @@ const walkPromiseChain = (chainExpression, context) => {
|
|
|
80064
79584
|
while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
|
|
80065
79585
|
const methodName = getStaticPropertyName(cursor.callee);
|
|
80066
79586
|
const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
|
|
80067
|
-
|
|
79587
|
+
const hasAbsorbingRejectionHandler = !handlerHasPotentiallyThrowingMemberRead(rejectionHandlerArgument, context) && (chainCarriesRejectionHandler(cursor, context.scopes) || isKnownNonRejectingHandler(rejectionHandlerArgument, context));
|
|
79588
|
+
if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
|
|
80068
79589
|
if (methodName === "then") {
|
|
80069
|
-
if (!didReachTerminalThen &&
|
|
79590
|
+
if (!didReachTerminalThen && hasAbsorbingRejectionHandler) hasRejectionHandlerArgument = true;
|
|
80070
79591
|
didReachTerminalThen = true;
|
|
80071
79592
|
sawThen = true;
|
|
80072
79593
|
const callbackArgument = cursor.arguments[0];
|
|
@@ -82393,45 +81914,6 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
82393
81914
|
} })
|
|
82394
81915
|
});
|
|
82395
81916
|
//#endregion
|
|
82396
|
-
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82397
|
-
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82398
|
-
"Math.random",
|
|
82399
|
-
"Date.now",
|
|
82400
|
-
"performance.now",
|
|
82401
|
-
"crypto.randomUUID",
|
|
82402
|
-
"crypto.getRandomValues"
|
|
82403
|
-
]);
|
|
82404
|
-
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82405
|
-
"nanoid",
|
|
82406
|
-
"uuid",
|
|
82407
|
-
"cuid",
|
|
82408
|
-
"ulid",
|
|
82409
|
-
"createId"
|
|
82410
|
-
]);
|
|
82411
|
-
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82412
|
-
const containsNonDeterministicSource = (root) => {
|
|
82413
|
-
let found = false;
|
|
82414
|
-
walkAst(root, (child) => {
|
|
82415
|
-
if (found) return false;
|
|
82416
|
-
if (isFunctionLike$1(child)) return false;
|
|
82417
|
-
if (isZeroArgDateConstruction(child)) {
|
|
82418
|
-
found = true;
|
|
82419
|
-
return false;
|
|
82420
|
-
}
|
|
82421
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82422
|
-
const callee = child.callee;
|
|
82423
|
-
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82424
|
-
found = true;
|
|
82425
|
-
return false;
|
|
82426
|
-
}
|
|
82427
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82428
|
-
found = true;
|
|
82429
|
-
return false;
|
|
82430
|
-
}
|
|
82431
|
-
});
|
|
82432
|
-
return found;
|
|
82433
|
-
};
|
|
82434
|
-
//#endregion
|
|
82435
81917
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
82436
81918
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
82437
81919
|
"DoWhileStatement",
|
|
@@ -82462,75 +81944,45 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
82462
81944
|
};
|
|
82463
81945
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
82464
81946
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82465
|
-
|
|
82466
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
82467
|
-
const callee = stripParenExpression(expression.callee);
|
|
82468
|
-
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82469
|
-
}
|
|
82470
|
-
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
81947
|
+
return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
|
|
82471
81948
|
};
|
|
82472
|
-
const
|
|
81949
|
+
const getInitializationConstructorName = (node, scopes) => {
|
|
82473
81950
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82474
81951
|
if (!expression) return null;
|
|
82475
|
-
if (isNodeOfType(expression, "NewExpression")
|
|
81952
|
+
if (isNodeOfType(expression, "NewExpression")) {
|
|
82476
81953
|
const callee = stripParenExpression(expression.callee);
|
|
82477
|
-
|
|
82478
|
-
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
81954
|
+
return isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
82479
81955
|
}
|
|
82480
81956
|
return null;
|
|
82481
81957
|
};
|
|
82482
|
-
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82483
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82484
|
-
const typeName = typeNode.typeName;
|
|
82485
|
-
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82486
|
-
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82487
|
-
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82488
|
-
const queriedName = returnTypeArgument.exprName;
|
|
82489
|
-
const expression = stripParenExpression(initializationValue);
|
|
82490
|
-
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82491
|
-
const callee = stripParenExpression(expression.callee);
|
|
82492
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82493
|
-
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82494
|
-
const calleeSymbol = scopes.symbolFor(callee);
|
|
82495
|
-
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82496
|
-
};
|
|
82497
81958
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
82498
81959
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
82499
81960
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82500
81961
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
82501
81962
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
82502
81963
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82503
|
-
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82504
81964
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82505
81965
|
const typeName = typeNode.typeName;
|
|
82506
|
-
|
|
82507
|
-
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
81966
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
|
|
82508
81967
|
};
|
|
82509
81968
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
82510
81969
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82511
81970
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82512
81971
|
const [initialValue] = initializer.arguments ?? [];
|
|
82513
|
-
if (initialValue
|
|
81972
|
+
if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
|
|
82514
81973
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
82515
|
-
if (!declaredType) return false;
|
|
82516
|
-
|
|
81974
|
+
if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
|
|
81975
|
+
let hasEmptySentinel = false;
|
|
82517
81976
|
let hasTruthyDomain = false;
|
|
82518
|
-
for (const memberType of
|
|
82519
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
81977
|
+
for (const memberType of declaredType.types ?? []) {
|
|
81978
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
|
|
81979
|
+
hasEmptySentinel = true;
|
|
81980
|
+
continue;
|
|
81981
|
+
}
|
|
82520
81982
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
82521
81983
|
hasTruthyDomain = true;
|
|
82522
81984
|
}
|
|
82523
|
-
return hasTruthyDomain;
|
|
82524
|
-
};
|
|
82525
|
-
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82526
|
-
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82527
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82528
|
-
const [initialValue] = initializer.arguments ?? [];
|
|
82529
|
-
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82530
|
-
};
|
|
82531
|
-
const refHasDeclaredType = (refSymbol) => {
|
|
82532
|
-
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82533
|
-
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
81985
|
+
return hasEmptySentinel && hasTruthyDomain;
|
|
82534
81986
|
};
|
|
82535
81987
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
82536
81988
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -82562,40 +82014,25 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
82562
82014
|
});
|
|
82563
82015
|
return didFindRefCurrent;
|
|
82564
82016
|
};
|
|
82565
|
-
const
|
|
82566
|
-
|
|
82567
|
-
|
|
82568
|
-
|
|
82569
|
-
|
|
82570
|
-
|
|
82571
|
-
|
|
82572
|
-
if (!isInputIndependent) return false;
|
|
82573
|
-
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82574
|
-
if (!isNodeOfType(child, "Identifier")) return;
|
|
82575
|
-
const symbol = scopes.symbolFor(child);
|
|
82576
|
-
if (!symbol) return;
|
|
82577
|
-
if (symbol.kind === "import") return false;
|
|
82578
|
-
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82579
|
-
isInputIndependent = false;
|
|
82580
|
-
return false;
|
|
82017
|
+
const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
|
|
82018
|
+
let writeCount = 0;
|
|
82019
|
+
walkAst(branchRoot, (child) => {
|
|
82020
|
+
if (writeCount > 1) return false;
|
|
82021
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82022
|
+
if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
|
|
82023
|
+
return;
|
|
82581
82024
|
}
|
|
82582
|
-
if (
|
|
82583
|
-
|
|
82584
|
-
|
|
82585
|
-
return false;
|
|
82025
|
+
if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
|
|
82026
|
+
if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
|
|
82027
|
+
return;
|
|
82586
82028
|
}
|
|
82587
|
-
if (
|
|
82588
|
-
|
|
82589
|
-
return false;
|
|
82029
|
+
if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
|
|
82030
|
+
if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
|
|
82590
82031
|
}
|
|
82591
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82592
|
-
visitedSymbolIds.add(symbol.id);
|
|
82593
|
-
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82594
|
-
return false;
|
|
82595
82032
|
});
|
|
82596
|
-
return
|
|
82033
|
+
return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
|
|
82597
82034
|
};
|
|
82598
|
-
const
|
|
82035
|
+
const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
|
|
82599
82036
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
82600
82037
|
let ancestor = node.parent;
|
|
82601
82038
|
while (ancestor && ancestor !== stop) {
|
|
@@ -82625,27 +82062,6 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
82625
82062
|
}
|
|
82626
82063
|
return true;
|
|
82627
82064
|
};
|
|
82628
|
-
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82629
|
-
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82630
|
-
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82631
|
-
let hasCompetingWrite = false;
|
|
82632
|
-
walkAst(renderOwner, (child) => {
|
|
82633
|
-
if (hasCompetingWrite) return false;
|
|
82634
|
-
let writtenExpression = null;
|
|
82635
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82636
|
-
if (child === assignmentExpression) return;
|
|
82637
|
-
writtenExpression = child.left;
|
|
82638
|
-
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82639
|
-
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82640
|
-
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82641
|
-
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82642
|
-
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82643
|
-
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82644
|
-
hasCompetingWrite = true;
|
|
82645
|
-
return false;
|
|
82646
|
-
});
|
|
82647
|
-
return !hasCompetingWrite;
|
|
82648
|
-
};
|
|
82649
82065
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
82650
82066
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
82651
82067
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -82661,17 +82077,16 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
82661
82077
|
});
|
|
82662
82078
|
return !hasCoExecutableWrite;
|
|
82663
82079
|
};
|
|
82664
|
-
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);
|
|
82665
82080
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
82081
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
82082
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
82666
82083
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
82667
82084
|
if (!renderOwner) return false;
|
|
82668
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82669
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
82670
82085
|
let descendant = assignmentExpression;
|
|
82671
82086
|
let ancestor = descendant.parent;
|
|
82672
82087
|
while (ancestor) {
|
|
82673
82088
|
const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
|
|
82674
|
-
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant &&
|
|
82089
|
+
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
|
|
82675
82090
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
82676
82091
|
"===",
|
|
82677
82092
|
"==",
|
|
@@ -82681,7 +82096,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
82681
82096
|
const { left, right } = test;
|
|
82682
82097
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
82683
82098
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
82684
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
82099
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
|
|
82685
82100
|
}
|
|
82686
82101
|
descendant = ancestor;
|
|
82687
82102
|
ancestor = descendant.parent;
|
|
@@ -110488,6 +109903,9 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
110488
109903
|
} })
|
|
110489
109904
|
});
|
|
110490
109905
|
//#endregion
|
|
109906
|
+
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
109907
|
+
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
109908
|
+
//#endregion
|
|
110491
109909
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
110492
109910
|
const rerenderLazyRefInit = defineRule({
|
|
110493
109911
|
id: "rerender-lazy-ref-init",
|
|
@@ -110506,6 +109924,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
110506
109924
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
110507
109925
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
110508
109926
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
109927
|
+
if (isTrivialBuiltInConstruction(initializer)) return;
|
|
110509
109928
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
110510
109929
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
110511
109930
|
context.report({
|