oxlint-plugin-react-doctor 0.7.6-dev.b36e439 → 0.7.6-dev.c1916d5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1452 -510
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6004,6 +6004,170 @@ const getStaticPropertyKeyName = (node, options = {}) => {
6004
6004
  return null;
6005
6005
  };
6006
6006
  //#endregion
6007
+ //#region src/plugin/utils/are-expressions-structurally-equal.ts
6008
+ const areExpressionsStructurallyEqual = (a, b) => {
6009
+ if (!a || !b) return a === b;
6010
+ const unwrappedA = stripParenExpression(a);
6011
+ const unwrappedB = stripParenExpression(b);
6012
+ if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
6013
+ if (a.type !== b.type) return false;
6014
+ if (isNodeOfType(a, "ThisExpression")) return true;
6015
+ if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
6016
+ if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
6017
+ if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
6018
+ if (a.computed !== b.computed) return false;
6019
+ return areExpressionsStructurallyEqual(a.object, b.object) && areExpressionsStructurallyEqual(a.property, b.property);
6020
+ }
6021
+ if (isNodeOfType(a, "CallExpression") && isNodeOfType(b, "CallExpression")) {
6022
+ if (!areExpressionsStructurallyEqual(a.callee, b.callee)) return false;
6023
+ const argumentsA = a.arguments ?? [];
6024
+ const argumentsB = b.arguments ?? [];
6025
+ if (argumentsA.length !== argumentsB.length) return false;
6026
+ return argumentsA.every((argument, index) => areExpressionsStructurallyEqual(argument, argumentsB[index]));
6027
+ }
6028
+ return false;
6029
+ };
6030
+ //#endregion
6031
+ //#region src/plugin/utils/has-keyboard-activatable-descendant.ts
6032
+ const NATIVE_KEYBOARD_ACTIVATABLE_TAGS = new Set([
6033
+ "a",
6034
+ "button",
6035
+ "input",
6036
+ "select",
6037
+ "summary",
6038
+ "textarea"
6039
+ ]);
6040
+ const KEYBOARD_ACTIVATABLE_COMPONENT_NAME_PATTERN = /button|link|nav|anchor/i;
6041
+ const EQUIVALENT_ACTION_COMPONENT_NAME_PATTERN = /(?:button|link|anchor)$/i;
6042
+ const UPPERCASE_COMPONENT_NAME_PATTERN = /^[A-Z]/;
6043
+ const DESCENDANT_ACTION_PROP_NAMES = ["onClick", "onPress"];
6044
+ const isStaticallyNullish = (expression) => isNullishExpression(stripParenExpression(expression));
6045
+ const isStaticallyNullishHandlerExpression = (expression, scopes) => {
6046
+ const strippedExpression = stripParenExpression(expression);
6047
+ if (isNullishExpression(strippedExpression)) return true;
6048
+ const symbol = resolveConstIdentifierAlias(strippedExpression, scopes);
6049
+ if (symbol?.kind !== "const" || !symbol.initializer) return false;
6050
+ return isNullishExpression(stripParenExpression(symbol.initializer));
6051
+ };
6052
+ const resolveSingleHandlerAction = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
6053
+ const strippedExpression = stripParenExpression(expression);
6054
+ if (isStaticallyNullishHandlerExpression(strippedExpression, scopes)) return null;
6055
+ if (isNodeOfType(strippedExpression, "ConditionalExpression")) {
6056
+ const consequent = strippedExpression.consequent;
6057
+ const alternate = strippedExpression.alternate;
6058
+ if (isStaticallyNullishHandlerExpression(consequent, scopes)) return resolveSingleHandlerAction(alternate, scopes, visitedSymbolIds);
6059
+ if (isStaticallyNullishHandlerExpression(alternate, scopes)) return resolveSingleHandlerAction(consequent, scopes, visitedSymbolIds);
6060
+ return null;
6061
+ }
6062
+ if (isNodeOfType(strippedExpression, "Identifier")) {
6063
+ const symbol = scopes.symbolFor(strippedExpression);
6064
+ if (symbol?.kind === "const" && symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "Identifier") && !visitedSymbolIds.has(symbol.id)) {
6065
+ visitedSymbolIds.add(symbol.id);
6066
+ return resolveSingleHandlerAction(symbol.initializer, scopes, visitedSymbolIds);
6067
+ }
6068
+ return strippedExpression;
6069
+ }
6070
+ if (isNodeOfType(strippedExpression, "ArrowFunctionExpression") || isNodeOfType(strippedExpression, "FunctionExpression") || isNodeOfType(strippedExpression, "FunctionDeclaration")) {
6071
+ const body = stripParenExpression(strippedExpression.body);
6072
+ if (!isNodeOfType(body, "BlockStatement")) return body;
6073
+ if (body.body.length !== 1) return null;
6074
+ const statement = body.body[0];
6075
+ if (isNodeOfType(statement, "ExpressionStatement")) return statement.expression;
6076
+ if (isNodeOfType(statement, "ReturnStatement") && statement.argument) return stripParenExpression(statement.argument);
6077
+ return null;
6078
+ }
6079
+ return strippedExpression;
6080
+ };
6081
+ const getAttributeAction = (attribute, scopes) => {
6082
+ if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
6083
+ return resolveSingleHandlerAction(attribute.value.expression, scopes);
6084
+ };
6085
+ const hasPotentiallyTruthyAttribute = (openingElement, attributeName) => {
6086
+ const attribute = hasJsxPropIgnoreCase(openingElement.attributes, attributeName);
6087
+ if (!attribute) return false;
6088
+ if (!attribute.value) return true;
6089
+ if (isNodeOfType(attribute.value, "Literal")) return attribute.value.value === true || attribute.value.value === "true";
6090
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return true;
6091
+ const expression = stripParenExpression(attribute.value.expression);
6092
+ return !isNodeOfType(expression, "Literal") || expression.value !== false;
6093
+ };
6094
+ const hasPotentiallyNonEmptyAttribute = (openingElement, attributeName) => {
6095
+ const attribute = hasJsxPropIgnoreCase(openingElement.attributes, attributeName);
6096
+ if (!attribute?.value) return false;
6097
+ if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
6098
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return true;
6099
+ const expression = stripParenExpression(attribute.value.expression);
6100
+ if (isNodeOfType(expression, "Literal")) return typeof expression.value === "string" && expression.value.trim().length > 0;
6101
+ return !isStaticallyNullish(expression);
6102
+ };
6103
+ const childMayProvideAccessibleName = (child) => {
6104
+ if (isNodeOfType(child, "JSXText")) return child.value.trim().length > 0;
6105
+ if (isNodeOfType(child, "JSXElement")) return hasAccessibleNameEvidence(child);
6106
+ if (isNodeOfType(child, "JSXFragment")) return child.children.some((nestedChild) => childMayProvideAccessibleName(nestedChild));
6107
+ if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
6108
+ const expression = stripParenExpression(child.expression);
6109
+ if (isNodeOfType(expression, "Literal")) {
6110
+ if (typeof expression.value === "string") return expression.value.trim().length > 0;
6111
+ return typeof expression.value === "number";
6112
+ }
6113
+ if (isStaticallyNullish(expression)) return false;
6114
+ if (isNodeOfType(expression, "JSXElement")) return hasAccessibleNameEvidence(expression);
6115
+ if (isNodeOfType(expression, "JSXFragment")) return expression.children.some((nestedChild) => childMayProvideAccessibleName(nestedChild));
6116
+ return true;
6117
+ };
6118
+ const hasAccessibleNameEvidence = (element) => {
6119
+ if (hasPotentiallyNonEmptyAttribute(element.openingElement, "aria-label") || hasPotentiallyNonEmptyAttribute(element.openingElement, "aria-labelledby")) return true;
6120
+ return element.children.some((child) => childMayProvideAccessibleName(child));
6121
+ };
6122
+ const hasNegativeStaticTabIndex = (openingElement) => {
6123
+ const tabIndexAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "tabIndex");
6124
+ if (!tabIndexAttribute?.value) return false;
6125
+ const tabIndexValue = parseJsxValue(tabIndexAttribute.value);
6126
+ return tabIndexValue !== null && tabIndexValue < 0;
6127
+ };
6128
+ const isHiddenSubtreeRoot = (openingElement, settings) => isHiddenFromScreenReader(openingElement, settings) || hasPotentiallyTruthyAttribute(openingElement, "hidden");
6129
+ const isKeyboardActivatableElement = (element, requiresAccessibleName) => {
6130
+ const openingElement = element.openingElement;
6131
+ const elementName = flattenJsxName$1(openingElement.name);
6132
+ if (!elementName) return false;
6133
+ if (HTML_TAGS.has(elementName)) {
6134
+ if (!NATIVE_KEYBOARD_ACTIVATABLE_TAGS.has(elementName)) return false;
6135
+ } else if (requiresAccessibleName) {
6136
+ if (!UPPERCASE_COMPONENT_NAME_PATTERN.test(elementName) || !EQUIVALENT_ACTION_COMPONENT_NAME_PATTERN.test(elementName)) return false;
6137
+ } else if (!UPPERCASE_COMPONENT_NAME_PATTERN.test(elementName) || !KEYBOARD_ACTIVATABLE_COMPONENT_NAME_PATTERN.test(elementName)) return false;
6138
+ if (!requiresAccessibleName) return true;
6139
+ if (elementName === "a" && !hasJsxPropIgnoreCase(openingElement.attributes, "href")) return false;
6140
+ if (hasPotentiallyTruthyAttribute(openingElement, "disabled") || hasPotentiallyTruthyAttribute(openingElement, "isDisabled") || hasPotentiallyTruthyAttribute(openingElement, "aria-disabled") || hasNegativeStaticTabIndex(openingElement)) return false;
6141
+ return hasAccessibleNameEvidence(element);
6142
+ };
6143
+ const findKeyboardActivatableDescendant = (node, expectedAction, scopes, settings) => {
6144
+ const walk = (descendant) => findKeyboardActivatableDescendant(descendant, expectedAction, scopes, settings);
6145
+ if (isNodeOfType(node, "JSXElement")) {
6146
+ if (expectedAction && isHiddenSubtreeRoot(node.openingElement, settings)) return false;
6147
+ if (isKeyboardActivatableElement(node, expectedAction !== null)) {
6148
+ if (!expectedAction) return true;
6149
+ for (const actionPropName of DESCENDANT_ACTION_PROP_NAMES) {
6150
+ const attribute = hasJsxPropIgnoreCase(node.openingElement.attributes, actionPropName);
6151
+ const action = attribute ? getAttributeAction(attribute, scopes) : null;
6152
+ if (action && areExpressionsStructurallyEqual(expectedAction, action)) return true;
6153
+ }
6154
+ }
6155
+ return node.children.some((child) => walk(child));
6156
+ }
6157
+ if (isNodeOfType(node, "JSXFragment")) return node.children.some((child) => walk(child));
6158
+ if (!expectedAction) return false;
6159
+ if (isNodeOfType(node, "JSXExpressionContainer")) return walk(node.expression);
6160
+ if (isNodeOfType(node, "LogicalExpression")) return walk(node.left) || walk(node.right);
6161
+ if (isNodeOfType(node, "ConditionalExpression")) return walk(node.consequent) || walk(node.alternate);
6162
+ return false;
6163
+ };
6164
+ const hasKeyboardActivatableDescendant = (element, interactionAttribute, scopes, settings) => {
6165
+ if (!element || !isNodeOfType(element, "JSXElement")) return false;
6166
+ const expectedAction = interactionAttribute ? getAttributeAction(interactionAttribute, scopes) : null;
6167
+ if (interactionAttribute && !expectedAction) return false;
6168
+ return element.children.some((child) => findKeyboardActivatableDescendant(child, expectedAction, scopes, settings));
6169
+ };
6170
+ //#endregion
6007
6171
  //#region src/plugin/utils/is-presentation-role.ts
6008
6172
  const isPresentationRole = (openingElement) => {
6009
6173
  const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
@@ -6012,6 +6176,53 @@ const isPresentationRole = (openingElement) => {
6012
6176
  return value !== null && PRESENTATION_ROLES$1.has(value);
6013
6177
  };
6014
6178
  //#endregion
6179
+ //#region src/plugin/utils/get-property-key-name.ts
6180
+ const getPropertyKeyName$2 = (propertyKey) => {
6181
+ if (isNodeOfType(propertyKey, "Identifier") || isNodeOfType(propertyKey, "PrivateIdentifier")) return propertyKey.name;
6182
+ };
6183
+ //#endregion
6184
+ //#region src/plugin/utils/resolve-member-handler-function.ts
6185
+ const asHandlerFunction = (value) => {
6186
+ if (isNodeOfType(value, "FunctionExpression") || isNodeOfType(value, "ArrowFunctionExpression")) return value;
6187
+ };
6188
+ const resolveFromClassBody = (classBody, propertyName) => {
6189
+ for (const classElement of classBody.body) {
6190
+ if (!isNodeOfType(classElement, "MethodDefinition") && !isNodeOfType(classElement, "PropertyDefinition")) continue;
6191
+ if (getPropertyKeyName$2(classElement.key) !== propertyName) continue;
6192
+ const resolvedHandler = asHandlerFunction(classElement.value);
6193
+ if (resolvedHandler) return resolvedHandler;
6194
+ }
6195
+ };
6196
+ const resolveFromObjectExpression = (objectExpression, propertyName) => {
6197
+ for (const objectProperty of objectExpression.properties) {
6198
+ if (!isNodeOfType(objectProperty, "Property")) continue;
6199
+ if (getPropertyKeyName$2(objectProperty.key) !== propertyName) continue;
6200
+ const resolvedHandler = asHandlerFunction(objectProperty.value);
6201
+ if (resolvedHandler) return resolvedHandler;
6202
+ }
6203
+ };
6204
+ const resolveMemberHandlerFunction = (handler) => {
6205
+ const propertyName = getPropertyKeyName$2(handler.property);
6206
+ if (propertyName === void 0) return void 0;
6207
+ const objectNode = handler.object;
6208
+ if (isNodeOfType(objectNode, "ThisExpression")) {
6209
+ let ancestor = handler.parent;
6210
+ while (ancestor) {
6211
+ if (isNodeOfType(ancestor, "ClassBody")) return resolveFromClassBody(ancestor, propertyName);
6212
+ if (isNodeOfType(ancestor, "ObjectExpression")) {
6213
+ const resolvedHandler = resolveFromObjectExpression(ancestor, propertyName);
6214
+ if (resolvedHandler) return resolvedHandler;
6215
+ }
6216
+ ancestor = ancestor.parent;
6217
+ }
6218
+ return;
6219
+ }
6220
+ if (isNodeOfType(objectNode, "Identifier")) {
6221
+ const binding = findVariableInitializer(objectNode, objectNode.name);
6222
+ if (isNodeOfType(binding?.initializer, "ObjectExpression")) return resolveFromObjectExpression(binding.initializer, propertyName);
6223
+ }
6224
+ };
6225
+ //#endregion
6015
6226
  //#region src/plugin/utils/is-pure-event-blocker-handler.ts
6016
6227
  const BLOCKER_METHOD_NAMES = new Set([
6017
6228
  "stopPropagation",
@@ -6043,9 +6254,10 @@ const isPureEventBlockerBody = (body) => {
6043
6254
  };
6044
6255
  const isPureEventBlockerHandler = (attribute) => {
6045
6256
  if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
6046
- const expression = attribute.value.expression;
6257
+ const expression = stripParenExpression(attribute.value.expression);
6047
6258
  if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return isPureEventBlockerBody(expression.body);
6048
- return false;
6259
+ if (!isNodeOfType(expression, "MemberExpression")) return false;
6260
+ return isPureEventBlockerBody(resolveMemberHandlerFunction(expression)?.body);
6049
6261
  };
6050
6262
  //#endregion
6051
6263
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
@@ -6197,29 +6409,6 @@ const hasCompositeItemRole = (node) => {
6197
6409
  const firstRole = roleValue.value.split(/\s+/)[0];
6198
6410
  return Boolean(firstRole && COMPOSITE_ITEM_ROLES$1.has(firstRole.toLowerCase()));
6199
6411
  };
6200
- const NATIVE_ACTIVATABLE_TAGS = new Set([
6201
- "a",
6202
- "button",
6203
- "input",
6204
- "select",
6205
- "textarea",
6206
- "summary"
6207
- ]);
6208
- const INTERACTIVE_COMPONENT_NAME_PATTERN = /button|link|nav|anchor/i;
6209
- const containsKeyboardActivatableDescendant = (element) => {
6210
- if (!element || !isNodeOfType(element, "JSXElement")) return false;
6211
- for (const child of element.children) {
6212
- const childNode = child;
6213
- if (!isNodeOfType(childNode, "JSXElement")) continue;
6214
- const name = flattenJsxName$1(childNode.openingElement.name);
6215
- if (name) {
6216
- if (NATIVE_ACTIVATABLE_TAGS.has(name)) return true;
6217
- if (/^[A-Z]/.test(name) && INTERACTIVE_COMPONENT_NAME_PATTERN.test(name)) return true;
6218
- }
6219
- if (containsKeyboardActivatableDescendant(childNode)) return true;
6220
- }
6221
- return false;
6222
- };
6223
6412
  const isTargetCurrentTargetComparison = (node) => {
6224
6413
  if (!isNodeOfType(node, "BinaryExpression")) return false;
6225
6414
  if (node.operator !== "===" && node.operator !== "==" && node.operator !== "!==") return false;
@@ -6276,7 +6465,8 @@ const clickEventsHaveKeyEvents = defineRule({
6276
6465
  if (hasCompositeItemRole(node)) return;
6277
6466
  if (isHoverSelectionListItem(tag, node)) return;
6278
6467
  if (onClick && isBackdropDismissHandler(onClick)) return;
6279
- if (containsKeyboardActivatableDescendant(node.parent)) return;
6468
+ if (hasKeyboardActivatableDescendant(node.parent, null, context.scopes, context.settings)) return;
6469
+ if (onClick && hasKeyboardActivatableDescendant(node.parent, onClick, context.scopes, context.settings)) return;
6280
6470
  if (isHiddenFromScreenReader(node, context.settings)) return;
6281
6471
  if (isPresentationRole(node)) return;
6282
6472
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
@@ -6890,6 +7080,27 @@ const createMethodMutationAnalysis = (context) => {
6890
7080
  };
6891
7081
  };
6892
7082
  //#endregion
7083
+ //#region src/plugin/utils/collect-function-return-statements.ts
7084
+ const collectFunctionReturnStatements = (functionNode) => {
7085
+ if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
7086
+ const returnStatements = [];
7087
+ walkAst(functionNode.body, (node) => {
7088
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
7089
+ if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
7090
+ });
7091
+ return returnStatements;
7092
+ };
7093
+ //#endregion
7094
+ //#region src/plugin/utils/find-enclosing-function.ts
7095
+ const findEnclosingFunction = (node) => {
7096
+ let cursor = node.parent;
7097
+ while (cursor) {
7098
+ if (isFunctionLike$2(cursor)) return cursor;
7099
+ cursor = cursor.parent ?? null;
7100
+ }
7101
+ return null;
7102
+ };
7103
+ //#endregion
6893
7104
  //#region src/plugin/utils/statement-always-exits.ts
6894
7105
  const statementAlwaysExits = (statement) => {
6895
7106
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -6899,17 +7110,109 @@ const statementAlwaysExits = (statement) => {
6899
7110
  };
6900
7111
  //#endregion
6901
7112
  //#region src/plugin/utils/function-returns-matching-expression.ts
7113
+ const REASSIGNABLE_BINDING_KINDS = new Set(["let", "var"]);
7114
+ const CONDITIONAL_EXPRESSION_TYPES = new Set(["ConditionalExpression", "LogicalExpression"]);
6902
7115
  const collectReturnedExpressions = (functionNode) => {
6903
- if (!isFunctionLike$2(functionNode)) return [];
6904
- const body = functionNode.body;
6905
- if (!body) return [];
6906
- if (!isNodeOfType(body, "BlockStatement")) return [body];
6907
- const returnedExpressions = [];
6908
- walkAst(body, (node) => {
6909
- if (node !== body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
6910
- if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
6911
- });
6912
- return returnedExpressions;
7116
+ if (!isFunctionLike$2(functionNode) || !functionNode.body) return [];
7117
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return [functionNode.body];
7118
+ return collectFunctionReturnStatements(functionNode).flatMap((returnStatement) => returnStatement.argument ? [returnStatement.argument] : []);
7119
+ };
7120
+ const getAssignedExpressionForWrite = (writeIdentifier) => {
7121
+ let assignmentTarget = writeIdentifier;
7122
+ let parent = assignmentTarget.parent;
7123
+ while (parent && stripParenExpression(parent) === writeIdentifier) {
7124
+ assignmentTarget = parent;
7125
+ parent = assignmentTarget.parent;
7126
+ }
7127
+ return parent && isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.left === assignmentTarget ? parent.right : null;
7128
+ };
7129
+ const isConditionalWithinBlock = (node, block, functionControlFlow) => {
7130
+ let current = node.parent ?? null;
7131
+ while (current && functionControlFlow.blockOf(current) === block) {
7132
+ if (CONDITIONAL_EXPRESSION_TYPES.has(current.type)) return true;
7133
+ current = current.parent ?? null;
7134
+ }
7135
+ return false;
7136
+ };
7137
+ const haveSameDefinitions = (left, right) => left.size === right.size && [...left].every((definition) => right.has(definition));
7138
+ const applyDefinitions = (incomingDefinitions, definitions) => {
7139
+ let currentDefinitions = new Set(incomingDefinitions);
7140
+ for (const definition of definitions) {
7141
+ if (!definition.isConditionalWithinBlock) currentDefinitions = /* @__PURE__ */ new Set();
7142
+ currentDefinitions.add(definition);
7143
+ }
7144
+ return currentDefinitions;
7145
+ };
7146
+ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
7147
+ if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
7148
+ if (!controlFlow) return [];
7149
+ const referenceFunction = findEnclosingFunction(referenceNode);
7150
+ if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
7151
+ if (!referenceFunction) return [];
7152
+ const functionControlFlow = controlFlow.cfgFor(referenceFunction);
7153
+ if (!functionControlFlow) return [];
7154
+ const referenceBlock = functionControlFlow.blockOf(referenceNode);
7155
+ if (!referenceBlock) return [];
7156
+ const referencePosition = getRangeStart(referenceNode);
7157
+ const bindingPosition = getRangeStart(symbol.bindingIdentifier);
7158
+ if (referencePosition === null || bindingPosition === null) return [];
7159
+ const definitionsByBlock = /* @__PURE__ */ new Map();
7160
+ const addDefinition = (expression, definitionNode, position) => {
7161
+ const block = functionControlFlow.blockOf(definitionNode);
7162
+ if (!block) return;
7163
+ const definitions = definitionsByBlock.get(block) ?? [];
7164
+ definitions.push({
7165
+ expression,
7166
+ position,
7167
+ isConditionalWithinBlock: isConditionalWithinBlock(definitionNode, block, functionControlFlow)
7168
+ });
7169
+ definitionsByBlock.set(block, definitions);
7170
+ };
7171
+ if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
7172
+ for (const reference of symbol.references) {
7173
+ const writePosition = getRangeStart(reference.identifier);
7174
+ if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
7175
+ const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
7176
+ if (!assignedExpression) continue;
7177
+ addDefinition(assignedExpression, reference.identifier, writePosition);
7178
+ }
7179
+ for (const definitions of definitionsByBlock.values()) definitions.sort((left, right) => left.position - right.position);
7180
+ const incomingDefinitionsByBlock = /* @__PURE__ */ new Map();
7181
+ const outgoingDefinitionsByBlock = /* @__PURE__ */ new Map();
7182
+ const reachableBlocks = new Set([functionControlFlow.entry]);
7183
+ const pendingBlocks = [functionControlFlow.entry];
7184
+ while (pendingBlocks.length > 0) {
7185
+ const block = pendingBlocks.pop();
7186
+ if (!block) break;
7187
+ for (const edge of block.successors) {
7188
+ if (reachableBlocks.has(edge.to)) continue;
7189
+ reachableBlocks.add(edge.to);
7190
+ pendingBlocks.push(edge.to);
7191
+ }
7192
+ }
7193
+ if (!reachableBlocks.has(referenceBlock)) return [];
7194
+ let didDefinitionsChange = true;
7195
+ while (didDefinitionsChange) {
7196
+ didDefinitionsChange = false;
7197
+ for (const block of functionControlFlow.blocks) {
7198
+ if (!reachableBlocks.has(block)) continue;
7199
+ const incomingDefinitions = /* @__PURE__ */ new Set();
7200
+ for (const predecessor of block.predecessors) {
7201
+ if (!reachableBlocks.has(predecessor.from)) continue;
7202
+ for (const definition of outgoingDefinitionsByBlock.get(predecessor.from) ?? []) incomingDefinitions.add(definition);
7203
+ }
7204
+ const outgoingDefinitions = applyDefinitions(incomingDefinitions, definitionsByBlock.get(block) ?? []);
7205
+ const previousIncomingDefinitions = incomingDefinitionsByBlock.get(block) ?? /* @__PURE__ */ new Set();
7206
+ const previousOutgoingDefinitions = outgoingDefinitionsByBlock.get(block) ?? /* @__PURE__ */ new Set();
7207
+ if (!haveSameDefinitions(incomingDefinitions, previousIncomingDefinitions) || !haveSameDefinitions(outgoingDefinitions, previousOutgoingDefinitions)) {
7208
+ incomingDefinitionsByBlock.set(block, incomingDefinitions);
7209
+ outgoingDefinitionsByBlock.set(block, outgoingDefinitions);
7210
+ didDefinitionsChange = true;
7211
+ }
7212
+ }
7213
+ }
7214
+ const definitionsBeforeReference = (definitionsByBlock.get(referenceBlock) ?? []).filter((definition) => definition.position < referencePosition);
7215
+ return [...applyDefinitions(incomingDefinitionsByBlock.get(referenceBlock) ?? /* @__PURE__ */ new Set(), definitionsBeforeReference)].map((definition) => definition.expression);
6913
7216
  };
6914
7217
  const functionHasBareReturn = (functionNode) => {
6915
7218
  if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return false;
@@ -6921,7 +7224,7 @@ const functionHasBareReturn = (functionNode) => {
6921
7224
  });
6922
7225
  return didFindBareReturn;
6923
7226
  };
6924
- const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression, matchMode = "some") => {
7227
+ const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression, controlFlow, matchMode = "some") => {
6925
7228
  const visitedExpressions = /* @__PURE__ */ new Set();
6926
7229
  const visitedFunctions = /* @__PURE__ */ new Set();
6927
7230
  const functionMatches = (candidateFunction) => {
@@ -6938,10 +7241,11 @@ const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpressi
6938
7241
  if (matchesExpression(unwrappedExpression)) return true;
6939
7242
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
6940
7243
  const symbol = scopes.symbolFor(unwrappedExpression);
6941
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
6942
- const initializer = stripParenExpression(symbol.initializer);
6943
- if (isFunctionLike$2(initializer)) return false;
6944
- return expressionMatches(initializer);
7244
+ if (!symbol || symbol.kind !== "const" && !REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return false;
7245
+ return collectPossibleAssignedExpressions(symbol, unwrappedExpression, controlFlow).some((assignedExpression) => {
7246
+ const assignedValue = stripParenExpression(assignedExpression);
7247
+ return !isFunctionLike$2(assignedValue) && expressionMatches(assignedValue);
7248
+ });
6945
7249
  }
6946
7250
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
6947
7251
  if (unwrappedExpression.arguments.length !== 0) return false;
@@ -7342,7 +7646,7 @@ const isProvenBrowserApiReceiver = (receiver, receiverKind, scopes, visitedSymbo
7342
7646
  const callee = stripParenExpression(expression.callee);
7343
7647
  if (isDomEventTarget && isNodeOfType(callee, "MemberExpression") && DOM_EVENT_TARGET_FACTORY_METHOD_NAMES.has(getStaticPropertyName(callee) ?? "") && isProvenBrowserApiReceiver(callee.object, receiverKind, scopes, visitedSymbolIds)) return true;
7344
7648
  const calledFunction = getSameFileCalledFunction(expression, scopes, visitedSymbolIds);
7345
- return Boolean(calledFunction && functionReturnsMatchingExpression(calledFunction, scopes, (returnedExpression) => isProvenBrowserApiReceiver(returnedExpression, receiverKind, scopes, new Set(visitedSymbolIds)), "every"));
7649
+ return Boolean(calledFunction && functionReturnsMatchingExpression(calledFunction, scopes, (returnedExpression) => isProvenBrowserApiReceiver(returnedExpression, receiverKind, scopes, new Set(visitedSymbolIds)), void 0, "every"));
7346
7650
  }
7347
7651
  if (!isNodeOfType(expression, "MemberExpression")) return false;
7348
7652
  const classMember = getClassMemberDefinition(expression);
@@ -8045,50 +8349,6 @@ const assignedHandlerCallsPreventDefault = (scopeOwner, handlerName) => {
8045
8349
  });
8046
8350
  return didFindPreventDefault;
8047
8351
  };
8048
- const asHandlerFunction = (value) => {
8049
- if (!value) return void 0;
8050
- if (isNodeOfType(value, "FunctionExpression") || isNodeOfType(value, "ArrowFunctionExpression")) return value;
8051
- };
8052
- const memberKeyName = (keyNode) => {
8053
- if (isNodeOfType(keyNode, "Identifier") || isNodeOfType(keyNode, "PrivateIdentifier")) return keyNode.name;
8054
- };
8055
- const resolveFromClassBody = (classBody, propertyName) => {
8056
- for (const element of classBody.body ?? []) {
8057
- if (!isNodeOfType(element, "MethodDefinition") && !isNodeOfType(element, "PropertyDefinition")) continue;
8058
- if (memberKeyName(element.key) !== propertyName) continue;
8059
- const resolved = asHandlerFunction(element.value);
8060
- if (resolved) return resolved;
8061
- }
8062
- };
8063
- const resolveFromObjectExpression = (objectExpression, propertyName) => {
8064
- for (const objectProperty of objectExpression.properties ?? []) {
8065
- if (!isNodeOfType(objectProperty, "Property")) continue;
8066
- if (memberKeyName(objectProperty.key) !== propertyName) continue;
8067
- const resolved = asHandlerFunction(objectProperty.value);
8068
- if (resolved) return resolved;
8069
- }
8070
- };
8071
- const resolveMemberHandlerFunction = (handler) => {
8072
- const propertyName = memberKeyName(handler.property);
8073
- if (propertyName === void 0) return void 0;
8074
- const objectNode = handler.object;
8075
- if (isNodeOfType(objectNode, "ThisExpression")) {
8076
- let ancestor = handler.parent;
8077
- while (ancestor) {
8078
- if (isNodeOfType(ancestor, "ClassBody")) return resolveFromClassBody(ancestor, propertyName);
8079
- if (isNodeOfType(ancestor, "ObjectExpression")) {
8080
- const resolved = resolveFromObjectExpression(ancestor, propertyName);
8081
- if (resolved) return resolved;
8082
- }
8083
- ancestor = ancestor.parent ?? null;
8084
- }
8085
- return;
8086
- }
8087
- if (isNodeOfType(objectNode, "Identifier")) {
8088
- const initializer = findVariableInitializer(objectNode, objectNode.name)?.initializer;
8089
- if (initializer && isNodeOfType(initializer, "ObjectExpression")) return resolveFromObjectExpression(initializer, propertyName);
8090
- }
8091
- };
8092
8352
  const handlerArgumentCallsPreventDefault = (handler) => {
8093
8353
  if (!handler) return false;
8094
8354
  if (handlerCallsPreventDefault(handler)) return true;
@@ -8250,7 +8510,7 @@ const handlerMayExposeEvent = (handler, context) => {
8250
8510
  }
8251
8511
  const propertyName = getStaticPropertyName(expression);
8252
8512
  if (isNodeOfType(memberObject, "ObjectExpression") && propertyName) {
8253
- const property = memberObject.properties.find((candidate) => isNodeOfType(candidate, "Property") && memberKeyName(candidate.key) === propertyName);
8513
+ const property = memberObject.properties.find((candidate) => isNodeOfType(candidate, "Property") && getPropertyKeyName$2(candidate.key) === propertyName);
8254
8514
  return Boolean(isNodeOfType(property, "Property") && expressionContainsParameterAlias(property.value));
8255
8515
  }
8256
8516
  if (isNodeOfType(memberObject, "ArrayExpression") && isNodeOfType(expression.property, "Literal") && typeof expression.property.value === "number") {
@@ -8291,9 +8551,9 @@ const handlerMayExposeEvent = (handler, context) => {
8291
8551
  continue;
8292
8552
  }
8293
8553
  if (!isNodeOfType(patternProperty, "Property")) continue;
8294
- const propertyName = memberKeyName(patternProperty.key);
8554
+ const propertyName = getPropertyKeyName$2(patternProperty.key);
8295
8555
  if (!propertyName) continue;
8296
- const sourceProperty = source.properties.find((property) => isNodeOfType(property, "Property") && memberKeyName(property.key) === propertyName);
8556
+ const sourceProperty = source.properties.find((property) => isNodeOfType(property, "Property") && getPropertyKeyName$2(property.key) === propertyName);
8297
8557
  if (isNodeOfType(sourceProperty, "Property")) addAliasesFromPattern(patternProperty.value, sourceProperty.value);
8298
8558
  }
8299
8559
  return;
@@ -10165,21 +10425,29 @@ const isListenerPathAmbiguous = (node, bodyNode, allowAmbiguousChild) => {
10165
10425
  };
10166
10426
  //#endregion
10167
10427
  //#region src/plugin/rules/state-and-effects/utils/resolve-event-listener-capture.ts
10168
- const resolveEventListenerCapture = (optionsNode) => {
10428
+ const resolveEventListenerCapture = (optionsNode, { allowComputedString = false, allowIndeterminateEntries = false } = {}) => {
10169
10429
  if (!optionsNode) return false;
10170
10430
  const unwrappedOptions = stripParenExpression(optionsNode);
10171
10431
  if (isNodeOfType(unwrappedOptions, "Literal")) return typeof unwrappedOptions.value === "boolean" ? unwrappedOptions.value : null;
10172
10432
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return null;
10173
10433
  let capture = false;
10174
- for (const property of unwrappedOptions.properties) {
10434
+ for (const property of unwrappedOptions.properties ?? []) {
10435
+ if (allowIndeterminateEntries && isNodeOfType(property, "SpreadElement")) {
10436
+ capture = null;
10437
+ continue;
10438
+ }
10175
10439
  if (!isNodeOfType(property, "Property")) return null;
10176
- const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
10440
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString });
10177
10441
  if (propertyName === null) return null;
10178
10442
  if (!property.computed && propertyName === "__proto__") return null;
10179
10443
  if (propertyName !== "capture") continue;
10180
10444
  const propertyValue = stripParenExpression(property.value);
10181
- if (!isNodeOfType(propertyValue, "Literal") || typeof propertyValue.value !== "boolean") return null;
10182
- capture = propertyValue.value;
10445
+ if (isNodeOfType(propertyValue, "Literal") && typeof propertyValue.value === "boolean") {
10446
+ capture = propertyValue.value;
10447
+ continue;
10448
+ }
10449
+ if (!allowIndeterminateEntries) return null;
10450
+ capture = null;
10183
10451
  }
10184
10452
  return capture;
10185
10453
  };
@@ -10422,7 +10690,7 @@ const readListenerCandidate = (node, methodName, context) => {
10422
10690
  const targetKey = resolveTargetKey(targetNode, context);
10423
10691
  const eventName = resolveStaticEventName(node.arguments?.[0], context);
10424
10692
  const callbackIdentity = resolveCallbackIdentity(node.arguments?.[1], context);
10425
- const capture = resolveEventListenerCapture(node.arguments?.[2]);
10693
+ const capture = resolveEventListenerCapture(node.arguments?.[2], { allowComputedString: true });
10426
10694
  if (targetKey === null || eventName === null) return null;
10427
10695
  return {
10428
10696
  node,
@@ -10456,7 +10724,7 @@ const readDestructuredRemovalCandidate = (node, context) => {
10456
10724
  targetKey,
10457
10725
  eventName,
10458
10726
  callbackIdentity: resolveCallbackIdentity(node.arguments?.[2], context),
10459
- capture: resolveEventListenerCapture(node.arguments?.[3])
10727
+ capture: resolveEventListenerCapture(node.arguments?.[3], { allowComputedString: true })
10460
10728
  };
10461
10729
  };
10462
10730
  const resolveReturnedCleanupBody = (returnedValue, context) => {
@@ -10816,16 +11084,6 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
10816
11084
  return displayNameFromFunctionBinding(functionNode);
10817
11085
  };
10818
11086
  //#endregion
10819
- //#region src/plugin/utils/find-enclosing-function.ts
10820
- const findEnclosingFunction = (node) => {
10821
- let cursor = node.parent;
10822
- while (cursor) {
10823
- if (isFunctionLike$2(cursor)) return cursor;
10824
- cursor = cursor.parent ?? null;
10825
- }
10826
- return null;
10827
- };
10828
- //#endregion
10829
11087
  //#region src/plugin/utils/enclosing-component-or-hook-name.ts
10830
11088
  const enclosingComponentOrHookName = (node) => {
10831
11089
  const functionNode = findEnclosingFunction(node);
@@ -10844,7 +11102,7 @@ const findDeclaratorForBinding = (bindingIdentifier) => {
10844
11102
  };
10845
11103
  //#endregion
10846
11104
  //#region src/plugin/utils/executes-during-render.ts
10847
- const SYNCHRONOUS_ITERATION_METHOD_NAMES$1 = new Set([
11105
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
10848
11106
  "map",
10849
11107
  "filter",
10850
11108
  "forEach",
@@ -10880,7 +11138,7 @@ const executesDuringRender = (functionNode, scopes) => {
10880
11138
  if (parent.callee === functionNode) return true;
10881
11139
  if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
10882
11140
  if (scopes && parent.arguments?.[1] === functionNode && (isGlobalArrayFromMember(parent.callee, scopes) || isConstAliasOfGlobalArrayFrom(parent.callee, scopes))) return true;
10883
- return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES$1.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
11141
+ return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
10884
11142
  };
10885
11143
  //#endregion
10886
11144
  //#region src/plugin/utils/find-render-phase-component-or-hook.ts
@@ -10911,6 +11169,26 @@ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(
10911
11169
  //#region src/plugin/utils/is-event-handler-attribute.ts
10912
11170
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
10913
11171
  //#endregion
11172
+ //#region src/plugin/utils/react-ref-origin.ts
11173
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
11174
+ const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
11175
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
11176
+ const symbol = resolveConstIdentifierAlias(receiver, scopes);
11177
+ if (!symbol?.initializer) return null;
11178
+ const initializer = stripParenExpression(symbol.initializer);
11179
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
11180
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
11181
+ };
11182
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
11183
+ const expression = stripParenExpression(node);
11184
+ if (resolveReactRefSymbol(expression, scopes)) return true;
11185
+ if (!isNodeOfType(expression, "Identifier")) return false;
11186
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
11187
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
11188
+ visitedSymbolIds.add(symbol.id);
11189
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
11190
+ };
11191
+ //#endregion
10914
11192
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
10915
11193
  const walkInsideStatementBlocks = (node, visitor) => {
10916
11194
  if (!node || typeof node !== "object") return;
@@ -10947,6 +11225,7 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
10947
11225
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
10948
11226
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
10949
11227
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
11228
+ const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
10950
11229
  const RESOURCE_NOUN_BY_KIND = {
10951
11230
  subscribe: "subscription",
10952
11231
  timer: "timer",
@@ -11176,9 +11455,108 @@ const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
11176
11455
  return !doMatchingNodesCoverEveryPathAfterUsage(usage.node, matchingReleaseCalls, context);
11177
11456
  });
11178
11457
  };
11458
+ const findForOfStatementForIteratorExpression = (expression, context) => {
11459
+ if (!expression) return null;
11460
+ const unwrappedExpression = stripParenExpression(expression);
11461
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
11462
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
11463
+ const bindingDeclarator = symbol?.bindingIdentifier.parent;
11464
+ const bindingDeclaration = bindingDeclarator?.parent;
11465
+ const forOfStatement = bindingDeclaration?.parent;
11466
+ const isStableIteratorBinding = isNodeOfType(bindingDeclaration, "VariableDeclaration") && symbol?.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier));
11467
+ return symbol && isNodeOfType(bindingDeclarator, "VariableDeclarator") && bindingDeclarator.id === symbol.bindingIdentifier && isStableIteratorBinding && bindingDeclaration.declarations.length === 1 && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === bindingDeclaration && forOfStatement.await !== true ? forOfStatement : null;
11468
+ };
11469
+ const isAssignmentFormForOfIteratorReference = (expression, context) => {
11470
+ if (!expression) return false;
11471
+ const unwrappedExpression = stripParenExpression(expression);
11472
+ const referencedSymbolIds = /* @__PURE__ */ new Set();
11473
+ walkAst(unwrappedExpression, (expressionChild) => {
11474
+ if (!isNodeOfType(expressionChild, "Identifier")) return;
11475
+ const symbol = context.scopes.symbolFor(expressionChild);
11476
+ if (symbol) referencedSymbolIds.add(symbol.id);
11477
+ });
11478
+ if (referencedSymbolIds.size === 0) return false;
11479
+ const assignsReferencedSymbol = (root, requireAssignmentTarget) => {
11480
+ let didAssignReferencedSymbol = false;
11481
+ walkAst(root, (child) => {
11482
+ if (!isNodeOfType(child, "Identifier")) return;
11483
+ if (requireAssignmentTarget && !isWithinAssignmentTarget(child)) return;
11484
+ const childSymbol = context.scopes.symbolFor(child);
11485
+ if (childSymbol && referencedSymbolIds.has(childSymbol.id)) {
11486
+ didAssignReferencedSymbol = true;
11487
+ return false;
11488
+ }
11489
+ });
11490
+ return didAssignReferencedSymbol;
11491
+ };
11492
+ let currentNode = unwrappedExpression.parent;
11493
+ while (currentNode && !isFunctionLike$2(currentNode)) {
11494
+ if (isNodeOfType(currentNode, "ForOfStatement")) {
11495
+ const loopTarget = stripParenExpression(currentNode.left);
11496
+ if (!isNodeOfType(loopTarget, "VariableDeclaration") && (assignsReferencedSymbol(loopTarget, false) || assignsReferencedSymbol(currentNode.body, true))) return true;
11497
+ }
11498
+ currentNode = currentNode.parent;
11499
+ }
11500
+ return false;
11501
+ };
11502
+ const isPrivatePlainConstIdentifier = (identifier, context) => {
11503
+ if (!isNodeOfType(identifier, "Identifier")) return false;
11504
+ const symbol = context.scopes.symbolFor(identifier);
11505
+ if (!symbol || symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
11506
+ return !isNodeOfType(symbol.declarationNode.parent?.parent, "ExportNamedDeclaration") && !isNodeOfType(symbol.declarationNode.parent?.parent, "ExportDefaultDeclaration");
11507
+ };
11508
+ const hasOnlyReplayableCollectionReferences = (identifier, context, visitedSymbolIds) => {
11509
+ if (!isNodeOfType(identifier, "Identifier") || !isPrivatePlainConstIdentifier(identifier, context)) return false;
11510
+ const symbol = context.scopes.symbolFor(identifier);
11511
+ if (!symbol) return false;
11512
+ if (visitedSymbolIds.has(symbol.id)) return true;
11513
+ visitedSymbolIds.add(symbol.id);
11514
+ return symbol.references.every((reference) => {
11515
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
11516
+ const parent = referenceRoot.parent;
11517
+ if (isNodeOfType(parent, "ForOfStatement") && parent.right === referenceRoot) return true;
11518
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot) {
11519
+ const declaration = parent.parent;
11520
+ return isNodeOfType(parent.id, "Identifier") && isNodeOfType(declaration, "VariableDeclaration") && declaration.kind === "const" ? hasOnlyReplayableCollectionReferences(parent.id, context, visitedSymbolIds) : false;
11521
+ }
11522
+ return false;
11523
+ });
11524
+ };
11525
+ const resolveReplayableIteratorCollectionKeyUncached = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
11526
+ if (!expression) return null;
11527
+ const unwrappedExpression = stripParenExpression(expression);
11528
+ if (!isNodeOfType(unwrappedExpression, "Identifier") || !isPrivatePlainConstIdentifier(unwrappedExpression, context)) return null;
11529
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
11530
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
11531
+ visitedSymbolIds.add(symbol.id);
11532
+ const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
11533
+ if (isNodeOfType(initializer, "ArrayExpression")) return (initializer.elements ?? []).every((element) => element === null || isNodeOfType(element, "Literal") && (element.value === null || typeof element.value === "boolean" || typeof element.value === "number" || typeof element.value === "string")) && hasOnlyReplayableCollectionReferences(symbol.bindingIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${symbol.id}` : null;
11534
+ if (!isNodeOfType(initializer, "Identifier")) return null;
11535
+ return resolveReplayableIteratorCollectionKeyUncached(initializer, context, visitedSymbolIds);
11536
+ };
11537
+ const resolveReplayableIteratorCollectionKey = (expression, context) => {
11538
+ if (!expression) return null;
11539
+ const unwrappedExpression = stripParenExpression(expression);
11540
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
11541
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
11542
+ if (!symbol) return null;
11543
+ let contextCache = REPLAYABLE_ITERATOR_COLLECTION_CACHE.get(context);
11544
+ if (!contextCache) {
11545
+ contextCache = /* @__PURE__ */ new Map();
11546
+ REPLAYABLE_ITERATOR_COLLECTION_CACHE.set(context, contextCache);
11547
+ }
11548
+ if (contextCache.has(symbol.id)) return contextCache.get(symbol.id) ?? null;
11549
+ const collectionKey = resolveReplayableIteratorCollectionKeyUncached(expression, context);
11550
+ contextCache.set(symbol.id, collectionKey);
11551
+ return collectionKey;
11552
+ };
11179
11553
  const resolveIteratorCollectionKey = (expression, context) => {
11180
- if (!expression || !isNodeOfType(expression, "Identifier")) return null;
11181
- const symbol = context.scopes.symbolFor(expression);
11554
+ if (!expression) return null;
11555
+ const unwrappedExpression = stripParenExpression(expression);
11556
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
11557
+ const forOfStatement = findForOfStatementForIteratorExpression(unwrappedExpression, context);
11558
+ if (forOfStatement) return resolveReplayableIteratorCollectionKey(forOfStatement.right, context);
11559
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
11182
11560
  if (!symbol || symbol.kind !== "parameter") return null;
11183
11561
  let callbackNode = symbol.bindingIdentifier.parent;
11184
11562
  while (callbackNode && !isFunctionLike$2(callbackNode)) callbackNode = callbackNode.parent;
@@ -11192,6 +11570,34 @@ const resolveIteratorCollectionKey = (expression, context) => {
11192
11570
  }
11193
11571
  return null;
11194
11572
  };
11573
+ const isStableLoopReceiver = (expression, context) => {
11574
+ if (!expression) return false;
11575
+ const unwrappedExpression = stripParenExpression(expression);
11576
+ return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "document" && context.scopes.isGlobalReference(unwrappedExpression);
11577
+ };
11578
+ const resolveStableLoopHandlerSymbolId = (expression, context) => {
11579
+ if (!expression) return null;
11580
+ const unwrappedExpression = stripParenExpression(expression);
11581
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
11582
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
11583
+ if (!symbol || symbol.kind !== "const" && symbol.kind !== "function" && symbol.kind !== "parameter" || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
11584
+ return symbol.id;
11585
+ };
11586
+ const isDirectExhaustiveForOfRelease = (releaseNode, forOfStatement) => {
11587
+ const releaseStatement = findTransparentExpressionRoot(releaseNode).parent;
11588
+ if (!isNodeOfType(releaseStatement, "ExpressionStatement")) return false;
11589
+ if (!(isNodeOfType(forOfStatement.body, "BlockStatement") ? releaseStatement.parent === forOfStatement.body : releaseStatement === forOfStatement.body)) return false;
11590
+ let hasAbruptLoopExit = false;
11591
+ walkAst(forOfStatement.body, (child) => {
11592
+ if (hasAbruptLoopExit) return false;
11593
+ if (child !== forOfStatement.body && isFunctionLike$2(child)) return false;
11594
+ if (isNodeOfType(child, "BreakStatement") || isNodeOfType(child, "ContinueStatement") || isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
11595
+ hasAbruptLoopExit = true;
11596
+ return false;
11597
+ }
11598
+ });
11599
+ return !hasAbruptLoopExit;
11600
+ };
11195
11601
  const findCollectionMappingCall = (callbackNode) => {
11196
11602
  if (!isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression") || callbackNode.async || callbackNode.generator) return null;
11197
11603
  const callNode = callbackNode.parent;
@@ -11339,23 +11745,26 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
11339
11745
  if (!isFunctionLike$2(cleanupFunction) || visitedFunctions.has(cleanupFunction)) return false;
11340
11746
  visitedFunctions.add(cleanupFunction);
11341
11747
  let didCleanupFunctionMatch = false;
11748
+ const matchingLoopOrHelperAnchors = [];
11342
11749
  walkAst(cleanupFunction.body, (cleanupChild) => {
11343
11750
  if (didCleanupFunctionMatch) return false;
11344
11751
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$2(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
11752
+ const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
11345
11753
  if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
11346
- didCleanupFunctionMatch = true;
11347
- return false;
11754
+ const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
11755
+ if (!cleanupForOfStatement) {
11756
+ didCleanupFunctionMatch = true;
11757
+ return false;
11758
+ }
11759
+ if (!cleanupFunction.async && !cleanupFunction.generator && isDirectExhaustiveForOfRelease(cleanupChild, cleanupForOfStatement)) matchingLoopOrHelperAnchors.push(cleanupForOfStatement);
11760
+ return;
11348
11761
  }
11349
- const helperCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
11350
- if (!isNodeOfType(helperCall, "CallExpression")) return;
11351
- const stableHelperFunction = resolveStableValue(helperCall.callee, context);
11762
+ if (!isNodeOfType(cleanupCall, "CallExpression")) return;
11763
+ const stableHelperFunction = resolveStableValue(cleanupCall.callee, context);
11352
11764
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
11353
- if (helperFunction && isFunctionLike$2(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, visitedFunctions)) {
11354
- didCleanupFunctionMatch = true;
11355
- return false;
11356
- }
11765
+ if (helperFunction && isFunctionLike$2(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
11357
11766
  });
11358
- return didCleanupFunctionMatch;
11767
+ return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
11359
11768
  };
11360
11769
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
11361
11770
  const callExpression = stripParenExpression(expression);
@@ -11372,6 +11781,7 @@ const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
11372
11781
  };
11373
11782
  const callbackReturnsCleanupForUsage = (callback, usage, context) => {
11374
11783
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11784
+ if (callback.async) return false;
11375
11785
  const doesReturnedValueReleaseUsage = (returnedValue) => {
11376
11786
  if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) return true;
11377
11787
  const cleanupFunction = resolveStableValue(returnedValue, context);
@@ -11436,6 +11846,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11436
11846
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
11437
11847
  const effectHasCleanupForUsage = (callback, usage, context) => {
11438
11848
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11849
+ if (callback.async) return false;
11439
11850
  if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
11440
11851
  if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
11441
11852
  const matchingCleanupReturns = [];
@@ -11559,10 +11970,29 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
11559
11970
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
11560
11971
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
11561
11972
  const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
11973
+ const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
11974
+ const releaseEventArgument = callNode.arguments?.[0];
11975
+ if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
11562
11976
  if (usage.eventKey !== null && releaseEventKey !== null && usage.eventKey !== releaseEventKey) {
11563
- const usageIteratorCollectionKey = isNodeOfType(usage.node, "CallExpression") ? resolveIteratorCollectionKey(usage.node.arguments?.[0], context) : null;
11977
+ if (!isNodeOfType(usage.node, "CallExpression")) return false;
11978
+ const usageForOfStatement = findForOfStatementForIteratorExpression(usageEventArgument, context);
11979
+ const releaseForOfStatement = findForOfStatementForIteratorExpression(releaseEventArgument, context);
11980
+ if (usageForOfStatement === null !== (releaseForOfStatement === null)) return false;
11981
+ const usageIteratorCollectionKey = resolveIteratorCollectionKey(usage.node.arguments?.[0], context);
11564
11982
  const releaseIteratorCollectionKey = resolveIteratorCollectionKey(callNode.arguments?.[0], context);
11565
11983
  if (usageIteratorCollectionKey === null || usageIteratorCollectionKey !== releaseIteratorCollectionKey) return false;
11984
+ if (usageForOfStatement && releaseForOfStatement) {
11985
+ const registrationHandlerSymbolId = resolveStableLoopHandlerSymbolId(usage.node.arguments?.[1], context);
11986
+ const releaseHandlerSymbolId = resolveStableLoopHandlerSymbolId(callNode.arguments?.[1], context);
11987
+ if (usage.registrationVerbName !== "addEventListener" || releaseVerbName !== "removeEventListener") return false;
11988
+ const registrationCallee = stripParenExpression(usage.node.callee);
11989
+ if (!isNodeOfType(registrationCallee, "MemberExpression")) return false;
11990
+ if (!isStableLoopReceiver(registrationCallee.object, context) || !isStableLoopReceiver(callee.object, context) || registrationHandlerSymbolId === null || registrationHandlerSymbolId !== releaseHandlerSymbolId) return false;
11991
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
11992
+ const releaseCapture = resolveEventListenerCapture(callNode.arguments?.[2], { allowIndeterminateEntries: true });
11993
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
11994
+ if (!isDirectExhaustiveForOfRelease(callNode, releaseForOfStatement)) return false;
11995
+ }
11566
11996
  }
11567
11997
  if (releaseVerbName === "on") {
11568
11998
  const handlerArgument = callNode.arguments?.[1];
@@ -11617,6 +12047,213 @@ const fileContainsReleaseForUsage = (usage, context) => {
11617
12047
  });
11618
12048
  return didFindRelease;
11619
12049
  };
12050
+ const resolveRefOwnedCleanupFunction = (expression, context) => {
12051
+ const resolvedExpression = resolveStableValue(expression, context);
12052
+ if (isFunctionLike$2(resolvedExpression)) return resolvedExpression;
12053
+ if (!isNodeOfType(resolvedExpression, "CallExpression") || !isReactApiCall(resolvedExpression, "useCallback", context.scopes)) return null;
12054
+ return getEffectCallback(resolvedExpression);
12055
+ };
12056
+ const findRefOwnedHandlerStorage = (retainedFunction, usage, context) => {
12057
+ if (!isFunctionLike$2(retainedFunction) || usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.handlerKey === null || !usage.receiverKey?.startsWith("global:") || !usage.eventKey?.startsWith("literal:")) return null;
12058
+ const usageStart = getRangeStart(usage.node);
12059
+ const functionCfg = context.cfg.cfgFor(retainedFunction);
12060
+ const usageBlock = functionCfg?.blockOf(usage.node);
12061
+ if (usageStart === null || !functionCfg || !usageBlock) return null;
12062
+ const matchingStorage = [];
12063
+ walkAst(retainedFunction.body, (child) => {
12064
+ if (child !== retainedFunction.body && isFunctionLike$2(child)) return false;
12065
+ if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=") return;
12066
+ const assignmentStart = getRangeStart(child);
12067
+ const refCurrentExpression = stripParenExpression(child.left);
12068
+ if (assignmentStart === null || assignmentStart >= usageStart || functionCfg.blockOf(child) !== usageBlock || !isNodeOfType(refCurrentExpression, "MemberExpression") || !resolveReactRefSymbol(refCurrentExpression, context.scopes)) return;
12069
+ const refCurrentKey = resolveExpressionKey(refCurrentExpression, context);
12070
+ const refKey = resolveExpressionKey(refCurrentExpression.object, context);
12071
+ const storedSession = stripParenExpression(child.right);
12072
+ if (!refCurrentKey || !refKey || !isNodeOfType(storedSession, "ObjectExpression")) return;
12073
+ const storedSessionProperties = storedSession.properties ?? [];
12074
+ if (storedSessionProperties.some((property) => !isNodeOfType(property, "Property"))) return;
12075
+ for (const property of storedSessionProperties) {
12076
+ if (!isNodeOfType(property, "Property")) continue;
12077
+ const propertyName = getStaticPropertyKeyName(property);
12078
+ if (propertyName && resolveExpressionKey(property.value, context) === usage.handlerKey) {
12079
+ const handlerKey = `${refCurrentKey}.${propertyName}`;
12080
+ matchingStorage.push({
12081
+ handlerKey,
12082
+ refCurrentKey,
12083
+ refKey,
12084
+ assignmentNode: child
12085
+ });
12086
+ }
12087
+ }
12088
+ });
12089
+ return matchingStorage.length === 1 ? matchingStorage[0] : null;
12090
+ };
12091
+ const doMatchingNodesCoverEveryPathBeforeUsage = (usageNode, matchingNodes, owner, context) => {
12092
+ const functionCfg = context.cfg.cfgFor(owner);
12093
+ const usageBlock = functionCfg?.blockOf(usageNode);
12094
+ const usageStart = getRangeStart(usageNode);
12095
+ if (!functionCfg || !usageBlock || usageStart === null) return false;
12096
+ const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
12097
+ if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
12098
+ const matchingStart = getRangeStart(matchingNode);
12099
+ if (matchingStart === null || matchingStart >= usageStart) return [];
12100
+ const matchingBlock = functionCfg.blockOf(matchingNode);
12101
+ return matchingBlock ? [matchingBlock] : [];
12102
+ }));
12103
+ if (matchingBlocks.size === 0) return false;
12104
+ if (matchingBlocks.has(usageBlock)) return true;
12105
+ const visitedBlocks = new Set([functionCfg.entry]);
12106
+ const pendingBlocks = [functionCfg.entry];
12107
+ while (pendingBlocks.length > 0) {
12108
+ const currentBlock = pendingBlocks.pop();
12109
+ if (!currentBlock) break;
12110
+ if (matchingBlocks.has(currentBlock)) continue;
12111
+ if (currentBlock === usageBlock) return false;
12112
+ for (const edge of currentBlock.successors) {
12113
+ if (visitedBlocks.has(edge.to)) continue;
12114
+ visitedBlocks.add(edge.to);
12115
+ pendingBlocks.push(edge.to);
12116
+ }
12117
+ }
12118
+ return true;
12119
+ };
12120
+ const retainedFunctionReleasesPreviousRefOwnedUsage = (retainedFunction, cleanupFunction, storageNode, context) => {
12121
+ if (!isFunctionLike$2(retainedFunction)) return false;
12122
+ const retainedFunctionBody = retainedFunction.body;
12123
+ const cleanupCalls = [];
12124
+ walkAst(retainedFunctionBody, (child) => {
12125
+ if (child !== retainedFunctionBody && isFunctionLike$2(child)) return false;
12126
+ if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) cleanupCalls.push(child);
12127
+ });
12128
+ return doMatchingNodesCoverEveryPathBeforeUsage(storageNode, cleanupCalls, retainedFunction, context);
12129
+ };
12130
+ const isDirectRefOwnedRelease = (releaseNode, cleanupFunction, usage, storedHandlerKey, refCurrentKey, context) => {
12131
+ if (!isFunctionLike$2(cleanupFunction)) return false;
12132
+ const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
12133
+ if (!isNodeOfType(releaseCall, "CallExpression") || !isNodeOfType(releaseCall.callee, "MemberExpression") || releaseCall.callee.computed || !isNodeOfType(releaseCall.callee.property, "Identifier") || releaseCall.callee.property.name !== "removeEventListener" || !isNodeOfType(usage.node, "CallExpression") || usage.node.arguments?.[2] !== void 0 || releaseCall.arguments?.[2] !== void 0) return false;
12134
+ const releaseStatement = findTransparentExpressionRoot(releaseNode).parent;
12135
+ const releaseBlock = releaseStatement?.parent;
12136
+ const releaseGuard = releaseBlock?.parent;
12137
+ const isDirectCleanupStatement = releaseBlock === cleanupFunction.body;
12138
+ const isRefPresenceGuardedStatement = Boolean(isNodeOfType(releaseBlock, "BlockStatement") && isNodeOfType(releaseGuard, "IfStatement") && releaseGuard.consequent === releaseBlock && releaseGuard.alternate === null && resolveExpressionKey(releaseGuard.test, context) === refCurrentKey);
12139
+ if (!isNodeOfType(releaseStatement, "ExpressionStatement") || !isNodeOfType(cleanupFunction.body, "BlockStatement") || !isDirectCleanupStatement && !isRefPresenceGuardedStatement) return false;
12140
+ return usage.receiverKey !== null && usage.receiverKey === resolveExpressionKey(releaseCall.callee.object, context) && usage.eventKey !== null && usage.eventKey === resolveExpressionKey(releaseCall.arguments?.[0], context) && resolveExpressionKey(releaseCall.arguments?.[1], context) === storedHandlerKey;
12141
+ };
12142
+ const isRefPresenceGuardedEarlyReturn = (returnStatement, refCurrentKey, context) => {
12143
+ const returnBranch = returnStatement.parent;
12144
+ const guardStatement = isNodeOfType(returnBranch, "BlockStatement") ? returnBranch.parent : returnBranch;
12145
+ const guardedConsequent = isNodeOfType(returnBranch, "BlockStatement") ? returnBranch : returnStatement;
12146
+ if (!isNodeOfType(guardStatement, "IfStatement") || guardStatement.consequent !== guardedConsequent || guardStatement.alternate !== null) return false;
12147
+ const guardTest = stripParenExpression(guardStatement.test);
12148
+ return isNodeOfType(guardTest, "UnaryExpression") && guardTest.operator === "!" && resolveExpressionKey(guardTest.argument, context) === refCurrentKey;
12149
+ };
12150
+ const hasUnprovenReturnBeforeRefOwnedRelease = (cleanupFunction, releaseNode, refCurrentKey, context) => {
12151
+ if (!isFunctionLike$2(cleanupFunction)) return true;
12152
+ const releaseStart = getRangeStart(releaseNode);
12153
+ if (releaseStart === null) return true;
12154
+ let hasUnprovenEarlyReturn = false;
12155
+ walkAst(cleanupFunction.body, (child) => {
12156
+ if (hasUnprovenEarlyReturn) return false;
12157
+ if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
12158
+ if (!isNodeOfType(child, "ReturnStatement")) return;
12159
+ const returnStart = getRangeStart(child);
12160
+ if ((returnStart === null || returnStart < releaseStart) && !isRefPresenceGuardedEarlyReturn(child, refCurrentKey, context)) {
12161
+ hasUnprovenEarlyReturn = true;
12162
+ return false;
12163
+ }
12164
+ });
12165
+ return hasUnprovenEarlyReturn;
12166
+ };
12167
+ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction, retainedFunction, usage, context) => {
12168
+ if (!isFunctionLike$2(cleanupFunction) || !isFunctionLike$2(componentFunction) || cleanupFunction.async || cleanupFunction.generator) return false;
12169
+ const storage = findRefOwnedHandlerStorage(retainedFunction, usage, context);
12170
+ if (!storage) return false;
12171
+ if (!retainedFunctionReleasesPreviousRefOwnedUsage(retainedFunction, cleanupFunction, storage.assignmentNode, context)) return false;
12172
+ let releaseNode = null;
12173
+ walkAst(cleanupFunction.body, (child) => {
12174
+ if (releaseNode) return false;
12175
+ if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
12176
+ if (isDirectRefOwnedRelease(child, cleanupFunction, usage, storage.handlerKey, storage.refCurrentKey, context)) {
12177
+ releaseNode = child;
12178
+ return false;
12179
+ }
12180
+ });
12181
+ if (!releaseNode) return false;
12182
+ if (hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, releaseNode, storage.refCurrentKey, context)) return false;
12183
+ let hasUnsafeRefWrite = false;
12184
+ walkAst(componentFunction.body, (child) => {
12185
+ if (hasUnsafeRefWrite) return false;
12186
+ if (isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
12187
+ const deleteTarget = stripParenExpression(child.argument);
12188
+ if (resolveExpressionKey(deleteTarget, context) === storage.handlerKey || isNodeOfType(deleteTarget, "MemberExpression") && resolveExpressionKey(deleteTarget.object, context) === storage.refCurrentKey) {
12189
+ hasUnsafeRefWrite = true;
12190
+ return false;
12191
+ }
12192
+ return;
12193
+ }
12194
+ if (isNodeOfType(child, "CallExpression")) {
12195
+ if ((child.arguments ?? []).some((argumentNode) => {
12196
+ const argumentKey = resolveExpressionKey(argumentNode, context);
12197
+ return argumentKey !== null && (argumentKey === storage.refKey || argumentKey === storage.refCurrentKey);
12198
+ })) {
12199
+ hasUnsafeRefWrite = true;
12200
+ return false;
12201
+ }
12202
+ return;
12203
+ }
12204
+ if (!isNodeOfType(child, "AssignmentExpression")) return;
12205
+ const assignmentTarget = stripParenExpression(child.left);
12206
+ if (isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && resolveExpressionKey(assignmentTarget.object, context) === storage.refCurrentKey) {
12207
+ hasUnsafeRefWrite = true;
12208
+ return false;
12209
+ }
12210
+ const assignedKey = resolveExpressionKey(child.left, context);
12211
+ if (assignedKey === storage.handlerKey) {
12212
+ hasUnsafeRefWrite = true;
12213
+ return false;
12214
+ }
12215
+ if (assignedKey !== storage.refCurrentKey) return;
12216
+ const assignedValue = stripParenExpression(child.right);
12217
+ if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
12218
+ const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
12219
+ const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
12220
+ if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
12221
+ hasUnsafeRefWrite = true;
12222
+ return false;
12223
+ }
12224
+ });
12225
+ return !hasUnsafeRefWrite;
12226
+ };
12227
+ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retainedFunction, usage, context) => {
12228
+ const matchesReturnedCleanup = (returnedValue) => {
12229
+ const cleanupFunction = resolveRefOwnedCleanupFunction(returnedValue, context);
12230
+ return Boolean(cleanupFunction && cleanupFunctionReleasesRefOwnedUsage(cleanupFunction, componentFunction, retainedFunction, usage, context));
12231
+ };
12232
+ if (!isFunctionLike$2(effectCallback)) return false;
12233
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) return matchesReturnedCleanup(stripParenExpression(effectCallback.body));
12234
+ const matchingReturns = [];
12235
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
12236
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
12237
+ });
12238
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
12239
+ };
12240
+ const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
12241
+ const componentFunction = findEnclosingFunction(retainedFunction);
12242
+ if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
12243
+ let didFindCleanupEffect = false;
12244
+ walkAst(componentFunction.body, (child) => {
12245
+ if (didFindCleanupEffect) return false;
12246
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
12247
+ const effectStatement = findTransparentExpressionRoot(child).parent;
12248
+ if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
12249
+ const effectCallback = getEffectCallback(child);
12250
+ if (effectCallback && effectReturnsRefOwnedCleanup(effectCallback, componentFunction, retainedFunction, usage, context)) {
12251
+ didFindCleanupEffect = true;
12252
+ return false;
12253
+ }
12254
+ });
12255
+ return didFindCleanupEffect;
12256
+ };
11620
12257
  const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
11621
12258
  const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
11622
12259
  if (!bindingIdentifier) return false;
@@ -11657,7 +12294,7 @@ const findRetainedFunctionLeak = (retainedFunction, context) => {
11657
12294
  let leak = null;
11658
12295
  const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
11659
12296
  const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
11660
- const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context);
12297
+ const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context) || hasGuaranteedRefOwnedUnmountCleanup(retainedFunction, usage, context);
11661
12298
  walkAst(body, (child) => {
11662
12299
  if (leak !== null) return false;
11663
12300
  if (isFunctionLike$2(child)) return false;
@@ -12866,7 +13503,7 @@ const stringifyMemberChain = (node) => {
12866
13503
  }
12867
13504
  return null;
12868
13505
  };
12869
- const collectCaptureDepKeys = (callback, scopes) => {
13506
+ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
12870
13507
  const keys = /* @__PURE__ */ new Set();
12871
13508
  const stableCapturedNames = /* @__PURE__ */ new Set();
12872
13509
  const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
@@ -12896,6 +13533,10 @@ const collectCaptureDepKeys = (callback, scopes) => {
12896
13533
  continue;
12897
13534
  }
12898
13535
  if (depKey === symbol.name) {
13536
+ if (declaredExactBindingKeys?.has(depKey)) {
13537
+ keys.add(depKey);
13538
+ continue;
13539
+ }
12899
13540
  const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
12900
13541
  if (identitySourceKeys) {
12901
13542
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
@@ -13004,13 +13645,13 @@ const isRegExpLiteral = (node) => {
13004
13645
  if (!isNodeOfType(node, "Literal")) return false;
13005
13646
  return Boolean(node.regex);
13006
13647
  };
13007
- const isUnstableInitializer = (node) => {
13648
+ const isUnstableInitializer = (node, isNestedInitializer = false) => {
13008
13649
  if (!node) return false;
13009
13650
  const stripped = unwrapExpression$3(node);
13010
13651
  if (isRegExpLiteral(stripped)) return true;
13011
- if (isNodeOfType(stripped, "ConditionalExpression")) return isUnstableInitializer(stripped.consequent) || isUnstableInitializer(stripped.alternate);
13012
- if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
13013
- return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
13652
+ if (isNodeOfType(stripped, "ConditionalExpression")) return isUnstableInitializer(stripped.consequent, true) || isUnstableInitializer(stripped.alternate, true);
13653
+ if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left, true) || isUnstableInitializer(stripped.right, true);
13654
+ return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNestedInitializer && (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
13014
13655
  };
13015
13656
  const isPotentiallyFreshComparedValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13016
13657
  const candidate = unwrapExpression$3(node);
@@ -13463,8 +14104,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
13463
14104
  });
13464
14105
  return;
13465
14106
  }
13466
- const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes);
13467
- for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
13468
14107
  const hasLiteralDepElement = depsArgument.elements.some((element) => {
13469
14108
  if (!element) return false;
13470
14109
  return isLiteralOrEmptyTemplate(unwrapExpression$3(element));
@@ -13478,6 +14117,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
13478
14117
  message: buildLiteralDepMessage(hookName)
13479
14118
  });
13480
14119
  const declaredKeys = /* @__PURE__ */ new Set();
14120
+ const declaredExactBindingKeys = /* @__PURE__ */ new Set();
13481
14121
  const declaredKeyToReportNode = /* @__PURE__ */ new Map();
13482
14122
  const seenDeclaredKeys = /* @__PURE__ */ new Set();
13483
14123
  let didReportRefCurrentDep = false;
@@ -13543,8 +14183,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
13543
14183
  }
13544
14184
  seenDeclaredKeys.add(key);
13545
14185
  declaredKeys.add(key);
14186
+ if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
13546
14187
  declaredKeyToReportNode.set(key, elementNode);
13547
14188
  }
14189
+ const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
14190
+ for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
13548
14191
  addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
13549
14192
  const missingCaptureKeys = [];
13550
14193
  for (const captureKey of captureKeys) {
@@ -15542,7 +16185,7 @@ const jotaiTqUseRawQueryAtom = defineRule({
15542
16185
  });
15543
16186
  //#endregion
15544
16187
  //#region src/plugin/rules/js-performance/js-async-reduce-without-awaited-acc.ts
15545
- const isAsyncFunctionLike$1 = (node) => {
16188
+ const isAsyncFunctionLike = (node) => {
15546
16189
  if (!node) return false;
15547
16190
  if (!isNodeOfType(node, "ArrowFunctionExpression") && !isNodeOfType(node, "FunctionExpression")) return false;
15548
16191
  return node.async === true;
@@ -15609,7 +16252,7 @@ const jsAsyncReduceWithoutAwaitedAcc = defineRule({
15609
16252
  const args = node.arguments ?? [];
15610
16253
  if (args.length === 0) return;
15611
16254
  const reducerCandidate = stripParenExpression(args[0]);
15612
- if (!isAsyncFunctionLike$1(reducerCandidate)) return;
16255
+ if (!isAsyncFunctionLike(reducerCandidate)) return;
15613
16256
  const reducer = reducerCandidate;
15614
16257
  if (!containsDirectAwait(reducer.body)) return;
15615
16258
  const firstParameter = classifyFirstParameter(reducer);
@@ -16893,30 +17536,6 @@ const jsIndexMaps = defineRule({
16893
17536
  } })
16894
17537
  });
16895
17538
  //#endregion
16896
- //#region src/plugin/utils/are-expressions-structurally-equal.ts
16897
- const areExpressionsStructurallyEqual = (a, b) => {
16898
- if (!a || !b) return a === b;
16899
- const unwrappedA = stripParenExpression(a);
16900
- const unwrappedB = stripParenExpression(b);
16901
- if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
16902
- if (a.type !== b.type) return false;
16903
- if (isNodeOfType(a, "ThisExpression")) return true;
16904
- if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
16905
- if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
16906
- if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
16907
- if (a.computed !== b.computed) return false;
16908
- return areExpressionsStructurallyEqual(a.object, b.object) && areExpressionsStructurallyEqual(a.property, b.property);
16909
- }
16910
- if (isNodeOfType(a, "CallExpression") && isNodeOfType(b, "CallExpression")) {
16911
- if (!areExpressionsStructurallyEqual(a.callee, b.callee)) return false;
16912
- const argumentsA = a.arguments ?? [];
16913
- const argumentsB = b.arguments ?? [];
16914
- if (argumentsA.length !== argumentsB.length) return false;
16915
- return argumentsA.every((argument, index) => areExpressionsStructurallyEqual(argument, argumentsB[index]));
16916
- }
16917
- return false;
16918
- };
16919
- //#endregion
16920
17539
  //#region src/plugin/utils/flatten-logical-and-chain.ts
16921
17540
  const flattenLogicalAndChain = (node) => {
16922
17541
  if (isNodeOfType(node, "LogicalExpression") && node.operator === "&&") return [...flattenLogicalAndChain(node.left), ...flattenLogicalAndChain(node.right)];
@@ -17173,12 +17792,23 @@ const jsLengthCheckFirst = defineRule({
17173
17792
  });
17174
17793
  //#endregion
17175
17794
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
17795
+ const builtinMutationByProgram = /* @__PURE__ */ new WeakMap();
17796
+ const RUNTIMELESS_SYMBOL_KINDS = new Set(["ts-interface", "ts-type-alias"]);
17797
+ const PROTOTYPE_METHOD_NAMES = new Set(["getPrototypeOf"]);
17798
+ const OBJECT_ASSIGN_METHOD_NAMES = new Set(["assign"]);
17799
+ const OBJECT_DEFINE_PROPERTIES_METHOD_NAMES = new Set(["defineProperties"]);
17800
+ const KEYED_MUTATION_METHOD_NAMES = new Set([
17801
+ "defineProperty",
17802
+ "deleteProperty",
17803
+ "set"
17804
+ ]);
17176
17805
  const numericComparatorDirection = (comparator) => {
17177
- if (!isInlineFunctionExpression(comparator)) return null;
17806
+ if (!isInlineFunctionExpression(comparator) || comparator.async || comparator.generator) return null;
17178
17807
  const parameters = comparator.params ?? [];
17179
17808
  if (parameters.length !== 2) return null;
17180
17809
  const [firstParameter, secondParameter] = parameters;
17181
17810
  if (!isNodeOfType(firstParameter, "Identifier") || !isNodeOfType(secondParameter, "Identifier")) return null;
17811
+ if (firstParameter.name === secondParameter.name) return null;
17182
17812
  let comparisonExpression = null;
17183
17813
  const comparatorBody = stripParenExpression(comparator.body);
17184
17814
  if (isNodeOfType(comparatorBody, "BinaryExpression")) comparisonExpression = comparatorBody;
@@ -17196,6 +17826,150 @@ const numericComparatorDirection = (comparator) => {
17196
17826
  if (leftName === secondParameter.name && rightName === firstParameter.name) return "descending";
17197
17827
  return null;
17198
17828
  };
17829
+ const getStaticFiniteNumericValue = (expression) => {
17830
+ const strippedExpression = stripParenExpression(expression);
17831
+ if (isNodeOfType(strippedExpression, "Literal")) return typeof strippedExpression.value === "number" && Number.isFinite(strippedExpression.value) ? strippedExpression.value : null;
17832
+ if (!isNodeOfType(strippedExpression, "UnaryExpression") || strippedExpression.operator !== "+" && strippedExpression.operator !== "-") return null;
17833
+ const argumentValue = getStaticFiniteNumericValue(strippedExpression.argument);
17834
+ if (argumentValue === null) return null;
17835
+ return strippedExpression.operator === "-" ? -argumentValue : argumentValue;
17836
+ };
17837
+ const isSafeFreshNumericArray = (arrayExpression) => {
17838
+ if (arrayExpression.elements.length === 0 || arrayExpression.elements.length > 1024) return false;
17839
+ let didFindPositiveZero = false;
17840
+ let didFindNegativeZero = false;
17841
+ for (const element of arrayExpression.elements) {
17842
+ if (!element || isNodeOfType(element, "SpreadElement")) return false;
17843
+ const numericValue = getStaticFiniteNumericValue(element);
17844
+ if (numericValue === null) return false;
17845
+ if (Object.is(numericValue, 0)) didFindPositiveZero = true;
17846
+ if (Object.is(numericValue, -0)) didFindNegativeZero = true;
17847
+ }
17848
+ return !(didFindPositiveZero && didFindNegativeZero);
17849
+ };
17850
+ const isGlobalObjectReference = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
17851
+ const strippedExpression = stripParenExpression(expression);
17852
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
17853
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
17854
+ const symbol = scopes.symbolFor(strippedExpression);
17855
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
17856
+ visitedSymbols.add(symbol.id);
17857
+ return isGlobalObjectReference(symbol.initializer, scopes, visitedSymbols);
17858
+ };
17859
+ const resolvesToGlobalNamespace = (expression, namespaceName, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
17860
+ const strippedExpression = stripParenExpression(expression);
17861
+ if (isNodeOfType(strippedExpression, "Identifier")) {
17862
+ if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
17863
+ const symbol = scopes.symbolFor(strippedExpression);
17864
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
17865
+ visitedSymbols.add(symbol.id);
17866
+ return resolvesToGlobalNamespace(symbol.initializer, namespaceName, scopes, visitedSymbols);
17867
+ }
17868
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isGlobalObjectReference(strippedExpression.object, scopes);
17869
+ };
17870
+ const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
17871
+ const strippedExpression = stripParenExpression(expression);
17872
+ if (isNodeOfType(strippedExpression, "Identifier")) {
17873
+ const symbol = scopes.symbolFor(strippedExpression);
17874
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
17875
+ visitedSymbols.add(symbol.id);
17876
+ return resolvesToGlobalMethod(symbol.initializer, namespaceName, methodNames, scopes, visitedSymbols);
17877
+ }
17878
+ return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && resolvesToGlobalNamespace(strippedExpression.object, namespaceName, scopes);
17879
+ };
17880
+ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
17881
+ const strippedExpression = stripParenExpression(expression);
17882
+ if (isNodeOfType(strippedExpression, "Identifier")) {
17883
+ const symbol = scopes.symbolFor(strippedExpression);
17884
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
17885
+ visitedSymbols.add(symbol.id);
17886
+ return resolvesToNativeArrayPrototype(symbol.initializer, scopes, visitedSymbols);
17887
+ }
17888
+ if (isNodeOfType(strippedExpression, "MemberExpression")) {
17889
+ const propertyName = getStaticPropertyName(strippedExpression);
17890
+ if (propertyName === "prototype") return resolvesToGlobalNamespace(strippedExpression.object, "Array", scopes);
17891
+ return propertyName === "__proto__" && isNodeOfType(stripParenExpression(strippedExpression.object), "ArrayExpression");
17892
+ }
17893
+ if (!isNodeOfType(strippedExpression, "CallExpression")) return false;
17894
+ if (!resolvesToGlobalMethod(strippedExpression.callee, "Object", PROTOTYPE_METHOD_NAMES, scopes) && !resolvesToGlobalMethod(strippedExpression.callee, "Reflect", PROTOTYPE_METHOD_NAMES, scopes)) return false;
17895
+ const argument = strippedExpression.arguments[0];
17896
+ return Boolean(argument && isNodeOfType(stripParenExpression(argument), "ArrayExpression"));
17897
+ };
17898
+ const isGlobalNamespaceReplacementTarget = (target, namespaceName, scopes) => {
17899
+ const strippedTarget = stripParenExpression(target);
17900
+ if (isNodeOfType(strippedTarget, "Identifier")) return strippedTarget.name === namespaceName && scopes.isGlobalReference(strippedTarget);
17901
+ return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isGlobalObjectReference(strippedTarget.object, scopes);
17902
+ };
17903
+ const isUnsafeBuiltinMemberTarget = (target, targetFunction, scopes) => {
17904
+ const strippedTarget = stripParenExpression(target);
17905
+ if (!isNodeOfType(strippedTarget, "MemberExpression")) return false;
17906
+ const propertyName = getStaticPropertyName(strippedTarget);
17907
+ if (resolvesToNativeArrayPrototype(strippedTarget.object, scopes)) return propertyName === null || propertyName === "sort";
17908
+ if (resolvesToGlobalNamespace(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
17909
+ return isGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
17910
+ };
17911
+ const isUnsafeBuiltinMutationApiCall = (callExpression, targetFunction, scopes) => {
17912
+ const target = callExpression.arguments[0];
17913
+ if (!target) return false;
17914
+ let propertyName = null;
17915
+ if (resolvesToNativeArrayPrototype(target, scopes)) propertyName = "sort";
17916
+ else if (resolvesToGlobalNamespace(target, "Math", scopes)) propertyName = targetFunction;
17917
+ else if (isGlobalObjectReference(target, scopes)) propertyName = "Math";
17918
+ if (!propertyName) return false;
17919
+ const canObjectExpressionSetProperty = (properties) => {
17920
+ if (!isNodeOfType(properties, "ObjectExpression")) return true;
17921
+ return properties.properties.some((property) => {
17922
+ if (isNodeOfType(property, "SpreadElement")) return true;
17923
+ const keyName = getStaticPropertyKeyName(property, { allowComputedString: true });
17924
+ return keyName === propertyName || keyName === null;
17925
+ });
17926
+ };
17927
+ if (resolvesToGlobalMethod(callExpression.callee, "Object", OBJECT_ASSIGN_METHOD_NAMES, scopes)) return callExpression.arguments.slice(1).some((properties) => canObjectExpressionSetProperty(properties));
17928
+ if (resolvesToGlobalMethod(callExpression.callee, "Object", OBJECT_DEFINE_PROPERTIES_METHOD_NAMES, scopes)) {
17929
+ const properties = callExpression.arguments[1];
17930
+ return !properties || canObjectExpressionSetProperty(properties);
17931
+ }
17932
+ if (!resolvesToGlobalMethod(callExpression.callee, "Object", KEYED_MUTATION_METHOD_NAMES, scopes) && !resolvesToGlobalMethod(callExpression.callee, "Reflect", KEYED_MUTATION_METHOD_NAMES, scopes)) return false;
17933
+ const propertyArgument = callExpression.arguments[1];
17934
+ if (!propertyArgument) return true;
17935
+ return isNodeOfType(propertyArgument, "Literal") ? propertyArgument.value === propertyName : true;
17936
+ };
17937
+ const hasUnsafeBuiltinMutation = (node, targetFunction, scopes) => {
17938
+ const programRoot = findProgramRoot(node);
17939
+ if (!programRoot) return true;
17940
+ let mutationByTargetFunction = builtinMutationByProgram.get(programRoot);
17941
+ if (!mutationByTargetFunction) {
17942
+ mutationByTargetFunction = /* @__PURE__ */ new Map();
17943
+ builtinMutationByProgram.set(programRoot, mutationByTargetFunction);
17944
+ }
17945
+ const cachedResult = mutationByTargetFunction.get(targetFunction);
17946
+ if (cachedResult !== void 0) return cachedResult;
17947
+ let didFindUnsafeMutation = false;
17948
+ walkAst(programRoot, (candidate) => {
17949
+ if (didFindUnsafeMutation) return false;
17950
+ if (isNodeOfType(candidate, "CallExpression")) {
17951
+ didFindUnsafeMutation = isUnsafeBuiltinMutationApiCall(candidate, targetFunction, scopes);
17952
+ return;
17953
+ }
17954
+ let mutationTarget = null;
17955
+ if (isNodeOfType(candidate, "AssignmentExpression")) mutationTarget = candidate.left;
17956
+ if (isNodeOfType(candidate, "UpdateExpression")) mutationTarget = candidate.argument;
17957
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete") mutationTarget = candidate.argument;
17958
+ if (!mutationTarget) return;
17959
+ didFindUnsafeMutation = isUnsafeBuiltinMemberTarget(mutationTarget, targetFunction, scopes) || isGlobalNamespaceReplacementTarget(mutationTarget, "Math", scopes);
17960
+ });
17961
+ mutationByTargetFunction.set(targetFunction, didFindUnsafeMutation);
17962
+ return didFindUnsafeMutation;
17963
+ };
17964
+ const hasUnsafeMathBinding = (node, scopes) => {
17965
+ let scope = scopes.scopeFor(node);
17966
+ while (scope) {
17967
+ const symbol = scope.symbolsByName.get("Math");
17968
+ if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && resolvesToGlobalNamespace(symbol.initializer, "Math", scopes));
17969
+ scope = scope.parent;
17970
+ }
17971
+ return false;
17972
+ };
17199
17973
  const jsMinMaxLoop = defineRule({
17200
17974
  id: "js-min-max-loop",
17201
17975
  title: "sort() to find min or max",
@@ -17206,18 +17980,18 @@ const jsMinMaxLoop = defineRule({
17206
17980
  if (!node.computed) return;
17207
17981
  const object = node.object;
17208
17982
  if (!isNodeOfType(object, "CallExpression") || !isMemberProperty(object.callee, "sort")) return;
17983
+ const sortReceiver = stripParenExpression(object.callee.object);
17984
+ if (!isNodeOfType(sortReceiver, "ArrayExpression") || !isSafeFreshNumericArray(sortReceiver)) return;
17209
17985
  const comparator = object.arguments?.[0];
17210
17986
  const direction = numericComparatorDirection(comparator);
17211
17987
  if (!direction) return;
17212
- const isFirstElement = isNodeOfType(node.property, "Literal") && node.property.value === 0;
17213
- const isLastElement = isNodeOfType(node.property, "BinaryExpression") && node.property.operator === "-" && isNodeOfType(node.property.right, "Literal") && node.property.right.value === 1;
17214
- if (isFirstElement || isLastElement) {
17215
- const targetFunction = (direction === "ascending" ? isFirstElement : isLastElement) ? "min" : "max";
17216
- context.report({
17217
- node,
17218
- message: `This is slow because array.sort()[${isFirstElement ? "0" : "length-1"}] sorts the whole list just to grab the smallest or largest, so use Math.${targetFunction}(...array) instead`
17219
- });
17220
- }
17988
+ if (!(isNodeOfType(node.property, "Literal") && node.property.value === 0)) return;
17989
+ const targetFunction = direction === "ascending" ? "min" : "max";
17990
+ if (hasUnsafeMathBinding(node, context.scopes) || hasUnsafeBuiltinMutation(node, targetFunction, context.scopes)) return;
17991
+ context.report({
17992
+ node,
17993
+ message: `This is slow because array.sort()[0] sorts the whole list just to grab the smallest or largest, so use Math.${targetFunction}(...array) instead`
17994
+ });
17221
17995
  } })
17222
17996
  });
17223
17997
  //#endregion
@@ -27691,6 +28465,152 @@ const noBarrelImport = defineRule({
27691
28465
  }
27692
28466
  });
27693
28467
  //#endregion
28468
+ //#region src/plugin/utils/has-static-property-write-before.ts
28469
+ const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
28470
+ const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
28471
+ "CatchClause",
28472
+ "ConditionalExpression",
28473
+ "DoWhileStatement",
28474
+ "ForInStatement",
28475
+ "ForOfStatement",
28476
+ "ForStatement",
28477
+ "IfStatement",
28478
+ "LogicalExpression",
28479
+ "SwitchCase",
28480
+ "SwitchStatement",
28481
+ "TryStatement",
28482
+ "WhileStatement"
28483
+ ]);
28484
+ const getResolvedStaticPropertyName = (memberExpression, scopes) => {
28485
+ if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
28486
+ const directPropertyName = getStaticPropertyName(memberExpression);
28487
+ if (directPropertyName || !memberExpression.computed) return directPropertyName;
28488
+ const property = stripParenExpression(memberExpression.property);
28489
+ if (!isNodeOfType(property, "Identifier")) return null;
28490
+ const propertySymbol = resolveConstIdentifierAlias(property, scopes);
28491
+ const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
28492
+ return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
28493
+ };
28494
+ const collectScopeSymbols = (scope, symbols) => {
28495
+ symbols.push(...scope.symbols);
28496
+ for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
28497
+ };
28498
+ const getEquivalentSymbols = (identifier, scopes) => {
28499
+ const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
28500
+ if (!rootSymbol) return [];
28501
+ let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
28502
+ if (!symbolsByRootId) {
28503
+ symbolsByRootId = /* @__PURE__ */ new Map();
28504
+ equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
28505
+ }
28506
+ const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
28507
+ if (cachedSymbols) return cachedSymbols;
28508
+ const allSymbols = [];
28509
+ collectScopeSymbols(scopes.rootScope, allSymbols);
28510
+ const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
28511
+ symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
28512
+ return equivalentSymbols;
28513
+ };
28514
+ const findExecutionBoundary = (node) => {
28515
+ let current = node;
28516
+ while (current) {
28517
+ if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
28518
+ current = current.parent ?? null;
28519
+ }
28520
+ return null;
28521
+ };
28522
+ const isOnUnconditionalPath = (node, boundary) => {
28523
+ let current = node.parent ?? null;
28524
+ while (current && current !== boundary) {
28525
+ if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
28526
+ current = current.parent ?? null;
28527
+ }
28528
+ return current === boundary;
28529
+ };
28530
+ const findFunctionBindingIdentifier = (functionNode) => {
28531
+ if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
28532
+ const parent = functionNode.parent;
28533
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
28534
+ return null;
28535
+ };
28536
+ const findDirectCall = (identifier) => {
28537
+ let callee = identifier;
28538
+ let parent = callee.parent;
28539
+ while (parent && stripParenExpression(parent) === identifier) {
28540
+ callee = parent;
28541
+ parent = callee.parent;
28542
+ }
28543
+ return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
28544
+ };
28545
+ const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
28546
+ if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
28547
+ visitedFunctionNodes.add(functionNode);
28548
+ const referenceBoundary = findExecutionBoundary(referenceNode);
28549
+ if (!referenceBoundary) return false;
28550
+ const invocationCalls = [];
28551
+ const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
28552
+ if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
28553
+ const call = findDirectCall(reference.identifier);
28554
+ if (call) invocationCalls.push(call);
28555
+ }
28556
+ else {
28557
+ const call = findDirectCall(functionNode);
28558
+ if (call) invocationCalls.push(call);
28559
+ }
28560
+ return invocationCalls.some((call) => {
28561
+ if (call.range[0] >= referenceNode.range[0]) return false;
28562
+ const callBoundary = findExecutionBoundary(call);
28563
+ if (!callBoundary) return false;
28564
+ if (callBoundary === referenceBoundary) return true;
28565
+ if (!isFunctionLike$2(callBoundary)) return false;
28566
+ return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
28567
+ });
28568
+ };
28569
+ const isMemberWriteTarget = (memberExpression) => {
28570
+ const parent = memberExpression.parent;
28571
+ if (!parent) return false;
28572
+ if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
28573
+ if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
28574
+ return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
28575
+ };
28576
+ const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
28577
+ let parent = reference.identifier.parent;
28578
+ while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
28579
+ if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
28580
+ const writeBoundary = findExecutionBoundary(parent);
28581
+ const referenceBoundary = findExecutionBoundary(referenceNode);
28582
+ if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
28583
+ if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
28584
+ return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
28585
+ });
28586
+ const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
28587
+ if (!isNodeOfType(identifier, "Identifier")) return false;
28588
+ return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
28589
+ };
28590
+ //#endregion
28591
+ //#region src/plugin/utils/has-symbol-write-before.ts
28592
+ const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
28593
+ if (reference.flag === "read") return false;
28594
+ const writeFunction = findEnclosingFunction(reference.identifier);
28595
+ if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
28596
+ if (!writeFunction) return true;
28597
+ return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
28598
+ });
28599
+ //#endregion
28600
+ //#region src/plugin/utils/has-stable-call-target.ts
28601
+ const hasStableCallTarget = (callExpression, scopes) => {
28602
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
28603
+ const callee = stripParenExpression(callExpression.callee);
28604
+ if (isNodeOfType(callee, "Identifier")) {
28605
+ const symbol = scopes.symbolFor(callee);
28606
+ return Boolean(symbol && !hasSymbolWriteBefore(symbol, callee, scopes));
28607
+ }
28608
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
28609
+ const propertyName = getStaticPropertyName(callee);
28610
+ const receiver = stripParenExpression(callee.object);
28611
+ return Boolean(propertyName && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, callee, scopes));
28612
+ };
28613
+ //#endregion
27694
28614
  //#region src/plugin/utils/function-contains-react-render-output.ts
27695
28615
  const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
27696
28616
  "FunctionDeclaration",
@@ -27703,19 +28623,68 @@ const REACT_CREATE_ELEMENT_OPTIONS = {
27703
28623
  allowGlobalReactNamespace: false,
27704
28624
  allowUnboundBareCalls: false
27705
28625
  };
27706
- const isCallArgumentFunctionExpression = (node) => {
28626
+ const LODASH_SORT_BY_MODULE_SOURCES = new Set([
28627
+ "lodash/sortBy",
28628
+ "lodash/sortBy.js",
28629
+ "lodash-es/sortBy",
28630
+ "lodash-es/sortBy.js"
28631
+ ]);
28632
+ const isArrayTypeAnnotation = (node) => {
28633
+ if (isNodeOfType(node, "TSArrayType") || isNodeOfType(node, "TSTupleType")) return true;
28634
+ if (!isNodeOfType(node, "TSTypeReference")) return false;
28635
+ return isNodeOfType(node.typeName, "Identifier") && (node.typeName.name === "Array" || node.typeName.name === "ReadonlyArray");
28636
+ };
28637
+ const hasArrayTypeAnnotation = (identifier) => {
28638
+ if (!isNodeOfType(identifier, "Identifier")) return false;
28639
+ const typeAnnotation = identifier.typeAnnotation;
28640
+ return Boolean(typeAnnotation && isNodeOfType(typeAnnotation, "TSTypeAnnotation") && isArrayTypeAnnotation(typeAnnotation.typeAnnotation));
28641
+ };
28642
+ const isProvenArrayProducerCall = (node, scopes) => {
28643
+ if (!isNodeOfType(node, "CallExpression")) return false;
28644
+ const callee = stripParenExpression(node.callee);
28645
+ if (!isNodeOfType(callee, "Identifier")) return false;
28646
+ const symbol = scopes.symbolFor(callee);
28647
+ if (!symbol || hasSymbolWriteBefore(symbol, callee, scopes)) return false;
28648
+ if (!isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") && !isNodeOfType(symbol.declarationNode, "ImportSpecifier")) return false;
28649
+ const importBinding = getImportBindingForName(callee, callee.name);
28650
+ if (!importBinding || importBinding.isNamespace) return false;
28651
+ if (LODASH_SORT_BY_MODULE_SOURCES.has(importBinding.source) && importBinding.exportedName === "default") return true;
28652
+ return (importBinding.source === "lodash" || importBinding.source === "lodash-es") && importBinding.exportedName === "sortBy";
28653
+ };
28654
+ const isProvenArrayExpression = (expression, scopes, referenceNode, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
28655
+ const candidate = stripParenExpression(expression);
28656
+ if (isNodeOfType(candidate, "ArrayExpression")) return true;
28657
+ if (isProvenArrayProducerCall(candidate, scopes)) return true;
28658
+ if (!isNodeOfType(candidate, "Identifier")) return false;
28659
+ const symbol = scopes.symbolFor(candidate);
28660
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, referenceNode, scopes)) return false;
28661
+ if (hasArrayTypeAnnotation(symbol.bindingIdentifier)) return true;
28662
+ if (!symbol.initializer) return false;
28663
+ visitedSymbolIds.add(symbol.id);
28664
+ return isProvenArrayExpression(symbol.initializer, scopes, referenceNode, visitedSymbolIds);
28665
+ };
28666
+ const isProvenArrayMapCall = (node, scopes) => {
28667
+ if (!isNodeOfType(node, "CallExpression")) return false;
28668
+ const callee = stripParenExpression(node.callee);
28669
+ if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "map") return false;
28670
+ const receiver = stripParenExpression(callee.object);
28671
+ if (!isProvenArrayExpression(receiver, scopes, node)) return false;
28672
+ return !(isNodeOfType(receiver, "Identifier") && hasStaticPropertyWriteBefore(receiver, "map", node, scopes));
28673
+ };
28674
+ const isRenderPreservingCallArgumentFunction = (node, scopes) => {
27707
28675
  if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression") return false;
27708
28676
  const parent = node.parent;
27709
28677
  if (!isNodeOfType(parent, "CallExpression")) return false;
27710
- return parent.arguments.some((argumentNode) => argumentNode === node);
28678
+ if (isReactApiCall(parent, "useMemo", scopes, { resolveNamedAliases: true }) && hasStableCallTarget(parent, scopes)) return parent.arguments[0] === node;
28679
+ return parent.arguments.some((argumentNode) => argumentNode === node) && isProvenArrayMapCall(parent, scopes);
27711
28680
  };
27712
- const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
28681
+ const isNestedRenderEvidenceBoundary = (node, scopes) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isRenderPreservingCallArgumentFunction(node, scopes);
27713
28682
  const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
27714
28683
  const containsRenderOutput$1 = (rootNode, scopes) => {
27715
28684
  let hasRenderOutput = false;
27716
28685
  walkAst(rootNode, (node) => {
27717
28686
  if (hasRenderOutput) return false;
27718
- if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
28687
+ if (node !== rootNode && isNestedRenderEvidenceBoundary(node, scopes)) return false;
27719
28688
  if (isRenderOutputExpression(node, scopes)) {
27720
28689
  hasRenderOutput = true;
27721
28690
  return false;
@@ -27724,12 +28693,13 @@ const containsRenderOutput$1 = (rootNode, scopes) => {
27724
28693
  return hasRenderOutput;
27725
28694
  };
27726
28695
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
27727
- const functionContainsReactRenderOutput = (functionNode, scopes) => {
28696
+ const functionContainsReactRenderOutput = (functionNode, scopes, controlFlow) => {
27728
28697
  const cachedEntry = renderOutputCache.get(functionNode);
27729
- if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
27730
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => isRenderOutputExpression(expression, scopes));
28698
+ if (cachedEntry && cachedEntry.scopes === scopes && cachedEntry.controlFlow === controlFlow) return cachedEntry.hasRenderOutput;
28699
+ const hasRenderOutput = functionReturnsMatchingExpression(functionNode, scopes, (expression) => containsRenderOutput$1(expression, scopes), controlFlow);
27731
28700
  renderOutputCache.set(functionNode, {
27732
28701
  scopes,
28702
+ controlFlow,
27733
28703
  hasRenderOutput
27734
28704
  });
27735
28705
  return hasRenderOutput;
@@ -27745,8 +28715,8 @@ const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration
27745
28715
  const message = (name) => `\`${name}\` is a component, so calling it as a plain function (\`${name}(...)\`) runs it outside React: its hooks break, it gets no fiber/state, and memoization is lost. Render it as \`<${name} />\` instead.`;
27746
28716
  const symbolIsLocalComponent = (symbol, context) => {
27747
28717
  const declaration = symbol.declarationNode;
27748
- if (isComponentDeclaration(declaration)) return functionContainsReactRenderOutput(declaration, context.scopes);
27749
- if (isComponentAssignment(declaration) && symbol.initializer) return functionContainsReactRenderOutput(symbol.initializer, context.scopes);
28718
+ if (isComponentDeclaration(declaration)) return functionContainsReactRenderOutput(declaration, context.scopes, context.cfg);
28719
+ if (isComponentAssignment(declaration) && symbol.initializer) return functionContainsReactRenderOutput(symbol.initializer, context.scopes, context.cfg);
27750
28720
  return false;
27751
28721
  };
27752
28722
  const isModuleScopeDeclaration = (declaration) => {
@@ -27844,271 +28814,21 @@ const noCallComponentAsFunction = defineRule({
27844
28814
  }
27845
28815
  });
27846
28816
  //#endregion
27847
- //#region src/plugin/utils/is-setter-identifier.ts
27848
- const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
27849
- //#endregion
27850
- //#region src/plugin/utils/is-setter-call.ts
27851
- const isSetterCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && isSetterIdentifier(node.callee.name);
27852
- //#endregion
27853
- //#region src/plugin/utils/is-hook-binding-in-scope.ts
27854
- const isHookBindingInScope = (node, query) => {
27855
- const { bindingName, hookName, destructureIndex } = query;
27856
- let cursor = node;
27857
- while (cursor) {
27858
- if (isNodeOfType(cursor, "BlockStatement") || isNodeOfType(cursor, "Program")) for (const statement of cursor.body ?? []) {
27859
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
27860
- for (const declarator of statement.declarations ?? []) {
27861
- if (!isNodeOfType(declarator.init, "CallExpression")) continue;
27862
- if (!isHookCall$2(declarator.init, hookName)) continue;
27863
- if (destructureIndex !== void 0) {
27864
- if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
27865
- const elements = declarator.id.elements ?? [];
27866
- if (elements.length <= destructureIndex) continue;
27867
- const element = elements[destructureIndex];
27868
- if (isNodeOfType(element, "Identifier") && element.name === bindingName) return true;
27869
- } else if (isNodeOfType(declarator.id, "Identifier") && declarator.id.name === bindingName) return true;
27870
- }
27871
- }
27872
- cursor = cursor.parent ?? null;
27873
- }
27874
- return false;
27875
- };
27876
- //#endregion
27877
- //#region src/plugin/utils/is-use-state-setter-in-scope.ts
27878
- const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node, {
27879
- bindingName: setterName,
27880
- hookName: "useState",
27881
- destructureIndex: 1
28817
+ //#region src/plugin/utils/define-retired-rule.ts
28818
+ const defineRetiredRule = (rule) => defineRule({
28819
+ ...rule,
28820
+ defaultEnabled: false,
28821
+ lifecycle: "retired",
28822
+ create: () => ({})
27882
28823
  });
27883
28824
  //#endregion
27884
28825
  //#region src/plugin/rules/state-and-effects/no-cascading-set-state.ts
27885
- const isAsyncFunctionLike = (node) => {
27886
- if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
27887
- return false;
27888
- };
27889
- const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
27890
- "forEach",
27891
- "map",
27892
- "filter",
27893
- "reduce",
27894
- "reduceRight",
27895
- "flatMap",
27896
- "some",
27897
- "every",
27898
- "find",
27899
- "findIndex",
27900
- "findLast",
27901
- "findLastIndex",
27902
- "sort"
27903
- ]);
27904
- const runsOnEffectDispatch = (functionNode) => {
27905
- const parent = functionNode.parent;
27906
- if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
27907
- if (parent.callee === functionNode) return true;
27908
- if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
27909
- const callee = parent.callee;
27910
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(callee.property.name);
27911
- };
27912
- const isTerminatingStatement = (statement) => isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement");
27913
- const analyzeBranchStatements = (branch, context) => analyzeStatementSequence(isNodeOfType(branch, "BlockStatement") ? branch.body ?? [] : [branch], context);
27914
- const analyzeStatementSequence = (statements, context) => {
27915
- let fallThroughCount = 0;
27916
- let maxTerminatedCount = 0;
27917
- for (const statement of statements) {
27918
- if (isNodeOfType(statement, "IfStatement")) {
27919
- fallThroughCount += countMaxPathSetStateCalls(statement.test, context);
27920
- const thenSummary = analyzeBranchStatements(statement.consequent, context);
27921
- const elseSummary = statement.alternate ? analyzeBranchStatements(statement.alternate, context) : {
27922
- fallThroughCount: 0,
27923
- maxTerminatedCount: 0,
27924
- doAllPathsTerminate: false
27925
- };
27926
- maxTerminatedCount = Math.max(maxTerminatedCount, fallThroughCount + thenSummary.maxTerminatedCount, fallThroughCount + elseSummary.maxTerminatedCount);
27927
- if (thenSummary.doAllPathsTerminate && elseSummary.doAllPathsTerminate) return {
27928
- fallThroughCount: 0,
27929
- maxTerminatedCount,
27930
- doAllPathsTerminate: true
27931
- };
27932
- const fallThroughBranchCounts = [...thenSummary.doAllPathsTerminate ? [] : [thenSummary.fallThroughCount], ...elseSummary.doAllPathsTerminate ? [] : [elseSummary.fallThroughCount]];
27933
- fallThroughCount += Math.max(...fallThroughBranchCounts);
27934
- continue;
27935
- }
27936
- if (isTerminatingStatement(statement)) {
27937
- const terminatedPathCount = fallThroughCount + countMaxPathSetStateCalls(statement, context);
27938
- return {
27939
- fallThroughCount: 0,
27940
- maxTerminatedCount: Math.max(maxTerminatedCount, terminatedPathCount),
27941
- doAllPathsTerminate: true
27942
- };
27943
- }
27944
- fallThroughCount += countMaxPathSetStateCalls(statement, context);
27945
- }
27946
- return {
27947
- fallThroughCount,
27948
- maxTerminatedCount,
27949
- doAllPathsTerminate: false
27950
- };
27951
- };
27952
- const countStatementSequenceSetStateCalls = (statements, context) => {
27953
- const summary = analyzeStatementSequence(statements, context);
27954
- return Math.max(summary.fallThroughCount, summary.maxTerminatedCount);
27955
- };
27956
- const collectLocalHelperFunctions = (root) => {
27957
- const helpersByName = /* @__PURE__ */ new Map();
27958
- const visit = (node) => {
27959
- if (isNodeOfType(node, "FunctionDeclaration") && node.id) helpersByName.set(node.id.name, node);
27960
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init && isFunctionLike$2(node.init)) helpersByName.set(node.id.name, node.init);
27961
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init && isNodeOfType(node.init, "CallExpression") && isNodeOfType(node.init.callee, "Identifier") && node.init.callee.name === "useCallback" && node.init.arguments?.[0] && isFunctionLike$2(node.init.arguments[0])) helpersByName.set(node.id.name, node.init.arguments[0]);
27962
- const record = node;
27963
- for (const key of Object.keys(record)) {
27964
- if (key === "parent") continue;
27965
- const child = record[key];
27966
- if (Array.isArray(child)) {
27967
- for (const item of child) if (item && typeof item === "object" && "type" in item) visit(item);
27968
- } else if (child && typeof child === "object" && "type" in child) visit(child);
27969
- }
27970
- };
27971
- visit(root);
27972
- return helpersByName;
27973
- };
27974
- const isWholesaleDelegationCall = (callNode, effectCallback) => {
27975
- const parent = callNode.parent;
27976
- if (!parent) return false;
27977
- if (parent === effectCallback) return true;
27978
- if (!isNodeOfType(parent, "ExpressionStatement")) return false;
27979
- return parent.parent === effectCallback.body;
27980
- };
27981
- const countFunctionBodySetStateCalls = (functionNode, context) => {
27982
- if (isAsyncFunctionLike(functionNode)) return 0;
27983
- const body = functionNode.body;
27984
- if (!body) return 0;
27985
- return countMaxPathSetStateCalls(body, context);
27986
- };
27987
- const countMaxPathSetStateCalls = (node, context) => {
27988
- if (!node || typeof node !== "object") return 0;
27989
- if (isFunctionLike$2(node)) return 0;
27990
- if (isNodeOfType(node, "BlockStatement") || isNodeOfType(node, "Program")) return countStatementSequenceSetStateCalls(node.body ?? [], context);
27991
- if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "ConditionalExpression")) {
27992
- const consequent = node.consequent;
27993
- const alternate = node.alternate;
27994
- const testCount = countMaxPathSetStateCalls(node.test, context);
27995
- const thenCount = countMaxPathSetStateCalls(consequent, context);
27996
- const elseCount = alternate ? countMaxPathSetStateCalls(alternate, context) : 0;
27997
- return testCount + Math.max(thenCount, elseCount);
27998
- }
27999
- if (isNodeOfType(node, "SwitchStatement")) {
28000
- let maxRunSetters = 0;
28001
- let currentRunSetters = 0;
28002
- for (const switchCase of node.cases ?? []) {
28003
- const consequent = switchCase.consequent ?? [];
28004
- let caseSetters = 0;
28005
- let runEnds = false;
28006
- for (const statement of consequent) {
28007
- caseSetters += countMaxPathSetStateCalls(statement, context);
28008
- if (isTerminatingStatement(statement)) runEnds = true;
28009
- }
28010
- currentRunSetters += caseSetters;
28011
- if (runEnds) {
28012
- if (currentRunSetters > maxRunSetters) maxRunSetters = currentRunSetters;
28013
- currentRunSetters = 0;
28014
- }
28015
- }
28016
- if (currentRunSetters > maxRunSetters) maxRunSetters = currentRunSetters;
28017
- return maxRunSetters;
28018
- }
28019
- if (isNodeOfType(node, "TryStatement")) {
28020
- const tryCount = countMaxPathSetStateCalls(node.block, context);
28021
- const catchCount = node.handler ? countMaxPathSetStateCalls(node.handler.body, context) : 0;
28022
- const finallyCount = node.finalizer ? countMaxPathSetStateCalls(node.finalizer, context) : 0;
28023
- return Math.max(tryCount, catchCount) + finallyCount;
28024
- }
28025
- if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
28026
- let nestedSettersInArgs = 0;
28027
- for (const argument of node.arguments ?? []) nestedSettersInArgs += isFunctionLike$2(argument) ? countFunctionBodySetStateCalls(argument, context) : countMaxPathSetStateCalls(argument, context);
28028
- return 1 + nestedSettersInArgs;
28029
- }
28030
- if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
28031
- const helperFunction = context.helpersByName.get(node.callee.name);
28032
- if (helperFunction && !context.activeHelpers.has(helperFunction) && isWholesaleDelegationCall(node, context.effectCallback)) {
28033
- context.activeHelpers.add(helperFunction);
28034
- let helperCount = countFunctionBodySetStateCalls(helperFunction, context);
28035
- context.activeHelpers.delete(helperFunction);
28036
- for (const argument of node.arguments ?? []) helperCount += countMaxPathSetStateCalls(argument, context);
28037
- return helperCount;
28038
- }
28039
- }
28040
- const countChild = (child) => {
28041
- if (isFunctionLike$2(child)) return runsOnEffectDispatch(child) ? countFunctionBodySetStateCalls(child, context) : 0;
28042
- return countMaxPathSetStateCalls(child, context);
28043
- };
28044
- let total = 0;
28045
- const nodeRecord = node;
28046
- for (const key of Object.keys(nodeRecord)) {
28047
- if (key === "parent") continue;
28048
- const child = nodeRecord[key];
28049
- if (Array.isArray(child)) {
28050
- for (const item of child) if (item && typeof item === "object" && "type" in item) total += countChild(item);
28051
- } else if (child && typeof child === "object" && "type" in child) total += countChild(child);
28052
- }
28053
- return total;
28054
- };
28055
- const isInitOnlyEffect = (node) => {
28056
- const depsArg = node.arguments?.[1];
28057
- if (!depsArg) return false;
28058
- if (!isNodeOfType(depsArg, "ArrayExpression")) return false;
28059
- return (depsArg.elements ?? []).length === 0;
28060
- };
28061
- const DEV_ENV_FLAG_NAMES = new Set([
28062
- "DEV",
28063
- "PROD",
28064
- "MODE",
28065
- "NODE_ENV"
28066
- ]);
28067
- const mentionsDevEnvFlag = (node) => {
28068
- if (!node || typeof node !== "object") return false;
28069
- if (isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && DEV_ENV_FLAG_NAMES.has(node.property.name) && isNodeOfType(node.object, "MemberExpression") && isNodeOfType(node.object.property, "Identifier") && node.object.property.name === "env") return true;
28070
- const record = node;
28071
- for (const key of Object.keys(record)) {
28072
- if (key === "parent") continue;
28073
- const child = record[key];
28074
- if (Array.isArray(child)) {
28075
- for (const item of child) if (item && typeof item === "object" && "type" in item && mentionsDevEnvFlag(item)) return true;
28076
- } else if (child && typeof child === "object" && "type" in child && mentionsDevEnvFlag(child)) return true;
28077
- }
28078
- return false;
28079
- };
28080
- const isDevOnlyGuardedEffect = (callback) => {
28081
- const body = callback.body;
28082
- if (!body || !isNodeOfType(body, "BlockStatement")) return false;
28083
- const firstStatement = (body.body ?? [])[0];
28084
- if (!firstStatement) return false;
28085
- if (!isNodeOfType(firstStatement, "IfStatement") || firstStatement.alternate) return false;
28086
- const consequent = firstStatement.consequent;
28087
- if (!(isTerminatingStatement(consequent) || isNodeOfType(consequent, "BlockStatement") && (consequent.body ?? []).some((inner) => isTerminatingStatement(inner)))) return false;
28088
- return mentionsDevEnvFlag(firstStatement.test);
28089
- };
28090
- const noCascadingSetState = defineRule({
28826
+ const noCascadingSetState = defineRetiredRule({
28091
28827
  id: "no-cascading-set-state",
28092
28828
  title: "Multiple setState calls in one effect",
28093
28829
  severity: "warn",
28094
28830
  tags: ["test-noise"],
28095
- recommendation: "Combine related updates in `useReducer` so one effect does not redraw the screen once per `setState` call.",
28096
- create: (context) => ({ CallExpression(node) {
28097
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
28098
- if (isInitOnlyEffect(node)) return;
28099
- const callback = getEffectCallback(node, context.scopes);
28100
- if (!callback) return;
28101
- if (isDevOnlyGuardedEffect(callback)) return;
28102
- const setStateCallCount = countFunctionBodySetStateCalls(callback, {
28103
- helpersByName: collectLocalHelperFunctions(findProgramRoot(node) ?? callback),
28104
- activeHelpers: /* @__PURE__ */ new Set(),
28105
- effectCallback: callback
28106
- });
28107
- if (setStateCallCount >= 3) context.report({
28108
- node,
28109
- message: `${setStateCallCount} setState calls in one useEffect redraw your screen each time they run together.`
28110
- });
28111
- } })
28831
+ recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
28112
28832
  });
28113
28833
  //#endregion
28114
28834
  //#region src/plugin/rules/state-and-effects/no-chain-state-updates.ts
@@ -28364,7 +29084,7 @@ const noCreateRefInFunctionComponent = defineRule({
28364
29084
  if (!enclosingFunction) return;
28365
29085
  const displayName = componentOrHookDisplayNameForFunction(enclosingFunction);
28366
29086
  if (!displayName) return;
28367
- if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
29087
+ if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
28368
29088
  context.report({
28369
29089
  node,
28370
29090
  message: MESSAGE$30
@@ -29982,6 +30702,9 @@ const noDirectMutationState = defineRule({
29982
30702
  })
29983
30703
  });
29984
30704
  //#endregion
30705
+ //#region src/plugin/utils/is-setter-identifier.ts
30706
+ const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
30707
+ //#endregion
29985
30708
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
29986
30709
  const collectUseStateBindings = (componentBody) => {
29987
30710
  const bindings = [];
@@ -30868,7 +31591,7 @@ const noEffectChain = defineRule({
30868
31591
  const effectInfos = [];
30869
31592
  for (const effectCall of findTopLevelEffectCalls(componentBody)) {
30870
31593
  const callback = getEffectCallback(effectCall, context.scopes);
30871
- if (!callback) continue;
31594
+ if (!callback || !isFunctionLike$2(callback) || callback.async) continue;
30872
31595
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
30873
31596
  const writtenStateNames = collectWrittenStateNamesInEffect(analysisFunctions, setterToStateName);
30874
31597
  effectInfos.push({
@@ -32252,6 +32975,9 @@ const noEventTriggerState = defineRule({
32252
32975
  }
32253
32976
  });
32254
32977
  //#endregion
32978
+ //#region src/plugin/utils/is-setter-call.ts
32979
+ const isSetterCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && isSetterIdentifier(node.callee.name);
32980
+ //#endregion
32255
32981
  //#region src/plugin/rules/state-and-effects/no-fetch-in-effect.ts
32256
32982
  const IMPORT_INITIALIZER_TYPES = new Set([
32257
32983
  "ImportSpecifier",
@@ -32853,7 +33579,7 @@ const noGiantComponent = defineRule({
32853
33579
  FunctionDeclaration(node) {
32854
33580
  if (!node.id?.name || !isUppercaseName(node.id.name)) return;
32855
33581
  if (getOversizedComponentLineCount(node) === null) return;
32856
- if (!functionContainsReactRenderOutput(node, context.scopes)) return;
33582
+ if (!functionContainsReactRenderOutput(node, context.scopes, context.cfg)) return;
32857
33583
  reportOversizedComponent(node.id, node.id.name);
32858
33584
  },
32859
33585
  VariableDeclarator(node) {
@@ -32861,7 +33587,7 @@ const noGiantComponent = defineRule({
32861
33587
  const functionNode = unwrapReactHocFunction(node.init);
32862
33588
  if (!functionNode) return;
32863
33589
  if (getOversizedComponentLineCount(functionNode) === null) return;
32864
- if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
33590
+ if (!functionContainsReactRenderOutput(functionNode, context.scopes, context.cfg)) return;
32865
33591
  reportOversizedComponent(node.id, node.id.name);
32866
33592
  }
32867
33593
  };
@@ -33499,26 +34225,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
33499
34225
  } })
33500
34226
  });
33501
34227
  //#endregion
33502
- //#region src/plugin/utils/react-ref-origin.ts
33503
- const resolveReactRefSymbol = (memberExpression, scopes) => {
33504
- const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
33505
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
33506
- const symbol = resolveConstIdentifierAlias(receiver, scopes);
33507
- if (!symbol?.initializer) return null;
33508
- const initializer = stripParenExpression(symbol.initializer);
33509
- if (!isNodeOfType(initializer, "CallExpression")) return null;
33510
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
33511
- };
33512
- const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33513
- const expression = stripParenExpression(node);
33514
- if (resolveReactRefSymbol(expression, scopes)) return true;
33515
- if (!isNodeOfType(expression, "Identifier")) return false;
33516
- const symbol = resolveConstIdentifierAlias(expression, scopes);
33517
- if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
33518
- visitedSymbolIds.add(symbol.id);
33519
- return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
33520
- };
33521
- //#endregion
33522
34228
  //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
33523
34229
  const TIMER_FUNCTION_NAMES = new Set([
33524
34230
  "cancelAnimationFrame",
@@ -35141,7 +35847,7 @@ const noManyBooleanProps = defineRule({
35141
35847
  if (!param) return;
35142
35848
  const propsBinding = resolveFirstArgumentBinding(param);
35143
35849
  if (!propsBinding) return;
35144
- if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
35850
+ if (!functionContainsReactRenderOutput(functionNode, context.scopes, context.cfg)) return;
35145
35851
  if (isNodeOfType(propsBinding, "ObjectPattern")) {
35146
35852
  const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
35147
35853
  const booleanLikePropNames = [];
@@ -38103,6 +38809,174 @@ const noPropCallbackInRender = defineRule({
38103
38809
  } })
38104
38810
  });
38105
38811
  //#endregion
38812
+ //#region src/plugin/utils/is-proven-react-class-component.ts
38813
+ const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
38814
+ const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
38815
+ const expression = stripParenExpression(node);
38816
+ if (isNodeOfType(expression, "MemberExpression")) {
38817
+ const propertyName = getStaticPropertyName(expression);
38818
+ const receiver = stripParenExpression(expression.object);
38819
+ return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
38820
+ }
38821
+ if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
38822
+ if (!isNodeOfType(expression, "Identifier")) return false;
38823
+ const symbol = scopes.symbolFor(expression);
38824
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
38825
+ visitedSymbolIds.add(symbol.id);
38826
+ if (isImportedFromReact(symbol)) {
38827
+ const importedName = getImportedName(symbol.declarationNode);
38828
+ return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
38829
+ }
38830
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
38831
+ return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
38832
+ };
38833
+ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38834
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
38835
+ visitedClassNodes.add(classNode);
38836
+ return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
38837
+ };
38838
+ //#endregion
38839
+ //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
38840
+ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
38841
+ if (!isFunctionLike$2(functionNode)) return false;
38842
+ let containsReactHookCall = false;
38843
+ walkAst(functionNode.body, (node) => {
38844
+ if (containsReactHookCall) return false;
38845
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
38846
+ if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
38847
+ containsReactHookCall = true;
38848
+ return false;
38849
+ }
38850
+ });
38851
+ return containsReactHookCall;
38852
+ };
38853
+ //#endregion
38854
+ //#region src/plugin/utils/function-returns-props-children.ts
38855
+ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
38856
+ if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
38857
+ const firstParameter = stripParenExpression(functionNode.params[0]);
38858
+ const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
38859
+ const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
38860
+ const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
38861
+ if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
38862
+ for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
38863
+ const propertyValue = stripParenExpression(property.value);
38864
+ const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
38865
+ if (!isNodeOfType(childrenBinding, "Identifier")) continue;
38866
+ const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
38867
+ if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
38868
+ }
38869
+ }
38870
+ return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
38871
+ const candidate = stripParenExpression(expression);
38872
+ if (isNodeOfType(candidate, "Identifier")) {
38873
+ const symbol = scopes.symbolFor(candidate);
38874
+ return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
38875
+ }
38876
+ if (!isNodeOfType(candidate, "MemberExpression")) return false;
38877
+ if (getStaticPropertyName(candidate) !== "children") return false;
38878
+ const receiver = stripParenExpression(candidate.object);
38879
+ if (!isNodeOfType(receiver, "Identifier")) return false;
38880
+ const receiverSymbol = scopes.symbolFor(receiver);
38881
+ if (!receiverSymbol || !propsParameterSymbol) return false;
38882
+ return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
38883
+ }, controlFlow);
38884
+ };
38885
+ //#endregion
38886
+ //#region src/plugin/utils/function-returns-only-null.ts
38887
+ const isNullExpression = (expression) => {
38888
+ const candidate = stripParenExpression(expression);
38889
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
38890
+ };
38891
+ const functionReturnsOnlyNull = (functionNode) => {
38892
+ if (!isFunctionLike$2(functionNode)) return false;
38893
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
38894
+ const returnStatements = collectFunctionReturnStatements(functionNode);
38895
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
38896
+ };
38897
+ //#endregion
38898
+ //#region src/plugin/utils/is-proven-styled-component-expression.ts
38899
+ const findFactoryRoot = (node) => {
38900
+ const candidate = stripParenExpression(node);
38901
+ if (isNodeOfType(candidate, "Identifier")) return candidate;
38902
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
38903
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
38904
+ return null;
38905
+ };
38906
+ const findFactoryPropertyName = (node) => {
38907
+ const candidate = stripParenExpression(node);
38908
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
38909
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
38910
+ return null;
38911
+ };
38912
+ const isProvenStyledComponentExpression = (expression, scopes) => {
38913
+ const candidate = stripParenExpression(expression);
38914
+ if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
38915
+ const factoryRoot = findFactoryRoot(candidate.tag);
38916
+ if (!factoryRoot) return false;
38917
+ const factoryPropertyName = findFactoryPropertyName(candidate.tag);
38918
+ if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
38919
+ const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
38920
+ if (!symbol || symbol.kind !== "import") return false;
38921
+ if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
38922
+ const importDeclaration = symbol.declarationNode.parent;
38923
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
38924
+ };
38925
+ //#endregion
38926
+ //#region src/plugin/utils/is-proven-react-component-symbol.ts
38927
+ const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
38928
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
38929
+ const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38930
+ const candidate = stripParenExpression(expression);
38931
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
38932
+ if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
38933
+ if (isProvenStyledComponentExpression(candidate, scopes)) return true;
38934
+ if (isNodeOfType(candidate, "Identifier")) {
38935
+ const symbol = scopes.symbolFor(candidate);
38936
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
38937
+ visitedSymbolIds.add(symbol.id);
38938
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
38939
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
38940
+ return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
38941
+ }
38942
+ if (!isNodeOfType(candidate, "CallExpression")) return false;
38943
+ if (!hasStableCallTarget(candidate, scopes)) return false;
38944
+ if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
38945
+ const wrappedComponent = candidate.arguments[0];
38946
+ return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
38947
+ }
38948
+ if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
38949
+ const factory = candidate.arguments[0];
38950
+ if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
38951
+ const unwrappedFactory = stripParenExpression(factory);
38952
+ if (!isInlineFunctionExpression(unwrappedFactory)) return false;
38953
+ if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
38954
+ const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
38955
+ const returnedExpression = returnStatements[0]?.argument;
38956
+ return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
38957
+ };
38958
+ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
38959
+ const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
38960
+ for (const candidateSymbol of candidateSymbols) {
38961
+ if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
38962
+ if (isComponentDeclaration(candidateSymbol.declarationNode)) {
38963
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
38964
+ continue;
38965
+ }
38966
+ const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
38967
+ if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
38968
+ if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
38969
+ continue;
38970
+ }
38971
+ if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
38972
+ if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
38973
+ continue;
38974
+ }
38975
+ if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
38976
+ }
38977
+ return false;
38978
+ };
38979
+ //#endregion
38106
38980
  //#region src/plugin/rules/architecture/no-prop-types.ts
38107
38981
  const PROP_TYPES_PROPERTY = "propTypes";
38108
38982
  const isPropTypesKey = (key, computed) => {
@@ -38110,20 +38984,19 @@ const isPropTypesKey = (key, computed) => {
38110
38984
  if (computed) return isNodeOfType(key, "Literal") && key.value === PROP_TYPES_PROPERTY;
38111
38985
  return isNodeOfType(key, "Identifier") && key.name === PROP_TYPES_PROPERTY;
38112
38986
  };
38113
- const getComponentNameFromPropTypesAssignment = (left) => {
38987
+ const getComponentFromPropTypesAssignment = (left) => {
38114
38988
  if (!isNodeOfType(left, "MemberExpression")) return null;
38115
38989
  if (!isPropTypesKey(left.property, Boolean(left.computed))) return null;
38116
- if (!isNodeOfType(left.object, "Identifier")) return null;
38117
- if (!isUppercaseName(left.object.name)) return null;
38118
- return left.object.name;
38990
+ const receiver = stripParenExpression(left.object);
38991
+ if (!isNodeOfType(receiver, "Identifier")) return null;
38992
+ if (!isUppercaseName(receiver.name)) return null;
38993
+ return receiver;
38119
38994
  };
38120
- const getComponentNameFromClassProperty = (node) => {
38995
+ const getComponentNameFromClassProperty = (node, scopes) => {
38121
38996
  if (!node.static) return null;
38122
38997
  if (!isPropTypesKey(node.key, Boolean(node.computed))) return null;
38123
- const classBody = node.parent;
38124
- if (!isNodeOfType(classBody, "ClassBody")) return null;
38125
- const classNode = classBody.parent;
38126
- if (!classNode) return null;
38998
+ const classNode = isNodeOfType(node.parent, "ClassBody") ? node.parent.parent : null;
38999
+ if (!classNode || !isProvenReactClassComponent(classNode, scopes)) return null;
38127
39000
  if ((isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression")) && classNode.id?.name && isUppercaseName(classNode.id.name)) return classNode.id.name;
38128
39001
  if (!isNodeOfType(classNode, "ClassExpression")) return null;
38129
39002
  const declarator = classNode.parent;
@@ -38144,15 +39017,17 @@ const noPropTypes = defineRule({
38144
39017
  create: (context) => ({
38145
39018
  AssignmentExpression(node) {
38146
39019
  if (node.operator !== "=") return;
38147
- const componentName = getComponentNameFromPropTypesAssignment(node.left);
38148
- if (!componentName) return;
39020
+ const component = getComponentFromPropTypesAssignment(node.left);
39021
+ if (!component) return;
39022
+ const symbol = context.scopes.symbolFor(component);
39023
+ if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component)) return;
38149
39024
  context.report({
38150
39025
  node: node.left,
38151
- message: buildMessage$11(componentName)
39026
+ message: buildMessage$11(component.name)
38152
39027
  });
38153
39028
  },
38154
39029
  PropertyDefinition(node) {
38155
- const componentName = getComponentNameFromClassProperty(node);
39030
+ const componentName = getComponentNameFromClassProperty(node, context.scopes);
38156
39031
  if (!componentName) return;
38157
39032
  context.report({
38158
39033
  node: node.key,
@@ -40313,10 +41188,10 @@ const noStaticElementInteractions = defineRule({
40313
41188
  const isTestlikeFile = isTestlikeFilename(context.filename);
40314
41189
  return { JSXOpeningElement(node) {
40315
41190
  if (isTestlikeFile) return;
40316
- let hasNonBlockerHandler = false;
40317
41191
  let hasAnyHandler = false;
40318
41192
  let isKeyboardTarget = null;
40319
41193
  let seenHandlerNames = null;
41194
+ let nonBlockerHandlerNamesLower = null;
40320
41195
  for (const attribute of node.attributes) {
40321
41196
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
40322
41197
  const attributeName = getJsxAttributeName(attribute.name);
@@ -40331,13 +41206,12 @@ const noStaticElementInteractions = defineRule({
40331
41206
  if (!isKeyboardTarget) continue;
40332
41207
  }
40333
41208
  hasAnyHandler = true;
40334
- if (!isPureEventBlockerHandler(attribute)) {
40335
- hasNonBlockerHandler = true;
40336
- break;
40337
- }
41209
+ if (!isPureEventBlockerHandler(attribute)) (nonBlockerHandlerNamesLower ??= /* @__PURE__ */ new Set()).add(handlerNameLower);
40338
41210
  }
40339
41211
  if (!hasAnyHandler) return;
40340
- if (!hasNonBlockerHandler) return;
41212
+ if (!nonBlockerHandlerNamesLower) return;
41213
+ const onClick = nonBlockerHandlerNamesLower.size === 1 && nonBlockerHandlerNamesLower.has("onclick") ? hasJsxPropIgnoreCase(node.attributes, "onClick") : null;
41214
+ if (onClick && hasKeyboardActivatableDescendant(node.parent, onClick, context.scopes, context.settings)) return;
40341
41215
  const elementType = getElementType(node, context.settings);
40342
41216
  if (!HTML_TAGS.has(elementType)) return;
40343
41217
  if (elementType === "svg") return;
@@ -40630,7 +41504,7 @@ const noThisInSfc = defineRule({
40630
41504
  const enclosingFunction = findEnclosingFunctionComponent(node);
40631
41505
  if (!enclosingFunction) return;
40632
41506
  if (!looksLikeFunctionComponent(enclosingFunction)) return;
40633
- if (!functionContainsReactRenderOutput(enclosingFunction, context.scopes)) return;
41507
+ if (!functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg)) return;
40634
41508
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
40635
41509
  context.report({
40636
41510
  node,
@@ -42295,21 +43169,21 @@ const expressionContainsJsxOrCreateElement = (root) => {
42295
43169
  });
42296
43170
  return found;
42297
43171
  };
42298
- const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
43172
+ const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
42299
43173
  const isReactClassComponent = (classNode) => {
42300
43174
  if (isEs6Component(classNode)) return true;
42301
43175
  return expressionContainsJsxOrCreateElement(classNode);
42302
43176
  };
42303
- const findEnclosingComponent = (node, scopes) => {
43177
+ const findEnclosingComponent = (node, scopes, controlFlow) => {
42304
43178
  let walker = node.parent;
42305
43179
  while (walker) {
42306
43180
  if (isFunctionLike$2(walker)) {
42307
43181
  const componentName = inferFunctionLikeName(walker);
42308
- if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes)) return {
43182
+ if (componentName && isReactComponentName(componentName) && functionContainsJsxOrCreateElement(walker, scopes, controlFlow)) return {
42309
43183
  component: walker,
42310
43184
  name: componentName
42311
43185
  };
42312
- if (!componentName && functionContainsJsxOrCreateElement(walker, scopes) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
43186
+ if (!componentName && functionContainsJsxOrCreateElement(walker, scopes, controlFlow) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
42313
43187
  component: walker,
42314
43188
  name: null
42315
43189
  };
@@ -42548,7 +43422,7 @@ const noUnstableNestedComponents = defineRule({
42548
43422
  if (renderPropRegex.test(propInfo.propName)) return;
42549
43423
  if (settings.allowAsProps) return;
42550
43424
  }
42551
- const enclosing = findEnclosingComponent(candidateNode, context.scopes);
43425
+ const enclosing = findEnclosingComponent(candidateNode, context.scopes, context.cfg);
42552
43426
  if (!enclosing) return;
42553
43427
  const gatedName = propInfo ? null : requiredInstantiationName;
42554
43428
  queuedReports.push({
@@ -42559,7 +43433,7 @@ const noUnstableNestedComponents = defineRule({
42559
43433
  });
42560
43434
  };
42561
43435
  const checkFunctionLike = (node) => {
42562
- if (!functionContainsJsxOrCreateElement(node, context.scopes)) return;
43436
+ if (!functionContainsJsxOrCreateElement(node, context.scopes, context.cfg)) return;
42563
43437
  const inferredName = inferFunctionLikeName(node);
42564
43438
  const propInfo = isComponentDeclaredInProp(node);
42565
43439
  const isObjectCallback = isObjectCallbackCandidate(node);
@@ -43170,7 +44044,7 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
43170
44044
  continue;
43171
44045
  }
43172
44046
  if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
43173
- if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionContainsReactRenderOutput(value, state.scopes)) return true;
44047
+ if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionContainsReactRenderOutput(value, state.scopes, state.controlFlow)) return true;
43174
44048
  if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
43175
44049
  }
43176
44050
  return false;
@@ -43280,11 +44154,12 @@ const onlyExportComponents = defineRule({
43280
44154
  allowExportNames: new Set(settings.allowExportNames),
43281
44155
  allowConstantExport: settings.allowConstantExport,
43282
44156
  localComponentNames,
43283
- scopes: context.scopes
44157
+ scopes: context.scopes,
44158
+ controlFlow: context.cfg
43284
44159
  };
43285
44160
  for (const child of componentCandidates) {
43286
44161
  if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
43287
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
44162
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes, context.cfg)) localComponentNames.add(child.id.name);
43288
44163
  }
43289
44164
  if (isNodeOfType(child, "ClassDeclaration") && child.id) {
43290
44165
  if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
@@ -43293,7 +44168,7 @@ const onlyExportComponents = defineRule({
43293
44168
  const initializer = child.init;
43294
44169
  if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
43295
44170
  const expression = initializer ? skipTsExpression(initializer) : null;
43296
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
44171
+ if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes, context.cfg)) localComponentNames.add(child.id.name);
43297
44172
  }
43298
44173
  }
43299
44174
  }
@@ -44793,19 +45668,63 @@ const preferTagOverRole = defineRule({
44793
45668
  });
44794
45669
  //#endregion
44795
45670
  //#region src/plugin/rules/state-and-effects/prefer-use-effect-event.ts
44796
- const collectFunctionTypedLocalBindings = (componentBody) => {
44797
- const functionTypedLocals = /* @__PURE__ */ new Set();
44798
- if (!isNodeOfType(componentBody, "BlockStatement")) return functionTypedLocals;
45671
+ const STABLE_REACT_HOOK_VALUE_NAMES = new Set([
45672
+ "useActionState",
45673
+ "useEffectEvent",
45674
+ "useReducer",
45675
+ "useRef",
45676
+ "useState",
45677
+ "useTransition"
45678
+ ]);
45679
+ const isStableReactHookDependency = (dependency, context) => {
45680
+ const unwrappedDependency = stripParenExpression(dependency);
45681
+ if (!isNodeOfType(unwrappedDependency, "Identifier")) return false;
45682
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
45683
+ let dependencySymbol = context.scopes.symbolFor(unwrappedDependency);
45684
+ while (dependencySymbol) {
45685
+ if (visitedSymbolIds.has(dependencySymbol.id)) return false;
45686
+ visitedSymbolIds.add(dependencySymbol.id);
45687
+ if (symbolHasStableHookOrigin(dependencySymbol, context.scopes)) {
45688
+ let declarator = dependencySymbol.declarationNode;
45689
+ while (declarator && !isNodeOfType(declarator, "VariableDeclarator")) declarator = declarator.parent;
45690
+ if (!declarator?.init) return false;
45691
+ return isReactApiCall(stripParenExpression(declarator.init), STABLE_REACT_HOOK_VALUE_NAMES, context.scopes, {
45692
+ allowGlobalReactNamespace: true,
45693
+ resolveNamedAliases: true
45694
+ });
45695
+ }
45696
+ if (dependencySymbol.kind !== "const" || dependencySymbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(dependencySymbol.declarationNode, "VariableDeclarator") || dependencySymbol.declarationNode.id !== dependencySymbol.bindingIdentifier || !dependencySymbol.initializer) return false;
45697
+ const aliasInitializer = stripParenExpression(dependencySymbol.initializer);
45698
+ if (!isNodeOfType(aliasInitializer, "Identifier")) return false;
45699
+ dependencySymbol = context.scopes.symbolFor(aliasInitializer);
45700
+ }
45701
+ return false;
45702
+ };
45703
+ const isPotentiallyChangingReactUseCallback = (initializer, context) => {
45704
+ const unwrappedInitializer = stripParenExpression(initializer);
45705
+ if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return false;
45706
+ if (!isReactApiCall(unwrappedInitializer, "useCallback", context.scopes, {
45707
+ allowGlobalReactNamespace: true,
45708
+ resolveNamedAliases: true
45709
+ })) return false;
45710
+ const dependencyList = unwrappedInitializer.arguments?.[1];
45711
+ if (!dependencyList) return true;
45712
+ const unwrappedDependencyList = stripParenExpression(dependencyList);
45713
+ if (!isNodeOfType(unwrappedDependencyList, "ArrayExpression")) return true;
45714
+ return (unwrappedDependencyList.elements ?? []).some((dependency) => dependency === null || !isStableReactHookDependency(dependency, context));
45715
+ };
45716
+ const collectPotentiallyChangingCallbackBindings = (componentBody, context) => {
45717
+ const potentiallyChangingCallbacks = /* @__PURE__ */ new Set();
45718
+ if (!isNodeOfType(componentBody, "BlockStatement")) return potentiallyChangingCallbacks;
44799
45719
  for (const statement of componentBody.body ?? []) {
44800
45720
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
44801
45721
  for (const declarator of statement.declarations ?? []) {
44802
45722
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
44803
- if (!isNodeOfType(declarator.init, "CallExpression")) continue;
44804
- if (!isHookCall$2(declarator.init, "useCallback")) continue;
44805
- functionTypedLocals.add(declarator.id.name);
45723
+ if (!declarator.init || !isPotentiallyChangingReactUseCallback(declarator.init, context)) continue;
45724
+ potentiallyChangingCallbacks.add(declarator.id.name);
44806
45725
  }
44807
45726
  }
44808
- return functionTypedLocals;
45727
+ return potentiallyChangingCallbacks;
44809
45728
  };
44810
45729
  const findEnclosingFunctionInsideEffect = (identifierNode, effectCallback) => {
44811
45730
  let cursor = identifierNode.parent ?? null;
@@ -44879,7 +45798,7 @@ const preferUseEffectEvent = defineRule({
44879
45798
  create: (context) => {
44880
45799
  const checkComponent = (componentBody) => {
44881
45800
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44882
- const functionTypedLocalBindings = collectFunctionTypedLocalBindings(componentBody);
45801
+ const potentiallyChangingCallbackBindings = collectPotentiallyChangingCallbackBindings(componentBody, context);
44883
45802
  for (const statement of componentBody.body ?? []) {
44884
45803
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
44885
45804
  const effectCall = statement.expression;
@@ -44897,7 +45816,7 @@ const preferUseEffectEvent = defineRule({
44897
45816
  if (!isNodeOfType(depElement, "Identifier")) continue;
44898
45817
  const depName = depElement.name;
44899
45818
  const isFunctionTypedPropDep = propStackTracker.isPropName(depName) && REACT_HANDLER_PROP_PATTERN.test(depName);
44900
- const isFunctionTypedLocalDep = functionTypedLocalBindings.has(depName);
45819
+ const isFunctionTypedLocalDep = potentiallyChangingCallbackBindings.has(depName);
44901
45820
  if (!isFunctionTypedPropDep && !isFunctionTypedLocalDep) continue;
44902
45821
  const classification = classifyCallableReadsInsideEffect(depName, callback);
44903
45822
  if (!classification.hasAnyRead) continue;
@@ -46443,6 +47362,37 @@ const containsLocaleEnvironmentRead = (expression) => {
46443
47362
  return readsLocaleEnvironment;
46444
47363
  };
46445
47364
  //#endregion
47365
+ //#region src/plugin/utils/is-hook-binding-in-scope.ts
47366
+ const isHookBindingInScope = (node, query) => {
47367
+ const { bindingName, hookName, destructureIndex } = query;
47368
+ let cursor = node;
47369
+ while (cursor) {
47370
+ if (isNodeOfType(cursor, "BlockStatement") || isNodeOfType(cursor, "Program")) for (const statement of cursor.body ?? []) {
47371
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
47372
+ for (const declarator of statement.declarations ?? []) {
47373
+ if (!isNodeOfType(declarator.init, "CallExpression")) continue;
47374
+ if (!isHookCall$2(declarator.init, hookName)) continue;
47375
+ if (destructureIndex !== void 0) {
47376
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
47377
+ const elements = declarator.id.elements ?? [];
47378
+ if (elements.length <= destructureIndex) continue;
47379
+ const element = elements[destructureIndex];
47380
+ if (isNodeOfType(element, "Identifier") && element.name === bindingName) return true;
47381
+ } else if (isNodeOfType(declarator.id, "Identifier") && declarator.id.name === bindingName) return true;
47382
+ }
47383
+ }
47384
+ cursor = cursor.parent ?? null;
47385
+ }
47386
+ return false;
47387
+ };
47388
+ //#endregion
47389
+ //#region src/plugin/utils/is-use-state-setter-in-scope.ts
47390
+ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node, {
47391
+ bindingName: setterName,
47392
+ hookName: "useState",
47393
+ destructureIndex: 1
47394
+ });
47395
+ //#endregion
46446
47396
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
46447
47397
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
46448
47398
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
@@ -47515,10 +48465,10 @@ const rerenderLazyStateInit = defineRule({
47515
48465
  //#endregion
47516
48466
  //#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
47517
48467
  const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
47518
- const callbackReturnsJsx = (callback, scopes) => {
48468
+ const callbackReturnsJsx = (callback, scopes, controlFlow) => {
47519
48469
  if (!callback) return false;
47520
48470
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
47521
- return functionReturnsMatchingExpression(callback, scopes, isJsxExpression);
48471
+ return functionReturnsMatchingExpression(callback, scopes, isJsxExpression, controlFlow);
47522
48472
  };
47523
48473
  const returnArgumentUsesAnyName = (returnStatement, names) => {
47524
48474
  if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
@@ -47596,7 +48546,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
47596
48546
  if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
47597
48547
  for (const declarator of stmt.declarations ?? []) {
47598
48548
  const init = declarator.init;
47599
- if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes)) {
48549
+ if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0], context.scopes, context.cfg)) {
47600
48550
  memoNode = declarator;
47601
48551
  callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
47602
48552
  if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
@@ -48132,14 +49082,6 @@ const rerenderTransitionsScroll = defineRule({
48132
49082
  } })
48133
49083
  });
48134
49084
  //#endregion
48135
- //#region src/plugin/utils/define-retired-rule.ts
48136
- const defineRetiredRule = (rule) => defineRule({
48137
- ...rule,
48138
- defaultEnabled: false,
48139
- lifecycle: "retired",
48140
- create: () => ({})
48141
- });
48142
- //#endregion
48143
49085
  //#region src/plugin/rules/react-native/rn-animate-layout-property.ts
48144
49086
  const rnAnimateLayoutProperty = defineRetiredRule({
48145
49087
  id: "rn-animate-layout-property",
@@ -57307,8 +58249,7 @@ const webhookSignatureRisk = defineRule({
57307
58249
  });
57308
58250
  //#endregion
57309
58251
  //#region src/plugin/rules/zod/utils/zod-ast.ts
57310
- const ZOD_MODULE = "zod";
57311
- const ZOD_MODULE_SOURCES = [ZOD_MODULE];
58252
+ const ZOD_MODULE_SOURCES = ["zod", "zod/v4"];
57312
58253
  const importInfoCache = /* @__PURE__ */ new WeakMap();
57313
58254
  const getImportInfoForIdentifier = (identifier) => {
57314
58255
  if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
@@ -57321,7 +58262,8 @@ const computeImportInfoForIdentifier = (identifier) => {
57321
58262
  if (!specifier) return null;
57322
58263
  const declaration = specifier.parent;
57323
58264
  if (!declaration || !isNodeOfType(declaration, "ImportDeclaration")) return null;
57324
- if (declaration.source?.value !== ZOD_MODULE) return null;
58265
+ const source = declaration.source?.value;
58266
+ if (typeof source !== "string" || !ZOD_MODULE_SOURCES.includes(source)) return null;
57325
58267
  if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) return {
57326
58268
  imported: null,
57327
58269
  isDefault: false,