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.
Files changed (2) hide show
  1. package/dist/index.js +123 -704
  2. 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
- 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;
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 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) => {
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, context.scopes)) return;
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 getDirectBlockEntry = (node, functionNode) => {
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 && !acceptedAssignments.has(candidate)) {
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 collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
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
- while (cursor && cursor !== protectingTry) {
69452
- if (isNodeOfType(cursor, "IfStatement")) {
69453
- if (cursor.consequent !== child || cursor.alternate !== null) return null;
69454
- positive.push(...collectLogicalOperands(cursor.test, "&&"));
69455
- } else if (isNodeOfType(cursor, "LogicalExpression")) {
69456
- if (cursor.operator !== "&&" || cursor.right !== child) return null;
69457
- positive.push(...collectLogicalOperands(cursor.left, "&&"));
69458
- } else if (isNodeOfType(cursor, "BlockStatement")) {
69459
- const childIndex = cursor.body.findIndex((statement) => statement === child);
69460
- if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
69461
- if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
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
- return cursor === protectingTry && positive.length + negative.length > 0 ? {
69469
- positive,
69470
- negative
69471
- } : null;
69472
- };
69473
- const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69474
- const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
69475
- const stripped = stripParenExpression(expression);
69476
- if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
69477
- return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69478
- };
69479
- const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
69480
- const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
69481
- 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)));
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 isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
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 && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
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 && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
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
- if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
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 && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
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
- if (!expression) return false;
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 getInitializationValueName = (node, scopes) => {
81949
+ const getInitializationConstructorName = (node, scopes) => {
82473
81950
  const expression = resolveImmutableInitializationValue(node, scopes);
82474
81951
  if (!expression) return null;
82475
- if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
81952
+ if (isNodeOfType(expression, "NewExpression")) {
82476
81953
  const callee = stripParenExpression(expression.callee);
82477
- if (!isNodeOfType(callee, "Identifier")) return null;
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
- if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
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 && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
81972
+ if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
82514
81973
  const [declaredType] = initializer.typeArguments?.params ?? [];
82515
- if (!declaredType) return false;
82516
- const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
81974
+ if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
81975
+ let hasEmptySentinel = false;
82517
81976
  let hasTruthyDomain = false;
82518
- for (const memberType of domainTypes) {
82519
- if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
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 isEmptySentinel = (node, scopes) => {
82566
- const expression = stripParenExpression(node);
82567
- return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
82568
- };
82569
- const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
82570
- let isInputIndependent = true;
82571
- walkAst(node, (child) => {
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 (isOutsideAllFunctions(symbol)) return false;
82583
- if (symbol.kind === "parameter") {
82584
- if (symbol.scope.node === renderOwner) isInputIndependent = false;
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 (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
82588
- isInputIndependent = false;
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 isInputIndependent;
82033
+ return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
82597
82034
  };
82598
- const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
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 && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
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 && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.9.2-dev.c126684",
3
+ "version": "0.9.2-dev.c6bdd2d",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",