oxlint-plugin-react-doctor 0.7.7-dev.9fc93f8 → 0.7.7-dev.a468589

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1349 -554
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,6 +7,13 @@ import { analyze } from "eslint-scope";
7
7
  //#region src/plugin/utils/is-node-of-type.ts
8
8
  const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
9
9
  //#endregion
10
+ //#region src/plugin/utils/is-type-only-import.ts
11
+ const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
12
+ if (!specifiers || specifiers.length === 0) return false;
13
+ return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
14
+ };
15
+ const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
16
+ //#endregion
10
17
  //#region src/plugin/utils/non-react-jsx-dialect.ts
11
18
  const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
12
19
  "solid-js",
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
21
28
  "vidode"
22
29
  ]);
23
30
  const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
31
+ const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
32
+ "react",
33
+ "react-dom",
34
+ "preact"
35
+ ];
24
36
  const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
25
- const fileImportsNonReactJsxDialect = (program) => {
37
+ const collectJsxRuntimeImports$1 = (program) => {
38
+ let hasNonReactRuntime = false;
39
+ let hasReactRuntime = false;
26
40
  for (const statement of program.body) {
27
41
  if (!isNodeOfType(statement, "ImportDeclaration")) continue;
28
- const source = statement.source;
42
+ const importDeclaration = statement;
43
+ if (isTypeOnlyImport(importDeclaration)) continue;
44
+ const source = importDeclaration.source;
29
45
  const value = source && typeof source.value === "string" ? source.value : null;
30
46
  if (!value) continue;
31
- if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
32
- if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
47
+ if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
48
+ if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
33
49
  }
34
- return false;
50
+ return {
51
+ hasNonReactRuntime,
52
+ hasReactRuntime
53
+ };
54
+ };
55
+ const fileImportsNonReactJsxDialect = (program) => {
56
+ const runtimeImports = collectJsxRuntimeImports$1(program);
57
+ return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
35
58
  };
36
59
  const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
37
60
  for (const attribute of openingNode.attributes) {
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
244
267
  const wrapCreateForReactJsxOnly = (create) => ((context) => {
245
268
  const innerVisitors = create(context);
246
269
  let fileIsNonReactJsx = false;
270
+ let fileImportsReactRuntime = false;
247
271
  const wrappedVisitors = {};
248
272
  for (const [key, visitor] of Object.entries(innerVisitors)) {
249
273
  if (typeof visitor !== "function") {
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
256
280
  }
257
281
  if (key === "Program") {
258
282
  wrappedVisitors.Program = (node) => {
259
- fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
283
+ const runtimeImports = collectJsxRuntimeImports$1(node);
284
+ fileImportsReactRuntime = runtimeImports.hasReactRuntime;
285
+ fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
260
286
  visitor(node);
261
287
  };
262
288
  continue;
263
289
  }
264
290
  if (key === "JSXOpeningElement") {
265
291
  wrappedVisitors.JSXOpeningElement = (node) => {
266
- if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
292
+ if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
267
293
  if (fileIsNonReactJsx) return;
268
294
  visitor(node);
269
295
  };
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
275
301
  };
276
302
  }
277
303
  if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
278
- fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
304
+ const runtimeImports = collectJsxRuntimeImports$1(node);
305
+ fileImportsReactRuntime = runtimeImports.hasReactRuntime;
306
+ fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
279
307
  };
280
308
  return wrappedVisitors;
281
309
  });
@@ -4314,6 +4342,397 @@ const findTransparentExpressionRoot = (node) => {
4314
4342
  return current;
4315
4343
  };
4316
4344
  //#endregion
4345
+ //#region src/plugin/utils/get-static-property-key-name.ts
4346
+ const getStaticPropertyKeyName = (node, options = {}) => {
4347
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
4348
+ const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
4349
+ if (node.computed) {
4350
+ if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
4351
+ if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
4352
+ return null;
4353
+ }
4354
+ if (isNodeOfType(key, "Identifier")) return key.name;
4355
+ if (isNodeOfType(key, "Literal")) {
4356
+ if (typeof key.value === "string") return key.value;
4357
+ if (options.stringifyNonStringLiterals) return String(key.value);
4358
+ }
4359
+ return null;
4360
+ };
4361
+ //#endregion
4362
+ //#region src/plugin/utils/get-destructured-binding-property-name.ts
4363
+ const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4364
+ let bindingNode = bindingIdentifier;
4365
+ if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
4366
+ const property = bindingNode.parent;
4367
+ if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
4368
+ return getStaticPropertyKeyName(property, { allowComputedString: true });
4369
+ };
4370
+ //#endregion
4371
+ //#region src/plugin/utils/get-static-property-name.ts
4372
+ const getStaticPropertyName = (memberExpression) => {
4373
+ const property = memberExpression.property;
4374
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
4375
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
4376
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
4377
+ return null;
4378
+ };
4379
+ //#endregion
4380
+ //#region src/plugin/utils/has-static-property-write-before.ts
4381
+ const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
4382
+ const potentiallyAliasedSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
4383
+ const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
4384
+ "CatchClause",
4385
+ "ConditionalExpression",
4386
+ "DoWhileStatement",
4387
+ "ForInStatement",
4388
+ "ForOfStatement",
4389
+ "ForStatement",
4390
+ "IfStatement",
4391
+ "LogicalExpression",
4392
+ "SwitchCase",
4393
+ "SwitchStatement",
4394
+ "TryStatement",
4395
+ "WhileStatement"
4396
+ ]);
4397
+ const getResolvedStaticPropertyName = (memberExpression, scopes) => {
4398
+ if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
4399
+ const directPropertyName = getStaticPropertyName(memberExpression);
4400
+ if (directPropertyName || !memberExpression.computed) return directPropertyName;
4401
+ const property = stripParenExpression(memberExpression.property);
4402
+ if (!isNodeOfType(property, "Identifier")) return null;
4403
+ const propertySymbol = resolveConstIdentifierAlias(property, scopes);
4404
+ const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
4405
+ return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
4406
+ };
4407
+ const collectScopeSymbols = (scope, symbols) => {
4408
+ symbols.push(...scope.symbols);
4409
+ for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
4410
+ };
4411
+ const getEquivalentSymbols = (identifier, scopes) => {
4412
+ const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
4413
+ if (!rootSymbol) return [];
4414
+ let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
4415
+ if (!symbolsByRootId) {
4416
+ symbolsByRootId = /* @__PURE__ */ new Map();
4417
+ equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
4418
+ }
4419
+ const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
4420
+ if (cachedSymbols) return cachedSymbols;
4421
+ const allSymbols = [];
4422
+ collectScopeSymbols(scopes.rootScope, allSymbols);
4423
+ const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
4424
+ symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
4425
+ return equivalentSymbols;
4426
+ };
4427
+ const getDirectAliasSourceSymbol = (expression, scopes) => {
4428
+ const unwrappedExpression = stripParenExpression(expression);
4429
+ return isNodeOfType(unwrappedExpression, "Identifier") ? scopes.symbolFor(unwrappedExpression) : null;
4430
+ };
4431
+ const getDirectAssignmentSourceSymbol = (identifier, scopes) => {
4432
+ const assignmentTarget = findTransparentExpressionRoot(identifier);
4433
+ const parent = assignmentTarget.parent;
4434
+ if (!parent || !isNodeOfType(parent, "AssignmentExpression") || parent.operator !== "=" || parent.left !== assignmentTarget) return null;
4435
+ return getDirectAliasSourceSymbol(parent.right, scopes);
4436
+ };
4437
+ const isDirectAliasSourceReference = (identifier) => {
4438
+ const aliasSource = findTransparentExpressionRoot(identifier);
4439
+ const parent = aliasSource.parent;
4440
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === aliasSource && (isNodeOfType(parent.id, "Identifier") || isNodeOfType(parent.id, "ObjectPattern"))) return true;
4441
+ return Boolean(parent && isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.right === aliasSource && isNodeOfType(stripParenExpression(parent.left), "Identifier"));
4442
+ };
4443
+ const isDirectAliasOfKnownSymbol = (symbol, knownSymbolIds, scopes) => {
4444
+ if (symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier) {
4445
+ const initializerSymbol = getDirectAliasSourceSymbol(symbol.initializer, scopes);
4446
+ if (initializerSymbol && knownSymbolIds.has(initializerSymbol.id)) return true;
4447
+ }
4448
+ return symbol.references.some((reference) => {
4449
+ const assignmentSourceSymbol = getDirectAssignmentSourceSymbol(reference.identifier, scopes);
4450
+ return Boolean(assignmentSourceSymbol && knownSymbolIds.has(assignmentSourceSymbol.id));
4451
+ });
4452
+ };
4453
+ const getPotentiallyAliasedSymbols = (identifier, scopes) => {
4454
+ const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
4455
+ if (!rootSymbol) return [];
4456
+ let symbolsByRootId = potentiallyAliasedSymbolsByAnalysis.get(scopes);
4457
+ if (!symbolsByRootId) {
4458
+ symbolsByRootId = /* @__PURE__ */ new Map();
4459
+ potentiallyAliasedSymbolsByAnalysis.set(scopes, symbolsByRootId);
4460
+ }
4461
+ const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
4462
+ if (cachedSymbols) return cachedSymbols;
4463
+ const allSymbols = [];
4464
+ collectScopeSymbols(scopes.rootScope, allSymbols);
4465
+ const aliasedSymbolIds = new Set([rootSymbol.id]);
4466
+ let didAddAlias = true;
4467
+ while (didAddAlias) {
4468
+ didAddAlias = false;
4469
+ for (const symbol of allSymbols) {
4470
+ if (aliasedSymbolIds.has(symbol.id)) continue;
4471
+ if (!isDirectAliasOfKnownSymbol(symbol, aliasedSymbolIds, scopes)) continue;
4472
+ aliasedSymbolIds.add(symbol.id);
4473
+ didAddAlias = true;
4474
+ }
4475
+ }
4476
+ const aliasedSymbols = allSymbols.filter((symbol) => aliasedSymbolIds.has(symbol.id));
4477
+ symbolsByRootId.set(rootSymbol.id, aliasedSymbols);
4478
+ return aliasedSymbols;
4479
+ };
4480
+ const findExecutionBoundary = (node) => {
4481
+ let current = node;
4482
+ while (current) {
4483
+ if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
4484
+ current = current.parent ?? null;
4485
+ }
4486
+ return null;
4487
+ };
4488
+ const isOnUnconditionalPath = (node, boundary) => {
4489
+ let current = node.parent ?? null;
4490
+ while (current && current !== boundary) {
4491
+ if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
4492
+ current = current.parent ?? null;
4493
+ }
4494
+ return current === boundary;
4495
+ };
4496
+ const findFunctionBindingIdentifier = (functionNode) => {
4497
+ if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
4498
+ const parent = functionNode.parent;
4499
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
4500
+ return null;
4501
+ };
4502
+ const findDirectCall = (identifier) => {
4503
+ let callee = identifier;
4504
+ let parent = callee.parent;
4505
+ while (parent && stripParenExpression(parent) === identifier) {
4506
+ callee = parent;
4507
+ parent = callee.parent;
4508
+ }
4509
+ return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
4510
+ };
4511
+ const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
4512
+ if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
4513
+ visitedFunctionNodes.add(functionNode);
4514
+ const referenceBoundary = findExecutionBoundary(referenceNode);
4515
+ if (!referenceBoundary) return false;
4516
+ const invocationCalls = [];
4517
+ const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
4518
+ if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
4519
+ const call = findDirectCall(reference.identifier);
4520
+ if (call) invocationCalls.push(call);
4521
+ }
4522
+ else {
4523
+ const call = findDirectCall(functionNode);
4524
+ if (call) invocationCalls.push(call);
4525
+ }
4526
+ return invocationCalls.some((call) => {
4527
+ if (call.range[0] >= referenceNode.range[0]) return false;
4528
+ const callBoundary = findExecutionBoundary(call);
4529
+ if (!callBoundary) return false;
4530
+ if (callBoundary === referenceBoundary) return true;
4531
+ if (!isFunctionLike$2(callBoundary)) return false;
4532
+ return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
4533
+ });
4534
+ };
4535
+ const isMemberWriteTarget = (memberExpression) => {
4536
+ const parent = memberExpression.parent;
4537
+ if (!parent) return false;
4538
+ if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
4539
+ if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
4540
+ return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
4541
+ };
4542
+ const getMemberWriteTarget = (identifier) => {
4543
+ let parent = identifier.parent;
4544
+ while (parent && stripParenExpression(parent) === identifier) parent = parent.parent;
4545
+ if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== identifier || !isMemberWriteTarget(parent)) return null;
4546
+ return parent;
4547
+ };
4548
+ const getStaticPropertyWriteTarget = (identifier, propertyName, scopes) => {
4549
+ const writeTarget = getMemberWriteTarget(identifier);
4550
+ return writeTarget && getResolvedStaticPropertyName(writeTarget, scopes) === propertyName ? writeTarget : null;
4551
+ };
4552
+ const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
4553
+ const writeTarget = getStaticPropertyWriteTarget(reference.identifier, propertyName, scopes);
4554
+ if (!writeTarget) return false;
4555
+ const writeBoundary = findExecutionBoundary(writeTarget);
4556
+ const referenceBoundary = findExecutionBoundary(referenceNode);
4557
+ if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(writeTarget, writeBoundary)) return false;
4558
+ if (writeBoundary === referenceBoundary) return writeTarget.range[0] < referenceNode.range[0];
4559
+ return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
4560
+ });
4561
+ const isStableStaticPropertyReference = (identifier) => {
4562
+ if (isDirectAliasSourceReference(identifier)) return true;
4563
+ const identifierRoot = findTransparentExpressionRoot(identifier);
4564
+ const memberExpression = identifierRoot.parent;
4565
+ return Boolean(memberExpression && isNodeOfType(memberExpression, "MemberExpression") && stripParenExpression(memberExpression.object) === identifierRoot);
4566
+ };
4567
+ const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
4568
+ if (!isNodeOfType(identifier, "Identifier")) return false;
4569
+ return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
4570
+ const writeTarget = getMemberWriteTarget(reference.identifier);
4571
+ if (!writeTarget) return false;
4572
+ const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
4573
+ return writtenPropertyName === null || writtenPropertyName === propertyName;
4574
+ }));
4575
+ };
4576
+ const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
4577
+ if (!isNodeOfType(identifier, "Identifier")) return false;
4578
+ if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
4579
+ return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => !isStableStaticPropertyReference(reference.identifier)));
4580
+ };
4581
+ const hasPossibleStaticMemberCallWrite = (callExpression, scopes) => {
4582
+ const unwrappedCallExpression = stripParenExpression(callExpression);
4583
+ if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
4584
+ const callee = stripParenExpression(unwrappedCallExpression.callee);
4585
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
4586
+ const propertyName = getStaticPropertyName(callee);
4587
+ if (propertyName === null) return false;
4588
+ const receiver = stripParenExpression(callee.object);
4589
+ return isNodeOfType(receiver, "Identifier") && hasPossibleStaticPropertyWrite(receiver, propertyName, scopes);
4590
+ };
4591
+ const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
4592
+ if (!isNodeOfType(identifier, "Identifier")) return false;
4593
+ return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
4594
+ };
4595
+ //#endregion
4596
+ //#region src/plugin/utils/find-enclosing-function.ts
4597
+ const findEnclosingFunction$1 = (node) => {
4598
+ let cursor = node.parent;
4599
+ while (cursor) {
4600
+ if (isFunctionLike$2(cursor)) return cursor;
4601
+ cursor = cursor.parent ?? null;
4602
+ }
4603
+ return null;
4604
+ };
4605
+ //#endregion
4606
+ //#region src/plugin/utils/has-symbol-write-before.ts
4607
+ const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
4608
+ if (reference.flag === "read") return false;
4609
+ const writeFunction = findEnclosingFunction$1(reference.identifier);
4610
+ if (writeFunction === findEnclosingFunction$1(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
4611
+ if (!writeFunction) return true;
4612
+ return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
4613
+ });
4614
+ //#endregion
4615
+ //#region src/plugin/utils/get-order-independent-local-function.ts
4616
+ const COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS = new Set([
4617
+ "+=",
4618
+ "-=",
4619
+ "*=",
4620
+ "/=",
4621
+ "%=",
4622
+ "&=",
4623
+ "^=",
4624
+ "|="
4625
+ ]);
4626
+ const isPureParameterExpression = (expression, parameterNames) => {
4627
+ const unwrappedExpression = stripParenExpression(expression);
4628
+ if (isNodeOfType(unwrappedExpression, "Literal")) return true;
4629
+ if (isNodeOfType(unwrappedExpression, "Identifier")) return parameterNames.has(unwrappedExpression.name);
4630
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression") || isNodeOfType(unwrappedExpression, "LogicalExpression")) return isPureParameterExpression(unwrappedExpression.left, parameterNames) && isPureParameterExpression(unwrappedExpression.right, parameterNames);
4631
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression")) return unwrappedExpression.operator !== "delete" && isPureParameterExpression(unwrappedExpression.argument, parameterNames);
4632
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isPureParameterExpression(unwrappedExpression.test, parameterNames) && isPureParameterExpression(unwrappedExpression.consequent, parameterNames) && isPureParameterExpression(unwrappedExpression.alternate, parameterNames);
4633
+ if (isNodeOfType(unwrappedExpression, "TemplateLiteral")) return unwrappedExpression.expressions.every((nestedExpression) => isPureParameterExpression(nestedExpression, parameterNames));
4634
+ return false;
4635
+ };
4636
+ const isOrderIndependentPromiseResolveCall = (expression, parameterNames, scopes) => {
4637
+ const unwrappedExpression = stripParenExpression(expression);
4638
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
4639
+ if (!unwrappedExpression.arguments.every((argument) => !isNodeOfType(argument, "SpreadElement") && isPureParameterExpression(argument, parameterNames))) return false;
4640
+ const callee = stripParenExpression(unwrappedExpression.callee);
4641
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
4642
+ if (getStaticPropertyName(callee) !== "resolve") return false;
4643
+ const receiver = stripParenExpression(callee.object);
4644
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && scopes.isGlobalReference(receiver);
4645
+ };
4646
+ const isHarmlessPromiseResolveAwait = (statement, parameterNames, scopes) => {
4647
+ if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AwaitExpression")) return false;
4648
+ return isOrderIndependentPromiseResolveCall(statement.expression.argument, parameterNames, scopes);
4649
+ };
4650
+ const isCommutativeParameterMutation = (statement, parameterNames) => {
4651
+ if (!isNodeOfType(statement, "ExpressionStatement") || !isNodeOfType(statement.expression, "AssignmentExpression") || !COMMUTATIVE_COMPOUND_ASSIGNMENT_OPERATORS.has(statement.expression.operator)) return false;
4652
+ const mutationTarget = stripParenExpression(statement.expression.left);
4653
+ if (!isNodeOfType(mutationTarget, "MemberExpression")) return false;
4654
+ if (getStaticPropertyName(mutationTarget) === null) return false;
4655
+ const receiver = stripParenExpression(mutationTarget.object);
4656
+ return isNodeOfType(receiver, "Identifier") && parameterNames.has(receiver.name) && isNodeOfType(stripParenExpression(statement.expression.right), "Literal");
4657
+ };
4658
+ const isOrderIndependentFunction = (functionNode, scopes) => {
4659
+ if (!isFunctionLike$2(functionNode)) return false;
4660
+ const parameterNames = /* @__PURE__ */ new Set();
4661
+ for (const parameter of functionNode.params) {
4662
+ if (!isNodeOfType(parameter, "Identifier")) return false;
4663
+ parameterNames.add(parameter.name);
4664
+ }
4665
+ if (!functionNode.async) {
4666
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isOrderIndependentPromiseResolveCall(functionNode.body, parameterNames, scopes);
4667
+ if (functionNode.body.body.length !== 1) return false;
4668
+ const [returnStatement] = functionNode.body.body;
4669
+ return Boolean(isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument && isOrderIndependentPromiseResolveCall(returnStatement.argument, parameterNames, scopes));
4670
+ }
4671
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isPureParameterExpression(functionNode.body, parameterNames);
4672
+ const statements = functionNode.body.body;
4673
+ for (let statementIndex = 0; statementIndex < statements.length; statementIndex++) {
4674
+ const statement = statements[statementIndex];
4675
+ const isTerminalStatement = statementIndex === statements.length - 1;
4676
+ if (isHarmlessPromiseResolveAwait(statement, parameterNames, scopes)) continue;
4677
+ if (isNodeOfType(statement, "ExpressionStatement") && isPureParameterExpression(statement.expression, parameterNames)) continue;
4678
+ if (isCommutativeParameterMutation(statement, parameterNames)) return isTerminalStatement;
4679
+ if (!isNodeOfType(statement, "ReturnStatement") || !isTerminalStatement) return false;
4680
+ return !statement.argument || isPureParameterExpression(statement.argument, parameterNames) || isOrderIndependentPromiseResolveCall(statement.argument, parameterNames, scopes);
4681
+ }
4682
+ return true;
4683
+ };
4684
+ const getObjectPropertyName = (property) => {
4685
+ if (!isNodeOfType(property, "Property")) return null;
4686
+ return getStaticPropertyKeyName(property, { allowComputedString: true });
4687
+ };
4688
+ const resolveOrderIndependentObjectPropertyFunction = (objectExpression, propertyName, callExpression, scopes, visitedSymbolIds) => {
4689
+ const unwrappedObject = stripParenExpression(objectExpression);
4690
+ if (isNodeOfType(unwrappedObject, "Identifier")) {
4691
+ const symbol = scopes.symbolFor(unwrappedObject);
4692
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes) || hasPossibleStaticPropertyMutationOrEscape(unwrappedObject, propertyName, scopes)) return null;
4693
+ visitedSymbolIds.add(symbol.id);
4694
+ return resolveOrderIndependentObjectPropertyFunction(symbol.initializer, propertyName, callExpression, scopes, visitedSymbolIds);
4695
+ }
4696
+ if (!isNodeOfType(unwrappedObject, "ObjectExpression")) return null;
4697
+ let matchingProperty = null;
4698
+ for (const property of unwrappedObject.properties) {
4699
+ if (!isNodeOfType(property, "Property")) return null;
4700
+ if (property.kind !== "init") return null;
4701
+ const candidatePropertyName = getObjectPropertyName(property);
4702
+ if (candidatePropertyName === null) return null;
4703
+ if (candidatePropertyName === propertyName) matchingProperty = property;
4704
+ }
4705
+ if (!matchingProperty || !isNodeOfType(matchingProperty, "Property")) return null;
4706
+ const propertyValue = stripParenExpression(matchingProperty.value);
4707
+ if (isFunctionLike$2(propertyValue)) return isOrderIndependentFunction(propertyValue, scopes) ? propertyValue : null;
4708
+ return resolveOrderIndependentLocalFunction(propertyValue, callExpression, scopes, visitedSymbolIds);
4709
+ };
4710
+ const resolveOrderIndependentLocalFunction = (callee, callExpression, scopes, visitedSymbolIds) => {
4711
+ const unwrappedCallee = stripParenExpression(callee);
4712
+ if (isNodeOfType(unwrappedCallee, "MemberExpression")) {
4713
+ const propertyName = getStaticPropertyName(unwrappedCallee);
4714
+ if (propertyName === null) return null;
4715
+ return resolveOrderIndependentObjectPropertyFunction(stripParenExpression(unwrappedCallee.object), propertyName, callExpression, scopes, visitedSymbolIds);
4716
+ }
4717
+ if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
4718
+ const symbol = scopes.symbolFor(unwrappedCallee);
4719
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, callExpression, scopes)) return null;
4720
+ visitedSymbolIds.add(symbol.id);
4721
+ if (!symbol.initializer) return null;
4722
+ const initializer = stripParenExpression(symbol.initializer);
4723
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
4724
+ if (destructuredPropertyName !== null) return resolveOrderIndependentObjectPropertyFunction(initializer, destructuredPropertyName, callExpression, scopes, visitedSymbolIds);
4725
+ if (isFunctionLike$2(initializer)) return isOrderIndependentFunction(initializer, scopes) ? initializer : null;
4726
+ if (symbol.kind !== "const") return null;
4727
+ return resolveOrderIndependentLocalFunction(initializer, callExpression, scopes, visitedSymbolIds);
4728
+ };
4729
+ const getOrderIndependentLocalFunction = (callExpression, scopes) => {
4730
+ const unwrappedCall = stripParenExpression(callExpression);
4731
+ if (!isNodeOfType(unwrappedCall, "CallExpression")) return null;
4732
+ if (hasPossibleStaticMemberCallWrite(unwrappedCall, scopes)) return null;
4733
+ return resolveOrderIndependentLocalFunction(unwrappedCall.callee, unwrappedCall, scopes, /* @__PURE__ */ new Set());
4734
+ };
4735
+ //#endregion
4317
4736
  //#region src/plugin/utils/is-inline-function-expression.ts
4318
4737
  /**
4319
4738
  * Type-guard for the two "inline function expression" ESTree forms:
@@ -4347,13 +4766,15 @@ const isIntentionalSequencingCallee = (callee) => {
4347
4766
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(callee.property.name);
4348
4767
  return false;
4349
4768
  };
4350
- const isAwaitingSleepLikeCall = (awaitNode) => {
4769
+ const isAwaitingSleepLikeCall = (awaitNode, context) => {
4351
4770
  if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
4352
4771
  const argument = awaitNode.argument;
4353
4772
  if (!argument) return false;
4354
4773
  if (!isNodeOfType(argument, "CallExpression")) return false;
4774
+ if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) return false;
4355
4775
  return isIntentionalSequencingCallee(argument.callee);
4356
4776
  };
4777
+ const isAwaitingPossiblyMutatedMemberCall = (awaitNode, context) => isNodeOfType(awaitNode, "AwaitExpression") && Boolean(awaitNode.argument) && hasPossibleStaticMemberCallWrite(awaitNode.argument, context.scopes);
4357
4778
  const PROMISE_CONCURRENCY_METHODS = new Set([
4358
4779
  "all",
4359
4780
  "allSettled",
@@ -4396,7 +4817,7 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
4396
4817
  });
4397
4818
  return isWaitLike;
4398
4819
  };
4399
- const isIntentionallySequentialAwait = (awaitNode) => isAwaitingSleepLikeCall(awaitNode) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
4820
+ const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
4400
4821
  const collectPatternIdentifiers = (pattern, target) => {
4401
4822
  if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
4402
4823
  else if (isNodeOfType(pattern, "ObjectPattern")) {
@@ -4587,12 +5008,12 @@ const getLoopLabelName = (loopNode) => {
4587
5008
  if (isNodeOfType(parent, "LabeledStatement") && isNodeOfType(parent.label, "Identifier")) return parent.label.name;
4588
5009
  return null;
4589
5010
  };
4590
- const loopBodyHasIntentionallySequentialAwait = (block) => {
5011
+ const loopBodyHasIntentionallySequentialAwait = (block, context) => {
4591
5012
  let foundIntentional = false;
4592
5013
  walkAst(block, (child) => {
4593
5014
  if (foundIntentional) return false;
4594
5015
  if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
4595
- if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child)) {
5016
+ if (isNodeOfType(child, "AwaitExpression") && isIntentionallySequentialAwait(child, context)) {
4596
5017
  foundIntentional = true;
4597
5018
  return false;
4598
5019
  }
@@ -4697,7 +5118,7 @@ const asyncAwaitInLoop = defineRule({
4697
5118
  const inspectLoop = (loopNode, label) => {
4698
5119
  const loopBody = loopNode.body;
4699
5120
  if (!loopBody) return;
4700
- if (loopBodyHasIntentionallySequentialAwait(loopBody)) return;
5121
+ if (loopBodyHasIntentionallySequentialAwait(loopBody, context)) return;
4701
5122
  if ((isNodeOfType(loopNode, "WhileStatement") || isNodeOfType(loopNode, "DoWhileStatement")) && isLoopTestDependentOnBodyState(loopNode.test, loopBody)) return;
4702
5123
  if (hasLoopCarriedDependency(loopBody)) return;
4703
5124
  if (loopBodyHasAwaitDependentEarlyExit(loopBody, getLoopLabelName(loopNode))) return;
@@ -4957,7 +5378,7 @@ const guardConsequentPerformsSideEffects = (consequent) => {
4957
5378
  });
4958
5379
  return performsSideEffects;
4959
5380
  };
4960
- const findEnclosingFunction$1 = (node) => {
5381
+ const findEnclosingFunction = (node) => {
4961
5382
  let ancestor = node.parent;
4962
5383
  while (ancestor) {
4963
5384
  if (isFunctionLike$2(ancestor)) return ancestor;
@@ -4970,7 +5391,7 @@ const guardTestReadsReassignedLocal = (test, guardStatement) => {
4970
5391
  const testIdentifierNames = /* @__PURE__ */ new Set();
4971
5392
  collectReferenceIdentifierNames(test, testIdentifierNames);
4972
5393
  if (testIdentifierNames.size === 0) return false;
4973
- const enclosingFunction = findEnclosingFunction$1(guardStatement);
5394
+ const enclosingFunction = findEnclosingFunction(guardStatement);
4974
5395
  if (!enclosingFunction || !isFunctionLike$2(enclosingFunction) || !enclosingFunction.body) return false;
4975
5396
  let readsReassignedLocal = false;
4976
5397
  walkAst(enclosingFunction.body, (child) => {
@@ -5141,19 +5562,36 @@ const isIntentionalSequencingAwait = (awaitedCall) => {
5141
5562
  return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
5142
5563
  };
5143
5564
  const isBareExpressionAwait = (statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression");
5565
+ const hasOrderIndependentBareAwaitArguments = (callExpression) => {
5566
+ const unwrappedCallExpression = stripParenExpression(callExpression);
5567
+ if (!isNodeOfType(unwrappedCallExpression, "CallExpression")) return false;
5568
+ return unwrappedCallExpression.arguments.every((argument) => {
5569
+ if (isNodeOfType(argument, "SpreadElement")) return false;
5570
+ const unwrappedArgument = stripParenExpression(argument);
5571
+ return isNodeOfType(unwrappedArgument, "Identifier") || isNodeOfType(unwrappedArgument, "Literal");
5572
+ });
5573
+ };
5144
5574
  const isNonCallAwait = (statement) => {
5145
5575
  const awaitedExpression = getAwaitedCall(statement);
5146
5576
  if (!awaitedExpression) return false;
5147
5577
  const stripped = stripParenExpression(awaitedExpression);
5148
5578
  return !isNodeOfType(stripped, "CallExpression") && !isNodeOfType(stripped, "NewExpression") && !isNodeOfType(stripped, "ImportExpression");
5149
5579
  };
5150
- const sequenceContainsSerializationSignal = (statements) => {
5580
+ const sequenceContainsSerializationSignal = (statements, context) => {
5581
+ let bareAwaitFunction = null;
5151
5582
  for (const statement of statements) {
5152
- if (isBareExpressionAwait(statement)) return true;
5153
5583
  if (isNonCallAwait(statement)) return true;
5154
5584
  const awaitedCall = getAwaitedCall(statement);
5585
+ if (awaitedCall && hasPossibleStaticMemberCallWrite(awaitedCall, context.scopes)) return true;
5586
+ const orderIndependentFunction = awaitedCall ? getOrderIndependentLocalFunction(awaitedCall, context.scopes) : null;
5587
+ if (isBareExpressionAwait(statement)) {
5588
+ if (orderIndependentFunction === null) return true;
5589
+ if (!awaitedCall || !hasOrderIndependentBareAwaitArguments(awaitedCall)) return true;
5590
+ if (bareAwaitFunction !== null && bareAwaitFunction !== orderIndependentFunction) return true;
5591
+ bareAwaitFunction = orderIndependentFunction;
5592
+ }
5155
5593
  if (isOrderedUiFlowAwait(awaitedCall)) return true;
5156
- if (isIntentionalSequencingAwait(awaitedCall)) return true;
5594
+ if (isIntentionalSequencingAwait(awaitedCall) && orderIndependentFunction === null) return true;
5157
5595
  }
5158
5596
  return false;
5159
5597
  };
@@ -5211,7 +5649,7 @@ const asyncParallel = defineRule({
5211
5649
  const consecutiveAwaitStatements = [];
5212
5650
  const flushConsecutiveAwaits = () => {
5213
5651
  if (consecutiveAwaitStatements.length >= 3) {
5214
- if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
5652
+ if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements, context)) reportIfIndependent(consecutiveAwaitStatements, context);
5215
5653
  }
5216
5654
  consecutiveAwaitStatements.length = 0;
5217
5655
  };
@@ -5989,23 +6427,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5989
6427
  }
5990
6428
  });
5991
6429
  //#endregion
5992
- //#region src/plugin/utils/get-static-property-key-name.ts
5993
- const getStaticPropertyKeyName = (node, options = {}) => {
5994
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
5995
- const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
5996
- if (node.computed) {
5997
- if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
5998
- if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
5999
- return null;
6000
- }
6001
- if (isNodeOfType(key, "Identifier")) return key.name;
6002
- if (isNodeOfType(key, "Literal")) {
6003
- if (typeof key.value === "string") return key.value;
6004
- if (options.stringifyNonStringLiterals) return String(key.value);
6005
- }
6006
- return null;
6007
- };
6008
- //#endregion
6009
6430
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
6010
6431
  const areExpressionsStructurallyEqual = (a, b) => {
6011
6432
  if (!a || !b) return a === b;
@@ -6689,15 +7110,6 @@ const getDirectConstInitializer = (symbol) => {
6689
7110
  return symbol.initializer;
6690
7111
  };
6691
7112
  //#endregion
6692
- //#region src/plugin/utils/get-static-property-name.ts
6693
- const getStaticPropertyName = (memberExpression) => {
6694
- const property = memberExpression.property;
6695
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
6696
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
6697
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
6698
- return null;
6699
- };
6700
- //#endregion
6701
7113
  //#region src/plugin/utils/get-range-start.ts
6702
7114
  const getRangeStart = (node) => {
6703
7115
  const rangeStart = node.range?.[0];
@@ -7093,16 +7505,6 @@ const collectFunctionReturnStatements = (functionNode) => {
7093
7505
  return returnStatements;
7094
7506
  };
7095
7507
  //#endregion
7096
- //#region src/plugin/utils/find-enclosing-function.ts
7097
- const findEnclosingFunction = (node) => {
7098
- let cursor = node.parent;
7099
- while (cursor) {
7100
- if (isFunctionLike$2(cursor)) return cursor;
7101
- cursor = cursor.parent ?? null;
7102
- }
7103
- return null;
7104
- };
7105
- //#endregion
7106
7508
  //#region src/plugin/utils/statement-always-exits.ts
7107
7509
  const statementAlwaysExits = (statement) => {
7108
7510
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -7148,8 +7550,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
7148
7550
  const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
7149
7551
  if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
7150
7552
  if (!controlFlow) return [];
7151
- const referenceFunction = findEnclosingFunction(referenceNode);
7152
- if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
7553
+ const referenceFunction = findEnclosingFunction$1(referenceNode);
7554
+ if (findEnclosingFunction$1(symbol.bindingIdentifier) !== referenceFunction) return [];
7153
7555
  if (!referenceFunction) return [];
7154
7556
  const functionControlFlow = controlFlow.cfgFor(referenceFunction);
7155
7557
  if (!functionControlFlow) return [];
@@ -7173,7 +7575,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
7173
7575
  if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
7174
7576
  for (const reference of symbol.references) {
7175
7577
  const writePosition = getRangeStart(reference.identifier);
7176
- if (reference.flag === "read" || findEnclosingFunction(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
7578
+ if (reference.flag === "read" || findEnclosingFunction$1(reference.identifier) !== referenceFunction || writePosition === null || writePosition >= referencePosition) continue;
7177
7579
  const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
7178
7580
  if (!assignedExpression) continue;
7179
7581
  addDefinition(assignedExpression, reference.identifier, writePosition);
@@ -8845,6 +9247,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
8845
9247
  const LABEL_ELEMENT = "label";
8846
9248
  const LABEL_COMPONENT_NAME = "Label";
8847
9249
  const POLYMORPHIC_COMPONENT_PROP = "component";
9250
+ const TITLE_ATTRIBUTE = "title";
8848
9251
  const WRAPPER_LABEL_PROP = "label";
8849
9252
  const SELECT_ELEMENT = "select";
8850
9253
  const DEFAULT_DEPTH = 5;
@@ -8890,6 +9293,96 @@ const hasNonEmptyPropValue = (attribute) => {
8890
9293
  }
8891
9294
  return true;
8892
9295
  };
9296
+ const getLastJsxPropIgnoreCase = (attributes, targetProp) => {
9297
+ const targetPropLower = targetProp.toLowerCase();
9298
+ for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
9299
+ const attribute = attributes[attributeIndex];
9300
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) continue;
9301
+ if (getJsxAttributeName(attribute.name)?.toLowerCase() === targetPropLower) return attribute;
9302
+ }
9303
+ };
9304
+ const getStaticNativeTitleArrayValue = (expression, scopes) => {
9305
+ const elementValues = [];
9306
+ for (const rawElement of expression.elements) {
9307
+ if (rawElement === null) {
9308
+ elementValues.push("");
9309
+ continue;
9310
+ }
9311
+ if (isNodeOfType(rawElement, "SpreadElement")) return null;
9312
+ const element = stripParenExpression(rawElement);
9313
+ if (isNodeOfType(element, "Literal")) {
9314
+ elementValues.push(element.value === null ? "" : String(element.value));
9315
+ continue;
9316
+ }
9317
+ if (isNodeOfType(element, "TemplateLiteral")) {
9318
+ const staticValue = getStaticTemplateLiteralValue(element);
9319
+ if (staticValue === null) return null;
9320
+ elementValues.push(staticValue);
9321
+ continue;
9322
+ }
9323
+ if (isNodeOfType(element, "Identifier") && element.name === "undefined" && scopes.isGlobalReference(element) || isNodeOfType(element, "UnaryExpression") && element.operator === "void") {
9324
+ elementValues.push("");
9325
+ continue;
9326
+ }
9327
+ if (isNodeOfType(element, "ArrayExpression")) {
9328
+ const nestedValue = getStaticNativeTitleArrayValue(element, scopes);
9329
+ if (nestedValue === null) return null;
9330
+ elementValues.push(nestedValue);
9331
+ continue;
9332
+ }
9333
+ return null;
9334
+ }
9335
+ return elementValues.join(",");
9336
+ };
9337
+ const isGlobalSymbolExpression = (expression, scopes) => {
9338
+ if (isNodeOfType(expression, "Identifier")) return expression.name === "Symbol" && scopes.isGlobalReference(expression);
9339
+ if (!isNodeOfType(expression, "MemberExpression")) return false;
9340
+ const object = stripParenExpression(expression.object);
9341
+ return isNodeOfType(object, "Identifier") && object.name === "Symbol" && scopes.isGlobalReference(object);
9342
+ };
9343
+ const hasNonEmptyNativeTitleExpression = (rawExpression, scopes) => {
9344
+ const expression = stripParenExpression(rawExpression);
9345
+ if (isNodeOfType(expression, "Literal")) {
9346
+ if (typeof expression.value === "string") return expression.value.trim().length > 0;
9347
+ return expression.value !== null && typeof expression.value !== "boolean";
9348
+ }
9349
+ if (isNodeOfType(expression, "TemplateLiteral")) {
9350
+ const staticValue = getStaticTemplateLiteralValue(expression);
9351
+ return staticValue === null || staticValue.trim().length > 0;
9352
+ }
9353
+ if (isNodeOfType(expression, "Identifier")) return expression.name !== "undefined" || !scopes.isGlobalReference(expression);
9354
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return false;
9355
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return false;
9356
+ if (isNodeOfType(expression, "ArrowFunctionExpression")) return false;
9357
+ if (isNodeOfType(expression, "FunctionExpression")) return false;
9358
+ if (isNodeOfType(expression, "ClassExpression")) return false;
9359
+ if (isNodeOfType(expression, "ArrayExpression")) {
9360
+ const staticValue = getStaticNativeTitleArrayValue(expression, scopes);
9361
+ return staticValue === null || staticValue.trim().length > 0;
9362
+ }
9363
+ if (isGlobalSymbolExpression(expression, scopes)) return false;
9364
+ if (isNodeOfType(expression, "CallExpression") && isGlobalSymbolExpression(expression.callee, scopes)) return false;
9365
+ if (isNodeOfType(expression, "ConditionalExpression")) return hasNonEmptyNativeTitleExpression(expression.consequent, scopes) && hasNonEmptyNativeTitleExpression(expression.alternate, scopes);
9366
+ if (isNodeOfType(expression, "SequenceExpression")) {
9367
+ const finalExpression = expression.expressions.at(-1);
9368
+ return finalExpression ? hasNonEmptyNativeTitleExpression(finalExpression, scopes) : false;
9369
+ }
9370
+ if (isNodeOfType(expression, "LogicalExpression")) {
9371
+ const leftExpression = stripParenExpression(expression.left);
9372
+ if (!isNodeOfType(leftExpression, "Literal")) return expression.operator !== "&&";
9373
+ if (expression.operator === "??") return hasNonEmptyNativeTitleExpression(leftExpression.value === null ? expression.right : leftExpression, scopes);
9374
+ const leftValueIsTruthy = Boolean(leftExpression.value);
9375
+ if (expression.operator === "&&") return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? expression.right : leftExpression, scopes);
9376
+ return hasNonEmptyNativeTitleExpression(leftValueIsTruthy ? leftExpression : expression.right, scopes);
9377
+ }
9378
+ return true;
9379
+ };
9380
+ const hasNonEmptyNativeTitle = (attribute, scopes) => {
9381
+ if (!attribute?.value) return false;
9382
+ if (isNodeOfType(attribute.value, "Literal")) return typeof attribute.value.value === "string" && attribute.value.value.trim().length > 0;
9383
+ if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return hasNonEmptyNativeTitleExpression(attribute.value.expression, scopes);
9384
+ return true;
9385
+ };
8893
9386
  const toAttributeMatchKey = (kind, value) => {
8894
9387
  const trimmedValue = value.trim();
8895
9388
  return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
@@ -9146,6 +9639,7 @@ const controlHasAssociatedLabel = defineRule({
9146
9639
  if (typeValue === "submit" || typeValue === "reset") return;
9147
9640
  if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
9148
9641
  }
9642
+ if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
9149
9643
  if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
9150
9644
  if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
9151
9645
  if (isInsideJsxAttribute(node)) return;
@@ -11095,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
11095
11589
  //#endregion
11096
11590
  //#region src/plugin/utils/enclosing-component-or-hook-name.ts
11097
11591
  const enclosingComponentOrHookName = (node) => {
11098
- const functionNode = findEnclosingFunction(node);
11592
+ const functionNode = findEnclosingFunction$1(node);
11099
11593
  return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
11100
11594
  };
11101
11595
  //#endregion
@@ -11152,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
11152
11646
  //#endregion
11153
11647
  //#region src/plugin/utils/find-render-phase-component-or-hook.ts
11154
11648
  const findRenderPhaseComponentOrHook = (node, scopes) => {
11155
- let functionNode = findEnclosingFunction(node);
11649
+ let functionNode = findEnclosingFunction$1(node);
11156
11650
  while (functionNode) {
11157
11651
  if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
11158
11652
  if (!executesDuringRender(functionNode, scopes)) return null;
11159
- functionNode = findEnclosingFunction(functionNode);
11653
+ functionNode = findEnclosingFunction$1(functionNode);
11160
11654
  }
11161
11655
  return null;
11162
11656
  };
@@ -11405,13 +11899,13 @@ const findSubscribeLikeUsages = (callback, context) => {
11405
11899
  };
11406
11900
  const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
11407
11901
  let pathAnchor = usageNode;
11408
- let pathOwner = findEnclosingFunction(pathAnchor);
11902
+ let pathOwner = findEnclosingFunction$1(pathAnchor);
11409
11903
  while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
11410
11904
  if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
11411
11905
  const iteratorCall = pathOwner.parent;
11412
11906
  if (!isNodeOfType(iteratorCall, "CallExpression")) break;
11413
11907
  pathAnchor = iteratorCall;
11414
- pathOwner = findEnclosingFunction(pathAnchor);
11908
+ pathOwner = findEnclosingFunction$1(pathAnchor);
11415
11909
  }
11416
11910
  const owner = context.cfg.enclosingFunction(pathAnchor);
11417
11911
  if (!owner) return false;
@@ -11640,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
11640
12134
  return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
11641
12135
  };
11642
12136
  const findMappedResourceCollectionKey = (resourceNode, context) => {
11643
- const callbackNode = findEnclosingFunction(resourceNode);
12137
+ const callbackNode = findEnclosingFunction$1(resourceNode);
11644
12138
  if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
11645
12139
  const mappingCall = findCollectionMappingCall(callbackNode);
11646
12140
  if (!mappingCall) return null;
@@ -11751,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
11751
12245
  });
11752
12246
  if (invocationCalls.length !== 1) return null;
11753
12247
  const invocationCall = invocationCalls[0];
11754
- return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
12248
+ return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
11755
12249
  };
11756
12250
  const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
11757
- const usageFunction = findEnclosingFunction(usageNode);
12251
+ const usageFunction = findEnclosingFunction$1(usageNode);
11758
12252
  if (!usageFunction || usageFunction === effectCallback) return usageNode;
11759
12253
  return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
11760
12254
  };
@@ -11769,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
11769
12263
  const assignmentReference = assignmentReferences[0];
11770
12264
  const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
11771
12265
  const assignmentNode = assignmentTarget.parent;
11772
- if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
12266
+ if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction$1(assignmentNode) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
11773
12267
  const assignedValue = stripParenExpression(assignmentNode.right);
11774
12268
  return isFunctionLike$2(assignedValue) ? assignedValue : null;
11775
12269
  };
@@ -11858,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
11858
12352
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
11859
12353
  };
11860
12354
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11861
- const componentFunction = findEnclosingFunction(callback);
12355
+ const componentFunction = findEnclosingFunction$1(callback);
11862
12356
  if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
11863
12357
  let didFindUnmountCleanup = false;
11864
12358
  walkAst(componentFunction.body, (child) => {
11865
12359
  if (didFindUnmountCleanup) return false;
11866
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
12360
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
11867
12361
  if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
11868
12362
  const dependencyList = child.arguments?.[1];
11869
12363
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
@@ -11959,7 +12453,7 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
11959
12453
  return didWriteGuard;
11960
12454
  };
11961
12455
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
11962
- const usageFunction = findEnclosingFunction(usage.node);
12456
+ const usageFunction = findEnclosingFunction$1(usage.node);
11963
12457
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
11964
12458
  if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
11965
12459
  return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
@@ -11967,7 +12461,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
11967
12461
  const effectHasCleanupForUsage = (callback, usage, context) => {
11968
12462
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11969
12463
  if (callback.async) return false;
11970
- if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
12464
+ if (usage.kind === "subscribe" && findEnclosingFunction$1(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
11971
12465
  if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
11972
12466
  const matchingCleanupReturns = [];
11973
12467
  walkInsideStatementBlocks(callback.body, (child) => {
@@ -12136,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
12136
12630
  parentNode = currentNode.parent;
12137
12631
  }
12138
12632
  if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
12139
- const effectCallback = findEnclosingFunction(parentNode);
12633
+ const effectCallback = findEnclosingFunction$1(parentNode);
12140
12634
  const effectCall = effectCallback?.parent;
12141
12635
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
12142
12636
  };
@@ -12146,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
12146
12640
  if (!bindingIdentifier) return false;
12147
12641
  const symbol = context.scopes.symbolFor(bindingIdentifier);
12148
12642
  if (!symbol) return false;
12149
- return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
12643
+ return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
12150
12644
  };
12151
12645
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
12152
12646
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
12153
- const releaseFunction = findEnclosingFunction(releaseNode);
12647
+ const releaseFunction = findEnclosingFunction$1(releaseNode);
12154
12648
  if (!releaseFunction) return true;
12155
- if (releaseFunction === findEnclosingFunction(usage.node)) return true;
12649
+ if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
12156
12650
  return isPotentiallyReachableFunction(releaseFunction, context);
12157
12651
  };
12158
12652
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -12335,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
12335
12829
  }
12336
12830
  if (assignedKey !== storage.refCurrentKey) return;
12337
12831
  const assignedValue = stripParenExpression(child.right);
12338
- if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
12832
+ if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
12339
12833
  const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
12340
12834
  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);
12341
- if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
12835
+ if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
12342
12836
  hasUnsafeRefWrite = true;
12343
12837
  return false;
12344
12838
  }
@@ -12359,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
12359
12853
  return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
12360
12854
  };
12361
12855
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
12362
- const componentFunction = findEnclosingFunction(retainedFunction);
12856
+ const componentFunction = findEnclosingFunction$1(retainedFunction);
12363
12857
  if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
12364
12858
  let didFindCleanupEffect = false;
12365
12859
  walkAst(componentFunction.body, (child) => {
12366
12860
  if (didFindCleanupEffect) return false;
12367
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
12861
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
12368
12862
  const effectStatement = findTransparentExpressionRoot(child).parent;
12369
12863
  if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
12370
12864
  const effectCallback = getEffectCallback(child);
@@ -13218,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
13218
13712
  };
13219
13713
  };
13220
13714
  //#endregion
13715
+ //#region src/plugin/utils/is-ast-descendant.ts
13716
+ /**
13717
+ * True when `inner` is `outer` itself or any descendant in the AST
13718
+ * `parent` chain. Walks `inner.parent` upward and stops at either a
13719
+ * match (`true`) or the chain's root (`false`).
13720
+ *
13721
+ * Was duplicated byte-identical in:
13722
+ * - exhaustive-deps-symbol-stability.ts
13723
+ * - semantic/closure-captures.ts
13724
+ */
13725
+ const isAstDescendant = (inner, outer) => {
13726
+ let current = inner;
13727
+ while (current) {
13728
+ if (current === outer) return true;
13729
+ current = current.parent ?? null;
13730
+ }
13731
+ return false;
13732
+ };
13733
+ //#endregion
13734
+ //#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
13735
+ const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
13736
+ const getUseStateSetterSymbol = (stateSymbol, scopes) => {
13737
+ const declaration = stateSymbol.declarationNode;
13738
+ if (!isNodeOfType(declaration, "VariableDeclarator")) return null;
13739
+ if (!isNodeOfType(declaration.id, "ArrayPattern")) return null;
13740
+ const stateBinding = declaration.id.elements[0];
13741
+ const setterBinding = declaration.id.elements[1];
13742
+ if (stateBinding !== stateSymbol.bindingIdentifier || !isNodeOfType(setterBinding, "Identifier")) return null;
13743
+ const initializer = declaration.init ? stripParenExpression(declaration.init) : null;
13744
+ if (!initializer || !isReactApiCall(initializer, "useState", scopes, {
13745
+ allowGlobalReactNamespace: true,
13746
+ allowUnboundBareCalls: true,
13747
+ resolveNamedAliases: true
13748
+ })) return null;
13749
+ return scopes.symbolFor(setterBinding);
13750
+ };
13751
+ const getDirectSetterCall = (setterSymbol, callback) => {
13752
+ if (setterSymbol.references.length !== 1) return null;
13753
+ const setterReference = setterSymbol.references[0];
13754
+ if (setterReference.flag !== "read") return null;
13755
+ const referenceRoot = findTransparentExpressionRoot(setterReference.identifier);
13756
+ const callExpression = referenceRoot.parent;
13757
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot || callExpression.arguments.length !== 1 || findEnclosingFunction$1(callExpression) !== callback) return null;
13758
+ const writtenValue = stripParenExpression(callExpression.arguments[0]);
13759
+ if (isNodeOfType(writtenValue, "ArrowFunctionExpression") || isNodeOfType(writtenValue, "FunctionExpression") || isNodeOfType(writtenValue, "SpreadElement")) return null;
13760
+ return {
13761
+ callExpression,
13762
+ writtenValue
13763
+ };
13764
+ };
13765
+ const isGlobalObjectIsCall = (callExpression, scopes) => {
13766
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
13767
+ const callee = stripParenExpression(callExpression.callee);
13768
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
13769
+ const propertyName = getStaticPropertyName(callee);
13770
+ const receiver = stripParenExpression(callee.object);
13771
+ return Boolean(propertyName === "is" && isNodeOfType(receiver, "Identifier") && receiver.name === "Object" && scopes.isGlobalReference(receiver));
13772
+ };
13773
+ const isGlobalNaNReference = (expression, scopes) => {
13774
+ const unwrappedExpression = stripParenExpression(expression);
13775
+ return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression);
13776
+ };
13777
+ const getEqualityComparison = (stateReference, candidate, scopes) => {
13778
+ const unwrappedStateReference = stripParenExpression(stateReference);
13779
+ if (isNodeOfType(candidate, "BinaryExpression") && EQUALITY_BINARY_OPERATORS.has(candidate.operator)) {
13780
+ if (stripParenExpression(candidate.left) === unwrappedStateReference) return isGlobalNaNReference(candidate.right, scopes) ? null : {
13781
+ comparison: candidate,
13782
+ counterpart: candidate.right,
13783
+ areValuesEqualWhenTruthy: candidate.operator === "==="
13784
+ };
13785
+ if (stripParenExpression(candidate.right) === unwrappedStateReference) return isGlobalNaNReference(candidate.left, scopes) ? null : {
13786
+ comparison: candidate,
13787
+ counterpart: candidate.left,
13788
+ areValuesEqualWhenTruthy: candidate.operator === "==="
13789
+ };
13790
+ return null;
13791
+ }
13792
+ if (!isNodeOfType(candidate, "CallExpression") || !isGlobalObjectIsCall(candidate, scopes) || candidate.arguments.length !== 2) return null;
13793
+ const firstArgument = candidate.arguments[0];
13794
+ const secondArgument = candidate.arguments[1];
13795
+ if (isNodeOfType(firstArgument, "SpreadElement") || isNodeOfType(secondArgument, "SpreadElement")) return null;
13796
+ if (stripParenExpression(firstArgument) === unwrappedStateReference) return {
13797
+ comparison: candidate,
13798
+ counterpart: secondArgument,
13799
+ areValuesEqualWhenTruthy: true
13800
+ };
13801
+ if (stripParenExpression(secondArgument) === unwrappedStateReference) return {
13802
+ comparison: candidate,
13803
+ counterpart: firstArgument,
13804
+ areValuesEqualWhenTruthy: true
13805
+ };
13806
+ return null;
13807
+ };
13808
+ const findEqualityComparison = (stateReference, test, scopes) => {
13809
+ let current = stateReference.parent;
13810
+ while (current && isAstDescendant(current, test)) {
13811
+ const comparison = getEqualityComparison(stateReference, current, scopes);
13812
+ if (comparison) return comparison;
13813
+ if (current === test) break;
13814
+ current = current.parent;
13815
+ }
13816
+ return null;
13817
+ };
13818
+ const doesTestOutcomeRequireComparisonOutcome = (comparison, test, testOutcome, comparisonOutcome) => {
13819
+ let requiredChildOutcome = testOutcome;
13820
+ let current = comparison;
13821
+ while (current !== test) {
13822
+ const parent = current.parent;
13823
+ if (!parent || !isAstDescendant(parent, test)) return false;
13824
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") requiredChildOutcome = !requiredChildOutcome;
13825
+ else if (isNodeOfType(parent, "LogicalExpression")) {
13826
+ if (parent.operator === "&&" && !requiredChildOutcome) return false;
13827
+ if (parent.operator === "||" && requiredChildOutcome) return false;
13828
+ if (parent.operator !== "&&" && parent.operator !== "||") return false;
13829
+ } else if (!TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type)) return false;
13830
+ current = parent;
13831
+ }
13832
+ return requiredChildOutcome === comparisonOutcome;
13833
+ };
13834
+ const resolveImmutableAliasExpression = (expression, scopes) => {
13835
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
13836
+ let current = stripParenExpression(expression);
13837
+ while (isNodeOfType(current, "Identifier")) {
13838
+ const symbol = scopes.symbolFor(current);
13839
+ if (!symbol || visitedSymbolIds.has(symbol.id)) break;
13840
+ const initializer = getDirectConstInitializer(symbol);
13841
+ if (!initializer || !isNodeOfType(stripParenExpression(initializer), "Identifier")) break;
13842
+ visitedSymbolIds.add(symbol.id);
13843
+ current = stripParenExpression(initializer);
13844
+ }
13845
+ return current;
13846
+ };
13847
+ const referencesSameValue = (leftExpression, rightExpression, scopes) => {
13848
+ const left = resolveImmutableAliasExpression(leftExpression, scopes);
13849
+ const right = resolveImmutableAliasExpression(rightExpression, scopes);
13850
+ if (left === right) return true;
13851
+ if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
13852
+ const leftSymbol = scopes.symbolFor(left);
13853
+ const rightSymbol = scopes.symbolFor(right);
13854
+ if (leftSymbol || rightSymbol) return leftSymbol?.id === rightSymbol?.id;
13855
+ return left.name === right.name;
13856
+ }
13857
+ if (isNodeOfType(left, "Literal") && isNodeOfType(right, "Literal")) return Object.is(left.value, right.value);
13858
+ return false;
13859
+ };
13860
+ const hasDeclaredTriggerDependency = (callback) => {
13861
+ const hookCall = findTransparentExpressionRoot(callback).parent;
13862
+ if (!isNodeOfType(hookCall, "CallExpression")) return false;
13863
+ const dependencyArray = hookCall.arguments[1];
13864
+ return Boolean(isNodeOfType(dependencyArray, "ArrayExpression") && dependencyArray.elements.length);
13865
+ };
13866
+ const doesBranchExitEffect = (branch) => {
13867
+ if (isNodeOfType(branch, "ReturnStatement") || isNodeOfType(branch, "ThrowStatement")) return true;
13868
+ if (!isNodeOfType(branch, "BlockStatement")) return false;
13869
+ const terminalStatement = branch.body.at(-1);
13870
+ return Boolean(terminalStatement && doesBranchExitEffect(terminalStatement));
13871
+ };
13872
+ const doesGuardDominateLaterSetter = (guard, setterCall) => {
13873
+ if (!isNodeOfType(guard, "IfStatement") || guard.alternate) return false;
13874
+ if (!doesBranchExitEffect(guard.consequent)) return false;
13875
+ const block = guard.parent;
13876
+ if (!isNodeOfType(block, "BlockStatement")) return false;
13877
+ const guardIndex = block.body.findIndex((statement) => statement === guard);
13878
+ return block.body.some((statement, statementIndex) => statementIndex > guardIndex && isAstDescendant(setterCall, statement));
13879
+ };
13880
+ const findDominatingGuardCounterpart = (stateReference, setterCall, callback, scopes) => {
13881
+ let current = stateReference.parent;
13882
+ while (current && current !== callback) {
13883
+ if (isNodeOfType(current, "IfStatement") && isAstDescendant(stateReference, current.test) && (isAstDescendant(setterCall, current.consequent) || Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) {
13884
+ const setterRunsWhenTestTruthy = isAstDescendant(setterCall, current.consequent);
13885
+ if (setterRunsWhenTestTruthy === (Boolean(current.alternate && isAstDescendant(setterCall, current.alternate)) || doesGuardDominateLaterSetter(current, setterCall))) return null;
13886
+ const equalityComparison = findEqualityComparison(stateReference, current.test, scopes);
13887
+ if (!equalityComparison) return null;
13888
+ const comparisonOutcomeForDifferentValues = !equalityComparison.areValuesEqualWhenTruthy;
13889
+ if (!doesTestOutcomeRequireComparisonOutcome(equalityComparison.comparison, current.test, setterRunsWhenTestTruthy, comparisonOutcomeForDifferentValues)) return null;
13890
+ return equalityComparison.counterpart;
13891
+ }
13892
+ current = current.parent;
13893
+ }
13894
+ return null;
13895
+ };
13896
+ const isSoleWriterEffectGuardCapture = (stateSymbol, callback, scopes) => {
13897
+ if (!hasDeclaredTriggerDependency(callback)) return false;
13898
+ if (stateSymbol.references.some((reference) => reference.flag !== "read")) return false;
13899
+ const setterSymbol = getUseStateSetterSymbol(stateSymbol, scopes);
13900
+ if (!setterSymbol) return false;
13901
+ const setterWrite = getDirectSetterCall(setterSymbol, callback);
13902
+ if (!setterWrite) return false;
13903
+ const callbackStateReferences = stateSymbol.references.filter((reference) => isAstDescendant(reference.identifier, callback));
13904
+ if (callbackStateReferences.length !== 1) return false;
13905
+ const stateReferenceRoot = findTransparentExpressionRoot(callbackStateReferences[0].identifier);
13906
+ if (isNodeOfType(stateReferenceRoot.parent, "MemberExpression") && stateReferenceRoot.parent.object === stateReferenceRoot) return false;
13907
+ const counterpart = findDominatingGuardCounterpart(callbackStateReferences[0].identifier, setterWrite.callExpression, callback, scopes);
13908
+ return Boolean(counterpart && referencesSameValue(counterpart, setterWrite.writtenValue, scopes));
13909
+ };
13910
+ //#endregion
13221
13911
  //#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
13222
13912
  const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
13223
13913
  const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
@@ -13289,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
13289
13979
  return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
13290
13980
  };
13291
13981
  //#endregion
13292
- //#region src/plugin/utils/is-ast-descendant.ts
13293
- /**
13294
- * True when `inner` is `outer` itself or any descendant in the AST
13295
- * `parent` chain. Walks `inner.parent` upward and stops at either a
13296
- * match (`true`) or the chain's root (`false`).
13297
- *
13298
- * Was duplicated byte-identical in:
13299
- * - exhaustive-deps-symbol-stability.ts
13300
- * - semantic/closure-captures.ts
13301
- */
13302
- const isAstDescendant = (inner, outer) => {
13303
- let current = inner;
13304
- while (current) {
13305
- if (current === outer) return true;
13306
- current = current.parent ?? null;
13307
- }
13308
- return false;
13309
- };
13310
- //#endregion
13311
13982
  //#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
13312
13983
  /**
13313
13984
  * Symbol-stability helpers consumed by the `exhaustive-deps` rule.
@@ -13488,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
13488
14159
  "useLayoutEffect",
13489
14160
  "useInsertionEffect"
13490
14161
  ]);
14162
+ const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
13491
14163
  const buildAdditionalHooksRegex = (additional) => {
13492
14164
  if (!additional) return null;
13493
14165
  try {
@@ -13624,7 +14296,7 @@ const stringifyMemberChain = (node) => {
13624
14296
  }
13625
14297
  return null;
13626
14298
  };
13627
- const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
14299
+ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
13628
14300
  const keys = /* @__PURE__ */ new Set();
13629
14301
  const stableCapturedNames = /* @__PURE__ */ new Set();
13630
14302
  const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
@@ -13635,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
13635
14307
  const symbol = reference.resolvedSymbol;
13636
14308
  if (!symbol) continue;
13637
14309
  if (isRecursiveInitializerCapture(symbol, callback)) continue;
14310
+ if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
14311
+ stableCapturedNames.add(symbol.name);
14312
+ continue;
14313
+ }
13638
14314
  if (symbolHasStableValue(symbol, scopes)) {
13639
14315
  stableCapturedNames.add(symbol.name);
13640
14316
  continue;
@@ -14307,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
14307
14983
  if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
14308
14984
  declaredKeyToReportNode.set(key, elementNode);
14309
14985
  }
14310
- const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys);
14986
+ const { keys: captureKeys, stableCapturedNames, moduleScopeCapturedNames, outerFunctionCapturedNames } = collectCaptureDepKeys(callbackToAnalyze ?? callbackArgument, context.scopes, declaredExactBindingKeys, SOLE_WRITER_GUARD_HOOKS.has(hookName));
14311
14987
  for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
14312
14988
  addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
14313
14989
  const missingCaptureKeys = [];
@@ -16121,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
16121
16797
  if (didFindRenderPathInvocation) return false;
16122
16798
  if (!isNodeOfType(node, "CallExpression")) return;
16123
16799
  if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
16124
- let enclosingFunction = findEnclosingFunction(node);
16800
+ let enclosingFunction = findEnclosingFunction$1(node);
16125
16801
  while (enclosingFunction) {
16126
16802
  if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
16127
16803
  if (containingFunctionIsComponentOrHook(enclosingFunction)) {
16128
16804
  didFindRenderPathInvocation = true;
16129
16805
  return;
16130
16806
  }
16131
- enclosingFunction = findEnclosingFunction(enclosingFunction);
16807
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
16132
16808
  }
16133
16809
  });
16134
16810
  return didFindRenderPathInvocation;
@@ -16163,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
16163
16839
  };
16164
16840
  return { CallExpression(node) {
16165
16841
  if (!isImportedSelectAtom(node)) return;
16166
- const nearestFunctionLike = findEnclosingFunction(node);
16842
+ const nearestFunctionLike = findEnclosingFunction$1(node);
16167
16843
  if (!nearestFunctionLike) return;
16168
16844
  if (isDeferredCallback(nearestFunctionLike)) return;
16169
16845
  if (isBindingUsedAsHandler(nearestFunctionLike)) return;
@@ -17160,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
17160
17836
  return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
17161
17837
  };
17162
17838
  const isInsideCacheMemo = (node) => {
17163
- const enclosingFunction = findEnclosingFunction(node);
17839
+ const enclosingFunction = findEnclosingFunction$1(node);
17164
17840
  let child = node;
17165
17841
  let cursor = node.parent ?? null;
17166
17842
  while (cursor) {
@@ -17198,7 +17874,7 @@ const getFunctionName = (functionNode) => {
17198
17874
  return null;
17199
17875
  };
17200
17876
  const isUncacheableOptionsMergeUtility = (node) => {
17201
- const enclosingFunction = findEnclosingFunction(node);
17877
+ const enclosingFunction = findEnclosingFunction$1(node);
17202
17878
  if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
17203
17879
  const functionName = getFunctionName(enclosingFunction);
17204
17880
  if (!functionName || isComponentOrHookName(functionName)) return false;
@@ -17389,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
17389
18065
  const callee = stripParenExpression(replaceAllCall.callee);
17390
18066
  return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
17391
18067
  };
17392
- const getDestructuredBindingPropertyName = (bindingIdentifier) => {
17393
- let bindingNode = bindingIdentifier;
17394
- if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
17395
- const property = bindingNode.parent;
17396
- if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
17397
- return getStaticPropertyKeyName(property, { allowComputedString: true });
17398
- };
17399
18068
  const extendGlobalPath = (basePath, propertyName) => {
17400
18069
  if (basePath === "global") {
17401
18070
  if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
@@ -17685,7 +18354,89 @@ const collectEarlierAndGuardOperands = (node) => {
17685
18354
  return earlierOperands;
17686
18355
  };
17687
18356
  //#endregion
18357
+ //#region src/plugin/utils/unwrap-object-integrity-expression.ts
18358
+ const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
18359
+ "freeze",
18360
+ "seal",
18361
+ "preventExtensions"
18362
+ ]);
18363
+ const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
18364
+ const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
18365
+ const expression = stripParenExpression(node);
18366
+ if (!isNodeOfType(expression, "CallExpression")) return null;
18367
+ const callee = stripParenExpression(expression.callee);
18368
+ const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
18369
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
18370
+ return callee.property.name;
18371
+ };
18372
+ const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
18373
+ let expression = stripParenExpression(node);
18374
+ while (isNodeOfType(expression, "CallExpression")) {
18375
+ if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
18376
+ const wrappedExpression = expression.arguments[0];
18377
+ if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
18378
+ expression = stripParenExpression(wrappedExpression);
18379
+ }
18380
+ return expression;
18381
+ };
18382
+ //#endregion
17688
18383
  //#region src/plugin/rules/js-performance/js-length-check-first.ts
18384
+ const OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES = new Set(["keys", "values"]);
18385
+ const getGlobalObjectProjection = (expression, context) => {
18386
+ const callExpression = stripParenExpression(expression);
18387
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
18388
+ const callee = stripParenExpression(callExpression.callee);
18389
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
18390
+ const receiver = stripParenExpression(callee.object);
18391
+ if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !context.scopes.isGlobalReference(receiver)) return null;
18392
+ const methodName = getStaticPropertyName(callee);
18393
+ if (!methodName || !OBJECT_CARDINALITY_PROJECTION_METHOD_NAMES.has(methodName)) return null;
18394
+ const callArguments = callExpression.arguments ?? [];
18395
+ if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
18396
+ return {
18397
+ methodName,
18398
+ source: callArguments[0]
18399
+ };
18400
+ };
18401
+ const isPlainDataObjectLiteral = (expression) => {
18402
+ const objectExpression = stripParenExpression(expression);
18403
+ if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
18404
+ return (objectExpression.properties ?? []).every((property) => {
18405
+ if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
18406
+ const propertyValue = stripParenExpression(property.value);
18407
+ return isNodeOfType(propertyValue, "Literal") || isNodeOfType(propertyValue, "TemplateLiteral") && propertyValue.expressions.length === 0;
18408
+ });
18409
+ };
18410
+ const getGlobalObjectFreezeArgument = (expression, context) => {
18411
+ const callExpression = stripParenExpression(expression);
18412
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
18413
+ if (getObjectIntegrityMethodName(callExpression, context.scopes) !== "freeze") return null;
18414
+ const callArguments = callExpression.arguments ?? [];
18415
+ if (callArguments.length !== 1 || isNodeOfType(callArguments[0], "SpreadElement")) return null;
18416
+ return callArguments[0];
18417
+ };
18418
+ const resolveStablePlainObjectRootId = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
18419
+ const source = stripParenExpression(expression);
18420
+ if (!isNodeOfType(source, "Identifier")) return null;
18421
+ const symbol = context.scopes.symbolFor(source);
18422
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
18423
+ const directConstInitializer = getDirectConstInitializer(symbol);
18424
+ if (!directConstInitializer) return null;
18425
+ visitedSymbolIds.add(symbol.id);
18426
+ const initializer = stripParenExpression(directConstInitializer);
18427
+ const frozenValue = getGlobalObjectFreezeArgument(initializer, context);
18428
+ if (frozenValue) return isPlainDataObjectLiteral(frozenValue) ? symbol.id : null;
18429
+ return resolveStablePlainObjectRootId(initializer, context, visitedSymbolIds);
18430
+ };
18431
+ const areEqualCardinalityObjectProjections = (receiverArray, indexedArray, context) => {
18432
+ const receiverProjection = getGlobalObjectProjection(receiverArray, context);
18433
+ const indexedProjection = getGlobalObjectProjection(indexedArray, context);
18434
+ if (!receiverProjection || !indexedProjection) return false;
18435
+ if (receiverProjection.methodName === indexedProjection.methodName) return false;
18436
+ const receiverRootId = resolveStablePlainObjectRootId(receiverProjection.source, context);
18437
+ const indexedRootId = resolveStablePlainObjectRootId(indexedProjection.source, context);
18438
+ return receiverRootId !== null && receiverRootId === indexedRootId;
18439
+ };
17689
18440
  const findIndexedArrayObject = (callbackBody, indexParameterName) => {
17690
18441
  let indexedArrayObject = null;
17691
18442
  walkAst(callbackBody, (child) => {
@@ -17900,6 +18651,7 @@ const jsLengthCheckFirst = defineRule({
17900
18651
  const resolvedReceiverSource = resolveComparedArraySource(receiverArrayObject, node);
17901
18652
  const resolvedIndexedSource = resolveComparedArraySource(indexedArrayObject, node);
17902
18653
  if (areExpressionsStructurallyEqual(resolvedReceiverSource, resolvedIndexedSource)) return;
18654
+ if (areEqualCardinalityObjectProjections(receiverArrayObject, indexedArrayObject, context) || areEqualCardinalityObjectProjections(resolvedReceiverSource, resolvedIndexedSource, context)) return;
17903
18655
  const comparedArrayPairs = [[receiverArrayObject, indexedArrayObject], [resolvedReceiverSource, resolvedIndexedSource]];
17904
18656
  if (collectEarlierAndGuardOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_ANY_COMPARISON_OPERATORS)))) return;
17905
18657
  if (collectEarlierOrOperands(node).some((guardOperand) => comparedArrayPairs.some(([receiverSide, indexedSide]) => isLengthComparison(guardOperand, receiverSide, indexedSide, LENGTH_MISMATCH_OPERATORS)))) return;
@@ -23777,32 +24529,6 @@ const hasDirective = (programNode, directive) => {
23777
24529
  return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
23778
24530
  };
23779
24531
  //#endregion
23780
- //#region src/plugin/utils/unwrap-object-integrity-expression.ts
23781
- const OBJECT_INTEGRITY_METHOD_NAMES = new Set([
23782
- "freeze",
23783
- "seal",
23784
- "preventExtensions"
23785
- ]);
23786
- const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
23787
- const getObjectIntegrityMethodName = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
23788
- const expression = stripParenExpression(node);
23789
- if (!isNodeOfType(expression, "CallExpression")) return null;
23790
- const callee = stripParenExpression(expression.callee);
23791
- const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : callee;
23792
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || !scopes.isGlobalReference(receiver) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) return null;
23793
- return callee.property.name;
23794
- };
23795
- const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
23796
- let expression = stripParenExpression(node);
23797
- while (isNodeOfType(expression, "CallExpression")) {
23798
- if (!getObjectIntegrityMethodName(expression, scopes, methodNames)) break;
23799
- const wrappedExpression = expression.arguments[0];
23800
- if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
23801
- expression = stripParenExpression(wrappedExpression);
23802
- }
23803
- return expression;
23804
- };
23805
- //#endregion
23806
24532
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
23807
24533
  const nextjsAsyncClientComponent = defineRule({
23808
24534
  id: "nextjs-async-client-component",
@@ -26142,7 +26868,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
26142
26868
  };
26143
26869
  const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
26144
26870
  if (!setterRef.resolved) return false;
26145
- const componentFunction = findEnclosingFunction(effectNode);
26871
+ const componentFunction = findEnclosingFunction$1(effectNode);
26146
26872
  if (!componentFunction) return false;
26147
26873
  for (const reference of setterRef.resolved.references) {
26148
26874
  if (reference.init) continue;
@@ -28915,138 +29641,6 @@ const noBarrelImport = defineRule({
28915
29641
  }
28916
29642
  });
28917
29643
  //#endregion
28918
- //#region src/plugin/utils/has-static-property-write-before.ts
28919
- const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
28920
- const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
28921
- "CatchClause",
28922
- "ConditionalExpression",
28923
- "DoWhileStatement",
28924
- "ForInStatement",
28925
- "ForOfStatement",
28926
- "ForStatement",
28927
- "IfStatement",
28928
- "LogicalExpression",
28929
- "SwitchCase",
28930
- "SwitchStatement",
28931
- "TryStatement",
28932
- "WhileStatement"
28933
- ]);
28934
- const getResolvedStaticPropertyName = (memberExpression, scopes) => {
28935
- if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
28936
- const directPropertyName = getStaticPropertyName(memberExpression);
28937
- if (directPropertyName || !memberExpression.computed) return directPropertyName;
28938
- const property = stripParenExpression(memberExpression.property);
28939
- if (!isNodeOfType(property, "Identifier")) return null;
28940
- const propertySymbol = resolveConstIdentifierAlias(property, scopes);
28941
- const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
28942
- return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
28943
- };
28944
- const collectScopeSymbols = (scope, symbols) => {
28945
- symbols.push(...scope.symbols);
28946
- for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
28947
- };
28948
- const getEquivalentSymbols = (identifier, scopes) => {
28949
- const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
28950
- if (!rootSymbol) return [];
28951
- let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
28952
- if (!symbolsByRootId) {
28953
- symbolsByRootId = /* @__PURE__ */ new Map();
28954
- equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
28955
- }
28956
- const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
28957
- if (cachedSymbols) return cachedSymbols;
28958
- const allSymbols = [];
28959
- collectScopeSymbols(scopes.rootScope, allSymbols);
28960
- const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
28961
- symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
28962
- return equivalentSymbols;
28963
- };
28964
- const findExecutionBoundary = (node) => {
28965
- let current = node;
28966
- while (current) {
28967
- if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
28968
- current = current.parent ?? null;
28969
- }
28970
- return null;
28971
- };
28972
- const isOnUnconditionalPath = (node, boundary) => {
28973
- let current = node.parent ?? null;
28974
- while (current && current !== boundary) {
28975
- if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
28976
- current = current.parent ?? null;
28977
- }
28978
- return current === boundary;
28979
- };
28980
- const findFunctionBindingIdentifier = (functionNode) => {
28981
- if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
28982
- const parent = functionNode.parent;
28983
- if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
28984
- return null;
28985
- };
28986
- const findDirectCall = (identifier) => {
28987
- let callee = identifier;
28988
- let parent = callee.parent;
28989
- while (parent && stripParenExpression(parent) === identifier) {
28990
- callee = parent;
28991
- parent = callee.parent;
28992
- }
28993
- return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
28994
- };
28995
- const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
28996
- if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
28997
- visitedFunctionNodes.add(functionNode);
28998
- const referenceBoundary = findExecutionBoundary(referenceNode);
28999
- if (!referenceBoundary) return false;
29000
- const invocationCalls = [];
29001
- const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
29002
- if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
29003
- const call = findDirectCall(reference.identifier);
29004
- if (call) invocationCalls.push(call);
29005
- }
29006
- else {
29007
- const call = findDirectCall(functionNode);
29008
- if (call) invocationCalls.push(call);
29009
- }
29010
- return invocationCalls.some((call) => {
29011
- if (call.range[0] >= referenceNode.range[0]) return false;
29012
- const callBoundary = findExecutionBoundary(call);
29013
- if (!callBoundary) return false;
29014
- if (callBoundary === referenceBoundary) return true;
29015
- if (!isFunctionLike$2(callBoundary)) return false;
29016
- return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
29017
- });
29018
- };
29019
- const isMemberWriteTarget = (memberExpression) => {
29020
- const parent = memberExpression.parent;
29021
- if (!parent) return false;
29022
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
29023
- if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
29024
- return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
29025
- };
29026
- const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
29027
- let parent = reference.identifier.parent;
29028
- while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
29029
- if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
29030
- const writeBoundary = findExecutionBoundary(parent);
29031
- const referenceBoundary = findExecutionBoundary(referenceNode);
29032
- if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
29033
- if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
29034
- return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
29035
- });
29036
- const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
29037
- if (!isNodeOfType(identifier, "Identifier")) return false;
29038
- return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
29039
- };
29040
- //#endregion
29041
- //#region src/plugin/utils/has-symbol-write-before.ts
29042
- const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
29043
- if (reference.flag === "read") return false;
29044
- const writeFunction = findEnclosingFunction(reference.identifier);
29045
- if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
29046
- if (!writeFunction) return true;
29047
- return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
29048
- });
29049
- //#endregion
29050
29644
  //#region src/plugin/utils/has-stable-call-target.ts
29051
29645
  const hasStableCallTarget = (callExpression, scopes) => {
29052
29646
  if (!isNodeOfType(callExpression, "CallExpression")) return false;
@@ -29515,8 +30109,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
29515
30109
  return isReactFunctionCall(parent, "useMemo");
29516
30110
  };
29517
30111
  const findEnclosingRenderFunction = (node) => {
29518
- let enclosingFunction = findEnclosingFunction(node);
29519
- while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
30112
+ let enclosingFunction = findEnclosingFunction$1(node);
30113
+ while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
29520
30114
  return enclosingFunction;
29521
30115
  };
29522
30116
  const noCreateRefInFunctionComponent = defineRule({
@@ -30567,7 +31161,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
30567
31161
  const isEffectDrivenResync = (useStateCall) => {
30568
31162
  const setterName = getStateSetterName(useStateCall);
30569
31163
  if (!setterName) return false;
30570
- const componentFunction = findEnclosingFunction(useStateCall);
31164
+ const componentFunction = findEnclosingFunction$1(useStateCall);
30571
31165
  if (!componentFunction) return false;
30572
31166
  let isExempt = false;
30573
31167
  walkAst(componentFunction, (child) => {
@@ -30671,7 +31265,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
30671
31265
  const setterName = getStateSetterName(useStateCall);
30672
31266
  if (!setterName) return false;
30673
31267
  const stateValueName = getStateValueName(useStateCall);
30674
- const componentFunction = findEnclosingFunction(useStateCall);
31268
+ const componentFunction = findEnclosingFunction$1(useStateCall);
30675
31269
  if (!componentFunction) return false;
30676
31270
  let isExempt = false;
30677
31271
  walkAst(componentFunction, (child) => {
@@ -30717,7 +31311,7 @@ const noDerivedUseState = defineRule({
30717
31311
  if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
30718
31312
  if (isEffectDrivenResync(node)) return;
30719
31313
  if (isNextjsDataFetchingPage(node)) return;
30720
- const componentFunction = findEnclosingFunction(node);
31314
+ const componentFunction = findEnclosingFunction$1(node);
30721
31315
  if (componentFunction) {
30722
31316
  if (isDefaultedDestructuredProp(componentFunction, propName)) return;
30723
31317
  if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
@@ -32192,6 +32786,29 @@ const isReturnOnlyStatement = (node) => {
32192
32786
  return isNodeOfType(node, "BlockStatement") && (node.body?.length ?? 0) === 1 && isNodeOfType(node.body?.[0], "ReturnStatement");
32193
32787
  };
32194
32788
  const hasEventLikeRemainingStatements = (statements) => statements.some((statement) => !isNodeOfType(statement, "ReturnStatement") && hasEventLikeNode(statement));
32789
+ const collectLeadingEarlyReturnGuards = (statements) => {
32790
+ const guardExpressions = [];
32791
+ for (const statement of statements) {
32792
+ if (isNodeOfType(statement, "VariableDeclaration")) continue;
32793
+ if (!isNodeOfType(statement, "IfStatement") || statement.alternate || !isReturnOnlyStatement(statement.consequent)) break;
32794
+ collectGuardExpressions(statement.test, guardExpressions);
32795
+ }
32796
+ return guardExpressions;
32797
+ };
32798
+ const collectImmutableExpressionOrigins = (expression, scopes) => {
32799
+ const origins = [];
32800
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
32801
+ let currentExpression = stripParenExpression(expression);
32802
+ while (currentExpression) {
32803
+ origins.push(currentExpression);
32804
+ if (!isNodeOfType(currentExpression, "Identifier")) break;
32805
+ const bindingSymbol = scopes.symbolFor(currentExpression);
32806
+ if (!bindingSymbol || bindingSymbol.kind !== "const" || visitedSymbolIds.has(bindingSymbol.id) || !bindingSymbol.initializer) break;
32807
+ visitedSymbolIds.add(bindingSymbol.id);
32808
+ currentExpression = stripParenExpression(bindingSymbol.initializer);
32809
+ }
32810
+ return origins;
32811
+ };
32195
32812
  const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
32196
32813
  const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
32197
32814
  if (!unwrappedDependencyExpression) return false;
@@ -32199,6 +32816,69 @@ const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
32199
32816
  return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
32200
32817
  };
32201
32818
  const hasDependencyMatch = (guardExpression, dependencyExpressions) => dependencyExpressions.some((dependencyExpression) => doesGuardMatchDependency(guardExpression, dependencyExpression));
32819
+ const hasAliasedDependencyMatch = (guardExpression, dependencyExpressions, scopes) => {
32820
+ const guardOrigins = collectImmutableExpressionOrigins(guardExpression.expression, scopes);
32821
+ return dependencyExpressions.some((dependencyExpression) => {
32822
+ if (!dependencyExpression) return false;
32823
+ const dependencyOrigins = collectImmutableExpressionOrigins(dependencyExpression, scopes);
32824
+ return guardOrigins.some((guardOrigin) => dependencyOrigins.some((dependencyOrigin) => areExpressionsStructurallyEqual(guardOrigin, dependencyOrigin)));
32825
+ });
32826
+ };
32827
+ const isStaticallyTrue = (expression, scopes) => collectImmutableExpressionOrigins(expression, scopes).some((origin) => isNodeOfType(origin, "Literal") && origin.value === true);
32828
+ const isReactRouterReplacementNavigation = (node, rootIdentifierNames, scopes) => {
32829
+ let didFindNavigation = false;
32830
+ let didFindOtherTriggeredSideEffect = false;
32831
+ walkAst(node, (child) => {
32832
+ if (!isNodeOfType(child, "CallExpression")) return;
32833
+ if (!(findTriggeredSideEffectCalleeName(child) !== null || hasDocumentClassListMutation(child))) return;
32834
+ const destinationExpression = child.arguments?.[0];
32835
+ const doesDestinationReferenceReconciliationValue = Boolean(destinationExpression && collectImmutableExpressionOrigins(destinationExpression, scopes).some((origin) => doesNodeReferenceAnyRoot(origin, rootIdentifierNames)));
32836
+ if (!isNodeOfType(child.callee, "Identifier") || !doesDestinationReferenceReconciliationValue) {
32837
+ didFindOtherTriggeredSideEffect = true;
32838
+ return;
32839
+ }
32840
+ const navigationSymbol = scopes.symbolFor(child.callee);
32841
+ const navigationInitializer = navigationSymbol?.initializer ? stripParenExpression(navigationSymbol.initializer) : null;
32842
+ if (navigationSymbol?.kind !== "const" || !isNodeOfType(navigationInitializer, "CallExpression") || !isNodeOfType(navigationInitializer.callee, "Identifier")) {
32843
+ didFindOtherTriggeredSideEffect = true;
32844
+ return;
32845
+ }
32846
+ if (scopes.symbolFor(navigationInitializer.callee)?.kind !== "import") {
32847
+ didFindOtherTriggeredSideEffect = true;
32848
+ return;
32849
+ }
32850
+ if ((getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router-dom") ?? getImportedNameFromModule(navigationInitializer.callee, navigationInitializer.callee.name, "react-router")) !== "useNavigate") {
32851
+ didFindOtherTriggeredSideEffect = true;
32852
+ return;
32853
+ }
32854
+ const navigationOptionsArgument = child.arguments?.[1];
32855
+ const navigationOptions = navigationOptionsArgument ? stripParenExpression(navigationOptionsArgument) : null;
32856
+ if (!isNodeOfType(navigationOptions, "ObjectExpression")) {
32857
+ didFindOtherTriggeredSideEffect = true;
32858
+ return;
32859
+ }
32860
+ let isReplacementGuaranteed = false;
32861
+ for (const property of navigationOptions.properties) {
32862
+ if (isNodeOfType(property, "SpreadElement")) {
32863
+ isReplacementGuaranteed = false;
32864
+ continue;
32865
+ }
32866
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
32867
+ if (propertyName === null) {
32868
+ isReplacementGuaranteed = false;
32869
+ continue;
32870
+ }
32871
+ if (propertyName !== "replace") continue;
32872
+ isReplacementGuaranteed = isStaticallyTrue(property.value, scopes);
32873
+ }
32874
+ if (isReplacementGuaranteed) {
32875
+ didFindNavigation = true;
32876
+ return;
32877
+ }
32878
+ didFindOtherTriggeredSideEffect = true;
32879
+ });
32880
+ return didFindNavigation && !didFindOtherTriggeredSideEffect;
32881
+ };
32202
32882
  const isEqualityToLiteralGuard = (guardExpression) => {
32203
32883
  const parent = guardExpression.expression.parent;
32204
32884
  if (!isNodeOfType(parent, "BinaryExpression")) return false;
@@ -32263,18 +32943,35 @@ const noEffectEventHandler = defineRule({
32263
32943
  if (statements.length === 0) return;
32264
32944
  const soleStatement = statements[0];
32265
32945
  if (!isNodeOfType(soleStatement, "IfStatement")) return;
32266
- const guardExpressions = [];
32267
- collectGuardExpressions(soleStatement.test, guardExpressions);
32268
- const matchingPropGuardExpressions = guardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
32946
+ const initialGuardExpressions = [];
32947
+ collectGuardExpressions(soleStatement.test, initialGuardExpressions);
32948
+ const guardExpressions = collectLeadingEarlyReturnGuards(statements);
32949
+ if (guardExpressions.length === 0) guardExpressions.push(...initialGuardExpressions);
32950
+ const matchingPropGuardExpressions = initialGuardExpressions.filter((guardExpression) => hasDependencyMatch(guardExpression, dependencyExpressions) && propStackTracker.isPropName(guardExpression.rootIdentifierName, node));
32269
32951
  if (matchingPropGuardExpressions.length === 0) return;
32270
32952
  const isSingleGuardedEventLikeStatement = statements.length === 1 && hasEventLikeNode(soleStatement.consequent);
32271
32953
  const isEarlyReturnGuardedEventLikeBody = statements.length > 1 && !soleStatement.alternate && isReturnOnlyStatement(soleStatement.consequent) && hasEventLikeRemainingStatements(statements.slice(1));
32272
32954
  if (!isSingleGuardedEventLikeStatement && !isEarlyReturnGuardedEventLikeBody) return;
32273
32955
  if (isEarlyReturnGuardedEventLikeBody && !isSingleGuardedEventLikeStatement && matchingPropGuardExpressions.every(isEqualityToLiteralGuard)) return;
32274
- if (guardExpressions.some((guardExpression) => !matchingPropGuardExpressions.some((matchingGuardExpression) => matchingGuardExpression.expression === guardExpression.expression))) {
32956
+ if (initialGuardExpressions.some((guardExpression) => !matchingPropGuardExpressions.some((matchingGuardExpression) => matchingGuardExpression.expression === guardExpression.expression))) {
32275
32957
  const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
32276
32958
  if (!(isSingleGuardedEventLikeStatement ? doesEventLikeCallReferenceAnyRoot(soleStatement.consequent, matchingPropRootNames) : doesAnyEventLikeCallReferenceAnyRoot(statements.slice(1), matchingPropRootNames))) return;
32277
32959
  }
32960
+ if (isEarlyReturnGuardedEventLikeBody) {
32961
+ const reconciliationGuardRootNames = new Set(guardExpressions.filter((guardExpression) => !propStackTracker.isPropName(guardExpression.rootIdentifierName, node) && hasAliasedDependencyMatch(guardExpression, dependencyExpressions, context.scopes) && guardExpressions.some((comparisonGuardExpression) => {
32962
+ if (comparisonGuardExpression.rootIdentifierName !== guardExpression.rootIdentifierName) return false;
32963
+ const comparisonExpression = comparisonGuardExpression.expression.parent;
32964
+ if (!isNodeOfType(comparisonExpression, "BinaryExpression") || comparisonExpression.operator !== "===" && comparisonExpression.operator !== "==") return false;
32965
+ const comparedRootIdentifierName = getRootIdentifierName(comparisonExpression.left === comparisonGuardExpression.expression ? comparisonExpression.right : comparisonExpression.left);
32966
+ return Boolean(comparedRootIdentifierName && propStackTracker.isPropName(comparedRootIdentifierName, node));
32967
+ })).map((guardExpression) => guardExpression.rootIdentifierName));
32968
+ if (reconciliationGuardRootNames.size > 0) {
32969
+ const matchingPropRootNames = new Set(matchingPropGuardExpressions.map((guardExpression) => guardExpression.rootIdentifierName));
32970
+ const eventLikeStatements = statements.slice(1);
32971
+ const triggeredStatements = eventLikeStatements.filter(hasEventLikeNode);
32972
+ if (triggeredStatements.length > 0 && triggeredStatements.every((statement) => isReactRouterReplacementNavigation(statement, reconciliationGuardRootNames, context.scopes)) && !doesAnyEventLikeCallReferenceAnyRoot(eventLikeStatements, matchingPropRootNames)) return;
32973
+ }
32974
+ }
32278
32975
  context.report({
32279
32976
  node,
32280
32977
  message: "This useEffect is simulating an event handler, which costs an extra render & runs late."
@@ -32555,7 +33252,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
32555
33252
  if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
32556
33253
  const parent = identifier.parent;
32557
33254
  if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
32558
- if (findEnclosingFunction(parent) === functionNode) return false;
33255
+ if (findEnclosingFunction$1(parent) === functionNode) return false;
32559
33256
  if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
32560
33257
  }
32561
33258
  return true;
@@ -32601,7 +33298,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
32601
33298
  if (!setterVariable) return false;
32602
33299
  const stateDeclarator = getUseStateDecl(analysis, stateReference);
32603
33300
  if (!stateDeclarator) return false;
32604
- const componentFunction = findEnclosingFunction(stateDeclarator);
33301
+ const componentFunction = findEnclosingFunction$1(stateDeclarator);
32605
33302
  if (!componentFunction) return false;
32606
33303
  let hasWriter = false;
32607
33304
  for (const reference of setterVariable.references) {
@@ -33663,8 +34360,8 @@ const isAwaitedInFunction = (request, functionNode) => {
33663
34360
  return false;
33664
34361
  };
33665
34362
  const isCompletionSinkForRequest = (completionSink, request) => {
33666
- const requestFunction = findEnclosingFunction(request);
33667
- const completionSinkFunction = findEnclosingFunction(completionSink);
34363
+ const requestFunction = findEnclosingFunction$1(request);
34364
+ const completionSinkFunction = findEnclosingFunction$1(completionSink);
33668
34365
  if (!requestFunction || !completionSinkFunction) return false;
33669
34366
  if (requestFunction === completionSinkFunction) {
33670
34367
  if (!isAwaitedInFunction(request, requestFunction)) return false;
@@ -33951,13 +34648,6 @@ const isPublishedLibraryPackage = (filename) => {
33951
34648
  return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
33952
34649
  };
33953
34650
  //#endregion
33954
- //#region src/plugin/utils/is-type-only-import.ts
33955
- const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
33956
- if (!specifiers || specifiers.length === 0) return false;
33957
- return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
33958
- };
33959
- const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
33960
- //#endregion
33961
34651
  //#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
33962
34652
  const isLibraryDevPage = (filename) => {
33963
34653
  if (!filename) return false;
@@ -34521,7 +35211,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
34521
35211
  return attribute ? !isEventHandlerAttribute(attribute) : true;
34522
35212
  }
34523
35213
  if (isNodeOfType(parentNode, "ReturnStatement")) {
34524
- if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
35214
+ if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
34525
35215
  }
34526
35216
  if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
34527
35217
  if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
@@ -34644,7 +35334,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
34644
35334
  if (consequentValues.length === 0 || alternateValues.length === 0) return;
34645
35335
  const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
34646
35336
  if (!componentOrHookNode) return;
34647
- const enclosingFunction = findEnclosingFunction(node);
35337
+ const enclosingFunction = findEnclosingFunction$1(node);
34648
35338
  if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
34649
35339
  for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
34650
35340
  if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
@@ -35092,10 +35782,10 @@ const isInsideInstanceField = (node) => {
35092
35782
  return false;
35093
35783
  };
35094
35784
  const isOneShotModuleInitialization = (node, context) => {
35095
- let functionNode = findEnclosingFunction(node);
35785
+ let functionNode = findEnclosingFunction$1(node);
35096
35786
  while (functionNode) {
35097
35787
  if (!executesDuringRender(functionNode, context.scopes)) return false;
35098
- functionNode = findEnclosingFunction(functionNode);
35788
+ functionNode = findEnclosingFunction$1(functionNode);
35099
35789
  }
35100
35790
  return !isInsideInstanceField(node);
35101
35791
  };
@@ -35613,6 +36303,221 @@ const noLegacyClassLifecycles = defineRule({
35613
36303
  }
35614
36304
  });
35615
36305
  //#endregion
36306
+ //#region src/plugin/utils/is-proven-react-class-component.ts
36307
+ const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
36308
+ const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
36309
+ const expression = stripParenExpression(node);
36310
+ if (isNodeOfType(expression, "MemberExpression")) {
36311
+ const propertyName = getStaticPropertyName(expression);
36312
+ const receiver = stripParenExpression(expression.object);
36313
+ return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
36314
+ }
36315
+ if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
36316
+ if (!isNodeOfType(expression, "Identifier")) return false;
36317
+ const symbol = scopes.symbolFor(expression);
36318
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
36319
+ visitedSymbolIds.add(symbol.id);
36320
+ if (isImportedFromReact(symbol)) {
36321
+ const importedName = getImportedName(symbol.declarationNode);
36322
+ return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
36323
+ }
36324
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
36325
+ return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
36326
+ };
36327
+ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36328
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
36329
+ visitedClassNodes.add(classNode);
36330
+ return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
36331
+ };
36332
+ //#endregion
36333
+ //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
36334
+ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
36335
+ if (!isFunctionLike$2(functionNode)) return false;
36336
+ let containsReactHookCall = false;
36337
+ walkAst(functionNode.body, (node) => {
36338
+ if (containsReactHookCall) return false;
36339
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
36340
+ if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
36341
+ containsReactHookCall = true;
36342
+ return false;
36343
+ }
36344
+ });
36345
+ return containsReactHookCall;
36346
+ };
36347
+ //#endregion
36348
+ //#region src/plugin/utils/function-returns-props-children.ts
36349
+ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
36350
+ if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
36351
+ const firstParameter = stripParenExpression(functionNode.params[0]);
36352
+ const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
36353
+ const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
36354
+ const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
36355
+ if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
36356
+ for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
36357
+ const propertyValue = stripParenExpression(property.value);
36358
+ const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
36359
+ if (!isNodeOfType(childrenBinding, "Identifier")) continue;
36360
+ const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
36361
+ if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
36362
+ }
36363
+ }
36364
+ return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
36365
+ const candidate = stripParenExpression(expression);
36366
+ if (isNodeOfType(candidate, "Identifier")) {
36367
+ const symbol = scopes.symbolFor(candidate);
36368
+ return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
36369
+ }
36370
+ if (!isNodeOfType(candidate, "MemberExpression")) return false;
36371
+ if (getStaticPropertyName(candidate) !== "children") return false;
36372
+ const receiver = stripParenExpression(candidate.object);
36373
+ if (!isNodeOfType(receiver, "Identifier")) return false;
36374
+ const receiverSymbol = scopes.symbolFor(receiver);
36375
+ if (!receiverSymbol || !propsParameterSymbol) return false;
36376
+ return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
36377
+ }, controlFlow);
36378
+ };
36379
+ //#endregion
36380
+ //#region src/plugin/utils/function-returns-only-null.ts
36381
+ const isNullExpression = (expression) => {
36382
+ const candidate = stripParenExpression(expression);
36383
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
36384
+ };
36385
+ const functionReturnsOnlyNull = (functionNode) => {
36386
+ if (!isFunctionLike$2(functionNode)) return false;
36387
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
36388
+ const returnStatements = collectFunctionReturnStatements(functionNode);
36389
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
36390
+ };
36391
+ //#endregion
36392
+ //#region src/plugin/utils/is-proven-styled-component-expression.ts
36393
+ const findFactoryRoot = (node) => {
36394
+ const candidate = stripParenExpression(node);
36395
+ if (isNodeOfType(candidate, "Identifier")) return candidate;
36396
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
36397
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
36398
+ return null;
36399
+ };
36400
+ const findFactoryPropertyName = (node) => {
36401
+ const candidate = stripParenExpression(node);
36402
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
36403
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
36404
+ return null;
36405
+ };
36406
+ const isProvenStyledComponentExpression = (expression, scopes) => {
36407
+ const candidate = stripParenExpression(expression);
36408
+ if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
36409
+ const factoryRoot = findFactoryRoot(candidate.tag);
36410
+ if (!factoryRoot) return false;
36411
+ const factoryPropertyName = findFactoryPropertyName(candidate.tag);
36412
+ if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
36413
+ const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
36414
+ if (!symbol || symbol.kind !== "import") return false;
36415
+ if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
36416
+ const importDeclaration = symbol.declarationNode.parent;
36417
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
36418
+ };
36419
+ //#endregion
36420
+ //#region src/plugin/utils/is-proven-react-component-symbol.ts
36421
+ const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
36422
+ const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
36423
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
36424
+ const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36425
+ const candidate = stripParenExpression(expression);
36426
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
36427
+ if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
36428
+ if (isProvenStyledComponentExpression(candidate, scopes)) return true;
36429
+ if (isNodeOfType(candidate, "Identifier")) {
36430
+ const symbol = scopes.symbolFor(candidate);
36431
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
36432
+ visitedSymbolIds.add(symbol.id);
36433
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
36434
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
36435
+ return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
36436
+ }
36437
+ if (!isNodeOfType(candidate, "CallExpression")) return false;
36438
+ if (!hasStableCallTarget(candidate, scopes)) return false;
36439
+ const factoryCallee = stripParenExpression(candidate.callee);
36440
+ if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
36441
+ if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
36442
+ const wrappedComponent = candidate.arguments[0];
36443
+ return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
36444
+ }
36445
+ if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
36446
+ const factory = candidate.arguments[0];
36447
+ if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
36448
+ const unwrappedFactory = stripParenExpression(factory);
36449
+ if (!isInlineFunctionExpression(unwrappedFactory)) return false;
36450
+ if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
36451
+ const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
36452
+ const returnedExpression = returnStatements[0]?.argument;
36453
+ return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
36454
+ };
36455
+ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
36456
+ const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
36457
+ for (const candidateSymbol of candidateSymbols) {
36458
+ if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
36459
+ if (isComponentDeclaration(candidateSymbol.declarationNode)) {
36460
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
36461
+ continue;
36462
+ }
36463
+ const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
36464
+ if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
36465
+ if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
36466
+ continue;
36467
+ }
36468
+ if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
36469
+ if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
36470
+ continue;
36471
+ }
36472
+ if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
36473
+ }
36474
+ return false;
36475
+ };
36476
+ //#endregion
36477
+ //#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
36478
+ const REACT_COMPONENT_TYPE_NAMES = new Set([
36479
+ "ComponentClass",
36480
+ "ComponentType",
36481
+ "FC",
36482
+ "FunctionComponent"
36483
+ ]);
36484
+ const findVisibleSymbol = (identifier, scopes) => {
36485
+ if (!isNodeOfType(identifier, "Identifier")) return null;
36486
+ let scope = scopes.scopeFor(identifier);
36487
+ while (true) {
36488
+ const symbol = scope.symbolsByName.get(identifier.name);
36489
+ if (symbol) return symbol;
36490
+ if (!scope.parent) return null;
36491
+ scope = scope.parent;
36492
+ }
36493
+ };
36494
+ const isReactNamespaceType = (identifier, scopes) => {
36495
+ const symbol = findVisibleSymbol(identifier, scopes);
36496
+ return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
36497
+ };
36498
+ const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
36499
+ if (!typeNode) return false;
36500
+ if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
36501
+ if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
36502
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
36503
+ const typeName = typeNode.typeName;
36504
+ if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
36505
+ if (!isNodeOfType(typeName, "Identifier")) return false;
36506
+ const typeSymbol = findVisibleSymbol(typeName, scopes);
36507
+ if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
36508
+ if (isImportedFromReact(typeSymbol)) {
36509
+ const importedName = getImportedName(typeSymbol.declarationNode);
36510
+ return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
36511
+ }
36512
+ if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
36513
+ visitedSymbolIds.add(typeSymbol.id);
36514
+ return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
36515
+ };
36516
+ const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
36517
+ const bindingIdentifier = symbol.bindingIdentifier;
36518
+ return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
36519
+ };
36520
+ //#endregion
35616
36521
  //#region src/plugin/rules/architecture/no-legacy-context-api.ts
35617
36522
  const LEGACY_CONTEXT_NAMES = new Set([
35618
36523
  "childContextTypes",
@@ -35623,15 +36528,6 @@ const buildLegacyContextMessage = (memberName) => {
35623
36528
  if (memberName === "childContextTypes" || memberName === "getChildContext") return `${memberName} uses the old context API that React 19 removes, so your provider stops passing data. Switch to \`createContext\` with \`<MyContext.Provider value={...}>\` & read it with \`useContext()\`, moving every consumer together.`;
35624
36529
  return "contextTypes uses the old context API that React 19 removes, so your component stops receiving context. Use `static contextType = MyContext` or `useContext()` in a function component, & update the provider too.";
35625
36530
  };
35626
- const isInsideClassBody = (node) => {
35627
- let current = node.parent;
35628
- while (current) {
35629
- if (isNodeOfType(current, "ClassBody")) return true;
35630
- if (isFunctionLike$2(current)) return false;
35631
- current = current.parent;
35632
- }
35633
- return false;
35634
- };
35635
36531
  const noLegacyContextApi = defineRule({
35636
36532
  id: "no-legacy-context-api",
35637
36533
  title: "Legacy context API",
@@ -35646,6 +36542,7 @@ const noLegacyContextApi = defineRule({
35646
36542
  if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
35647
36543
  if (!isNodeOfType(memberNode.key, "Identifier")) return;
35648
36544
  if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
36545
+ if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
35649
36546
  context.report({
35650
36547
  node: memberNode.key,
35651
36548
  message: buildLegacyContextMessage(memberNode.key.name)
@@ -35653,6 +36550,8 @@ const noLegacyContextApi = defineRule({
35653
36550
  };
35654
36551
  return {
35655
36552
  ClassBody(node) {
36553
+ const classNode = node.parent;
36554
+ if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
35656
36555
  for (const member of node.body ?? []) checkMember(member);
35657
36556
  },
35658
36557
  AssignmentExpression(node) {
@@ -35662,9 +36561,11 @@ const noLegacyContextApi = defineRule({
35662
36561
  if (left.computed) return;
35663
36562
  if (!isNodeOfType(left.property, "Identifier")) return;
35664
36563
  if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
35665
- if (!isNodeOfType(left.object, "Identifier")) return;
35666
- if (!isUppercaseName(left.object.name)) return;
35667
- if (isInsideClassBody(node)) return;
36564
+ if (left.property.name === "getChildContext") return;
36565
+ const component = stripParenExpression(left.object);
36566
+ if (!isNodeOfType(component, "Identifier")) return;
36567
+ const symbol = context.scopes.symbolFor(component);
36568
+ if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
35668
36569
  context.report({
35669
36570
  node: left,
35670
36571
  message: buildLegacyContextMessage(left.property.name)
@@ -35868,10 +36769,10 @@ const getMethodCalls = (functionNode, scopes) => {
35868
36769
  const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
35869
36770
  if (visitedFunctionNodes.has(functionNode)) return false;
35870
36771
  visitedFunctionNodes.add(functionNode);
35871
- const usageFunction = findEnclosingFunction(usageNode);
36772
+ const usageFunction = findEnclosingFunction$1(usageNode);
35872
36773
  const immediateCall = getDirectCallForExpression(functionNode);
35873
36774
  if (immediateCall) {
35874
- const immediateCallFunction = findEnclosingFunction(immediateCall);
36775
+ const immediateCallFunction = findEnclosingFunction$1(immediateCall);
35875
36776
  if (immediateCallFunction === usageFunction) {
35876
36777
  const immediateCallStart = getRangeStart(immediateCall);
35877
36778
  return immediateCallStart === null || immediateCallStart < usageBoundary;
@@ -35880,7 +36781,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
35880
36781
  return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
35881
36782
  }
35882
36783
  for (const methodCall of getMethodCalls(functionNode, scopes)) {
35883
- const methodCallFunction = findEnclosingFunction(methodCall);
36784
+ const methodCallFunction = findEnclosingFunction$1(methodCall);
35884
36785
  if (methodCallFunction === usageFunction) {
35885
36786
  const methodCallStart = getRangeStart(methodCall);
35886
36787
  if (methodCallStart === null || methodCallStart < usageBoundary) return true;
@@ -35903,7 +36804,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
35903
36804
  if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
35904
36805
  const call = getDirectCallForExpression(child);
35905
36806
  if (!call) return false;
35906
- const callFunction = findEnclosingFunction(call);
36807
+ const callFunction = findEnclosingFunction$1(call);
35907
36808
  if (callFunction === usageFunction) {
35908
36809
  const callStart = getRangeStart(call);
35909
36810
  wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
@@ -35934,14 +36835,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
35934
36835
  visitedMutationSymbolIds.add(symbol.id);
35935
36836
  const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
35936
36837
  if (typeof usageBoundary !== "number") return true;
35937
- const usageFunction = findEnclosingFunction(usageNode);
36838
+ const usageFunction = findEnclosingFunction$1(usageNode);
35938
36839
  return symbol.references.some((reference) => {
35939
36840
  const referenceStart = getRangeStart(reference.identifier);
35940
36841
  const simpleAlias = getSimpleAlias(reference.identifier, scopes);
35941
36842
  if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
35942
36843
  if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
35943
36844
  if (referenceStart === null) return true;
35944
- const mutationFunction = findEnclosingFunction(reference.identifier);
36845
+ const mutationFunction = findEnclosingFunction$1(reference.identifier);
35945
36846
  if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
35946
36847
  if (!mutationFunction) return usageFunction !== null;
35947
36848
  if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
@@ -36523,13 +37424,6 @@ const isReactNamespaceExpression = (node, scopes) => {
36523
37424
  return isReactNamespaceImport(node, scopes);
36524
37425
  };
36525
37426
  const isReactHocMemberReference = (node, scopes) => Boolean(isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && REACT_HOC_NAMES.has(node.property.name) && isReactNamespaceExpression(node.object, scopes));
36526
- const getDestructuredPropertyName$1 = (symbol) => {
36527
- const property = symbol.bindingIdentifier.parent;
36528
- if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
36529
- if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
36530
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
36531
- return null;
36532
- };
36533
37427
  const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
36534
37428
  if (visitedSymbolIds.has(symbol.id)) return false;
36535
37429
  visitedSymbolIds.add(symbol.id);
@@ -36539,7 +37433,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
36539
37433
  }
36540
37434
  const init = symbol.initializer;
36541
37435
  if (!init) return false;
36542
- const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
37436
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
36543
37437
  if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
36544
37438
  if (isReactHocMemberReference(init, scopes)) return true;
36545
37439
  if (isNodeOfType(init, "Identifier")) {
@@ -38293,12 +39187,6 @@ const getDeclarationKind = (declarator) => {
38293
39187
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
38294
39188
  };
38295
39189
  const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
38296
- const getDestructuredPropertyName = (bindingIdentifier) => {
38297
- const property = bindingIdentifier.parent;
38298
- if (!property || !isNodeOfType(property, "Property")) return null;
38299
- if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
38300
- return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
38301
- };
38302
39190
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
38303
39191
  const unwrappedExpression = stripParenExpression(expression);
38304
39192
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -38308,7 +39196,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
38308
39196
  if (isProp(analysis, callbackReference)) {
38309
39197
  if (isWholePropsObjectReference(analysis, callbackReference)) return null;
38310
39198
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
38311
- return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
39199
+ return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
38312
39200
  }
38313
39201
  if (hasMutableBindingWrite(callbackReference)) return null;
38314
39202
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -38318,7 +39206,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
38318
39206
  visitedVariables.add(callbackVariable);
38319
39207
  if (isNodeOfType(declarator.id, "ObjectPattern")) {
38320
39208
  const bindingIdentifier = callbackVariable.defs[0]?.name;
38321
- const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
39209
+ const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
38322
39210
  const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
38323
39211
  return propertyName && propsReference ? propertyName : null;
38324
39212
  }
@@ -38421,7 +39309,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
38421
39309
  const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
38422
39310
  if (!bindingProvenance) return null;
38423
39311
  const { refCall, variables } = bindingProvenance;
38424
- const componentFunction = findEnclosingFunction(effectCall);
39312
+ const componentFunction = findEnclosingFunction$1(effectCall);
38425
39313
  if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
38426
39314
  if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
38427
39315
  const callbackPropNames = /* @__PURE__ */ new Set();
@@ -39257,174 +40145,6 @@ const noPropCallbackInRender = defineRule({
39257
40145
  } })
39258
40146
  });
39259
40147
  //#endregion
39260
- //#region src/plugin/utils/is-proven-react-class-component.ts
39261
- const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
39262
- const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
39263
- const expression = stripParenExpression(node);
39264
- if (isNodeOfType(expression, "MemberExpression")) {
39265
- const propertyName = getStaticPropertyName(expression);
39266
- const receiver = stripParenExpression(expression.object);
39267
- return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
39268
- }
39269
- if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
39270
- if (!isNodeOfType(expression, "Identifier")) return false;
39271
- const symbol = scopes.symbolFor(expression);
39272
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
39273
- visitedSymbolIds.add(symbol.id);
39274
- if (isImportedFromReact(symbol)) {
39275
- const importedName = getImportedName(symbol.declarationNode);
39276
- return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
39277
- }
39278
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
39279
- return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
39280
- };
39281
- const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39282
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
39283
- visitedClassNodes.add(classNode);
39284
- return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
39285
- };
39286
- //#endregion
39287
- //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
39288
- const functionContainsProvenReactHookCall = (functionNode, scopes) => {
39289
- if (!isFunctionLike$2(functionNode)) return false;
39290
- let containsReactHookCall = false;
39291
- walkAst(functionNode.body, (node) => {
39292
- if (containsReactHookCall) return false;
39293
- if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
39294
- if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
39295
- containsReactHookCall = true;
39296
- return false;
39297
- }
39298
- });
39299
- return containsReactHookCall;
39300
- };
39301
- //#endregion
39302
- //#region src/plugin/utils/function-returns-props-children.ts
39303
- const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39304
- if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
39305
- const firstParameter = stripParenExpression(functionNode.params[0]);
39306
- const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
39307
- const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
39308
- const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
39309
- if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
39310
- for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
39311
- const propertyValue = stripParenExpression(property.value);
39312
- const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
39313
- if (!isNodeOfType(childrenBinding, "Identifier")) continue;
39314
- const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
39315
- if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
39316
- }
39317
- }
39318
- return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
39319
- const candidate = stripParenExpression(expression);
39320
- if (isNodeOfType(candidate, "Identifier")) {
39321
- const symbol = scopes.symbolFor(candidate);
39322
- return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
39323
- }
39324
- if (!isNodeOfType(candidate, "MemberExpression")) return false;
39325
- if (getStaticPropertyName(candidate) !== "children") return false;
39326
- const receiver = stripParenExpression(candidate.object);
39327
- if (!isNodeOfType(receiver, "Identifier")) return false;
39328
- const receiverSymbol = scopes.symbolFor(receiver);
39329
- if (!receiverSymbol || !propsParameterSymbol) return false;
39330
- return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
39331
- }, controlFlow);
39332
- };
39333
- //#endregion
39334
- //#region src/plugin/utils/function-returns-only-null.ts
39335
- const isNullExpression = (expression) => {
39336
- const candidate = stripParenExpression(expression);
39337
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
39338
- };
39339
- const functionReturnsOnlyNull = (functionNode) => {
39340
- if (!isFunctionLike$2(functionNode)) return false;
39341
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
39342
- const returnStatements = collectFunctionReturnStatements(functionNode);
39343
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
39344
- };
39345
- //#endregion
39346
- //#region src/plugin/utils/is-proven-styled-component-expression.ts
39347
- const findFactoryRoot = (node) => {
39348
- const candidate = stripParenExpression(node);
39349
- if (isNodeOfType(candidate, "Identifier")) return candidate;
39350
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
39351
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
39352
- return null;
39353
- };
39354
- const findFactoryPropertyName = (node) => {
39355
- const candidate = stripParenExpression(node);
39356
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
39357
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
39358
- return null;
39359
- };
39360
- const isProvenStyledComponentExpression = (expression, scopes) => {
39361
- const candidate = stripParenExpression(expression);
39362
- if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
39363
- const factoryRoot = findFactoryRoot(candidate.tag);
39364
- if (!factoryRoot) return false;
39365
- const factoryPropertyName = findFactoryPropertyName(candidate.tag);
39366
- if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
39367
- const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
39368
- if (!symbol || symbol.kind !== "import") return false;
39369
- if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
39370
- const importDeclaration = symbol.declarationNode.parent;
39371
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
39372
- };
39373
- //#endregion
39374
- //#region src/plugin/utils/is-proven-react-component-symbol.ts
39375
- const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
39376
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39377
- const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39378
- const candidate = stripParenExpression(expression);
39379
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
39380
- if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
39381
- if (isProvenStyledComponentExpression(candidate, scopes)) return true;
39382
- if (isNodeOfType(candidate, "Identifier")) {
39383
- const symbol = scopes.symbolFor(candidate);
39384
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
39385
- visitedSymbolIds.add(symbol.id);
39386
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
39387
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
39388
- return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
39389
- }
39390
- if (!isNodeOfType(candidate, "CallExpression")) return false;
39391
- if (!hasStableCallTarget(candidate, scopes)) return false;
39392
- if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
39393
- const wrappedComponent = candidate.arguments[0];
39394
- return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
39395
- }
39396
- if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
39397
- const factory = candidate.arguments[0];
39398
- if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
39399
- const unwrappedFactory = stripParenExpression(factory);
39400
- if (!isInlineFunctionExpression(unwrappedFactory)) return false;
39401
- if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
39402
- const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
39403
- const returnedExpression = returnStatements[0]?.argument;
39404
- return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
39405
- };
39406
- const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
39407
- const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
39408
- for (const candidateSymbol of candidateSymbols) {
39409
- if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
39410
- if (isComponentDeclaration(candidateSymbol.declarationNode)) {
39411
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
39412
- continue;
39413
- }
39414
- const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
39415
- if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
39416
- if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
39417
- continue;
39418
- }
39419
- if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
39420
- if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
39421
- continue;
39422
- }
39423
- if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
39424
- }
39425
- return false;
39426
- };
39427
- //#endregion
39428
40148
  //#region src/plugin/rules/architecture/no-prop-types.ts
39429
40149
  const PROP_TYPES_PROPERTY = "propTypes";
39430
40150
  const isPropTypesKey = (key, computed) => {
@@ -39577,7 +40297,7 @@ const isModuleScopedBinding = (identifier) => {
39577
40297
  const binding = findVariableInitializer(identifier, identifier.name);
39578
40298
  if (!binding) return false;
39579
40299
  if (isNodeOfType(binding.scopeOwner, "Program")) return true;
39580
- if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
40300
+ if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
39581
40301
  return false;
39582
40302
  };
39583
40303
  const looksLikeFreshUpdateExpression = (expression) => {
@@ -41494,9 +42214,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
41494
42214
  return isReturnedFromEffect;
41495
42215
  };
41496
42216
  const isInsideEffectCleanupReturn = (node, ownerScope) => {
41497
- let functionNode = findEnclosingFunction(node);
42217
+ let functionNode = findEnclosingFunction$1(node);
41498
42218
  while (functionNode) {
41499
- const outerFunction = findEnclosingFunction(functionNode);
42219
+ const outerFunction = findEnclosingFunction$1(functionNode);
41500
42220
  if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
41501
42221
  if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
41502
42222
  functionNode = outerFunction;
@@ -41504,7 +42224,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
41504
42224
  return false;
41505
42225
  };
41506
42226
  const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
41507
- const enclosingFunction = findEnclosingFunction(clearCall);
42227
+ const enclosingFunction = findEnclosingFunction$1(clearCall);
41508
42228
  if (!isFunctionLike$2(enclosingFunction)) return false;
41509
42229
  if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
41510
42230
  const clearStart = getRangeStart(clearCall);
@@ -42334,7 +43054,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
42334
43054
  return false;
42335
43055
  };
42336
43056
  const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
42337
- const enclosingFunction = findEnclosingFunction(node);
43057
+ const enclosingFunction = findEnclosingFunction$1(node);
42338
43058
  if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
42339
43059
  let currentNode = node;
42340
43060
  while (currentNode !== enclosingFunction) {
@@ -43601,7 +44321,11 @@ const resolveSettings$8 = (settings) => {
43601
44321
  propNamePattern: ruleSettings.propNamePattern ?? "render*"
43602
44322
  };
43603
44323
  };
43604
- const expressionContainsJsxOrCreateElement = (root) => {
44324
+ const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
44325
+ allowGlobalReactNamespace: true,
44326
+ resolveNamedAliases: true
44327
+ });
44328
+ const expressionContainsJsxOrCreateElement = (root, scopes) => {
43605
44329
  let found = false;
43606
44330
  walkAst(root, (node) => {
43607
44331
  if (found) return false;
@@ -43610,17 +44334,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
43610
44334
  found = true;
43611
44335
  return false;
43612
44336
  }
43613
- if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
44337
+ if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
43614
44338
  found = true;
43615
44339
  return false;
43616
44340
  }
43617
44341
  });
43618
44342
  return found;
43619
44343
  };
43620
- const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
43621
- const isReactClassComponent = (classNode) => {
44344
+ const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
44345
+ const isReactClassComponent = (classNode, scopes) => {
43622
44346
  if (isEs6Component(classNode)) return true;
43623
- return expressionContainsJsxOrCreateElement(classNode);
44347
+ return expressionContainsJsxOrCreateElement(classNode, scopes);
43624
44348
  };
43625
44349
  const findEnclosingComponent = (node, scopes, controlFlow) => {
43626
44350
  let walker = node.parent;
@@ -43637,7 +44361,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
43637
44361
  };
43638
44362
  }
43639
44363
  if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
43640
- if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
44364
+ if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
43641
44365
  component: walker,
43642
44366
  name: walker.id.name
43643
44367
  };
@@ -43726,12 +44450,12 @@ const isObjectCallbackCandidate = (node) => {
43726
44450
  }
43727
44451
  return false;
43728
44452
  };
43729
- const hocCallContainsComponent = (call) => {
44453
+ const hocCallContainsComponent = (call, scopes) => {
43730
44454
  const firstArgument = call.arguments[0];
43731
44455
  if (!firstArgument) return false;
43732
- if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
43733
- if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
43734
- return expressionContainsJsxOrCreateElement(firstArgument);
44456
+ if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
44457
+ if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
44458
+ return expressionContainsJsxOrCreateElement(firstArgument, scopes);
43735
44459
  };
43736
44460
  const isFirstArgumentOfHocCall = (node) => {
43737
44461
  const parent = node.parent;
@@ -43764,19 +44488,35 @@ const isReturnOfMapCallback = (node) => {
43764
44488
  }
43765
44489
  return false;
43766
44490
  };
43767
- const isCloneElementCall = (node) => {
43768
- if (!isNodeOfType(node, "CallExpression")) return false;
43769
- const callee = node.callee;
43770
- if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
43771
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
43772
- };
43773
44491
  const TS_VALUE_PASSTHROUGH_TYPES = new Set([
43774
44492
  "TSAsExpression",
43775
44493
  "TSNonNullExpression",
43776
44494
  "TSSatisfiesExpression",
43777
44495
  "TSTypeAssertion"
43778
44496
  ]);
43779
- const isRenderFlowingReadReference = (identifier) => {
44497
+ const ELEMENT_TYPE_PROP_NAMES = new Set([
44498
+ "as",
44499
+ "body",
44500
+ "calendarcontainer",
44501
+ "component",
44502
+ "fallback",
44503
+ "tooltip"
44504
+ ]);
44505
+ const isElementTypeJsxAttribute = (node) => {
44506
+ if (!isNodeOfType(node, "JSXAttribute")) return false;
44507
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
44508
+ const attributeName = node.name.name;
44509
+ return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
44510
+ };
44511
+ const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
44512
+ allowGlobalReactNamespace: true,
44513
+ resolveNamedAliases: true
44514
+ });
44515
+ const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
44516
+ allowGlobalReactNamespace: true,
44517
+ resolveNamedAliases: true
44518
+ });
44519
+ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
43780
44520
  let valueNode = identifier;
43781
44521
  let parent = valueNode.parent;
43782
44522
  while (parent) {
@@ -43786,20 +44526,26 @@ const isRenderFlowingReadReference = (identifier) => {
43786
44526
  continue;
43787
44527
  }
43788
44528
  switch (parent.type) {
43789
- case "JSXExpressionContainer":
43790
- case "ReturnStatement": return true;
43791
- case "ArrowFunctionExpression": return parent.body === valueNode;
44529
+ case "JSXOpeningElement": return parent.name === valueNode;
44530
+ case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
44531
+ case "ReturnStatement": return false;
44532
+ case "ArrowFunctionExpression": return false;
43792
44533
  case "CallExpression":
43793
44534
  if (parent.callee === valueNode) return false;
43794
- if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
44535
+ if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
44536
+ if (isReactCreateElementCall(parent, scopes)) return true;
43795
44537
  valueNode = parent;
43796
44538
  parent = parent.parent;
43797
44539
  continue;
43798
- case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
43799
- case "AssignmentExpression": {
43800
- const assignmentTarget = parent.left;
43801
- return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
44540
+ case "VariableDeclarator": {
44541
+ if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
44542
+ const aliasSymbol = scopes.symbolFor(parent.id);
44543
+ if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
44544
+ const nextVisitedSymbols = new Set(visitedSymbols);
44545
+ nextVisitedSymbols.add(aliasSymbol.id);
44546
+ return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
43802
44547
  }
44548
+ case "AssignmentExpression": return false;
43803
44549
  case "Property":
43804
44550
  if (parent.value !== valueNode) return false;
43805
44551
  valueNode = parent;
@@ -43837,6 +44583,7 @@ const noUnstableNestedComponents = defineRule({
43837
44583
  severity: "warn",
43838
44584
  recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
43839
44585
  category: "Performance",
44586
+ tags: ["react-jsx-only"],
43840
44587
  create: (context) => {
43841
44588
  const settings = resolveSettings$8(context.settings);
43842
44589
  const renderPropRegex = compileGlob(settings.propNamePattern);
@@ -43899,7 +44646,7 @@ const noUnstableNestedComponents = defineRule({
43899
44646
  },
43900
44647
  Identifier(node) {
43901
44648
  if (!isReactComponentName(node.name)) return;
43902
- if (!isRenderFlowingReadReference(node)) return;
44649
+ if (!isRenderFlowingReadReference(node, context.scopes)) return;
43903
44650
  recordInstantiation(node, node.name);
43904
44651
  },
43905
44652
  FunctionDeclaration: checkFunctionLike,
@@ -43908,28 +44655,33 @@ const noUnstableNestedComponents = defineRule({
43908
44655
  ClassDeclaration(node) {
43909
44656
  if (!node.id) return;
43910
44657
  if (!isReactComponentName(node.id.name)) return;
43911
- if (!isReactClassComponent(node)) return;
44658
+ if (!isReactClassComponent(node, context.scopes)) return;
43912
44659
  enqueueCandidate(node, null);
43913
44660
  },
43914
44661
  ClassExpression(node) {
43915
44662
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
43916
44663
  if (!inferredName || !isReactComponentName(inferredName)) return;
43917
- if (!isReactClassComponent(node)) return;
44664
+ if (!isReactClassComponent(node, context.scopes)) return;
43918
44665
  enqueueCandidate(node, null);
43919
44666
  },
43920
44667
  CallExpression(node) {
43921
- if (isCreateElementCall(node)) {
44668
+ if (isReactCreateElementCall(node, context.scopes)) {
43922
44669
  const firstArgument = node.arguments[0];
43923
44670
  if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
43924
44671
  else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
43925
44672
  }
43926
- if (!isHocCallee$1(node)) return;
43927
- if (!hocCallContainsComponent(node)) return;
43928
- enqueueCandidate(node, null);
44673
+ const isReactLazy = isReactLazyCall(node, context.scopes);
44674
+ if (!isReactLazy && !isHocCallee$1(node)) return;
44675
+ if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
44676
+ const inferredName = inferFunctionLikeName(node);
44677
+ const propInfo = isComponentDeclaredInProp(node);
44678
+ if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
44679
+ enqueueCandidate(node, propInfo === null ? inferredName : null);
43929
44680
  },
43930
44681
  "Program:exit"() {
43931
44682
  for (const report of queuedReports) {
43932
44683
  if (report.requiredInstantiationName !== null) {
44684
+ if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
43933
44685
  if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
43934
44686
  }
43935
44687
  context.report({
@@ -44962,7 +45714,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
44962
45714
  const symbol = scopes.symbolFor(identifier);
44963
45715
  if (symbol) {
44964
45716
  if (symbol.kind !== "parameter") return null;
44965
- const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
45717
+ const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
44966
45718
  if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
44967
45719
  return null;
44968
45720
  }
@@ -45008,7 +45760,7 @@ const isChildrenMemberExpression = (node, scopes) => {
45008
45760
  const symbol = scopes.symbolFor(propsObject);
45009
45761
  if (symbol) {
45010
45762
  if (symbol.kind !== "parameter") return false;
45011
- const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
45763
+ const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
45012
45764
  return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
45013
45765
  }
45014
45766
  return hasComponentLikeAncestorFunction(node);
@@ -45660,7 +46412,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
45660
46412
  //#endregion
45661
46413
  //#region src/plugin/utils/enclosing-component-or-hook-scope.ts
45662
46414
  const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
45663
- const functionNode = findEnclosingFunction(startNode);
46415
+ const functionNode = findEnclosingFunction$1(startNode);
45664
46416
  if (!functionNode) return null;
45665
46417
  const displayName = componentOrHookDisplayNameForFunction(functionNode);
45666
46418
  if (!displayName) return null;
@@ -46722,7 +47474,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
46722
47474
  //#region src/plugin/rules/tanstack-query/query-destructure-result.ts
46723
47475
  const isHookReturnForwardingSpread = (objectExpression) => {
46724
47476
  const objectParent = objectExpression.parent;
46725
- const enclosingFunction = findEnclosingFunction(objectExpression);
47477
+ const enclosingFunction = findEnclosingFunction$1(objectExpression);
46726
47478
  if (!enclosingFunction) return false;
46727
47479
  if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
46728
47480
  const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
@@ -47100,10 +47852,10 @@ const hasSuspensionBefore = (functionNode, boundary, context) => {
47100
47852
  return hasSuspension;
47101
47853
  };
47102
47854
  const isFunctionAncestor = (ancestor, functionNode) => {
47103
- let enclosingFunction = findEnclosingFunction(functionNode);
47855
+ let enclosingFunction = findEnclosingFunction$1(functionNode);
47104
47856
  while (enclosingFunction) {
47105
47857
  if (enclosingFunction === ancestor) return true;
47106
- enclosingFunction = findEnclosingFunction(enclosingFunction);
47858
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
47107
47859
  }
47108
47860
  return false;
47109
47861
  };
@@ -47125,7 +47877,7 @@ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedF
47125
47877
  return invokesTarget;
47126
47878
  };
47127
47879
  const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
47128
- const boundaryFunction = findEnclosingFunction(boundary);
47880
+ const boundaryFunction = findEnclosingFunction$1(boundary);
47129
47881
  const boundaryStart = getRangeStart(boundary);
47130
47882
  if (!boundaryFunction || boundaryStart === null) return false;
47131
47883
  let isInvokedBefore = false;
@@ -47162,11 +47914,11 @@ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, conte
47162
47914
  const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
47163
47915
  const writeStart = getRangeStart(writeNode);
47164
47916
  const boundaryStart = getRangeStart(boundary);
47165
- const writeFunction = findEnclosingFunction(writeNode);
47917
+ const writeFunction = findEnclosingFunction$1(writeNode);
47166
47918
  const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
47167
47919
  if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
47168
- const boundaryFunction = findEnclosingFunction(boundary);
47169
- const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
47920
+ const boundaryFunction = findEnclosingFunction$1(boundary);
47921
+ const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
47170
47922
  if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
47171
47923
  if (!writeFunction) return writeStart < boundaryStart;
47172
47924
  if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
@@ -47438,8 +48190,8 @@ const queryStableQueryClient = defineRule({
47438
48190
  create: (context) => ({ NewExpression(node) {
47439
48191
  if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
47440
48192
  if (isStableHookWrapperArgument(node)) return;
47441
- let enclosingFunction = findEnclosingFunction(node);
47442
- while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
48193
+ let enclosingFunction = findEnclosingFunction$1(node);
48194
+ while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
47443
48195
  if (!enclosingFunction) return;
47444
48196
  if (!isComponentFunction(enclosingFunction)) return;
47445
48197
  context.report({
@@ -47534,7 +48286,7 @@ const reactCompilerNoManualMemoization = defineRule({
47534
48286
  const comparatorArgument = node.arguments?.[1];
47535
48287
  if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
47536
48288
  } else {
47537
- const enclosingFunction = findEnclosingFunction(node);
48289
+ const enclosingFunction = findEnclosingFunction$1(node);
47538
48290
  if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
47539
48291
  }
47540
48292
  const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
@@ -50639,7 +51391,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
50639
51391
  };
50640
51392
  const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
50641
51393
  const isInsideStyleFactoryCallback = (node) => {
50642
- const enclosingFunction = findEnclosingFunction(node);
51394
+ const enclosingFunction = findEnclosingFunction$1(node);
50643
51395
  if (!enclosingFunction) return false;
50644
51396
  const callExpression = enclosingFunction.parent;
50645
51397
  if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
@@ -57495,12 +58247,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
57495
58247
  }
57496
58248
  return false;
57497
58249
  };
57498
- const declarationAwaitsGate = (declaration) => {
58250
+ const declarationAwaitsGate = (declaration, context) => {
57499
58251
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
57500
58252
  for (const declarator of declaration.declarations ?? []) {
57501
58253
  if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
57502
58254
  const argument = declarator.init.argument;
57503
58255
  if (!isNodeOfType(argument, "CallExpression")) continue;
58256
+ if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
58257
+ if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
57504
58258
  const calleeName = getCalleeName$2(argument);
57505
58259
  if (!calleeName) continue;
57506
58260
  if (isAuthGuardName(calleeName)) return true;
@@ -57528,7 +58282,7 @@ const serverSequentialIndependentAwait = defineRule({
57528
58282
  if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
57529
58283
  if (declarationAwaitsExistingPromise(nextStatement)) continue;
57530
58284
  if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
57531
- if (declarationAwaitsGate(currentStatement)) continue;
58285
+ if (declarationAwaitsGate(currentStatement, context)) continue;
57532
58286
  context.report({
57533
58287
  node: nextStatement,
57534
58288
  message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
@@ -57618,6 +58372,26 @@ const stateInConstructor = defineRule({
57618
58372
  }
57619
58373
  });
57620
58374
  //#endregion
58375
+ //#region src/plugin/utils/collect-jsx-runtime-imports.ts
58376
+ const REACT_RUNTIME_PACKAGE_PREFIXES = ["react", "react-dom"];
58377
+ const matchesPackage = (source, packageName) => source === packageName || source.startsWith(`${packageName}/`);
58378
+ const collectJsxRuntimeImports = (program) => {
58379
+ let hasReactRuntime = false;
58380
+ let hasSolidRuntime = false;
58381
+ for (const statement of program.body) {
58382
+ if (!isNodeOfType(statement, "ImportDeclaration")) continue;
58383
+ if (isTypeOnlyImport(statement)) continue;
58384
+ const source = statement.source.value;
58385
+ if (typeof source !== "string") continue;
58386
+ if (matchesPackage(source, "solid-js")) hasSolidRuntime = true;
58387
+ if (REACT_RUNTIME_PACKAGE_PREFIXES.some((packageName) => matchesPackage(source, packageName))) hasReactRuntime = true;
58388
+ }
58389
+ return {
58390
+ hasReactRuntime,
58391
+ hasSolidRuntime
58392
+ };
58393
+ };
58394
+ //#endregion
57621
58395
  //#region src/plugin/rules/react-builtins/style-prop-object.ts
57622
58396
  const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
57623
58397
  const resolveSettings = (settings) => {
@@ -57679,6 +58453,13 @@ const isInvalidStyleExpression = (expression) => {
57679
58453
  }
57680
58454
  return false;
57681
58455
  };
58456
+ const hasObjectValuedClassList = (openingElement) => openingElement.attributes.some((attribute) => {
58457
+ if (!isNodeOfType(attribute, "JSXAttribute")) return false;
58458
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) return false;
58459
+ if (attribute.name.name !== "classList") return false;
58460
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
58461
+ return isNodeOfType(attribute.value.expression, "ObjectExpression");
58462
+ });
57682
58463
  const stylePropObject = defineRule({
57683
58464
  id: "style-prop-object",
57684
58465
  title: "Style prop is not an object",
@@ -57688,8 +58469,21 @@ const stylePropObject = defineRule({
57688
58469
  create: (context) => {
57689
58470
  const { allow } = resolveSettings(context.settings);
57690
58471
  const allowSet = new Set(allow);
58472
+ let fileIsProvenSolidJsx = false;
57691
58473
  return {
58474
+ Program: (node) => {
58475
+ const runtimeImports = collectJsxRuntimeImports(node);
58476
+ let hasSolidSyntaxMarker = false;
58477
+ if (!runtimeImports.hasReactRuntime && !runtimeImports.hasSolidRuntime) walkAst(node, (descendantNode) => {
58478
+ if (isNodeOfType(descendantNode, "JSXOpeningElement") && hasObjectValuedClassList(descendantNode)) {
58479
+ hasSolidSyntaxMarker = true;
58480
+ return false;
58481
+ }
58482
+ });
58483
+ fileIsProvenSolidJsx = !runtimeImports.hasReactRuntime && (runtimeImports.hasSolidRuntime || hasSolidSyntaxMarker);
58484
+ },
57692
58485
  JSXOpeningElement(node) {
58486
+ if (fileIsProvenSolidJsx) return;
57693
58487
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
57694
58488
  const elementName = resolveJsxElementType(node);
57695
58489
  if (elementName && allowSet.has(elementName)) return;
@@ -57720,6 +58514,7 @@ const stylePropObject = defineRule({
57720
58514
  }
57721
58515
  },
57722
58516
  CallExpression(node) {
58517
+ if (fileIsProvenSolidJsx) return;
57723
58518
  if (!isCreateElementCall(node)) return;
57724
58519
  const firstArgument = node.arguments[0];
57725
58520
  if (!firstArgument) return;
@@ -58514,7 +59309,7 @@ const tanstackStartNoNavigateInRender = defineRule({
58514
59309
  const isReturnedFromCustomHook = (functionNode) => {
58515
59310
  const parent = functionNode.parent;
58516
59311
  if (isNodeOfType(parent, "ReturnStatement")) {
58517
- const outerFunction = findEnclosingFunction(parent);
59312
+ const outerFunction = findEnclosingFunction$1(parent);
58518
59313
  const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
58519
59314
  return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
58520
59315
  }
@@ -58531,13 +59326,13 @@ const tanstackStartNoNavigateInRender = defineRule({
58531
59326
  return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
58532
59327
  };
58533
59328
  const isDeferredNavigateCall = (callNode) => {
58534
- let enclosingFunction = findEnclosingFunction(callNode);
59329
+ let enclosingFunction = findEnclosingFunction$1(callNode);
58535
59330
  while (enclosingFunction) {
58536
59331
  if (isDeferredCallbackPosition(enclosingFunction)) return true;
58537
59332
  if (isWiredAsEventHandler(enclosingFunction)) return true;
58538
59333
  if (isReturnedFromCustomHook(enclosingFunction)) return true;
58539
59334
  if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
58540
- enclosingFunction = findEnclosingFunction(enclosingFunction);
59335
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
58541
59336
  }
58542
59337
  return false;
58543
59338
  };