oxlint-plugin-react-doctor 0.7.7-dev.bdd1321 → 0.7.7-dev.ce4179b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +46 -0
  2. package/dist/index.js +2163 -727
  3. 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 = (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(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(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(node);
305
+ fileImportsReactRuntime = runtimeImports.hasReactRuntime;
306
+ fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
279
307
  };
280
308
  return wrappedVisitors;
281
309
  });
@@ -2312,7 +2340,7 @@ const anchorAmbiguousText = defineRule({
2312
2340
  });
2313
2341
  //#endregion
2314
2342
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2315
- const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2343
+ const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2316
2344
  const isTransComponentsTemplate = (node) => {
2317
2345
  let current = node.parent;
2318
2346
  while (current) {
@@ -2348,7 +2376,7 @@ const anchorHasContent = defineRule({
2348
2376
  if (isTransComponentsTemplate(node)) return;
2349
2377
  context.report({
2350
2378
  node: opening.name,
2351
- message: MESSAGE$61
2379
+ message: MESSAGE$62
2352
2380
  });
2353
2381
  } };
2354
2382
  }
@@ -2780,7 +2808,7 @@ const parseJsxValue = (value) => {
2780
2808
  };
2781
2809
  //#endregion
2782
2810
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2783
- const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2811
+ const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2784
2812
  const mayBeContentEditable = (node) => {
2785
2813
  const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
2786
2814
  if (!attribute) return false;
@@ -2811,7 +2839,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2811
2839
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2812
2840
  context.report({
2813
2841
  node: node.name,
2814
- message: MESSAGE$60
2842
+ message: MESSAGE$61
2815
2843
  });
2816
2844
  return;
2817
2845
  }
@@ -2819,7 +2847,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2819
2847
  if (mayBeContentEditable(node)) return;
2820
2848
  context.report({
2821
2849
  node: node.name,
2822
- message: MESSAGE$60
2850
+ message: MESSAGE$61
2823
2851
  });
2824
2852
  } })
2825
2853
  });
@@ -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
  };
@@ -5224,7 +5662,7 @@ const asyncParallel = defineRule({
5224
5662
  });
5225
5663
  //#endregion
5226
5664
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
5227
- const MESSAGE$59 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
5665
+ const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
5228
5666
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
5229
5667
  const STORAGE_GLOBALS = new Set([
5230
5668
  "window",
@@ -5289,7 +5727,7 @@ const authTokenInWebStorage = defineRule({
5289
5727
  if (keyString === null || !isAuthCredentialKey(keyString)) return;
5290
5728
  context.report({
5291
5729
  node,
5292
- message: MESSAGE$59
5730
+ message: MESSAGE$60
5293
5731
  });
5294
5732
  },
5295
5733
  AssignmentExpression(node) {
@@ -5300,7 +5738,7 @@ const authTokenInWebStorage = defineRule({
5300
5738
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
5301
5739
  context.report({
5302
5740
  node: target,
5303
- message: MESSAGE$59
5741
+ message: MESSAGE$60
5304
5742
  });
5305
5743
  }
5306
5744
  }))
@@ -5439,7 +5877,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
5439
5877
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
5440
5878
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
5441
5879
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
5442
- const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5880
+ const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5443
5881
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
5444
5882
  const scanWorkflowContent = (content) => {
5445
5883
  const lines = content.split("\n");
@@ -5484,7 +5922,7 @@ const scanWorkflowContent = (content) => {
5484
5922
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
5485
5923
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
5486
5924
  return [{
5487
- message: MESSAGE$58,
5925
+ message: MESSAGE$59,
5488
5926
  line: step.startLineIndex + installLineOffset + 1,
5489
5927
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
5490
5928
  }];
@@ -5494,7 +5932,7 @@ const scanWorkflowContent = (content) => {
5494
5932
  const scanNonWorkflowConfig = scanByPattern({
5495
5933
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
5496
5934
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
5497
- message: MESSAGE$58
5935
+ message: MESSAGE$59
5498
5936
  });
5499
5937
  const scan = (file) => {
5500
5938
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -5989,21 +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")) return null;
5995
- if (node.computed) {
5996
- if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5997
- return null;
5998
- }
5999
- if (isNodeOfType(node.key, "Identifier")) return node.key.name;
6000
- if (isNodeOfType(node.key, "Literal")) {
6001
- if (typeof node.key.value === "string") return node.key.value;
6002
- if (options.stringifyNonStringLiterals) return String(node.key.value);
6003
- }
6004
- return null;
6005
- };
6006
- //#endregion
6007
6430
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
6008
6431
  const areExpressionsStructurallyEqual = (a, b) => {
6009
6432
  if (!a || !b) return a === b;
@@ -6261,7 +6684,7 @@ const isPureEventBlockerHandler = (attribute) => {
6261
6684
  };
6262
6685
  //#endregion
6263
6686
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
6264
- const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6687
+ const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6265
6688
  const KEY_HANDLERS = [
6266
6689
  "onKeyUp",
6267
6690
  "onKeyDown",
@@ -6472,7 +6895,7 @@ const clickEventsHaveKeyEvents = defineRule({
6472
6895
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
6473
6896
  context.report({
6474
6897
  node: node.name,
6475
- message: MESSAGE$57
6898
+ message: MESSAGE$58
6476
6899
  });
6477
6900
  } };
6478
6901
  }
@@ -6687,15 +7110,6 @@ const getDirectConstInitializer = (symbol) => {
6687
7110
  return symbol.initializer;
6688
7111
  };
6689
7112
  //#endregion
6690
- //#region src/plugin/utils/get-static-property-name.ts
6691
- const getStaticPropertyName = (memberExpression) => {
6692
- const property = memberExpression.property;
6693
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
6694
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
6695
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
6696
- return null;
6697
- };
6698
- //#endregion
6699
7113
  //#region src/plugin/utils/get-range-start.ts
6700
7114
  const getRangeStart = (node) => {
6701
7115
  const rangeStart = node.range?.[0];
@@ -7091,16 +7505,6 @@ const collectFunctionReturnStatements = (functionNode) => {
7091
7505
  return returnStatements;
7092
7506
  };
7093
7507
  //#endregion
7094
- //#region src/plugin/utils/find-enclosing-function.ts
7095
- const findEnclosingFunction = (node) => {
7096
- let cursor = node.parent;
7097
- while (cursor) {
7098
- if (isFunctionLike$2(cursor)) return cursor;
7099
- cursor = cursor.parent ?? null;
7100
- }
7101
- return null;
7102
- };
7103
- //#endregion
7104
7508
  //#region src/plugin/utils/statement-always-exits.ts
7105
7509
  const statementAlwaysExits = (statement) => {
7106
7510
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -7146,8 +7550,8 @@ const applyDefinitions = (incomingDefinitions, definitions) => {
7146
7550
  const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow) => {
7147
7551
  if (!REASSIGNABLE_BINDING_KINDS.has(symbol.kind)) return symbol.initializer ? [symbol.initializer] : [];
7148
7552
  if (!controlFlow) return [];
7149
- const referenceFunction = findEnclosingFunction(referenceNode);
7150
- if (findEnclosingFunction(symbol.bindingIdentifier) !== referenceFunction) return [];
7553
+ const referenceFunction = findEnclosingFunction$1(referenceNode);
7554
+ if (findEnclosingFunction$1(symbol.bindingIdentifier) !== referenceFunction) return [];
7151
7555
  if (!referenceFunction) return [];
7152
7556
  const functionControlFlow = controlFlow.cfgFor(referenceFunction);
7153
7557
  if (!functionControlFlow) return [];
@@ -7171,7 +7575,7 @@ const collectPossibleAssignedExpressions = (symbol, referenceNode, controlFlow)
7171
7575
  if (symbol.initializer) addDefinition(symbol.initializer, symbol.bindingIdentifier, bindingPosition);
7172
7576
  for (const reference of symbol.references) {
7173
7577
  const writePosition = getRangeStart(reference.identifier);
7174
- 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;
7175
7579
  const assignedExpression = getAssignedExpressionForWrite(reference.identifier);
7176
7580
  if (!assignedExpression) continue;
7177
7581
  addDefinition(assignedExpression, reference.identifier, writePosition);
@@ -8725,7 +9129,7 @@ const getClassNameLiteral = (classAttribute) => {
8725
9129
  };
8726
9130
  //#endregion
8727
9131
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
8728
- const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
9132
+ const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
8729
9133
  const NON_OPERABLE_ELEMENTS = new Set([
8730
9134
  "td",
8731
9135
  "th",
@@ -8843,6 +9247,7 @@ const HTML_FOR_ATTRIBUTE = "htmlFor";
8843
9247
  const LABEL_ELEMENT = "label";
8844
9248
  const LABEL_COMPONENT_NAME = "Label";
8845
9249
  const POLYMORPHIC_COMPONENT_PROP = "component";
9250
+ const TITLE_ATTRIBUTE = "title";
8846
9251
  const WRAPPER_LABEL_PROP = "label";
8847
9252
  const SELECT_ELEMENT = "select";
8848
9253
  const DEFAULT_DEPTH = 5;
@@ -8888,6 +9293,96 @@ const hasNonEmptyPropValue = (attribute) => {
8888
9293
  }
8889
9294
  return true;
8890
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
+ };
8891
9386
  const toAttributeMatchKey = (kind, value) => {
8892
9387
  const trimmedValue = value.trim();
8893
9388
  return trimmedValue.length > 0 ? `${kind}:${trimmedValue}` : null;
@@ -9144,6 +9639,7 @@ const controlHasAssociatedLabel = defineRule({
9144
9639
  if (typeValue === "submit" || typeValue === "reset") return;
9145
9640
  if (typeValue === "button" && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "value"))) return;
9146
9641
  }
9642
+ if (isDomElement && hasNonEmptyNativeTitle(getLastJsxPropIgnoreCase(opening.attributes, TITLE_ATTRIBUTE), context.scopes)) return;
9147
9643
  if (supportsPlaceholderNameFallback(tagName, opening) && hasNonEmptyPropValue(hasJsxPropIgnoreCase(opening.attributes, "placeholder"))) return;
9148
9644
  if (hasLabellingProp(opening.attributes, settings.labelAttributes)) return;
9149
9645
  if (isInsideJsxAttribute(node)) return;
@@ -9163,7 +9659,7 @@ const controlHasAssociatedLabel = defineRule({
9163
9659
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
9164
9660
  context.report({
9165
9661
  node: candidate.opening,
9166
- message: MESSAGE$56
9662
+ message: MESSAGE$57
9167
9663
  });
9168
9664
  }
9169
9665
  }
@@ -9924,7 +10420,7 @@ const noVagueButtonLabel = defineRule({
9924
10420
  });
9925
10421
  //#endregion
9926
10422
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
9927
- const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
10423
+ const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
9928
10424
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
9929
10425
  const NAME_PROVIDING_ATTRIBUTES = [
9930
10426
  "aria-label",
@@ -9949,7 +10445,7 @@ const dialogHasAccessibleName = defineRule({
9949
10445
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
9950
10446
  context.report({
9951
10447
  node: node.name,
9952
- message: MESSAGE$55
10448
+ message: MESSAGE$56
9953
10449
  });
9954
10450
  } };
9955
10451
  }
@@ -9989,7 +10485,7 @@ const isEs6Component = (node) => {
9989
10485
  };
9990
10486
  //#endregion
9991
10487
  //#region src/plugin/rules/react-builtins/display-name.ts
9992
- const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
10488
+ const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
9993
10489
  const DEFAULT_ADDITIONAL_HOCS = [
9994
10490
  "observer",
9995
10491
  "lazy",
@@ -10182,7 +10678,7 @@ const displayName = defineRule({
10182
10678
  const reportAt = (node) => {
10183
10679
  context.report({
10184
10680
  node,
10185
- message: MESSAGE$54
10681
+ message: MESSAGE$55
10186
10682
  });
10187
10683
  };
10188
10684
  return {
@@ -11003,6 +11499,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
11003
11499
  "catch",
11004
11500
  "finally"
11005
11501
  ]);
11502
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11503
+ const getPromiseChainCallForCallback = (candidate) => {
11504
+ let callbackContainer = candidate.parent;
11505
+ while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
11506
+ if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
11507
+ if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
11508
+ return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
11509
+ };
11006
11510
  const collectEffectInvokedFunctions = (effectCallback) => {
11007
11511
  const invokedFunctions = new Set([effectCallback]);
11008
11512
  const localFunctionBindings = /* @__PURE__ */ new Map();
@@ -11014,7 +11518,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
11014
11518
  invokedFunctions.add(strippedCandidate);
11015
11519
  pendingFunctions.push(strippedCandidate);
11016
11520
  };
11017
- const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11018
11521
  while (pendingFunctions.length > 0) {
11019
11522
  const currentFunction = pendingFunctions.pop();
11020
11523
  if (!currentFunction) break;
@@ -11086,7 +11589,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
11086
11589
  //#endregion
11087
11590
  //#region src/plugin/utils/enclosing-component-or-hook-name.ts
11088
11591
  const enclosingComponentOrHookName = (node) => {
11089
- const functionNode = findEnclosingFunction(node);
11592
+ const functionNode = findEnclosingFunction$1(node);
11090
11593
  return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
11091
11594
  };
11092
11595
  //#endregion
@@ -11143,11 +11646,11 @@ const executesDuringRender = (functionNode, scopes) => {
11143
11646
  //#endregion
11144
11647
  //#region src/plugin/utils/find-render-phase-component-or-hook.ts
11145
11648
  const findRenderPhaseComponentOrHook = (node, scopes) => {
11146
- let functionNode = findEnclosingFunction(node);
11649
+ let functionNode = findEnclosingFunction$1(node);
11147
11650
  while (functionNode) {
11148
11651
  if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
11149
11652
  if (!executesDuringRender(functionNode, scopes)) return null;
11150
- functionNode = findEnclosingFunction(functionNode);
11653
+ functionNode = findEnclosingFunction$1(functionNode);
11151
11654
  }
11152
11655
  return null;
11153
11656
  };
@@ -11222,6 +11725,50 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
11222
11725
  return true;
11223
11726
  };
11224
11727
  //#endregion
11728
+ //#region src/plugin/utils/is-node-reachable-within-function.ts
11729
+ const isInsideStaticallyUnreachableBranch = (node) => {
11730
+ let child = node;
11731
+ let parent = node.parent;
11732
+ while (parent) {
11733
+ if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
11734
+ if (parent.test.value === false && parent.consequent === child) return true;
11735
+ if (parent.test.value === true && parent.alternate === child) return true;
11736
+ }
11737
+ if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
11738
+ if (parent.test.value === false && parent.consequent === child) return true;
11739
+ if (parent.test.value === true && parent.alternate === child) return true;
11740
+ }
11741
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
11742
+ if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
11743
+ }
11744
+ child = parent;
11745
+ parent = parent.parent;
11746
+ }
11747
+ return false;
11748
+ };
11749
+ const isNodeReachableWithinFunction = (node, context) => {
11750
+ if (isInsideStaticallyUnreachableBranch(node)) return false;
11751
+ const owner = context.cfg.enclosingFunction(node);
11752
+ if (!owner) return true;
11753
+ const functionCfg = context.cfg.cfgFor(owner);
11754
+ if (!functionCfg) return true;
11755
+ const targetBlock = functionCfg.blockOf(node);
11756
+ if (!targetBlock) return true;
11757
+ const visitedBlocks = new Set([functionCfg.entry]);
11758
+ const pendingBlocks = [functionCfg.entry];
11759
+ while (pendingBlocks.length > 0) {
11760
+ const currentBlock = pendingBlocks.pop();
11761
+ if (!currentBlock) break;
11762
+ if (currentBlock === targetBlock) return true;
11763
+ for (const edge of currentBlock.successors) {
11764
+ if (visitedBlocks.has(edge.to)) continue;
11765
+ visitedBlocks.add(edge.to);
11766
+ pendingBlocks.push(edge.to);
11767
+ }
11768
+ }
11769
+ return false;
11770
+ };
11771
+ //#endregion
11225
11772
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
11226
11773
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
11227
11774
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
@@ -11350,36 +11897,15 @@ const findSubscribeLikeUsages = (callback, context) => {
11350
11897
  });
11351
11898
  return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
11352
11899
  };
11353
- const isNodeReachableWithinFunction = (node, context) => {
11354
- const owner = context.cfg.enclosingFunction(node);
11355
- if (!owner) return true;
11356
- const functionCfg = context.cfg.cfgFor(owner);
11357
- if (!functionCfg) return true;
11358
- const targetBlock = functionCfg.blockOf(node);
11359
- if (!targetBlock) return true;
11360
- const visitedBlocks = new Set([functionCfg.entry]);
11361
- const pendingBlocks = [functionCfg.entry];
11362
- while (pendingBlocks.length > 0) {
11363
- const currentBlock = pendingBlocks.pop();
11364
- if (!currentBlock) break;
11365
- if (currentBlock === targetBlock) return true;
11366
- for (const edge of currentBlock.successors) {
11367
- if (visitedBlocks.has(edge.to)) continue;
11368
- visitedBlocks.add(edge.to);
11369
- pendingBlocks.push(edge.to);
11370
- }
11371
- }
11372
- return false;
11373
- };
11374
11900
  const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
11375
11901
  let pathAnchor = usageNode;
11376
- let pathOwner = findEnclosingFunction(pathAnchor);
11902
+ let pathOwner = findEnclosingFunction$1(pathAnchor);
11377
11903
  while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
11378
11904
  if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
11379
11905
  const iteratorCall = pathOwner.parent;
11380
11906
  if (!isNodeOfType(iteratorCall, "CallExpression")) break;
11381
11907
  pathAnchor = iteratorCall;
11382
- pathOwner = findEnclosingFunction(pathAnchor);
11908
+ pathOwner = findEnclosingFunction$1(pathAnchor);
11383
11909
  }
11384
11910
  const owner = context.cfg.enclosingFunction(pathAnchor);
11385
11911
  if (!owner) return false;
@@ -11608,7 +12134,7 @@ const findCollectionMappingCall = (callbackNode) => {
11608
12134
  return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
11609
12135
  };
11610
12136
  const findMappedResourceCollectionKey = (resourceNode, context) => {
11611
- const callbackNode = findEnclosingFunction(resourceNode);
12137
+ const callbackNode = findEnclosingFunction$1(resourceNode);
11612
12138
  if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
11613
12139
  const mappingCall = findCollectionMappingCall(callbackNode);
11614
12140
  if (!mappingCall) return null;
@@ -11719,10 +12245,10 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
11719
12245
  });
11720
12246
  if (invocationCalls.length !== 1) return null;
11721
12247
  const invocationCall = invocationCalls[0];
11722
- return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
12248
+ return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
11723
12249
  };
11724
12250
  const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
11725
- const usageFunction = findEnclosingFunction(usageNode);
12251
+ const usageFunction = findEnclosingFunction$1(usageNode);
11726
12252
  if (!usageFunction || usageFunction === effectCallback) return usageNode;
11727
12253
  return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
11728
12254
  };
@@ -11737,7 +12263,7 @@ const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
11737
12263
  const assignmentReference = assignmentReferences[0];
11738
12264
  const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
11739
12265
  const assignmentNode = assignmentTarget.parent;
11740
- 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;
11741
12267
  const assignedValue = stripParenExpression(assignmentNode.right);
11742
12268
  return isFunctionLike$2(assignedValue) ? assignedValue : null;
11743
12269
  };
@@ -11826,12 +12352,12 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
11826
12352
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
11827
12353
  };
11828
12354
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11829
- const componentFunction = findEnclosingFunction(callback);
12355
+ const componentFunction = findEnclosingFunction$1(callback);
11830
12356
  if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
11831
12357
  let didFindUnmountCleanup = false;
11832
12358
  walkAst(componentFunction.body, (child) => {
11833
12359
  if (didFindUnmountCleanup) return false;
11834
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
12360
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
11835
12361
  if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
11836
12362
  const dependencyList = child.arguments?.[1];
11837
12363
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
@@ -11844,10 +12370,98 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11844
12370
  return didFindUnmountCleanup;
11845
12371
  };
11846
12372
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
12373
+ const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
12374
+ const unwrappedExpression = stripParenExpression(expression);
12375
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
12376
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
12377
+ if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
12378
+ return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
12379
+ }
12380
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
12381
+ "===",
12382
+ "==",
12383
+ "!==",
12384
+ "!="
12385
+ ].includes(unwrappedExpression.operator)) {
12386
+ const leftValue = readStaticBoolean(unwrappedExpression.left);
12387
+ const rightValue = readStaticBoolean(unwrappedExpression.right);
12388
+ const booleanValue = leftValue ?? rightValue;
12389
+ const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
12390
+ if (booleanValue === null || comparedKey === null) return [];
12391
+ return [{
12392
+ key: comparedKey,
12393
+ value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
12394
+ }];
12395
+ }
12396
+ const expressionKey = resolveExpressionKey(unwrappedExpression, context);
12397
+ return expressionKey === null ? [] : [{
12398
+ key: expressionKey,
12399
+ value: blockedExpressionValue
12400
+ }];
12401
+ };
12402
+ const isDirectEarlyReturnConsequent = (ifStatement) => {
12403
+ if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
12404
+ if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
12405
+ return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
12406
+ };
12407
+ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
12408
+ if (!isFunctionLike$2(callback) || callback.async) return [];
12409
+ const guardStates = [];
12410
+ walkAst(callback.body, (child) => {
12411
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
12412
+ if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
12413
+ });
12414
+ let descendant = usageNode;
12415
+ let ancestor = descendant.parent;
12416
+ while (ancestor && ancestor !== callback) {
12417
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
12418
+ descendant = ancestor;
12419
+ ancestor = ancestor.parent;
12420
+ }
12421
+ return guardStates;
12422
+ };
12423
+ const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
12424
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
12425
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
12426
+ if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
12427
+ let didInvalidateGuard = false;
12428
+ walkAst(cleanupFunction.body, (child) => {
12429
+ if (didInvalidateGuard) return false;
12430
+ if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
12431
+ if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
12432
+ didInvalidateGuard = true;
12433
+ return false;
12434
+ }
12435
+ });
12436
+ return didInvalidateGuard;
12437
+ };
12438
+ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
12439
+ const usageStart = getRangeStart(usageNode);
12440
+ if (!isFunctionLike$2(callback) || usageStart === null) return true;
12441
+ let didWriteGuard = false;
12442
+ walkAst(callback.body, (child) => {
12443
+ if (didWriteGuard) return false;
12444
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
12445
+ const childStart = getRangeStart(child);
12446
+ if (childStart === null || childStart >= usageStart) return;
12447
+ const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
12448
+ if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
12449
+ didWriteGuard = true;
12450
+ return false;
12451
+ }
12452
+ });
12453
+ return didWriteGuard;
12454
+ };
12455
+ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
12456
+ const usageFunction = findEnclosingFunction$1(usage.node);
12457
+ const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
12458
+ if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
12459
+ return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
12460
+ };
11847
12461
  const effectHasCleanupForUsage = (callback, usage, context) => {
11848
12462
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11849
12463
  if (callback.async) return false;
11850
- 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;
11851
12465
  if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
11852
12466
  const matchingCleanupReturns = [];
11853
12467
  walkInsideStatementBlocks(callback.body, (child) => {
@@ -11883,6 +12497,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
11883
12497
  if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
11884
12498
  if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
11885
12499
  });
12500
+ if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
11886
12501
  return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
11887
12502
  };
11888
12503
  const findFirstUsageWithoutCleanup = (callback, usages, context) => {
@@ -12015,7 +12630,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
12015
12630
  parentNode = currentNode.parent;
12016
12631
  }
12017
12632
  if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
12018
- const effectCallback = findEnclosingFunction(parentNode);
12633
+ const effectCallback = findEnclosingFunction$1(parentNode);
12019
12634
  const effectCall = effectCallback?.parent;
12020
12635
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
12021
12636
  };
@@ -12025,13 +12640,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
12025
12640
  if (!bindingIdentifier) return false;
12026
12641
  const symbol = context.scopes.symbolFor(bindingIdentifier);
12027
12642
  if (!symbol) return false;
12028
- return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
12643
+ return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
12029
12644
  };
12030
12645
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
12031
12646
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
12032
- const releaseFunction = findEnclosingFunction(releaseNode);
12647
+ const releaseFunction = findEnclosingFunction$1(releaseNode);
12033
12648
  if (!releaseFunction) return true;
12034
- if (releaseFunction === findEnclosingFunction(usage.node)) return true;
12649
+ if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
12035
12650
  return isPotentiallyReachableFunction(releaseFunction, context);
12036
12651
  };
12037
12652
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -12214,10 +12829,10 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
12214
12829
  }
12215
12830
  if (assignedKey !== storage.refCurrentKey) return;
12216
12831
  const assignedValue = stripParenExpression(child.right);
12217
- if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction(child) === cleanupFunction) return;
12832
+ if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
12218
12833
  const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
12219
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);
12220
- if (findEnclosingFunction(child) !== retainedFunction || !storesMatchingHandler) {
12835
+ if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
12221
12836
  hasUnsafeRefWrite = true;
12222
12837
  return false;
12223
12838
  }
@@ -12238,12 +12853,12 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
12238
12853
  return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
12239
12854
  };
12240
12855
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
12241
- const componentFunction = findEnclosingFunction(retainedFunction);
12856
+ const componentFunction = findEnclosingFunction$1(retainedFunction);
12242
12857
  if (!componentFunction || !isFunctionLike$2(componentFunction)) return false;
12243
12858
  let didFindCleanupEffect = false;
12244
12859
  walkAst(componentFunction.body, (child) => {
12245
12860
  if (didFindCleanupEffect) return false;
12246
- 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;
12247
12862
  const effectStatement = findTransparentExpressionRoot(child).parent;
12248
12863
  if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
12249
12864
  const effectCallback = getEffectCallback(child);
@@ -13097,6 +13712,202 @@ const resolveExhaustiveDepsSettings = (settings) => {
13097
13712
  };
13098
13713
  };
13099
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
13100
13911
  //#region src/plugin/rules/react-builtins/exhaustive-deps-suppression.ts
13101
13912
  const DISABLE_COMMENT_RULE_NAME_PATTERN$1 = /(?:^|[\s,/])exhaustive-deps(?:$|[\s,:])/;
13102
13913
  const DISABLE_NEXT_LINE_PATTERN$1 = /\b(?:eslint|oxlint)-disable-next-line\b([^\n]*)/;
@@ -13168,25 +13979,6 @@ const isExhaustiveDepsSuppressedAt = (filename, nodeStartOffset) => {
13168
13979
  return index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf16NewlineOffsets)) || index.suppressedLines.has(lineForOffset$1(nodeStartOffset, index.utf8NewlineOffsets));
13169
13980
  };
13170
13981
  //#endregion
13171
- //#region src/plugin/utils/is-ast-descendant.ts
13172
- /**
13173
- * True when `inner` is `outer` itself or any descendant in the AST
13174
- * `parent` chain. Walks `inner.parent` upward and stops at either a
13175
- * match (`true`) or the chain's root (`false`).
13176
- *
13177
- * Was duplicated byte-identical in:
13178
- * - exhaustive-deps-symbol-stability.ts
13179
- * - semantic/closure-captures.ts
13180
- */
13181
- const isAstDescendant = (inner, outer) => {
13182
- let current = inner;
13183
- while (current) {
13184
- if (current === outer) return true;
13185
- current = current.parent ?? null;
13186
- }
13187
- return false;
13188
- };
13189
- //#endregion
13190
13982
  //#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
13191
13983
  /**
13192
13984
  * Symbol-stability helpers consumed by the `exhaustive-deps` rule.
@@ -13367,6 +14159,7 @@ const EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS = new Set([
13367
14159
  "useLayoutEffect",
13368
14160
  "useInsertionEffect"
13369
14161
  ]);
14162
+ const SOLE_WRITER_GUARD_HOOKS = new Set(["useEffect", "useLayoutEffect"]);
13370
14163
  const buildAdditionalHooksRegex = (additional) => {
13371
14164
  if (!additional) return null;
13372
14165
  try {
@@ -13503,7 +14296,7 @@ const stringifyMemberChain = (node) => {
13503
14296
  }
13504
14297
  return null;
13505
14298
  };
13506
- const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
14299
+ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allowSoleWriterEffectGuards = false) => {
13507
14300
  const keys = /* @__PURE__ */ new Set();
13508
14301
  const stableCapturedNames = /* @__PURE__ */ new Set();
13509
14302
  const moduleScopeCapturedNames = /* @__PURE__ */ new Set();
@@ -13514,6 +14307,10 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys) => {
13514
14307
  const symbol = reference.resolvedSymbol;
13515
14308
  if (!symbol) continue;
13516
14309
  if (isRecursiveInitializerCapture(symbol, callback)) continue;
14310
+ if (allowSoleWriterEffectGuards && isSoleWriterEffectGuardCapture(symbol, callback, scopes)) {
14311
+ stableCapturedNames.add(symbol.name);
14312
+ continue;
14313
+ }
13517
14314
  if (symbolHasStableValue(symbol, scopes)) {
13518
14315
  stableCapturedNames.add(symbol.name);
13519
14316
  continue;
@@ -14186,7 +14983,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
14186
14983
  if (isNodeOfType(stripped, "Identifier")) declaredExactBindingKeys.add(key);
14187
14984
  declaredKeyToReportNode.set(key, elementNode);
14188
14985
  }
14189
- 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));
14190
14987
  for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
14191
14988
  addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
14192
14989
  const missingCaptureKeys = [];
@@ -14626,7 +15423,7 @@ const forbidElements = defineRule({
14626
15423
  });
14627
15424
  //#endregion
14628
15425
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
14629
- const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
15426
+ const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
14630
15427
  const forwardRefUsesRef = defineRule({
14631
15428
  id: "forward-ref-uses-ref",
14632
15429
  title: "forwardRef without ref parameter",
@@ -14649,7 +15446,7 @@ const forwardRefUsesRef = defineRule({
14649
15446
  if (isNodeOfType(onlyParam, "RestElement")) return;
14650
15447
  context.report({
14651
15448
  node: inner,
14652
- message: MESSAGE$53
15449
+ message: MESSAGE$54
14653
15450
  });
14654
15451
  } })
14655
15452
  });
@@ -14690,7 +15487,7 @@ const gitProviderUrlInjectionRisk = defineRule({
14690
15487
  });
14691
15488
  //#endregion
14692
15489
  //#region src/plugin/rules/a11y/heading-has-content.ts
14693
- const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
15490
+ const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
14694
15491
  const DEFAULT_HEADING_TAGS = [
14695
15492
  "h1",
14696
15493
  "h2",
@@ -14724,7 +15521,7 @@ const headingHasContent = defineRule({
14724
15521
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
14725
15522
  context.report({
14726
15523
  node,
14727
- message: MESSAGE$52
15524
+ message: MESSAGE$53
14728
15525
  });
14729
15526
  } };
14730
15527
  }
@@ -14888,7 +15685,7 @@ const hooksNoNanInDeps = defineRule({
14888
15685
  });
14889
15686
  //#endregion
14890
15687
  //#region src/plugin/rules/a11y/html-has-lang.ts
14891
- const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
15688
+ const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
14892
15689
  const resolveSettings$39 = (settings) => {
14893
15690
  const reactDoctor = settings?.["react-doctor"];
14894
15691
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -14935,13 +15732,13 @@ const htmlHasLang = defineRule({
14935
15732
  if (!lang) {
14936
15733
  context.report({
14937
15734
  node: node.name,
14938
- message: MESSAGE$51
15735
+ message: MESSAGE$52
14939
15736
  });
14940
15737
  return;
14941
15738
  }
14942
15739
  if (evaluateLang(lang.value) === "empty") context.report({
14943
15740
  node: lang,
14944
- message: MESSAGE$51
15741
+ message: MESSAGE$52
14945
15742
  });
14946
15743
  } };
14947
15744
  }
@@ -15181,7 +15978,7 @@ const htmlNoNestedInteractive = defineRule({
15181
15978
  });
15182
15979
  //#endregion
15183
15980
  //#region src/plugin/rules/a11y/iframe-has-title.ts
15184
- const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15981
+ const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15185
15982
  const evaluateTitleValue = (value) => {
15186
15983
  if (!value) return "missing";
15187
15984
  if (isNodeOfType(value, "Literal")) {
@@ -15221,14 +16018,14 @@ const iframeHasTitle = defineRule({
15221
16018
  if (!titleAttr) {
15222
16019
  if (hasSpread || tag === "iframe") context.report({
15223
16020
  node: node.name,
15224
- message: MESSAGE$50
16021
+ message: MESSAGE$51
15225
16022
  });
15226
16023
  return;
15227
16024
  }
15228
16025
  const verdict = evaluateTitleValue(titleAttr.value);
15229
16026
  if (verdict === "missing" || verdict === "empty") context.report({
15230
16027
  node: titleAttr,
15231
- message: MESSAGE$50
16028
+ message: MESSAGE$51
15232
16029
  });
15233
16030
  } })
15234
16031
  });
@@ -15353,7 +16150,7 @@ const iframeMissingSandbox = defineRule({
15353
16150
  });
15354
16151
  //#endregion
15355
16152
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
15356
- const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
16153
+ const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
15357
16154
  const DEFAULT_COMPONENTS = ["img"];
15358
16155
  const DEFAULT_REDUNDANT_WORDS = [
15359
16156
  "image",
@@ -15418,7 +16215,7 @@ const imgRedundantAlt = defineRule({
15418
16215
  if (!altAttribute) return;
15419
16216
  if (altValueRedundant(altAttribute, settings.words)) context.report({
15420
16217
  node: altAttribute,
15421
- message: MESSAGE$49
16218
+ message: MESSAGE$50
15422
16219
  });
15423
16220
  } };
15424
16221
  }
@@ -16000,14 +16797,14 @@ const isBindingInvokedOnRenderPath = (root, bindingName) => {
16000
16797
  if (didFindRenderPathInvocation) return false;
16001
16798
  if (!isNodeOfType(node, "CallExpression")) return;
16002
16799
  if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== bindingName) return;
16003
- let enclosingFunction = findEnclosingFunction(node);
16800
+ let enclosingFunction = findEnclosingFunction$1(node);
16004
16801
  while (enclosingFunction) {
16005
16802
  if (isDeferredCallbackPosition$1(enclosingFunction) || getHandlerNamedBindingName(enclosingFunction)) return;
16006
16803
  if (containingFunctionIsComponentOrHook(enclosingFunction)) {
16007
16804
  didFindRenderPathInvocation = true;
16008
16805
  return;
16009
16806
  }
16010
- enclosingFunction = findEnclosingFunction(enclosingFunction);
16807
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
16011
16808
  }
16012
16809
  });
16013
16810
  return didFindRenderPathInvocation;
@@ -16042,7 +16839,7 @@ const jotaiSelectAtomInRenderBody = defineRule({
16042
16839
  };
16043
16840
  return { CallExpression(node) {
16044
16841
  if (!isImportedSelectAtom(node)) return;
16045
- const nearestFunctionLike = findEnclosingFunction(node);
16842
+ const nearestFunctionLike = findEnclosingFunction$1(node);
16046
16843
  if (!nearestFunctionLike) return;
16047
16844
  if (isDeferredCallback(nearestFunctionLike)) return;
16048
16845
  if (isBindingUsedAsHandler(nearestFunctionLike)) return;
@@ -17039,7 +17836,7 @@ const isPersistentCacheSetWrite = (callExpression, newExpression, enclosingFunct
17039
17836
  return !isAstDescendant(receiverBinding.scopeOwner, enclosingFunction);
17040
17837
  };
17041
17838
  const isInsideCacheMemo = (node) => {
17042
- const enclosingFunction = findEnclosingFunction(node);
17839
+ const enclosingFunction = findEnclosingFunction$1(node);
17043
17840
  let child = node;
17044
17841
  let cursor = node.parent ?? null;
17045
17842
  while (cursor) {
@@ -17077,7 +17874,7 @@ const getFunctionName = (functionNode) => {
17077
17874
  return null;
17078
17875
  };
17079
17876
  const isUncacheableOptionsMergeUtility = (node) => {
17080
- const enclosingFunction = findEnclosingFunction(node);
17877
+ const enclosingFunction = findEnclosingFunction$1(node);
17081
17878
  if (!enclosingFunction || !isFunctionLike$2(enclosingFunction)) return false;
17082
17879
  const functionName = getFunctionName(enclosingFunction);
17083
17880
  if (!functionName || isComponentOrHookName(functionName)) return false;
@@ -17201,7 +17998,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
17201
17998
  ]);
17202
17999
  const STRING_PROTOTYPE_PATH = "String.prototype";
17203
18000
  const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
17204
- const getStaticStringValue = (argument) => {
18001
+ const getStaticStringValue$1 = (argument) => {
17205
18002
  if (!argument) return null;
17206
18003
  const unwrappedArgument = stripParenExpression(argument);
17207
18004
  if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
@@ -17209,7 +18006,7 @@ const getStaticStringValue = (argument) => {
17209
18006
  return null;
17210
18007
  };
17211
18008
  const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
17212
- if (flagsArgument) return getStaticStringValue(flagsArgument);
18009
+ if (flagsArgument) return getStaticStringValue$1(flagsArgument);
17213
18010
  if (!patternArgument) return "";
17214
18011
  const unwrappedPattern = stripParenExpression(patternArgument);
17215
18012
  if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
@@ -17268,13 +18065,6 @@ const isSafeStatefulReplaceAllSearch = (node, flags, context) => {
17268
18065
  const callee = stripParenExpression(replaceAllCall.callee);
17269
18066
  return isNodeOfType(callee, "MemberExpression") && !callee.computed && !callee.optional && !replaceAllCall.optional && isNodeOfType(callee.property, "Identifier") && callee.property.name === "replaceAll" && isProvenNativeStringReceiver(callee.object, context);
17270
18067
  };
17271
- const getDestructuredBindingPropertyName = (bindingIdentifier) => {
17272
- let bindingNode = bindingIdentifier;
17273
- if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
17274
- const property = bindingNode.parent;
17275
- if (!isNodeOfType(property, "Property") || property.value !== bindingNode || !isNodeOfType(property.parent, "ObjectPattern")) return null;
17276
- return getStaticPropertyKeyName(property, { allowComputedString: true });
17277
- };
17278
18068
  const extendGlobalPath = (basePath, propertyName) => {
17279
18069
  if (basePath === "global") {
17280
18070
  if (propertyName === null || GLOBAL_OBJECT_NAMES.has(propertyName)) return "global";
@@ -17359,7 +18149,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
17359
18149
  const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
17360
18150
  const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
17361
18151
  if (isSinglePropertyMutation) {
17362
- const propertyName = getStaticStringValue(node.arguments?.[1]);
18152
+ const propertyName = getStaticStringValue$1(node.arguments?.[1]);
17363
18153
  return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
17364
18154
  }
17365
18155
  if (!isPropertyCollectionMutation) return "none";
@@ -17388,7 +18178,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
17388
18178
  if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
17389
18179
  return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
17390
18180
  };
17391
- const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
18181
+ const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
17392
18182
  const jsHoistRegexp = defineRule({
17393
18183
  id: "js-hoist-regexp",
17394
18184
  title: "RegExp built inside a loop",
@@ -17405,7 +18195,7 @@ const jsHoistRegexp = defineRule({
17405
18195
  if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
17406
18196
  context.report({
17407
18197
  node,
17408
- message: MESSAGE$48
18198
+ message: MESSAGE$49
17409
18199
  });
17410
18200
  };
17411
18201
  return createLoopAwareVisitors({
@@ -18333,6 +19123,26 @@ const MEMBERSHIP_COMPARISON_OPERATORS = new Set([
18333
19123
  ]);
18334
19124
  const isNegativeOneLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && expression.argument.value === 1;
18335
19125
  const isZeroLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "Literal") && expression.value === 0;
19126
+ const isToIntegerOrInfinityZero = (value) => Number.isNaN(value) || Math.trunc(value) === 0;
19127
+ const isZeroFromIndex = (expression) => {
19128
+ if (!expression) return false;
19129
+ const strippedExpression = stripParenExpression(expression);
19130
+ if (isNodeOfType(strippedExpression, "Literal")) {
19131
+ if (typeof strippedExpression.value === "number") return isToIntegerOrInfinityZero(strippedExpression.value);
19132
+ if (typeof strippedExpression.value === "string") return isToIntegerOrInfinityZero(Number(strippedExpression.value));
19133
+ return strippedExpression.value === null || strippedExpression.value === false;
19134
+ }
19135
+ if (isNodeOfType(strippedExpression, "UnaryExpression") && strippedExpression.operator === "void") return true;
19136
+ if (isNodeOfType(strippedExpression, "UnaryExpression") && (strippedExpression.operator === "-" || strippedExpression.operator === "+") && isNodeOfType(strippedExpression.argument, "Literal") && typeof strippedExpression.argument.value === "number") return isToIntegerOrInfinityZero(strippedExpression.operator === "-" ? -strippedExpression.argument.value : strippedExpression.argument.value);
19137
+ if (isNodeOfType(strippedExpression, "Identifier")) return (strippedExpression.name === "undefined" || strippedExpression.name === "NaN") && findVariableInitializer(strippedExpression, strippedExpression.name) === null;
19138
+ return isNodeOfType(strippedExpression, "MemberExpression") && !strippedExpression.computed && isNodeOfType(strippedExpression.object, "Identifier") && strippedExpression.object.name === "Number" && isNodeOfType(strippedExpression.property, "Identifier") && strippedExpression.property.name === "NaN" && findVariableInitializer(strippedExpression.object, strippedExpression.object.name) === null;
19139
+ };
19140
+ const hasSemanticsPreservingIncludesArguments = (node) => {
19141
+ if (node.arguments.length === 1) return !isNodeOfType(node.arguments[0], "SpreadElement");
19142
+ if (node.arguments.length !== 2) return false;
19143
+ if (isNodeOfType(node.arguments[0], "SpreadElement") || isNodeOfType(node.arguments[1], "SpreadElement")) return false;
19144
+ return isZeroFromIndex(node.arguments[1]);
19145
+ };
18336
19146
  const PARENT_WRAPPER_TYPES = new Set([
18337
19147
  "ParenthesizedExpression",
18338
19148
  "ChainExpression",
@@ -18353,6 +19163,306 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
18353
19163
  if (isNegativeOneLiteral(otherOperand)) return true;
18354
19164
  return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
18355
19165
  };
19166
+ const getTypeAnnotation = (node) => {
19167
+ if (!node || !("typeAnnotation" in node)) return null;
19168
+ const annotation = node.typeAnnotation;
19169
+ if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
19170
+ return annotation.typeAnnotation;
19171
+ };
19172
+ const getDeclaredPropertyType = (members, propertyName) => {
19173
+ for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
19174
+ return null;
19175
+ };
19176
+ const getArrayElementType = (typeNode) => {
19177
+ if (!typeNode) return null;
19178
+ if (isNodeOfType(typeNode, "TSArrayType")) return typeNode.elementType;
19179
+ if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier") && (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray")) return typeNode.typeArguments?.params?.[0] ?? null;
19180
+ if (isNodeOfType(typeNode, "TSUnionType")) {
19181
+ const arrayElementTypes = typeNode.types.map(getArrayElementType).filter(Boolean);
19182
+ return arrayElementTypes.length === 1 ? arrayElementTypes[0] : null;
19183
+ }
19184
+ return null;
19185
+ };
19186
+ const getDestructuredDeclaredType = (identifier) => {
19187
+ const binding = findVariableInitializer(identifier, identifier.name);
19188
+ if (!binding) return null;
19189
+ const property = binding.bindingIdentifier.parent;
19190
+ const objectPattern = property?.parent;
19191
+ if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
19192
+ const propsType = getTypeAnnotation(objectPattern);
19193
+ if (!propsType) return null;
19194
+ if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
19195
+ if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
19196
+ const program = findProgramRoot(identifier);
19197
+ if (!program) return null;
19198
+ for (const statement of program.body) {
19199
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
19200
+ if (isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name) return getDeclaredPropertyType(declaration.body.body, property.key.name);
19201
+ if (isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name && isNodeOfType(declaration.typeAnnotation, "TSTypeLiteral")) return getDeclaredPropertyType(declaration.typeAnnotation.members ?? [], property.key.name);
19202
+ }
19203
+ return null;
19204
+ };
19205
+ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
19206
+ const binding = findVariableInitializer(identifier, identifier.name);
19207
+ if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
19208
+ visitedBindingIdentifiers.add(binding.bindingIdentifier);
19209
+ const directType = getTypeAnnotation(binding.bindingIdentifier);
19210
+ if (directType) return directType;
19211
+ const initializer = binding.initializer;
19212
+ if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
19213
+ const destructuredType = getDestructuredDeclaredType(identifier);
19214
+ if (destructuredType) return destructuredType;
19215
+ const declarator = binding.bindingIdentifier.parent;
19216
+ const declaration = declarator?.parent;
19217
+ const forOfStatement = declaration?.parent;
19218
+ if (isNodeOfType(declarator, "VariableDeclarator") && isNodeOfType(declaration, "VariableDeclaration") && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isNodeOfType(forOfStatement.right, "Identifier")) return getArrayElementType(getIdentifierDeclaredType(forOfStatement.right, visitedBindingIdentifiers));
19219
+ return null;
19220
+ };
19221
+ const isNativeIterationIndex = (identifier) => {
19222
+ const binding = findVariableInitializer(identifier, identifier.name);
19223
+ if (!binding) return false;
19224
+ const callback = binding.bindingIdentifier.parent;
19225
+ if (!isInlineFunctionExpression(callback)) return false;
19226
+ const callbackCall = callback.parent;
19227
+ if (!isNodeOfType(callbackCall, "CallExpression") || !isIterationCallbackCall(callbackCall) || !isNodeOfType(callbackCall.callee, "MemberExpression") || !isNodeOfType(callbackCall.callee.property, "Identifier")) return false;
19228
+ const indexParameterPosition = callbackCall.callee.property.name === "reduce" || callbackCall.callee.property.name === "reduceRight" ? 2 : 1;
19229
+ return callback.params?.[indexParameterPosition] === binding.bindingIdentifier;
19230
+ };
19231
+ const hasSameIdentifierBinding = (leftIdentifier, rightIdentifier) => {
19232
+ if (leftIdentifier.name !== rightIdentifier.name) return false;
19233
+ const leftBinding = findVariableInitializer(leftIdentifier, leftIdentifier.name);
19234
+ const rightBinding = findVariableInitializer(rightIdentifier, rightIdentifier.name);
19235
+ return Boolean(leftBinding && rightBinding && leftBinding.bindingIdentifier === rightBinding.bindingIdentifier);
19236
+ };
19237
+ const NON_NAN_RELATIONAL_OPERATORS = new Set([
19238
+ "<",
19239
+ "<=",
19240
+ ">",
19241
+ ">="
19242
+ ]);
19243
+ const testProvesIdentifierIsNotNaN = (test, identifier) => {
19244
+ if (!test) return false;
19245
+ const strippedTest = stripParenExpression(test);
19246
+ if (isNodeOfType(strippedTest, "LogicalExpression") && strippedTest.operator === "&&") return testProvesIdentifierIsNotNaN(strippedTest.left, identifier) || testProvesIdentifierIsNotNaN(strippedTest.right, identifier);
19247
+ if (!isNodeOfType(strippedTest, "BinaryExpression") || !NON_NAN_RELATIONAL_OPERATORS.has(strippedTest.operator)) return false;
19248
+ return isNodeOfType(strippedTest.left, "Identifier") && hasSameIdentifierBinding(strippedTest.left, identifier) || isNodeOfType(strippedTest.right, "Identifier") && hasSameIdentifierBinding(strippedTest.right, identifier);
19249
+ };
19250
+ const writeTargetContainsIdentifierBinding = (writeTarget, identifier) => {
19251
+ let containsBinding = false;
19252
+ walkAst(writeTarget, (child) => {
19253
+ if (isNodeOfType(child, "Identifier") && hasSameIdentifierBinding(child, identifier)) {
19254
+ containsBinding = true;
19255
+ return false;
19256
+ }
19257
+ });
19258
+ return containsBinding;
19259
+ };
19260
+ const hasWriteBeforeQuery = (body, identifier) => {
19261
+ const queryStart = getRangeStart(identifier);
19262
+ if (queryStart === null) return true;
19263
+ let hasEarlierWrite = false;
19264
+ walkAst(body, (child) => {
19265
+ if (hasEarlierWrite) return false;
19266
+ const childStart = getRangeStart(child);
19267
+ if (childStart !== null && childStart >= queryStart) return false;
19268
+ if (child !== body && isFunctionLike$2(child)) return false;
19269
+ const writeTarget = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement") ? child.left : null;
19270
+ if (writeTarget && writeTargetContainsIdentifierBinding(writeTarget, identifier)) {
19271
+ hasEarlierWrite = true;
19272
+ return false;
19273
+ }
19274
+ });
19275
+ return hasEarlierWrite;
19276
+ };
19277
+ const isProtectedByRelationalLoopGuard = (identifier) => {
19278
+ let descendant = identifier;
19279
+ let ancestor = identifier.parent;
19280
+ while (ancestor) {
19281
+ if (isFunctionLike$2(ancestor)) return false;
19282
+ if ((isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "WhileStatement")) && ancestor.body === descendant && testProvesIdentifierIsNotNaN(ancestor.test, identifier)) return !hasWriteBeforeQuery(ancestor.body, identifier);
19283
+ descendant = ancestor;
19284
+ ancestor = ancestor.parent;
19285
+ }
19286
+ return false;
19287
+ };
19288
+ const isKnownSafeIndexOfQuery = (query) => {
19289
+ if (!query) return false;
19290
+ const strippedQuery = stripParenExpression(query);
19291
+ if (isNodeOfType(strippedQuery, "Literal")) return typeof strippedQuery.value !== "number" || Number.isFinite(strippedQuery.value);
19292
+ if (!isNodeOfType(strippedQuery, "Identifier")) return false;
19293
+ if (isNativeIterationIndex(strippedQuery)) return true;
19294
+ return isProtectedByRelationalLoopGuard(strippedQuery);
19295
+ };
19296
+ const findSameFileTypeAlias = (reference, typeName) => {
19297
+ const program = findProgramRoot(reference);
19298
+ if (!program) return null;
19299
+ for (const statement of program.body) {
19300
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
19301
+ if (declaration && isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
19302
+ }
19303
+ return null;
19304
+ };
19305
+ const hasDeclaredMembershipMethod = (members, methodName) => members.some((member) => (isNodeOfType(member, "TSMethodSignature") || isNodeOfType(member, "TSPropertySignature")) && isNodeOfType(member.key, "Identifier") && member.key.name === methodName);
19306
+ const isKnownUserlandMembershipReceiver = (receiver, methodName) => {
19307
+ if (!isNodeOfType(receiver, "Identifier")) return false;
19308
+ const declaredType = getIdentifierDeclaredType(receiver);
19309
+ if (!declaredType) return false;
19310
+ if (isNodeOfType(declaredType, "TSTypeLiteral")) return hasDeclaredMembershipMethod(declaredType.members ?? [], methodName);
19311
+ if (!isNodeOfType(declaredType, "TSTypeReference") || !isNodeOfType(declaredType.typeName, "Identifier")) return false;
19312
+ const typeAlias = findSameFileTypeAlias(receiver, declaredType.typeName.name);
19313
+ if (typeAlias && isNodeOfType(typeAlias.typeAnnotation, "TSTypeLiteral")) return hasDeclaredMembershipMethod(typeAlias.typeAnnotation.members ?? [], methodName);
19314
+ const program = findProgramRoot(receiver);
19315
+ if (!program) return false;
19316
+ for (const statement of program.body) {
19317
+ const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
19318
+ if (declaration && isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === declaredType.typeName.name) return hasDeclaredMembershipMethod(declaration.body.body, methodName);
19319
+ }
19320
+ return false;
19321
+ };
19322
+ const findTypeParameter = (reference, typeName) => {
19323
+ let ancestor = reference.parent;
19324
+ while (ancestor) {
19325
+ if (isFunctionLike$2(ancestor) || isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) {
19326
+ const matchingTypeParameter = ancestor.typeParameters?.params?.find((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeName);
19327
+ if (matchingTypeParameter && isNodeOfType(matchingTypeParameter, "TSTypeParameter")) return matchingTypeParameter;
19328
+ }
19329
+ ancestor = ancestor.parent;
19330
+ }
19331
+ return null;
19332
+ };
19333
+ const POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES = new Set(["NonNullable", "PropertyKey"]);
19334
+ const buildTypeAliasArguments = (typeAlias, typeReference, inheritedArguments) => {
19335
+ const typeArguments = new Map(inheritedArguments);
19336
+ for (const [index, typeParameter] of (typeAlias.typeParameters?.params ?? []).entries()) {
19337
+ if (!isNodeOfType(typeParameter, "TSTypeParameter")) continue;
19338
+ if (!isNodeOfType(typeParameter.name, "Identifier")) continue;
19339
+ const argument = typeReference.typeArguments?.params?.[index] ?? typeParameter.default;
19340
+ if (argument) typeArguments.set(typeParameter.name.name, argument);
19341
+ }
19342
+ return typeArguments;
19343
+ };
19344
+ const typeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
19345
+ if (!typeNode) return false;
19346
+ if (isNodeOfType(typeNode, "TSNumberKeyword") || isNodeOfType(typeNode, "TSAnyKeyword") || isNodeOfType(typeNode, "TSUnknownKeyword")) return true;
19347
+ if (isNodeOfType(typeNode, "TSStringKeyword") || isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSBigIntKeyword") || isNodeOfType(typeNode, "TSSymbolKeyword") || isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSObjectKeyword") || isNodeOfType(typeNode, "TSLiteralType") || isNodeOfType(typeNode, "TSFunctionType")) return false;
19348
+ if (isNodeOfType(typeNode, "TSTypeLiteral")) return (typeNode.members?.length ?? 0) === 0;
19349
+ if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "keyof") return false;
19350
+ if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => typeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
19351
+ if (isNodeOfType(typeNode, "TSOptionalType") || isNodeOfType(typeNode, "TSRestType")) return typeCanHaveSameValueZeroDifference(typeNode.typeAnnotation, reference, activeTypeNodes, typeArguments);
19352
+ if (isNodeOfType(typeNode, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
19353
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return true;
19354
+ if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
19355
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
19356
+ if (substitutedType) {
19357
+ const remainingArguments = new Map(typeArguments);
19358
+ remainingArguments.delete(typeNode.typeName.name);
19359
+ return typeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
19360
+ }
19361
+ const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
19362
+ if (typeParameter) {
19363
+ if (!typeParameter.constraint || activeTypeNodes.has(typeParameter)) return true;
19364
+ activeTypeNodes.add(typeParameter);
19365
+ const constraintCanDiffer = typeCanHaveSameValueZeroDifference(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
19366
+ activeTypeNodes.delete(typeParameter);
19367
+ return constraintCanDiffer;
19368
+ }
19369
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
19370
+ if (!typeAlias) return POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES.has(typeNode.typeName.name) || /(?:number|numeric)/i.test(typeNode.typeName.name);
19371
+ if (activeTypeNodes.has(typeAlias)) return true;
19372
+ activeTypeNodes.add(typeAlias);
19373
+ const canDiffer = typeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
19374
+ activeTypeNodes.delete(typeAlias);
19375
+ return canDiffer;
19376
+ };
19377
+ const arrayTypeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
19378
+ if (!typeNode) return false;
19379
+ if (isNodeOfType(typeNode, "TSArrayType")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
19380
+ if (isNodeOfType(typeNode, "TSTupleType")) return typeNode.elementTypes.some((elementType) => {
19381
+ if (isNodeOfType(elementType, "TSRestType")) return arrayTypeCanHaveSameValueZeroDifference(elementType.typeAnnotation, reference, activeTypeNodes, typeArguments);
19382
+ if (isNodeOfType(elementType, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(elementType.elementType, reference, activeTypeNodes, typeArguments);
19383
+ return typeCanHaveSameValueZeroDifference(elementType, reference, activeTypeNodes, typeArguments);
19384
+ });
19385
+ if (isNodeOfType(typeNode, "TSTypeOperator")) return arrayTypeCanHaveSameValueZeroDifference(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
19386
+ if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => arrayTypeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
19387
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
19388
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
19389
+ if (substitutedType) {
19390
+ const remainingArguments = new Map(typeArguments);
19391
+ remainingArguments.delete(typeNode.typeName.name);
19392
+ return arrayTypeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
19393
+ }
19394
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return typeCanHaveSameValueZeroDifference(typeNode.typeArguments?.params?.[0] ?? null, reference, activeTypeNodes, typeArguments);
19395
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
19396
+ if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
19397
+ activeTypeNodes.add(typeAlias);
19398
+ const canDiffer = arrayTypeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
19399
+ activeTypeNodes.delete(typeAlias);
19400
+ return canDiffer;
19401
+ };
19402
+ const isKnownArrayType = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
19403
+ if (!typeNode) return false;
19404
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return true;
19405
+ if (isNodeOfType(typeNode, "TSTypeOperator")) return isKnownArrayType(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
19406
+ if (isNodeOfType(typeNode, "TSUnionType")) {
19407
+ const nonNullishTypes = typeNode.types.filter((memberType) => !isNodeOfType(memberType, "TSNullKeyword") && !isNodeOfType(memberType, "TSUndefinedKeyword"));
19408
+ return nonNullishTypes.length > 0 && nonNullishTypes.every((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
19409
+ }
19410
+ if (isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
19411
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
19412
+ const substitutedType = typeArguments.get(typeNode.typeName.name);
19413
+ if (substitutedType) {
19414
+ const remainingArguments = new Map(typeArguments);
19415
+ remainingArguments.delete(typeNode.typeName.name);
19416
+ return isKnownArrayType(substitutedType, reference, activeTypeNodes, remainingArguments);
19417
+ }
19418
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return true;
19419
+ const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
19420
+ if (typeParameter?.constraint) {
19421
+ if (activeTypeNodes.has(typeParameter)) return false;
19422
+ activeTypeNodes.add(typeParameter);
19423
+ const isConstraintArray = isKnownArrayType(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
19424
+ activeTypeNodes.delete(typeParameter);
19425
+ return isConstraintArray;
19426
+ }
19427
+ const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
19428
+ if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
19429
+ activeTypeNodes.add(typeAlias);
19430
+ const isArray = isKnownArrayType(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
19431
+ activeTypeNodes.delete(typeAlias);
19432
+ return isArray;
19433
+ };
19434
+ const isKnownNativeArrayReceiver = (receiver) => {
19435
+ if (isNodeOfType(receiver, "ArrayExpression")) return true;
19436
+ if (isNodeOfType(receiver, "Identifier") && isKnownArrayType(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set())) return true;
19437
+ const initializer = getResolvedInitializer(receiver)?.initializer;
19438
+ if (!initializer) return false;
19439
+ const strippedInitializer = stripParenExpression(initializer);
19440
+ if (isNodeOfType(strippedInitializer, "ArrayExpression")) return true;
19441
+ if (isNodeOfType(strippedInitializer, "NewExpression") && isNodeOfType(strippedInitializer.callee, "Identifier") && strippedInitializer.callee.name === "Array" && findVariableInitializer(strippedInitializer.callee, strippedInitializer.callee.name) === null) return true;
19442
+ return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
19443
+ };
19444
+ const isKnownDenseArrayReceiver = (receiver) => {
19445
+ const initializer = isNodeOfType(receiver, "Identifier") ? getResolvedInitializer(receiver)?.initializer : receiver;
19446
+ if (!initializer) return false;
19447
+ const strippedInitializer = stripParenExpression(initializer);
19448
+ if (isNodeOfType(strippedInitializer, "ArrayExpression")) return (strippedInitializer.elements ?? []).every((element) => element !== null && !isNodeOfType(element, "SpreadElement"));
19449
+ return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
19450
+ };
19451
+ const isKnownUnsafeIndexOfQuery = (query, receiver) => {
19452
+ if (!query) return true;
19453
+ const strippedQuery = stripParenExpression(query);
19454
+ if (isNodeOfType(strippedQuery, "Identifier")) {
19455
+ if (strippedQuery.name === "undefined" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return !isKnownDenseArrayReceiver(receiver);
19456
+ if (strippedQuery.name === "NaN" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return true;
19457
+ return typeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(strippedQuery), strippedQuery, /* @__PURE__ */ new Set());
19458
+ }
19459
+ if (isNodeOfType(strippedQuery, "MemberExpression")) return !strippedQuery.computed && isNodeOfType(strippedQuery.object, "Identifier") && strippedQuery.object.name === "Number" && isNodeOfType(strippedQuery.property, "Identifier") && strippedQuery.property.name === "NaN" && findVariableInitializer(strippedQuery.object, strippedQuery.object.name) === null;
19460
+ return true;
19461
+ };
19462
+ const isKnownUnsafeIndexOfReceiver = (receiver) => {
19463
+ if (!isNodeOfType(receiver, "Identifier")) return false;
19464
+ return arrayTypeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set());
19465
+ };
18356
19466
  const ITERATION_CALLBACK_METHOD_NAMES = new Set([
18357
19467
  "forEach",
18358
19468
  "map",
@@ -18481,16 +19591,22 @@ const jsSetMapLookups = defineRule({
18481
19591
  if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
18482
19592
  const methodName = node.callee.property.name;
18483
19593
  if (methodName !== "includes" && methodName !== "indexOf") return;
18484
- if (methodName === "indexOf" && !isIndexOfResultUsedAsMembershipTest(node)) return;
19594
+ if (methodName === "includes" && !hasSemanticsPreservingIncludesArguments(node)) return;
19595
+ if (methodName === "indexOf" && (node.arguments.length !== 1 || isNodeOfType(node.arguments[0], "SpreadElement") || !isIndexOfResultUsedAsMembershipTest(node))) return;
18485
19596
  const rawReceiver = node.callee.object;
18486
19597
  if (!rawReceiver) return;
18487
19598
  const receiver = stripParenExpression(rawReceiver);
19599
+ const isKnownNativeArray = isKnownNativeArrayReceiver(receiver);
19600
+ if (isKnownUserlandMembershipReceiver(receiver, methodName)) return;
19601
+ if (methodName === "includes" && node.arguments.length === 2 && !isKnownNativeArray) return;
19602
+ const query = node.arguments[0];
19603
+ if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
18488
19604
  if (isLikelyStringReceiver(receiver)) return;
18489
19605
  if (isSmallInlineLiteralArray(receiver)) return;
18490
19606
  if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
18491
19607
  if (isSmallFixedListMember(receiver)) return;
18492
- if (isSubstringSearchLiteral(node.arguments?.[0])) return;
18493
- if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
19608
+ if (isSubstringSearchLiteral(query)) return;
19609
+ if (isIndexedArrayElementWithStringArgument(receiver, query)) return;
18494
19610
  const resolvedInitializer = getResolvedInitializer(receiver);
18495
19611
  if (resolvedInitializer) {
18496
19612
  if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
@@ -19699,7 +20815,7 @@ const jsxMaxDepth = defineRule({
19699
20815
  });
19700
20816
  //#endregion
19701
20817
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
19702
- const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
20818
+ const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
19703
20819
  const LITERAL_TEXT_TAGS = new Set([
19704
20820
  "code",
19705
20821
  "pre",
@@ -19769,7 +20885,7 @@ const jsxNoCommentTextnodes = defineRule({
19769
20885
  if (isDeliberateStyledCommentToken(node)) return;
19770
20886
  context.report({
19771
20887
  node,
19772
- message: MESSAGE$47
20888
+ message: MESSAGE$48
19773
20889
  });
19774
20890
  } })
19775
20891
  });
@@ -19800,7 +20916,7 @@ const isInsideFunctionScope = (node) => {
19800
20916
  };
19801
20917
  //#endregion
19802
20918
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
19803
- const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
20919
+ const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
19804
20920
  const CONTEXT_MODULES$1 = [
19805
20921
  "react",
19806
20922
  "use-context-selector",
@@ -19898,7 +21014,7 @@ const jsxNoConstructedContextValues = defineRule({
19898
21014
  if (!isConstructedValue(innerExpression)) continue;
19899
21015
  context.report({
19900
21016
  node: attribute,
19901
- message: MESSAGE$46
21017
+ message: MESSAGE$47
19902
21018
  });
19903
21019
  }
19904
21020
  }
@@ -20753,7 +21869,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
20753
21869
  ];
20754
21870
  //#endregion
20755
21871
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
20756
- const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
21872
+ const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
20757
21873
  const isDataArrayPropName = (propName) => {
20758
21874
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
20759
21875
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -20840,7 +21956,7 @@ const jsxNoNewArrayAsProp = defineRule({
20840
21956
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
20841
21957
  context.report({
20842
21958
  node,
20843
- message: MESSAGE$45
21959
+ message: MESSAGE$46
20844
21960
  });
20845
21961
  }
20846
21962
  };
@@ -21098,7 +22214,7 @@ const SAFE_RECEIVER_NAMES = new Set([
21098
22214
  ]);
21099
22215
  //#endregion
21100
22216
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
21101
- const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
22217
+ const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
21102
22218
  const isAccessorPredicateName = (propName) => {
21103
22219
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
21104
22220
  if (propName.length <= prefix.length) continue;
@@ -21304,7 +22420,7 @@ const jsxNoNewFunctionAsProp = defineRule({
21304
22420
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
21305
22421
  context.report({
21306
22422
  node,
21307
- message: MESSAGE$44
22423
+ message: MESSAGE$45
21308
22424
  });
21309
22425
  }
21310
22426
  };
@@ -21524,7 +22640,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
21524
22640
  ];
21525
22641
  //#endregion
21526
22642
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
21527
- const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
22643
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
21528
22644
  const isConfigObjectPropName = (propName) => {
21529
22645
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
21530
22646
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -21613,7 +22729,7 @@ const jsxNoNewObjectAsProp = defineRule({
21613
22729
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
21614
22730
  context.report({
21615
22731
  node,
21616
- message: MESSAGE$43
22732
+ message: MESSAGE$44
21617
22733
  });
21618
22734
  }
21619
22735
  };
@@ -21621,7 +22737,7 @@ const jsxNoNewObjectAsProp = defineRule({
21621
22737
  });
21622
22738
  //#endregion
21623
22739
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
21624
- const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
22740
+ const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
21625
22741
  const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
21626
22742
  const resolveSettings$29 = (settings) => {
21627
22743
  const reactDoctor = settings?.["react-doctor"];
@@ -21659,7 +22775,7 @@ const jsxNoScriptUrl = defineRule({
21659
22775
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
21660
22776
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
21661
22777
  node: attribute,
21662
- message: MESSAGE$42
22778
+ message: MESSAGE$43
21663
22779
  });
21664
22780
  }
21665
22781
  } };
@@ -22279,7 +23395,7 @@ const jsxPropsNoSpreadMulti = defineRule({
22279
23395
  });
22280
23396
  //#endregion
22281
23397
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
22282
- const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
23398
+ const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
22283
23399
  const resolveSettings$25 = (settings) => {
22284
23400
  const reactDoctor = settings?.["react-doctor"];
22285
23401
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -22322,7 +23438,7 @@ const jsxPropsNoSpreading = defineRule({
22322
23438
  }
22323
23439
  context.report({
22324
23440
  node: attribute,
22325
- message: MESSAGE$41
23441
+ message: MESSAGE$42
22326
23442
  });
22327
23443
  didReportInFile = true;
22328
23444
  return;
@@ -22598,7 +23714,7 @@ const labelHasAssociatedControl = defineRule({
22598
23714
  });
22599
23715
  //#endregion
22600
23716
  //#region src/plugin/rules/a11y/lang.ts
22601
- const MESSAGE$40 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
23717
+ const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
22602
23718
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
22603
23719
  "aa",
22604
23720
  "ab",
@@ -23078,7 +24194,7 @@ const lang = defineRule({
23078
24194
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
23079
24195
  context.report({
23080
24196
  node: langAttr,
23081
- message: MESSAGE$40
24197
+ message: MESSAGE$41
23082
24198
  });
23083
24199
  return;
23084
24200
  }
@@ -23087,7 +24203,7 @@ const lang = defineRule({
23087
24203
  if (value === null) return;
23088
24204
  if (!isValidLangTag(value)) context.report({
23089
24205
  node: langAttr,
23090
- message: MESSAGE$40
24206
+ message: MESSAGE$41
23091
24207
  });
23092
24208
  } })
23093
24209
  });
@@ -23138,7 +24254,7 @@ const mdxSsrExecutionRisk = defineRule({
23138
24254
  });
23139
24255
  //#endregion
23140
24256
  //#region src/plugin/rules/a11y/media-has-caption.ts
23141
- const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
24257
+ const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
23142
24258
  const DEFAULT_AUDIO = ["audio"];
23143
24259
  const DEFAULT_VIDEO = ["video"];
23144
24260
  const DEFAULT_TRACK = ["track"];
@@ -23227,7 +24343,7 @@ const mediaHasCaption = defineRule({
23227
24343
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
23228
24344
  context.report({
23229
24345
  node: node.name,
23230
- message: MESSAGE$39
24346
+ message: MESSAGE$40
23231
24347
  });
23232
24348
  return;
23233
24349
  }
@@ -23246,7 +24362,7 @@ const mediaHasCaption = defineRule({
23246
24362
  return kindValue.value.toLowerCase() === "captions";
23247
24363
  })) context.report({
23248
24364
  node: node.name,
23249
- message: MESSAGE$39
24365
+ message: MESSAGE$40
23250
24366
  });
23251
24367
  } };
23252
24368
  }
@@ -24789,7 +25905,7 @@ const nextjsNoVercelOgImport = defineRule({
24789
25905
  });
24790
25906
  //#endregion
24791
25907
  //#region src/plugin/rules/a11y/no-access-key.ts
24792
- const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
25908
+ const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
24793
25909
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
24794
25910
  const noAccessKey = defineRule({
24795
25911
  id: "no-access-key",
@@ -24808,7 +25924,7 @@ const noAccessKey = defineRule({
24808
25924
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
24809
25925
  context.report({
24810
25926
  node: accessKey,
24811
- message: MESSAGE$38
25927
+ message: MESSAGE$39
24812
25928
  });
24813
25929
  return;
24814
25930
  }
@@ -24818,7 +25934,7 @@ const noAccessKey = defineRule({
24818
25934
  if (isUndefinedIdentifier(expression)) return;
24819
25935
  context.report({
24820
25936
  node: accessKey,
24821
- message: MESSAGE$38
25937
+ message: MESSAGE$39
24822
25938
  });
24823
25939
  }
24824
25940
  } };
@@ -25123,8 +26239,11 @@ const unwrapUseCallback = (node) => {
25123
26239
  const callee = node.callee;
25124
26240
  return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
25125
26241
  };
26242
+ const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
25126
26243
  const resolveToFunction = (ref) => {
25127
- const definitionNode = ref.resolved?.defs[0]?.node;
26244
+ const definition = ref.resolved?.defs[0];
26245
+ if (!definition || hasParameterDefinition(ref)) return null;
26246
+ const definitionNode = definition.node;
25128
26247
  if (!definitionNode) return null;
25129
26248
  if (isFunctionLike$2(definitionNode)) return definitionNode;
25130
26249
  if (isNodeOfType(definitionNode, "VariableDeclarator")) {
@@ -25534,7 +26653,7 @@ const isCleanupReturnArgument = (analysis, node) => {
25534
26653
  if (isNodeOfType(node, "MemberExpression")) return true;
25535
26654
  if (isNodeOfType(node, "Identifier")) {
25536
26655
  const ref = getRef(analysis, node);
25537
- if (ref && resolveToFunction(ref)) return true;
26656
+ if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
25538
26657
  }
25539
26658
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
25540
26659
  return false;
@@ -25692,7 +26811,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
25692
26811
  };
25693
26812
  const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
25694
26813
  if (!setterRef.resolved) return false;
25695
- const componentFunction = findEnclosingFunction(effectNode);
26814
+ const componentFunction = findEnclosingFunction$1(effectNode);
25696
26815
  if (!componentFunction) return false;
25697
26816
  for (const reference of setterRef.resolved.references) {
25698
26817
  if (reference.init) continue;
@@ -26006,7 +27125,7 @@ const resolveWrappedCallable = (analysis, node) => {
26006
27125
  if (isNodeOfType(candidate, "Identifier")) {
26007
27126
  const reference = getRef(analysis, candidate);
26008
27127
  if (!reference) return null;
26009
- if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
27128
+ if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
26010
27129
  const resolved = resolveToFunction(reference);
26011
27130
  if (resolved) return resolved;
26012
27131
  const definitionNode = reference.resolved?.defs[0]?.node;
@@ -26670,7 +27789,7 @@ const noAdjustStateOnPropChange = defineRule({
26670
27789
  });
26671
27790
  //#endregion
26672
27791
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
26673
- const MESSAGE$37 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
27792
+ const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
26674
27793
  const ALWAYS_FOCUSABLE_TAGS = new Set([
26675
27794
  "button",
26676
27795
  "embed",
@@ -26782,7 +27901,7 @@ const noAriaHiddenOnFocusable = defineRule({
26782
27901
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
26783
27902
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
26784
27903
  node: ariaHidden,
26785
- message: MESSAGE$37
27904
+ message: MESSAGE$38
26786
27905
  });
26787
27906
  } })
26788
27907
  });
@@ -27704,7 +28823,7 @@ const isAllLiteralArrayExpression = (node) => {
27704
28823
  };
27705
28824
  //#endregion
27706
28825
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
27707
- const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
28826
+ const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
27708
28827
  const SECOND_INDEX_METHODS = new Set([
27709
28828
  "every",
27710
28829
  "filter",
@@ -27823,14 +28942,14 @@ const noArrayIndexKey = defineRule({
27823
28942
  if (propName !== "key") continue;
27824
28943
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
27825
28944
  node: property,
27826
- message: MESSAGE$36
28945
+ message: MESSAGE$37
27827
28946
  });
27828
28947
  }
27829
28948
  } })
27830
28949
  });
27831
28950
  //#endregion
27832
28951
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
27833
- const MESSAGE$35 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
28952
+ const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
27834
28953
  const noAsyncEffectCallback = defineRule({
27835
28954
  id: "no-async-effect-callback",
27836
28955
  title: "Async effect callback",
@@ -27848,13 +28967,13 @@ const noAsyncEffectCallback = defineRule({
27848
28967
  if (!callback.async) return;
27849
28968
  context.report({
27850
28969
  node: callback,
27851
- message: MESSAGE$35
28970
+ message: MESSAGE$36
27852
28971
  });
27853
28972
  } })
27854
28973
  });
27855
28974
  //#endregion
27856
28975
  //#region src/plugin/rules/a11y/no-autofocus.ts
27857
- const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
28976
+ const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
27858
28977
  const resolveSettings$21 = (settings) => {
27859
28978
  const reactDoctor = settings?.["react-doctor"];
27860
28979
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -27951,7 +29070,7 @@ const noAutofocus = defineRule({
27951
29070
  if (isConditionallyRendered(node)) return;
27952
29071
  context.report({
27953
29072
  node: autoFocusAttribute,
27954
- message: MESSAGE$34
29073
+ message: MESSAGE$35
27955
29074
  });
27956
29075
  } };
27957
29076
  }
@@ -28465,138 +29584,6 @@ const noBarrelImport = defineRule({
28465
29584
  }
28466
29585
  });
28467
29586
  //#endregion
28468
- //#region src/plugin/utils/has-static-property-write-before.ts
28469
- const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
28470
- const CONDITIONAL_EXECUTION_NODE_TYPES = new Set([
28471
- "CatchClause",
28472
- "ConditionalExpression",
28473
- "DoWhileStatement",
28474
- "ForInStatement",
28475
- "ForOfStatement",
28476
- "ForStatement",
28477
- "IfStatement",
28478
- "LogicalExpression",
28479
- "SwitchCase",
28480
- "SwitchStatement",
28481
- "TryStatement",
28482
- "WhileStatement"
28483
- ]);
28484
- const getResolvedStaticPropertyName = (memberExpression, scopes) => {
28485
- if (!isNodeOfType(memberExpression, "MemberExpression")) return null;
28486
- const directPropertyName = getStaticPropertyName(memberExpression);
28487
- if (directPropertyName || !memberExpression.computed) return directPropertyName;
28488
- const property = stripParenExpression(memberExpression.property);
28489
- if (!isNodeOfType(property, "Identifier")) return null;
28490
- const propertySymbol = resolveConstIdentifierAlias(property, scopes);
28491
- const initializer = propertySymbol?.initializer ? stripParenExpression(propertySymbol.initializer) : null;
28492
- return initializer && isNodeOfType(initializer, "Literal") && typeof initializer.value === "string" ? initializer.value : null;
28493
- };
28494
- const collectScopeSymbols = (scope, symbols) => {
28495
- symbols.push(...scope.symbols);
28496
- for (const childScope of scope.children) collectScopeSymbols(childScope, symbols);
28497
- };
28498
- const getEquivalentSymbols = (identifier, scopes) => {
28499
- const rootSymbol = resolveConstIdentifierAlias(identifier, scopes);
28500
- if (!rootSymbol) return [];
28501
- let symbolsByRootId = equivalentSymbolsByAnalysis.get(scopes);
28502
- if (!symbolsByRootId) {
28503
- symbolsByRootId = /* @__PURE__ */ new Map();
28504
- equivalentSymbolsByAnalysis.set(scopes, symbolsByRootId);
28505
- }
28506
- const cachedSymbols = symbolsByRootId.get(rootSymbol.id);
28507
- if (cachedSymbols) return cachedSymbols;
28508
- const allSymbols = [];
28509
- collectScopeSymbols(scopes.rootScope, allSymbols);
28510
- const equivalentSymbols = allSymbols.filter((symbol) => resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes)?.id === rootSymbol.id);
28511
- symbolsByRootId.set(rootSymbol.id, equivalentSymbols);
28512
- return equivalentSymbols;
28513
- };
28514
- const findExecutionBoundary = (node) => {
28515
- let current = node;
28516
- while (current) {
28517
- if (isFunctionLike$2(current) || isNodeOfType(current, "Program")) return current;
28518
- current = current.parent ?? null;
28519
- }
28520
- return null;
28521
- };
28522
- const isOnUnconditionalPath = (node, boundary) => {
28523
- let current = node.parent ?? null;
28524
- while (current && current !== boundary) {
28525
- if (CONDITIONAL_EXECUTION_NODE_TYPES.has(current.type)) return false;
28526
- current = current.parent ?? null;
28527
- }
28528
- return current === boundary;
28529
- };
28530
- const findFunctionBindingIdentifier = (functionNode) => {
28531
- if (isNodeOfType(functionNode, "FunctionDeclaration")) return functionNode.id ?? null;
28532
- const parent = functionNode.parent;
28533
- if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
28534
- return null;
28535
- };
28536
- const findDirectCall = (identifier) => {
28537
- let callee = identifier;
28538
- let parent = callee.parent;
28539
- while (parent && stripParenExpression(parent) === identifier) {
28540
- callee = parent;
28541
- parent = callee.parent;
28542
- }
28543
- return parent && isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
28544
- };
28545
- const isFunctionSynchronouslyInvokedBefore = (functionNode, referenceNode, scopes, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
28546
- if (visitedFunctionNodes.has(functionNode) || !isFunctionLike$2(functionNode) || functionNode.generator) return false;
28547
- visitedFunctionNodes.add(functionNode);
28548
- const referenceBoundary = findExecutionBoundary(referenceNode);
28549
- if (!referenceBoundary) return false;
28550
- const invocationCalls = [];
28551
- const bindingIdentifier = findFunctionBindingIdentifier(functionNode);
28552
- if (bindingIdentifier) for (const symbol of getEquivalentSymbols(bindingIdentifier, scopes)) for (const reference of symbol.references) {
28553
- const call = findDirectCall(reference.identifier);
28554
- if (call) invocationCalls.push(call);
28555
- }
28556
- else {
28557
- const call = findDirectCall(functionNode);
28558
- if (call) invocationCalls.push(call);
28559
- }
28560
- return invocationCalls.some((call) => {
28561
- if (call.range[0] >= referenceNode.range[0]) return false;
28562
- const callBoundary = findExecutionBoundary(call);
28563
- if (!callBoundary) return false;
28564
- if (callBoundary === referenceBoundary) return true;
28565
- if (!isFunctionLike$2(callBoundary)) return false;
28566
- return isFunctionSynchronouslyInvokedBefore(callBoundary, referenceNode, scopes, new Set(visitedFunctionNodes));
28567
- });
28568
- };
28569
- const isMemberWriteTarget = (memberExpression) => {
28570
- const parent = memberExpression.parent;
28571
- if (!parent) return false;
28572
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === memberExpression;
28573
- if (isNodeOfType(parent, "UpdateExpression")) return parent.argument === memberExpression;
28574
- return isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === memberExpression;
28575
- };
28576
- const symbolHasStaticPropertyWriteBefore = (symbol, propertyName, referenceNode, scopes) => symbol.references.some((reference) => {
28577
- let parent = reference.identifier.parent;
28578
- while (parent && stripParenExpression(parent) === reference.identifier) parent = parent.parent;
28579
- if (!parent || !isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== reference.identifier || getResolvedStaticPropertyName(parent, scopes) !== propertyName || !isMemberWriteTarget(parent)) return false;
28580
- const writeBoundary = findExecutionBoundary(parent);
28581
- const referenceBoundary = findExecutionBoundary(referenceNode);
28582
- if (!writeBoundary || !referenceBoundary || !isOnUnconditionalPath(parent, writeBoundary)) return false;
28583
- if (writeBoundary === referenceBoundary) return parent.range[0] < referenceNode.range[0];
28584
- return isFunctionLike$2(writeBoundary) && isFunctionSynchronouslyInvokedBefore(writeBoundary, referenceNode, scopes);
28585
- });
28586
- const hasStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
28587
- if (!isNodeOfType(identifier, "Identifier")) return false;
28588
- return getEquivalentSymbols(identifier, scopes).some((symbol) => symbolHasStaticPropertyWriteBefore(symbol, propertyName, referenceNode, scopes));
28589
- };
28590
- //#endregion
28591
- //#region src/plugin/utils/has-symbol-write-before.ts
28592
- const hasSymbolWriteBefore = (symbol, referenceNode, scopes) => symbol.references.some((reference) => {
28593
- if (reference.flag === "read") return false;
28594
- const writeFunction = findEnclosingFunction(reference.identifier);
28595
- if (writeFunction === findEnclosingFunction(referenceNode)) return reference.identifier.range[0] < referenceNode.range[0];
28596
- if (!writeFunction) return true;
28597
- return isFunctionSynchronouslyInvokedBefore(writeFunction, referenceNode, scopes);
28598
- });
28599
- //#endregion
28600
29587
  //#region src/plugin/utils/has-stable-call-target.ts
28601
29588
  const hasStableCallTarget = (callExpression, scopes) => {
28602
29589
  if (!isNodeOfType(callExpression, "CallExpression")) return false;
@@ -28924,7 +29911,7 @@ const noChainStateUpdates = defineRule({
28924
29911
  });
28925
29912
  //#endregion
28926
29913
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
28927
- const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
29914
+ const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
28928
29915
  const noChildrenProp = defineRule({
28929
29916
  id: "no-children-prop",
28930
29917
  title: "Children passed as a prop",
@@ -28936,7 +29923,7 @@ const noChildrenProp = defineRule({
28936
29923
  if (node.name.name !== "children") return;
28937
29924
  context.report({
28938
29925
  node: node.name,
28939
- message: MESSAGE$33
29926
+ message: MESSAGE$34
28940
29927
  });
28941
29928
  },
28942
29929
  CallExpression(node) {
@@ -28949,7 +29936,7 @@ const noChildrenProp = defineRule({
28949
29936
  const propertyKey = property.key;
28950
29937
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
28951
29938
  node: propertyKey,
28952
- message: MESSAGE$33
29939
+ message: MESSAGE$34
28953
29940
  });
28954
29941
  }
28955
29942
  }
@@ -28957,7 +29944,7 @@ const noChildrenProp = defineRule({
28957
29944
  });
28958
29945
  //#endregion
28959
29946
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
28960
- const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
29947
+ const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
28961
29948
  const noCloneElement = defineRule({
28962
29949
  id: "no-clone-element",
28963
29950
  title: "cloneElement makes child props fragile",
@@ -28970,7 +29957,7 @@ const noCloneElement = defineRule({
28970
29957
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
28971
29958
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
28972
29959
  node: callee,
28973
- message: MESSAGE$32
29960
+ message: MESSAGE$33
28974
29961
  });
28975
29962
  return;
28976
29963
  }
@@ -28983,7 +29970,7 @@ const noCloneElement = defineRule({
28983
29970
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
28984
29971
  context.report({
28985
29972
  node: callee,
28986
- message: MESSAGE$32
29973
+ message: MESSAGE$33
28987
29974
  });
28988
29975
  }
28989
29976
  } })
@@ -29003,7 +29990,7 @@ const getCallMethodName = (callee) => {
29003
29990
  };
29004
29991
  //#endregion
29005
29992
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
29006
- const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29993
+ const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29007
29994
  const CONTEXT_MODULES = [
29008
29995
  "react",
29009
29996
  "use-context-selector",
@@ -29051,13 +30038,13 @@ const noCreateContextInRender = defineRule({
29051
30038
  if (!componentOrHookName) return;
29052
30039
  context.report({
29053
30040
  node,
29054
- message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
30041
+ message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
29055
30042
  });
29056
30043
  } })
29057
30044
  });
29058
30045
  //#endregion
29059
30046
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
29060
- const MESSAGE$30 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
30047
+ const MESSAGE$31 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
29061
30048
  const isUseMemoCallbackArgument = (functionNode) => {
29062
30049
  const parent = functionNode.parent;
29063
30050
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -29065,8 +30052,8 @@ const isUseMemoCallbackArgument = (functionNode) => {
29065
30052
  return isReactFunctionCall(parent, "useMemo");
29066
30053
  };
29067
30054
  const findEnclosingRenderFunction = (node) => {
29068
- let enclosingFunction = findEnclosingFunction(node);
29069
- while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
30055
+ let enclosingFunction = findEnclosingFunction$1(node);
30056
+ while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
29070
30057
  return enclosingFunction;
29071
30058
  };
29072
30059
  const noCreateRefInFunctionComponent = defineRule({
@@ -29087,7 +30074,7 @@ const noCreateRefInFunctionComponent = defineRule({
29087
30074
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
29088
30075
  context.report({
29089
30076
  node,
29090
- message: MESSAGE$30
30077
+ message: MESSAGE$31
29091
30078
  });
29092
30079
  } })
29093
30080
  });
@@ -29227,7 +30214,7 @@ const noCreateStoreInRender = defineRule({
29227
30214
  });
29228
30215
  //#endregion
29229
30216
  //#region src/plugin/rules/react-builtins/no-danger.ts
29230
- const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
30217
+ const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
29231
30218
  const noDanger = defineRule({
29232
30219
  id: "no-danger",
29233
30220
  title: "Raw HTML injection can run unsafe markup",
@@ -29241,7 +30228,7 @@ const noDanger = defineRule({
29241
30228
  if (!propAttribute) return;
29242
30229
  context.report({
29243
30230
  node: propAttribute.name,
29244
- message: MESSAGE$29
30231
+ message: MESSAGE$30
29245
30232
  });
29246
30233
  },
29247
30234
  CallExpression(node) {
@@ -29253,7 +30240,7 @@ const noDanger = defineRule({
29253
30240
  const propertyKey = property.key;
29254
30241
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
29255
30242
  node: propertyKey,
29256
- message: MESSAGE$29
30243
+ message: MESSAGE$30
29257
30244
  });
29258
30245
  }
29259
30246
  }
@@ -29276,7 +30263,7 @@ const isMeaningfulJsxChild = (child) => {
29276
30263
  };
29277
30264
  //#endregion
29278
30265
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
29279
- const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
30266
+ const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
29280
30267
  const mergePropsShape = (target, source) => {
29281
30268
  target.hasDangerously ||= source.hasDangerously;
29282
30269
  target.hasChildren ||= source.hasChildren;
@@ -29351,7 +30338,7 @@ const noDangerWithChildren = defineRule({
29351
30338
  if (!hasChildrenProp && !hasNestedChildren) return;
29352
30339
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
29353
30340
  node: opening,
29354
- message: MESSAGE$28
30341
+ message: MESSAGE$29
29355
30342
  });
29356
30343
  },
29357
30344
  CallExpression(node) {
@@ -29364,7 +30351,7 @@ const noDangerWithChildren = defineRule({
29364
30351
  const positionalChildren = node.arguments.slice(2);
29365
30352
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
29366
30353
  node,
29367
- message: MESSAGE$28
30354
+ message: MESSAGE$29
29368
30355
  });
29369
30356
  }
29370
30357
  })
@@ -30117,7 +31104,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
30117
31104
  const isEffectDrivenResync = (useStateCall) => {
30118
31105
  const setterName = getStateSetterName(useStateCall);
30119
31106
  if (!setterName) return false;
30120
- const componentFunction = findEnclosingFunction(useStateCall);
31107
+ const componentFunction = findEnclosingFunction$1(useStateCall);
30121
31108
  if (!componentFunction) return false;
30122
31109
  let isExempt = false;
30123
31110
  walkAst(componentFunction, (child) => {
@@ -30221,7 +31208,7 @@ const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
30221
31208
  const setterName = getStateSetterName(useStateCall);
30222
31209
  if (!setterName) return false;
30223
31210
  const stateValueName = getStateValueName(useStateCall);
30224
- const componentFunction = findEnclosingFunction(useStateCall);
31211
+ const componentFunction = findEnclosingFunction$1(useStateCall);
30225
31212
  if (!componentFunction) return false;
30226
31213
  let isExempt = false;
30227
31214
  walkAst(componentFunction, (child) => {
@@ -30267,7 +31254,7 @@ const noDerivedUseState = defineRule({
30267
31254
  if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
30268
31255
  if (isEffectDrivenResync(node)) return;
30269
31256
  if (isNextjsDataFetchingPage(node)) return;
30270
- const componentFunction = findEnclosingFunction(node);
31257
+ const componentFunction = findEnclosingFunction$1(node);
30271
31258
  if (componentFunction) {
30272
31259
  if (isDefaultedDestructuredProp(componentFunction, propName)) return;
30273
31260
  if (isDraftCommittedToParent(componentFunction, getStateValueName(node), propStackTracker.isPropName)) return;
@@ -30329,7 +31316,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
30329
31316
  //#endregion
30330
31317
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
30331
31318
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
30332
- const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
31319
+ const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
30333
31320
  const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
30334
31321
  const getEnclosingLifecycleFunction = (setStateCall) => {
30335
31322
  let ancestor = setStateCall.parent;
@@ -30457,7 +31444,7 @@ const noDidMountSetState = defineRule({
30457
31444
  }
30458
31445
  context.report({
30459
31446
  node: node.callee,
30460
- message: MESSAGE$27
31447
+ message: MESSAGE$28
30461
31448
  });
30462
31449
  } };
30463
31450
  }
@@ -30465,7 +31452,7 @@ const noDidMountSetState = defineRule({
30465
31452
  //#endregion
30466
31453
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
30467
31454
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
30468
- const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
31455
+ const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
30469
31456
  const EQUALITY_OPERATORS = new Set([
30470
31457
  "==",
30471
31458
  "===",
@@ -30639,7 +31626,7 @@ const noDidUpdateSetState = defineRule({
30639
31626
  if (isInsideDiffGuard(node)) return;
30640
31627
  context.report({
30641
31628
  node: node.callee,
30642
- message: MESSAGE$26
31629
+ message: MESSAGE$27
30643
31630
  });
30644
31631
  } };
30645
31632
  }
@@ -30662,7 +31649,7 @@ const isStateMemberExpression = (node) => {
30662
31649
  };
30663
31650
  //#endregion
30664
31651
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
30665
- const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
31652
+ const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
30666
31653
  const shouldIgnoreMutation = (node) => {
30667
31654
  let isConstructor = false;
30668
31655
  let isInsideCallExpression = false;
@@ -30684,7 +31671,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
30684
31671
  if (shouldIgnoreMutation(reportNode)) return;
30685
31672
  context.report({
30686
31673
  node: reportNode,
30687
- message: MESSAGE$25
31674
+ message: MESSAGE$26
30688
31675
  });
30689
31676
  };
30690
31677
  const noDirectMutationState = defineRule({
@@ -31035,7 +32022,7 @@ const noDocumentStartViewTransition = defineRule({
31035
32022
  });
31036
32023
  //#endregion
31037
32024
  //#region src/plugin/rules/js-performance/no-document-write.ts
31038
- const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
32025
+ const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
31039
32026
  const WRITE_METHODS = new Set(["write", "writeln"]);
31040
32027
  const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
31041
32028
  const noDocumentWrite = defineRule({
@@ -31052,7 +32039,7 @@ const noDocumentWrite = defineRule({
31052
32039
  if (methodName === null || !WRITE_METHODS.has(methodName)) return;
31053
32040
  context.report({
31054
32041
  node,
31055
- message: MESSAGE$24
32042
+ message: MESSAGE$25
31056
32043
  });
31057
32044
  } })
31058
32045
  });
@@ -32105,7 +33092,7 @@ const isFunctionUsedOutsideHandlers = (analysis, functionNode, componentFunction
32105
33092
  if (isInsideInlineEventHandler(identifier, componentFunction)) return false;
32106
33093
  const parent = identifier.parent;
32107
33094
  if (parent && isNodeOfType(parent, "CallExpression") && parent.callee === identifier) {
32108
- if (findEnclosingFunction(parent) === functionNode) return false;
33095
+ if (findEnclosingFunction$1(parent) === functionNode) return false;
32109
33096
  if (isInsideProvenEventHandler(analysis, parent, componentFunction, false)) return false;
32110
33097
  }
32111
33098
  return true;
@@ -32151,7 +33138,7 @@ const isStateWrittenOnlyFromEventHandlers = (analysis, stateReference) => {
32151
33138
  if (!setterVariable) return false;
32152
33139
  const stateDeclarator = getUseStateDecl(analysis, stateReference);
32153
33140
  if (!stateDeclarator) return false;
32154
- const componentFunction = findEnclosingFunction(stateDeclarator);
33141
+ const componentFunction = findEnclosingFunction$1(stateDeclarator);
32155
33142
  if (!componentFunction) return false;
32156
33143
  let hasWriter = false;
32157
33144
  for (const reference of setterVariable.references) {
@@ -33213,8 +34200,8 @@ const isAwaitedInFunction = (request, functionNode) => {
33213
34200
  return false;
33214
34201
  };
33215
34202
  const isCompletionSinkForRequest = (completionSink, request) => {
33216
- const requestFunction = findEnclosingFunction(request);
33217
- const completionSinkFunction = findEnclosingFunction(completionSink);
34203
+ const requestFunction = findEnclosingFunction$1(request);
34204
+ const completionSinkFunction = findEnclosingFunction$1(completionSink);
33218
34205
  if (!requestFunction || !completionSinkFunction) return false;
33219
34206
  if (requestFunction === completionSinkFunction) {
33220
34207
  if (!isAwaitedInFunction(request, requestFunction)) return false;
@@ -33270,7 +34257,7 @@ const ALLOWED_NAMESPACES = new Set([
33270
34257
  "ReactDOM",
33271
34258
  "ReactDom"
33272
34259
  ]);
33273
- const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
34260
+ const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
33274
34261
  const noFindDomNode = defineRule({
33275
34262
  id: "no-find-dom-node",
33276
34263
  title: "findDOMNode breaks component encapsulation",
@@ -33281,7 +34268,7 @@ const noFindDomNode = defineRule({
33281
34268
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
33282
34269
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
33283
34270
  node: callee,
33284
- message: MESSAGE$23
34271
+ message: MESSAGE$24
33285
34272
  });
33286
34273
  return;
33287
34274
  }
@@ -33292,7 +34279,7 @@ const noFindDomNode = defineRule({
33292
34279
  if (callee.property.name !== "findDOMNode") return;
33293
34280
  context.report({
33294
34281
  node: callee.property,
33295
- message: MESSAGE$23
34282
+ message: MESSAGE$24
33296
34283
  });
33297
34284
  }
33298
34285
  } })
@@ -33501,13 +34488,6 @@ const isPublishedLibraryPackage = (filename) => {
33501
34488
  return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
33502
34489
  };
33503
34490
  //#endregion
33504
- //#region src/plugin/utils/is-type-only-import.ts
33505
- const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
33506
- if (!specifiers || specifiers.length === 0) return false;
33507
- return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
33508
- };
33509
- const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
33510
- //#endregion
33511
34491
  //#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
33512
34492
  const isLibraryDevPage = (filename) => {
33513
34493
  if (!filename) return false;
@@ -34071,7 +35051,7 @@ const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
34071
35051
  return attribute ? !isEventHandlerAttribute(attribute) : true;
34072
35052
  }
34073
35053
  if (isNodeOfType(parentNode, "ReturnStatement")) {
34074
- if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
35054
+ if (findEnclosingFunction$1(parentNode) === componentOrHookNode) return true;
34075
35055
  }
34076
35056
  if (parentNode === componentOrHookNode) return isFunctionLike$2(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
34077
35057
  if (isFunctionLike$2(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
@@ -34194,7 +35174,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
34194
35174
  if (consequentValues.length === 0 || alternateValues.length === 0) return;
34195
35175
  const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
34196
35176
  if (!componentOrHookNode) return;
34197
- const enclosingFunction = findEnclosingFunction(node);
35177
+ const enclosingFunction = findEnclosingFunction$1(node);
34198
35178
  if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
34199
35179
  for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
34200
35180
  if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
@@ -34206,7 +35186,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
34206
35186
  });
34207
35187
  //#endregion
34208
35188
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
34209
- const MESSAGE$22 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
35189
+ const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
34210
35190
  const noImgLazyWithHighFetchpriority = defineRule({
34211
35191
  id: "no-img-lazy-with-high-fetchpriority",
34212
35192
  title: "Lazy image with high fetchPriority",
@@ -34220,7 +35200,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
34220
35200
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
34221
35201
  context.report({
34222
35202
  node: node.name,
34223
- message: MESSAGE$22
35203
+ message: MESSAGE$23
34224
35204
  });
34225
35205
  } })
34226
35206
  });
@@ -34409,7 +35389,7 @@ const noImpureStateUpdater = defineRule({
34409
35389
  });
34410
35390
  //#endregion
34411
35391
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
34412
- const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
35392
+ const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
34413
35393
  const REACT_USE_REF_OPTIONS = {
34414
35394
  allowGlobalReactNamespace: false,
34415
35395
  allowUnboundBareCalls: false
@@ -34510,7 +35490,7 @@ const noIndeterminateAttribute = defineRule({
34510
35490
  if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
34511
35491
  context.report({
34512
35492
  node: indeterminateAttribute,
34513
- message: MESSAGE$21
35493
+ message: MESSAGE$22
34514
35494
  });
34515
35495
  },
34516
35496
  CallExpression(node) {
@@ -34519,7 +35499,7 @@ const noIndeterminateAttribute = defineRule({
34519
35499
  if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
34520
35500
  context.report({
34521
35501
  node,
34522
- message: MESSAGE$21
35502
+ message: MESSAGE$22
34523
35503
  });
34524
35504
  }
34525
35505
  };
@@ -34642,10 +35622,10 @@ const isInsideInstanceField = (node) => {
34642
35622
  return false;
34643
35623
  };
34644
35624
  const isOneShotModuleInitialization = (node, context) => {
34645
- let functionNode = findEnclosingFunction(node);
35625
+ let functionNode = findEnclosingFunction$1(node);
34646
35626
  while (functionNode) {
34647
35627
  if (!executesDuringRender(functionNode, context.scopes)) return false;
34648
- functionNode = findEnclosingFunction(functionNode);
35628
+ functionNode = findEnclosingFunction$1(functionNode);
34649
35629
  }
34650
35630
  return !isInsideInstanceField(node);
34651
35631
  };
@@ -34807,7 +35787,7 @@ const noIsMounted = defineRule({
34807
35787
  });
34808
35788
  //#endregion
34809
35789
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
34810
- const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
35790
+ const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
34811
35791
  const isJsonMethodCall = (node, method) => {
34812
35792
  if (!isNodeOfType(node, "CallExpression")) return false;
34813
35793
  const callee = node.callee;
@@ -34871,13 +35851,13 @@ const noJsonParseStringifyClone = defineRule({
34871
35851
  if (isCatchParameterRoundTrip(firstArgument)) return;
34872
35852
  context.report({
34873
35853
  node,
34874
- message: MESSAGE$20
35854
+ message: MESSAGE$21
34875
35855
  });
34876
35856
  } })
34877
35857
  });
34878
35858
  //#endregion
34879
35859
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
34880
- const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
35860
+ const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
34881
35861
  const isJsxElementTypeReference = (node) => {
34882
35862
  if (!isNodeOfType(node, "TSTypeReference")) return false;
34883
35863
  const typeName = node.typeName;
@@ -34931,7 +35911,7 @@ const noJsxElementType = defineRule({
34931
35911
  if (isJsxImported) return;
34932
35912
  for (const typeAnnotation of flaggedAnnotations) context.report({
34933
35913
  node: typeAnnotation,
34934
- message: MESSAGE$19
35914
+ message: MESSAGE$20
34935
35915
  });
34936
35916
  }
34937
35917
  };
@@ -35163,6 +36143,221 @@ const noLegacyClassLifecycles = defineRule({
35163
36143
  }
35164
36144
  });
35165
36145
  //#endregion
36146
+ //#region src/plugin/utils/is-proven-react-class-component.ts
36147
+ const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
36148
+ const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
36149
+ const expression = stripParenExpression(node);
36150
+ if (isNodeOfType(expression, "MemberExpression")) {
36151
+ const propertyName = getStaticPropertyName(expression);
36152
+ const receiver = stripParenExpression(expression.object);
36153
+ return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
36154
+ }
36155
+ if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
36156
+ if (!isNodeOfType(expression, "Identifier")) return false;
36157
+ const symbol = scopes.symbolFor(expression);
36158
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
36159
+ visitedSymbolIds.add(symbol.id);
36160
+ if (isImportedFromReact(symbol)) {
36161
+ const importedName = getImportedName(symbol.declarationNode);
36162
+ return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
36163
+ }
36164
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
36165
+ return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
36166
+ };
36167
+ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36168
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
36169
+ visitedClassNodes.add(classNode);
36170
+ return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
36171
+ };
36172
+ //#endregion
36173
+ //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
36174
+ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
36175
+ if (!isFunctionLike$2(functionNode)) return false;
36176
+ let containsReactHookCall = false;
36177
+ walkAst(functionNode.body, (node) => {
36178
+ if (containsReactHookCall) return false;
36179
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
36180
+ if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
36181
+ containsReactHookCall = true;
36182
+ return false;
36183
+ }
36184
+ });
36185
+ return containsReactHookCall;
36186
+ };
36187
+ //#endregion
36188
+ //#region src/plugin/utils/function-returns-props-children.ts
36189
+ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
36190
+ if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
36191
+ const firstParameter = stripParenExpression(functionNode.params[0]);
36192
+ const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
36193
+ const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
36194
+ const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
36195
+ if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
36196
+ for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
36197
+ const propertyValue = stripParenExpression(property.value);
36198
+ const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
36199
+ if (!isNodeOfType(childrenBinding, "Identifier")) continue;
36200
+ const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
36201
+ if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
36202
+ }
36203
+ }
36204
+ return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
36205
+ const candidate = stripParenExpression(expression);
36206
+ if (isNodeOfType(candidate, "Identifier")) {
36207
+ const symbol = scopes.symbolFor(candidate);
36208
+ return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
36209
+ }
36210
+ if (!isNodeOfType(candidate, "MemberExpression")) return false;
36211
+ if (getStaticPropertyName(candidate) !== "children") return false;
36212
+ const receiver = stripParenExpression(candidate.object);
36213
+ if (!isNodeOfType(receiver, "Identifier")) return false;
36214
+ const receiverSymbol = scopes.symbolFor(receiver);
36215
+ if (!receiverSymbol || !propsParameterSymbol) return false;
36216
+ return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
36217
+ }, controlFlow);
36218
+ };
36219
+ //#endregion
36220
+ //#region src/plugin/utils/function-returns-only-null.ts
36221
+ const isNullExpression = (expression) => {
36222
+ const candidate = stripParenExpression(expression);
36223
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
36224
+ };
36225
+ const functionReturnsOnlyNull = (functionNode) => {
36226
+ if (!isFunctionLike$2(functionNode)) return false;
36227
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
36228
+ const returnStatements = collectFunctionReturnStatements(functionNode);
36229
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
36230
+ };
36231
+ //#endregion
36232
+ //#region src/plugin/utils/is-proven-styled-component-expression.ts
36233
+ const findFactoryRoot = (node) => {
36234
+ const candidate = stripParenExpression(node);
36235
+ if (isNodeOfType(candidate, "Identifier")) return candidate;
36236
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
36237
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
36238
+ return null;
36239
+ };
36240
+ const findFactoryPropertyName = (node) => {
36241
+ const candidate = stripParenExpression(node);
36242
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
36243
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
36244
+ return null;
36245
+ };
36246
+ const isProvenStyledComponentExpression = (expression, scopes) => {
36247
+ const candidate = stripParenExpression(expression);
36248
+ if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
36249
+ const factoryRoot = findFactoryRoot(candidate.tag);
36250
+ if (!factoryRoot) return false;
36251
+ const factoryPropertyName = findFactoryPropertyName(candidate.tag);
36252
+ if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
36253
+ const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
36254
+ if (!symbol || symbol.kind !== "import") return false;
36255
+ if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
36256
+ const importDeclaration = symbol.declarationNode.parent;
36257
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
36258
+ };
36259
+ //#endregion
36260
+ //#region src/plugin/utils/is-proven-react-component-symbol.ts
36261
+ const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
36262
+ const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
36263
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
36264
+ const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36265
+ const candidate = stripParenExpression(expression);
36266
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
36267
+ if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
36268
+ if (isProvenStyledComponentExpression(candidate, scopes)) return true;
36269
+ if (isNodeOfType(candidate, "Identifier")) {
36270
+ const symbol = scopes.symbolFor(candidate);
36271
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
36272
+ visitedSymbolIds.add(symbol.id);
36273
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
36274
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
36275
+ return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
36276
+ }
36277
+ if (!isNodeOfType(candidate, "CallExpression")) return false;
36278
+ if (!hasStableCallTarget(candidate, scopes)) return false;
36279
+ const factoryCallee = stripParenExpression(candidate.callee);
36280
+ if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
36281
+ if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
36282
+ const wrappedComponent = candidate.arguments[0];
36283
+ return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
36284
+ }
36285
+ if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
36286
+ const factory = candidate.arguments[0];
36287
+ if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
36288
+ const unwrappedFactory = stripParenExpression(factory);
36289
+ if (!isInlineFunctionExpression(unwrappedFactory)) return false;
36290
+ if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
36291
+ const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
36292
+ const returnedExpression = returnStatements[0]?.argument;
36293
+ return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
36294
+ };
36295
+ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
36296
+ const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
36297
+ for (const candidateSymbol of candidateSymbols) {
36298
+ if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
36299
+ if (isComponentDeclaration(candidateSymbol.declarationNode)) {
36300
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
36301
+ continue;
36302
+ }
36303
+ const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
36304
+ if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
36305
+ if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
36306
+ continue;
36307
+ }
36308
+ if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
36309
+ if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
36310
+ continue;
36311
+ }
36312
+ if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
36313
+ }
36314
+ return false;
36315
+ };
36316
+ //#endregion
36317
+ //#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
36318
+ const REACT_COMPONENT_TYPE_NAMES = new Set([
36319
+ "ComponentClass",
36320
+ "ComponentType",
36321
+ "FC",
36322
+ "FunctionComponent"
36323
+ ]);
36324
+ const findVisibleSymbol = (identifier, scopes) => {
36325
+ if (!isNodeOfType(identifier, "Identifier")) return null;
36326
+ let scope = scopes.scopeFor(identifier);
36327
+ while (true) {
36328
+ const symbol = scope.symbolsByName.get(identifier.name);
36329
+ if (symbol) return symbol;
36330
+ if (!scope.parent) return null;
36331
+ scope = scope.parent;
36332
+ }
36333
+ };
36334
+ const isReactNamespaceType = (identifier, scopes) => {
36335
+ const symbol = findVisibleSymbol(identifier, scopes);
36336
+ return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
36337
+ };
36338
+ const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
36339
+ if (!typeNode) return false;
36340
+ if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
36341
+ if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
36342
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
36343
+ const typeName = typeNode.typeName;
36344
+ 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);
36345
+ if (!isNodeOfType(typeName, "Identifier")) return false;
36346
+ const typeSymbol = findVisibleSymbol(typeName, scopes);
36347
+ if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
36348
+ if (isImportedFromReact(typeSymbol)) {
36349
+ const importedName = getImportedName(typeSymbol.declarationNode);
36350
+ return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
36351
+ }
36352
+ if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
36353
+ visitedSymbolIds.add(typeSymbol.id);
36354
+ return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
36355
+ };
36356
+ const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
36357
+ const bindingIdentifier = symbol.bindingIdentifier;
36358
+ return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
36359
+ };
36360
+ //#endregion
35166
36361
  //#region src/plugin/rules/architecture/no-legacy-context-api.ts
35167
36362
  const LEGACY_CONTEXT_NAMES = new Set([
35168
36363
  "childContextTypes",
@@ -35173,15 +36368,6 @@ const buildLegacyContextMessage = (memberName) => {
35173
36368
  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.`;
35174
36369
  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.";
35175
36370
  };
35176
- const isInsideClassBody = (node) => {
35177
- let current = node.parent;
35178
- while (current) {
35179
- if (isNodeOfType(current, "ClassBody")) return true;
35180
- if (isFunctionLike$2(current)) return false;
35181
- current = current.parent;
35182
- }
35183
- return false;
35184
- };
35185
36371
  const noLegacyContextApi = defineRule({
35186
36372
  id: "no-legacy-context-api",
35187
36373
  title: "Legacy context API",
@@ -35196,6 +36382,7 @@ const noLegacyContextApi = defineRule({
35196
36382
  if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
35197
36383
  if (!isNodeOfType(memberNode.key, "Identifier")) return;
35198
36384
  if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
36385
+ if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
35199
36386
  context.report({
35200
36387
  node: memberNode.key,
35201
36388
  message: buildLegacyContextMessage(memberNode.key.name)
@@ -35203,6 +36390,8 @@ const noLegacyContextApi = defineRule({
35203
36390
  };
35204
36391
  return {
35205
36392
  ClassBody(node) {
36393
+ const classNode = node.parent;
36394
+ if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
35206
36395
  for (const member of node.body ?? []) checkMember(member);
35207
36396
  },
35208
36397
  AssignmentExpression(node) {
@@ -35212,9 +36401,11 @@ const noLegacyContextApi = defineRule({
35212
36401
  if (left.computed) return;
35213
36402
  if (!isNodeOfType(left.property, "Identifier")) return;
35214
36403
  if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
35215
- if (!isNodeOfType(left.object, "Identifier")) return;
35216
- if (!isUppercaseName(left.object.name)) return;
35217
- if (isInsideClassBody(node)) return;
36404
+ if (left.property.name === "getChildContext") return;
36405
+ const component = stripParenExpression(left.object);
36406
+ if (!isNodeOfType(component, "Identifier")) return;
36407
+ const symbol = context.scopes.symbolFor(component);
36408
+ if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
35218
36409
  context.report({
35219
36410
  node: left,
35220
36411
  message: buildLegacyContextMessage(left.property.name)
@@ -35418,10 +36609,10 @@ const getMethodCalls = (functionNode, scopes) => {
35418
36609
  const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
35419
36610
  if (visitedFunctionNodes.has(functionNode)) return false;
35420
36611
  visitedFunctionNodes.add(functionNode);
35421
- const usageFunction = findEnclosingFunction(usageNode);
36612
+ const usageFunction = findEnclosingFunction$1(usageNode);
35422
36613
  const immediateCall = getDirectCallForExpression(functionNode);
35423
36614
  if (immediateCall) {
35424
- const immediateCallFunction = findEnclosingFunction(immediateCall);
36615
+ const immediateCallFunction = findEnclosingFunction$1(immediateCall);
35425
36616
  if (immediateCallFunction === usageFunction) {
35426
36617
  const immediateCallStart = getRangeStart(immediateCall);
35427
36618
  return immediateCallStart === null || immediateCallStart < usageBoundary;
@@ -35430,7 +36621,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
35430
36621
  return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
35431
36622
  }
35432
36623
  for (const methodCall of getMethodCalls(functionNode, scopes)) {
35433
- const methodCallFunction = findEnclosingFunction(methodCall);
36624
+ const methodCallFunction = findEnclosingFunction$1(methodCall);
35434
36625
  if (methodCallFunction === usageFunction) {
35435
36626
  const methodCallStart = getRangeStart(methodCall);
35436
36627
  if (methodCallStart === null || methodCallStart < usageBoundary) return true;
@@ -35453,7 +36644,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
35453
36644
  if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
35454
36645
  const call = getDirectCallForExpression(child);
35455
36646
  if (!call) return false;
35456
- const callFunction = findEnclosingFunction(call);
36647
+ const callFunction = findEnclosingFunction$1(call);
35457
36648
  if (callFunction === usageFunction) {
35458
36649
  const callStart = getRangeStart(call);
35459
36650
  wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
@@ -35484,14 +36675,14 @@ const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutat
35484
36675
  visitedMutationSymbolIds.add(symbol.id);
35485
36676
  const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
35486
36677
  if (typeof usageBoundary !== "number") return true;
35487
- const usageFunction = findEnclosingFunction(usageNode);
36678
+ const usageFunction = findEnclosingFunction$1(usageNode);
35488
36679
  return symbol.references.some((reference) => {
35489
36680
  const referenceStart = getRangeStart(reference.identifier);
35490
36681
  const simpleAlias = getSimpleAlias(reference.identifier, scopes);
35491
36682
  if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
35492
36683
  if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
35493
36684
  if (referenceStart === null) return true;
35494
- const mutationFunction = findEnclosingFunction(reference.identifier);
36685
+ const mutationFunction = findEnclosingFunction$1(reference.identifier);
35495
36686
  if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
35496
36687
  if (!mutationFunction) return usageFunction !== null;
35497
36688
  if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
@@ -36036,7 +37227,7 @@ const noMoment = defineRule({
36036
37227
  });
36037
37228
  //#endregion
36038
37229
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
36039
- const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
37230
+ const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
36040
37231
  const resolveSettings$16 = (settings) => {
36041
37232
  const reactDoctor = settings?.["react-doctor"];
36042
37233
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -36073,13 +37264,6 @@ const isReactNamespaceExpression = (node, scopes) => {
36073
37264
  return isReactNamespaceImport(node, scopes);
36074
37265
  };
36075
37266
  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));
36076
- const getDestructuredPropertyName$1 = (symbol) => {
36077
- const property = symbol.bindingIdentifier.parent;
36078
- if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
36079
- if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
36080
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
36081
- return null;
36082
- };
36083
37267
  const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
36084
37268
  if (visitedSymbolIds.has(symbol.id)) return false;
36085
37269
  visitedSymbolIds.add(symbol.id);
@@ -36089,7 +37273,7 @@ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
36089
37273
  }
36090
37274
  const init = symbol.initializer;
36091
37275
  if (!init) return false;
36092
- const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
37276
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
36093
37277
  if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
36094
37278
  if (isReactHocMemberReference(init, scopes)) return true;
36095
37279
  if (isNodeOfType(init, "Identifier")) {
@@ -36395,7 +37579,7 @@ const noMultiComp = defineRule({
36395
37579
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
36396
37580
  for (const component of flagged.slice(1)) context.report({
36397
37581
  node: component.reportNode,
36398
- message: MESSAGE$18
37582
+ message: MESSAGE$19
36399
37583
  });
36400
37584
  } };
36401
37585
  }
@@ -36608,7 +37792,7 @@ const resolveReducerFunction = (node, currentFilename) => {
36608
37792
  };
36609
37793
  //#endregion
36610
37794
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
36611
- const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
37795
+ const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
36612
37796
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
36613
37797
  "copyWithin",
36614
37798
  "fill",
@@ -36817,7 +38001,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
36817
38001
  reportedNodes.add(options.crossFileConsumerCallSite);
36818
38002
  context.report({
36819
38003
  node: options.crossFileConsumerCallSite,
36820
- message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
38004
+ message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
36821
38005
  });
36822
38006
  return;
36823
38007
  }
@@ -36826,7 +38010,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
36826
38010
  reportedNodes.add(mutation.node);
36827
38011
  context.report({
36828
38012
  node: mutation.node,
36829
- message: MESSAGE$17
38013
+ message: MESSAGE$18
36830
38014
  });
36831
38015
  }
36832
38016
  };
@@ -37233,7 +38417,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
37233
38417
  });
37234
38418
  //#endregion
37235
38419
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
37236
- const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
38420
+ const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
37237
38421
  const KEYBOARD_HANDLER_PROP_NAMES = [
37238
38422
  "onKeyDown",
37239
38423
  "onKeyUp",
@@ -37352,7 +38536,7 @@ const noNoninteractiveTabindex = defineRule({
37352
38536
  if (numeric === null) {
37353
38537
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
37354
38538
  node: tabIndex,
37355
- message: MESSAGE$16
38539
+ message: MESSAGE$17
37356
38540
  });
37357
38541
  return;
37358
38542
  }
@@ -37374,7 +38558,7 @@ const noNoninteractiveTabindex = defineRule({
37374
38558
  if (!roleAttribute) {
37375
38559
  context.report({
37376
38560
  node: tabIndex,
37377
- message: MESSAGE$16
38561
+ message: MESSAGE$17
37378
38562
  });
37379
38563
  return;
37380
38564
  }
@@ -37388,7 +38572,7 @@ const noNoninteractiveTabindex = defineRule({
37388
38572
  }
37389
38573
  context.report({
37390
38574
  node: tabIndex,
37391
- message: MESSAGE$16
38575
+ message: MESSAGE$17
37392
38576
  });
37393
38577
  } };
37394
38578
  }
@@ -37815,8 +38999,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
37815
38999
  if (!wrapped || !isFunctionLike$2(wrapped)) return null;
37816
39000
  return wrapped;
37817
39001
  };
37818
- const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
37819
- const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
37820
39002
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
37821
39003
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
37822
39004
  if (!isProp(analysis, innerRef)) return false;
@@ -37845,12 +39027,6 @@ const getDeclarationKind = (declarator) => {
37845
39027
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
37846
39028
  };
37847
39029
  const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
37848
- const getDestructuredPropertyName = (bindingIdentifier) => {
37849
- const property = bindingIdentifier.parent;
37850
- if (!property || !isNodeOfType(property, "Property")) return null;
37851
- if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
37852
- return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
37853
- };
37854
39030
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
37855
39031
  const unwrappedExpression = stripParenExpression(expression);
37856
39032
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -37860,7 +39036,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
37860
39036
  if (isProp(analysis, callbackReference)) {
37861
39037
  if (isWholePropsObjectReference(analysis, callbackReference)) return null;
37862
39038
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
37863
- return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
39039
+ return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
37864
39040
  }
37865
39041
  if (hasMutableBindingWrite(callbackReference)) return null;
37866
39042
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -37870,7 +39046,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
37870
39046
  visitedVariables.add(callbackVariable);
37871
39047
  if (isNodeOfType(declarator.id, "ObjectPattern")) {
37872
39048
  const bindingIdentifier = callbackVariable.defs[0]?.name;
37873
- const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
39049
+ const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
37874
39050
  const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
37875
39051
  return propertyName && propsReference ? propertyName : null;
37876
39052
  }
@@ -37973,7 +39149,7 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
37973
39149
  const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
37974
39150
  if (!bindingProvenance) return null;
37975
39151
  const { refCall, variables } = bindingProvenance;
37976
- const componentFunction = findEnclosingFunction(effectCall);
39152
+ const componentFunction = findEnclosingFunction$1(effectCall);
37977
39153
  if (!componentFunction || !isFunctionLike$2(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
37978
39154
  if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
37979
39155
  const callbackPropNames = /* @__PURE__ */ new Set();
@@ -38137,7 +39313,7 @@ const noPassDataToParent = defineRule({
38137
39313
  if (isConstant(argRef)) return false;
38138
39314
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
38139
39315
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
38140
- if (resolvesToFunctionBinding(argRef)) return false;
39316
+ if (resolveToFunction(argRef)) return false;
38141
39317
  const argIdentifier = argRef.identifier;
38142
39318
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
38143
39319
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
@@ -38809,174 +39985,6 @@ const noPropCallbackInRender = defineRule({
38809
39985
  } })
38810
39986
  });
38811
39987
  //#endregion
38812
- //#region src/plugin/utils/is-proven-react-class-component.ts
38813
- const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
38814
- const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
38815
- const expression = stripParenExpression(node);
38816
- if (isNodeOfType(expression, "MemberExpression")) {
38817
- const propertyName = getStaticPropertyName(expression);
38818
- const receiver = stripParenExpression(expression.object);
38819
- return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
38820
- }
38821
- if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
38822
- if (!isNodeOfType(expression, "Identifier")) return false;
38823
- const symbol = scopes.symbolFor(expression);
38824
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
38825
- visitedSymbolIds.add(symbol.id);
38826
- if (isImportedFromReact(symbol)) {
38827
- const importedName = getImportedName(symbol.declarationNode);
38828
- return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
38829
- }
38830
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
38831
- return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
38832
- };
38833
- const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38834
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
38835
- visitedClassNodes.add(classNode);
38836
- return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
38837
- };
38838
- //#endregion
38839
- //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
38840
- const functionContainsProvenReactHookCall = (functionNode, scopes) => {
38841
- if (!isFunctionLike$2(functionNode)) return false;
38842
- let containsReactHookCall = false;
38843
- walkAst(functionNode.body, (node) => {
38844
- if (containsReactHookCall) return false;
38845
- if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
38846
- if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
38847
- containsReactHookCall = true;
38848
- return false;
38849
- }
38850
- });
38851
- return containsReactHookCall;
38852
- };
38853
- //#endregion
38854
- //#region src/plugin/utils/function-returns-props-children.ts
38855
- const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
38856
- if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
38857
- const firstParameter = stripParenExpression(functionNode.params[0]);
38858
- const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
38859
- const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
38860
- const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
38861
- if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
38862
- for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
38863
- const propertyValue = stripParenExpression(property.value);
38864
- const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
38865
- if (!isNodeOfType(childrenBinding, "Identifier")) continue;
38866
- const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
38867
- if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
38868
- }
38869
- }
38870
- return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
38871
- const candidate = stripParenExpression(expression);
38872
- if (isNodeOfType(candidate, "Identifier")) {
38873
- const symbol = scopes.symbolFor(candidate);
38874
- return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
38875
- }
38876
- if (!isNodeOfType(candidate, "MemberExpression")) return false;
38877
- if (getStaticPropertyName(candidate) !== "children") return false;
38878
- const receiver = stripParenExpression(candidate.object);
38879
- if (!isNodeOfType(receiver, "Identifier")) return false;
38880
- const receiverSymbol = scopes.symbolFor(receiver);
38881
- if (!receiverSymbol || !propsParameterSymbol) return false;
38882
- return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
38883
- }, controlFlow);
38884
- };
38885
- //#endregion
38886
- //#region src/plugin/utils/function-returns-only-null.ts
38887
- const isNullExpression = (expression) => {
38888
- const candidate = stripParenExpression(expression);
38889
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
38890
- };
38891
- const functionReturnsOnlyNull = (functionNode) => {
38892
- if (!isFunctionLike$2(functionNode)) return false;
38893
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
38894
- const returnStatements = collectFunctionReturnStatements(functionNode);
38895
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
38896
- };
38897
- //#endregion
38898
- //#region src/plugin/utils/is-proven-styled-component-expression.ts
38899
- const findFactoryRoot = (node) => {
38900
- const candidate = stripParenExpression(node);
38901
- if (isNodeOfType(candidate, "Identifier")) return candidate;
38902
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
38903
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
38904
- return null;
38905
- };
38906
- const findFactoryPropertyName = (node) => {
38907
- const candidate = stripParenExpression(node);
38908
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
38909
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
38910
- return null;
38911
- };
38912
- const isProvenStyledComponentExpression = (expression, scopes) => {
38913
- const candidate = stripParenExpression(expression);
38914
- if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
38915
- const factoryRoot = findFactoryRoot(candidate.tag);
38916
- if (!factoryRoot) return false;
38917
- const factoryPropertyName = findFactoryPropertyName(candidate.tag);
38918
- if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
38919
- const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
38920
- if (!symbol || symbol.kind !== "import") return false;
38921
- if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
38922
- const importDeclaration = symbol.declarationNode.parent;
38923
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
38924
- };
38925
- //#endregion
38926
- //#region src/plugin/utils/is-proven-react-component-symbol.ts
38927
- const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
38928
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
38929
- const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38930
- const candidate = stripParenExpression(expression);
38931
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
38932
- if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
38933
- if (isProvenStyledComponentExpression(candidate, scopes)) return true;
38934
- if (isNodeOfType(candidate, "Identifier")) {
38935
- const symbol = scopes.symbolFor(candidate);
38936
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
38937
- visitedSymbolIds.add(symbol.id);
38938
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
38939
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
38940
- return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
38941
- }
38942
- if (!isNodeOfType(candidate, "CallExpression")) return false;
38943
- if (!hasStableCallTarget(candidate, scopes)) return false;
38944
- if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
38945
- const wrappedComponent = candidate.arguments[0];
38946
- return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
38947
- }
38948
- if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
38949
- const factory = candidate.arguments[0];
38950
- if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
38951
- const unwrappedFactory = stripParenExpression(factory);
38952
- if (!isInlineFunctionExpression(unwrappedFactory)) return false;
38953
- if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
38954
- const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
38955
- const returnedExpression = returnStatements[0]?.argument;
38956
- return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
38957
- };
38958
- const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
38959
- const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
38960
- for (const candidateSymbol of candidateSymbols) {
38961
- if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
38962
- if (isComponentDeclaration(candidateSymbol.declarationNode)) {
38963
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
38964
- continue;
38965
- }
38966
- const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
38967
- if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
38968
- if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
38969
- continue;
38970
- }
38971
- if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
38972
- if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
38973
- continue;
38974
- }
38975
- if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
38976
- }
38977
- return false;
38978
- };
38979
- //#endregion
38980
39988
  //#region src/plugin/rules/architecture/no-prop-types.ts
38981
39989
  const PROP_TYPES_PROPERTY = "propTypes";
38982
39990
  const isPropTypesKey = (key, computed) => {
@@ -39129,7 +40137,7 @@ const isModuleScopedBinding = (identifier) => {
39129
40137
  const binding = findVariableInitializer(identifier, identifier.name);
39130
40138
  if (!binding) return false;
39131
40139
  if (isNodeOfType(binding.scopeOwner, "Program")) return true;
39132
- if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction(binding.scopeOwner) === null;
40140
+ if (isNodeOfType(binding.scopeOwner, "BlockStatement")) return findEnclosingFunction$1(binding.scopeOwner) === null;
39133
40141
  return false;
39134
40142
  };
39135
40143
  const looksLikeFreshUpdateExpression = (expression) => {
@@ -39184,7 +40192,7 @@ const noRandomKey = defineRule({
39184
40192
  });
39185
40193
  //#endregion
39186
40194
  //#region src/plugin/rules/react-builtins/no-react-children.ts
39187
- const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
40195
+ const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
39188
40196
  const isChildrenIdentifier = (node, contextNode) => {
39189
40197
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
39190
40198
  return isImportedFromModule(contextNode, "Children", "react");
@@ -39210,13 +40218,13 @@ const noReactChildren = defineRule({
39210
40218
  if (isChildrenIdentifier(memberObject, node)) {
39211
40219
  context.report({
39212
40220
  node: calleeOuter,
39213
- message: MESSAGE$15
40221
+ message: MESSAGE$16
39214
40222
  });
39215
40223
  return;
39216
40224
  }
39217
40225
  if (isReactNamespaceMember(memberObject, node)) context.report({
39218
40226
  node: calleeOuter,
39219
- message: MESSAGE$15
40227
+ message: MESSAGE$16
39220
40228
  });
39221
40229
  } })
39222
40230
  });
@@ -39897,7 +40905,7 @@ const noRenderPropChildren = defineRule({
39897
40905
  });
39898
40906
  //#endregion
39899
40907
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
39900
- const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
40908
+ const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
39901
40909
  const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
39902
40910
  const isUsedAsReturnValue = (parent) => {
39903
40911
  if (!parent) return false;
@@ -39915,7 +40923,7 @@ const noRenderReturnValue = defineRule({
39915
40923
  if (!isUsedAsReturnValue(node.parent)) return;
39916
40924
  context.report({
39917
40925
  node: node.callee,
39918
- message: MESSAGE$14
40926
+ message: MESSAGE$15
39919
40927
  });
39920
40928
  } })
39921
40929
  });
@@ -40724,7 +41732,7 @@ const noSelfUpdatingEffect = defineRule({
40724
41732
  });
40725
41733
  //#endregion
40726
41734
  //#region src/plugin/rules/react-builtins/no-set-state.ts
40727
- const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
41735
+ const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
40728
41736
  const noSetState = defineRule({
40729
41737
  id: "no-set-state",
40730
41738
  title: "Local class state forbidden",
@@ -40739,7 +41747,7 @@ const noSetState = defineRule({
40739
41747
  if (!getParentComponent(node)) return;
40740
41748
  context.report({
40741
41749
  node: node.callee,
40742
- message: MESSAGE$13
41750
+ message: MESSAGE$14
40743
41751
  });
40744
41752
  } })
40745
41753
  });
@@ -41046,9 +42054,9 @@ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
41046
42054
  return isReturnedFromEffect;
41047
42055
  };
41048
42056
  const isInsideEffectCleanupReturn = (node, ownerScope) => {
41049
- let functionNode = findEnclosingFunction(node);
42057
+ let functionNode = findEnclosingFunction$1(node);
41050
42058
  while (functionNode) {
41051
- const outerFunction = findEnclosingFunction(functionNode);
42059
+ const outerFunction = findEnclosingFunction$1(functionNode);
41052
42060
  if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
41053
42061
  if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
41054
42062
  functionNode = outerFunction;
@@ -41056,7 +42064,7 @@ const isInsideEffectCleanupReturn = (node, ownerScope) => {
41056
42064
  return false;
41057
42065
  };
41058
42066
  const hasRefCurrentReassignmentAfterClear = (clearCall, refName) => {
41059
- const enclosingFunction = findEnclosingFunction(clearCall);
42067
+ const enclosingFunction = findEnclosingFunction$1(clearCall);
41060
42068
  if (!isFunctionLike$2(enclosingFunction)) return false;
41061
42069
  if (!isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
41062
42070
  const clearStart = getRangeStart(clearCall);
@@ -41109,7 +42117,7 @@ const isAbstractRole = (openingElement, settings) => {
41109
42117
  };
41110
42118
  //#endregion
41111
42119
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
41112
- const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
42120
+ const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
41113
42121
  const DEFAULT_HANDLERS = [
41114
42122
  "onClick",
41115
42123
  "onMouseDown",
@@ -41224,7 +42232,7 @@ const noStaticElementInteractions = defineRule({
41224
42232
  if (!roleAttribute || !roleAttribute.value) {
41225
42233
  context.report({
41226
42234
  node: node.name,
41227
- message: MESSAGE$12
42235
+ message: MESSAGE$13
41228
42236
  });
41229
42237
  return;
41230
42238
  }
@@ -41234,7 +42242,7 @@ const noStaticElementInteractions = defineRule({
41234
42242
  if (isRecognizedRoleString(attributeValue.value)) return;
41235
42243
  context.report({
41236
42244
  node: node.name,
41237
- message: MESSAGE$12
42245
+ message: MESSAGE$13
41238
42246
  });
41239
42247
  return;
41240
42248
  }
@@ -41242,7 +42250,7 @@ const noStaticElementInteractions = defineRule({
41242
42250
  if (!settings.allowExpressionValues) {
41243
42251
  context.report({
41244
42252
  node: node.name,
41245
- message: MESSAGE$12
42253
+ message: MESSAGE$13
41246
42254
  });
41247
42255
  return;
41248
42256
  }
@@ -41250,7 +42258,7 @@ const noStaticElementInteractions = defineRule({
41250
42258
  if (isStaticNullishExpression(expression)) {
41251
42259
  context.report({
41252
42260
  node: node.name,
41253
- message: MESSAGE$12
42261
+ message: MESSAGE$13
41254
42262
  });
41255
42263
  return;
41256
42264
  }
@@ -41260,7 +42268,7 @@ const noStaticElementInteractions = defineRule({
41260
42268
  if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
41261
42269
  context.report({
41262
42270
  node: node.name,
41263
- message: MESSAGE$12
42271
+ message: MESSAGE$13
41264
42272
  });
41265
42273
  return;
41266
42274
  }
@@ -41268,7 +42276,7 @@ const noStaticElementInteractions = defineRule({
41268
42276
  }
41269
42277
  context.report({
41270
42278
  node: node.name,
41271
- message: MESSAGE$12
42279
+ message: MESSAGE$13
41272
42280
  });
41273
42281
  } };
41274
42282
  }
@@ -41374,7 +42382,7 @@ const noStringRefs = defineRule({
41374
42382
  });
41375
42383
  //#endregion
41376
42384
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
41377
- const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
42385
+ const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
41378
42386
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
41379
42387
  const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
41380
42388
  const noSyncXhr = defineRule({
@@ -41395,7 +42403,7 @@ const noSyncXhr = defineRule({
41395
42403
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
41396
42404
  context.report({
41397
42405
  node,
41398
- message: MESSAGE$11
42406
+ message: MESSAGE$12
41399
42407
  });
41400
42408
  };
41401
42409
  return {
@@ -41420,7 +42428,7 @@ const noSyncXhr = defineRule({
41420
42428
  });
41421
42429
  //#endregion
41422
42430
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
41423
- const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
42431
+ const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
41424
42432
  const isInsideClassMethod = (node, customClassFactoryNames) => {
41425
42433
  let ancestor = node.parent;
41426
42434
  while (ancestor) {
@@ -41508,7 +42516,7 @@ const noThisInSfc = defineRule({
41508
42516
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
41509
42517
  context.report({
41510
42518
  node,
41511
- message: MESSAGE$10
42519
+ message: MESSAGE$11
41512
42520
  });
41513
42521
  } };
41514
42522
  }
@@ -41886,7 +42894,7 @@ const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
41886
42894
  return false;
41887
42895
  };
41888
42896
  const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
41889
- const enclosingFunction = findEnclosingFunction(node);
42897
+ const enclosingFunction = findEnclosingFunction$1(node);
41890
42898
  if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$2(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
41891
42899
  let currentNode = node;
41892
42900
  while (currentNode !== enclosingFunction) {
@@ -43153,7 +44161,11 @@ const resolveSettings$8 = (settings) => {
43153
44161
  propNamePattern: ruleSettings.propNamePattern ?? "render*"
43154
44162
  };
43155
44163
  };
43156
- const expressionContainsJsxOrCreateElement = (root) => {
44164
+ const isReactCreateElementCall = (node, scopes) => isReactApiCall(node, "createElement", scopes, {
44165
+ allowGlobalReactNamespace: true,
44166
+ resolveNamedAliases: true
44167
+ });
44168
+ const expressionContainsJsxOrCreateElement = (root, scopes) => {
43157
44169
  let found = false;
43158
44170
  walkAst(root, (node) => {
43159
44171
  if (found) return false;
@@ -43162,17 +44174,17 @@ const expressionContainsJsxOrCreateElement = (root) => {
43162
44174
  found = true;
43163
44175
  return false;
43164
44176
  }
43165
- if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
44177
+ if (isNodeOfType(node, "CallExpression") && isReactCreateElementCall(node, scopes)) {
43166
44178
  found = true;
43167
44179
  return false;
43168
44180
  }
43169
44181
  });
43170
44182
  return found;
43171
44183
  };
43172
- const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement, controlFlow);
43173
- const isReactClassComponent = (classNode) => {
44184
+ const functionContainsJsxOrCreateElement = (functionNode, scopes, controlFlow) => expressionContainsJsxOrCreateElement(functionNode, scopes) || functionReturnsMatchingExpression(functionNode, scopes, (expression) => expressionContainsJsxOrCreateElement(expression, scopes), controlFlow);
44185
+ const isReactClassComponent = (classNode, scopes) => {
43174
44186
  if (isEs6Component(classNode)) return true;
43175
- return expressionContainsJsxOrCreateElement(classNode);
44187
+ return expressionContainsJsxOrCreateElement(classNode, scopes);
43176
44188
  };
43177
44189
  const findEnclosingComponent = (node, scopes, controlFlow) => {
43178
44190
  let walker = node.parent;
@@ -43189,7 +44201,7 @@ const findEnclosingComponent = (node, scopes, controlFlow) => {
43189
44201
  };
43190
44202
  }
43191
44203
  if (isNodeOfType(walker, "ClassDeclaration") || isNodeOfType(walker, "ClassExpression")) {
43192
- if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker)) return {
44204
+ if (walker.id && isReactComponentName(walker.id.name) && isReactClassComponent(walker, scopes)) return {
43193
44205
  component: walker,
43194
44206
  name: walker.id.name
43195
44207
  };
@@ -43278,12 +44290,12 @@ const isObjectCallbackCandidate = (node) => {
43278
44290
  }
43279
44291
  return false;
43280
44292
  };
43281
- const hocCallContainsComponent = (call) => {
44293
+ const hocCallContainsComponent = (call, scopes) => {
43282
44294
  const firstArgument = call.arguments[0];
43283
44295
  if (!firstArgument) return false;
43284
- if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument);
43285
- if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument);
43286
- return expressionContainsJsxOrCreateElement(firstArgument);
44296
+ if (isNodeOfType(firstArgument, "FunctionExpression") || isNodeOfType(firstArgument, "ArrowFunctionExpression") || isNodeOfType(firstArgument, "ClassExpression")) return expressionContainsJsxOrCreateElement(firstArgument, scopes);
44297
+ if (isNodeOfType(firstArgument, "CallExpression") && isHocCallee$1(firstArgument)) return hocCallContainsComponent(firstArgument, scopes);
44298
+ return expressionContainsJsxOrCreateElement(firstArgument, scopes);
43287
44299
  };
43288
44300
  const isFirstArgumentOfHocCall = (node) => {
43289
44301
  const parent = node.parent;
@@ -43316,19 +44328,35 @@ const isReturnOfMapCallback = (node) => {
43316
44328
  }
43317
44329
  return false;
43318
44330
  };
43319
- const isCloneElementCall = (node) => {
43320
- if (!isNodeOfType(node, "CallExpression")) return false;
43321
- const callee = node.callee;
43322
- if (isNodeOfType(callee, "Identifier")) return callee.name === "cloneElement";
43323
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "cloneElement";
43324
- };
43325
44331
  const TS_VALUE_PASSTHROUGH_TYPES = new Set([
43326
44332
  "TSAsExpression",
43327
44333
  "TSNonNullExpression",
43328
44334
  "TSSatisfiesExpression",
43329
44335
  "TSTypeAssertion"
43330
44336
  ]);
43331
- const isRenderFlowingReadReference = (identifier) => {
44337
+ const ELEMENT_TYPE_PROP_NAMES = new Set([
44338
+ "as",
44339
+ "body",
44340
+ "calendarcontainer",
44341
+ "component",
44342
+ "fallback",
44343
+ "tooltip"
44344
+ ]);
44345
+ const isElementTypeJsxAttribute = (node) => {
44346
+ if (!isNodeOfType(node, "JSXAttribute")) return false;
44347
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return false;
44348
+ const attributeName = node.name.name;
44349
+ return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
44350
+ };
44351
+ const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
44352
+ allowGlobalReactNamespace: true,
44353
+ resolveNamedAliases: true
44354
+ });
44355
+ const isReactLazyCall = (call, scopes) => isReactApiCall(call, "lazy", scopes, {
44356
+ allowGlobalReactNamespace: true,
44357
+ resolveNamedAliases: true
44358
+ });
44359
+ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
43332
44360
  let valueNode = identifier;
43333
44361
  let parent = valueNode.parent;
43334
44362
  while (parent) {
@@ -43338,20 +44366,26 @@ const isRenderFlowingReadReference = (identifier) => {
43338
44366
  continue;
43339
44367
  }
43340
44368
  switch (parent.type) {
43341
- case "JSXExpressionContainer":
43342
- case "ReturnStatement": return true;
43343
- case "ArrowFunctionExpression": return parent.body === valueNode;
44369
+ case "JSXOpeningElement": return parent.name === valueNode;
44370
+ case "JSXExpressionContainer": return Boolean(parent.parent && isElementTypeJsxAttribute(parent.parent));
44371
+ case "ReturnStatement": return false;
44372
+ case "ArrowFunctionExpression": return false;
43344
44373
  case "CallExpression":
43345
44374
  if (parent.callee === valueNode) return false;
43346
- if (isCreateElementCall(parent) || isCloneElementCall(parent)) return true;
44375
+ if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
44376
+ if (isReactCreateElementCall(parent, scopes)) return true;
43347
44377
  valueNode = parent;
43348
44378
  parent = parent.parent;
43349
44379
  continue;
43350
- case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
43351
- case "AssignmentExpression": {
43352
- const assignmentTarget = parent.left;
43353
- return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
44380
+ case "VariableDeclarator": {
44381
+ if (parent.init !== valueNode || !isNodeOfType(parent.id, "Identifier")) return false;
44382
+ const aliasSymbol = scopes.symbolFor(parent.id);
44383
+ if (!aliasSymbol || aliasSymbol.kind !== "const" || visitedSymbols.has(aliasSymbol.id)) return false;
44384
+ const nextVisitedSymbols = new Set(visitedSymbols);
44385
+ nextVisitedSymbols.add(aliasSymbol.id);
44386
+ return aliasSymbol.references.some((reference) => reference.flag === "read" && isRenderFlowingReadReference(reference.identifier, scopes, nextVisitedSymbols));
43354
44387
  }
44388
+ case "AssignmentExpression": return false;
43355
44389
  case "Property":
43356
44390
  if (parent.value !== valueNode) return false;
43357
44391
  valueNode = parent;
@@ -43389,6 +44423,7 @@ const noUnstableNestedComponents = defineRule({
43389
44423
  severity: "warn",
43390
44424
  recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
43391
44425
  category: "Performance",
44426
+ tags: ["react-jsx-only"],
43392
44427
  create: (context) => {
43393
44428
  const settings = resolveSettings$8(context.settings);
43394
44429
  const renderPropRegex = compileGlob(settings.propNamePattern);
@@ -43451,7 +44486,7 @@ const noUnstableNestedComponents = defineRule({
43451
44486
  },
43452
44487
  Identifier(node) {
43453
44488
  if (!isReactComponentName(node.name)) return;
43454
- if (!isRenderFlowingReadReference(node)) return;
44489
+ if (!isRenderFlowingReadReference(node, context.scopes)) return;
43455
44490
  recordInstantiation(node, node.name);
43456
44491
  },
43457
44492
  FunctionDeclaration: checkFunctionLike,
@@ -43460,28 +44495,33 @@ const noUnstableNestedComponents = defineRule({
43460
44495
  ClassDeclaration(node) {
43461
44496
  if (!node.id) return;
43462
44497
  if (!isReactComponentName(node.id.name)) return;
43463
- if (!isReactClassComponent(node)) return;
44498
+ if (!isReactClassComponent(node, context.scopes)) return;
43464
44499
  enqueueCandidate(node, null);
43465
44500
  },
43466
44501
  ClassExpression(node) {
43467
44502
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
43468
44503
  if (!inferredName || !isReactComponentName(inferredName)) return;
43469
- if (!isReactClassComponent(node)) return;
44504
+ if (!isReactClassComponent(node, context.scopes)) return;
43470
44505
  enqueueCandidate(node, null);
43471
44506
  },
43472
44507
  CallExpression(node) {
43473
- if (isCreateElementCall(node)) {
44508
+ if (isReactCreateElementCall(node, context.scopes)) {
43474
44509
  const firstArgument = node.arguments[0];
43475
44510
  if (firstArgument && isNodeOfType(firstArgument, "Identifier")) recordInstantiation(firstArgument, firstArgument.name);
43476
44511
  else if (firstArgument && isNodeOfType(firstArgument, "MemberExpression")) recordMemberChainInstantiation(firstArgument);
43477
44512
  }
43478
- if (!isHocCallee$1(node)) return;
43479
- if (!hocCallContainsComponent(node)) return;
43480
- enqueueCandidate(node, null);
44513
+ const isReactLazy = isReactLazyCall(node, context.scopes);
44514
+ if (!isReactLazy && !isHocCallee$1(node)) return;
44515
+ if (!isReactLazy && !hocCallContainsComponent(node, context.scopes)) return;
44516
+ const inferredName = inferFunctionLikeName(node);
44517
+ const propInfo = isComponentDeclaredInProp(node);
44518
+ if (propInfo === null && (!inferredName || !isReactComponentName(inferredName))) return;
44519
+ enqueueCandidate(node, propInfo === null ? inferredName : null);
43481
44520
  },
43482
44521
  "Program:exit"() {
43483
44522
  for (const report of queuedReports) {
43484
44523
  if (report.requiredInstantiationName !== null) {
44524
+ if ((report.requiredInstantiationBinding ? context.scopes.symbolFor(report.requiredInstantiationBinding) : null)?.references.some((reference) => reference.flag === "write" || reference.flag === "read-write")) continue;
43485
44525
  if (!(report.requiredInstantiationBinding !== null ? instantiatedBindingIdentifiers.has(report.requiredInstantiationBinding) : instantiatedComponentNames.has(report.requiredInstantiationName))) continue;
43486
44526
  }
43487
44527
  context.report({
@@ -43655,7 +44695,7 @@ const noWideLetterSpacing = defineRule({
43655
44695
  //#endregion
43656
44696
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
43657
44697
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
43658
- const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
44698
+ const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
43659
44699
  const resolveSettings$7 = (settings) => {
43660
44700
  const reactDoctor = settings?.["react-doctor"];
43661
44701
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -43689,7 +44729,7 @@ const noWillUpdateSetState = defineRule({
43689
44729
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
43690
44730
  context.report({
43691
44731
  node: node.callee,
43692
- message: MESSAGE$9
44732
+ message: MESSAGE$10
43693
44733
  });
43694
44734
  } };
43695
44735
  }
@@ -44514,7 +45554,7 @@ const findChildrenDestructuringFunction = (identifier, scopes) => {
44514
45554
  const symbol = scopes.symbolFor(identifier);
44515
45555
  if (symbol) {
44516
45556
  if (symbol.kind !== "parameter") return null;
44517
- const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
45557
+ const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
44518
45558
  if (declaringFunction && destructuresChildrenAsFirstParam(declaringFunction)) return declaringFunction;
44519
45559
  return null;
44520
45560
  }
@@ -44560,7 +45600,7 @@ const isChildrenMemberExpression = (node, scopes) => {
44560
45600
  const symbol = scopes.symbolFor(propsObject);
44561
45601
  if (symbol) {
44562
45602
  if (symbol.kind !== "parameter") return false;
44563
- const declaringFunction = findEnclosingFunction(symbol.bindingIdentifier);
45603
+ const declaringFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
44564
45604
  return declaringFunction ? isDeclaringFunctionComponentLike(declaringFunction) : false;
44565
45605
  }
44566
45606
  return hasComponentLikeAncestorFunction(node);
@@ -44685,7 +45725,7 @@ const preactNoRenderArguments = defineRule({
44685
45725
  });
44686
45726
  //#endregion
44687
45727
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
44688
- const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
45728
+ const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
44689
45729
  const preactPreferOndblclick = defineRule({
44690
45730
  id: "preact-prefer-ondblclick",
44691
45731
  title: "onDoubleClick instead of onDblClick",
@@ -44700,7 +45740,7 @@ const preactPreferOndblclick = defineRule({
44700
45740
  if (!onDoubleClickAttribute) return;
44701
45741
  context.report({
44702
45742
  node: onDoubleClickAttribute,
44703
- message: MESSAGE$8
45743
+ message: MESSAGE$9
44704
45744
  });
44705
45745
  } })
44706
45746
  });
@@ -44951,7 +45991,7 @@ const preferExplicitVariants = defineRule({
44951
45991
  });
44952
45992
  //#endregion
44953
45993
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
44954
- const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
45994
+ const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
44955
45995
  const resolveSettings$4 = (settings) => {
44956
45996
  const reactDoctor = settings?.["react-doctor"];
44957
45997
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -44990,7 +46030,7 @@ const preferFunctionComponent = defineRule({
44990
46030
  const reportNode = node.id ?? node;
44991
46031
  context.report({
44992
46032
  node: reportNode,
44993
- message: MESSAGE$7
46033
+ message: MESSAGE$8
44994
46034
  });
44995
46035
  };
44996
46036
  return {
@@ -45212,7 +46252,7 @@ const doesFunctionReturnsObjectLiteral = (functionNode) => {
45212
46252
  //#endregion
45213
46253
  //#region src/plugin/utils/enclosing-component-or-hook-scope.ts
45214
46254
  const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
45215
- const functionNode = findEnclosingFunction(startNode);
46255
+ const functionNode = findEnclosingFunction$1(startNode);
45216
46256
  if (!functionNode) return null;
45217
46257
  const displayName = componentOrHookDisplayNameForFunction(functionNode);
45218
46258
  if (!displayName) return null;
@@ -46274,7 +47314,7 @@ const isTanstackQuerySource = (source) => TANSTACK_QUERY_PACKAGE_PATTERN.test(so
46274
47314
  //#region src/plugin/rules/tanstack-query/query-destructure-result.ts
46275
47315
  const isHookReturnForwardingSpread = (objectExpression) => {
46276
47316
  const objectParent = objectExpression.parent;
46277
- const enclosingFunction = findEnclosingFunction(objectExpression);
47317
+ const enclosingFunction = findEnclosingFunction$1(objectExpression);
46278
47318
  if (!enclosingFunction) return false;
46279
47319
  if (!(isNodeOfType(objectParent, "ReturnStatement") || isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && enclosingFunction.body === objectExpression)) return false;
46280
47320
  const enclosingName = componentOrHookDisplayNameForFunction(enclosingFunction);
@@ -46568,7 +47608,258 @@ const queryMutationMissingInvalidation = defineRule({
46568
47608
  }
46569
47609
  });
46570
47610
  //#endregion
47611
+ //#region src/plugin/utils/is-node-conditionally-executed.ts
47612
+ const isNodeConditionallyExecuted = (node, boundary) => {
47613
+ let child = node;
47614
+ let parent = child.parent ?? null;
47615
+ while (parent && parent !== boundary) {
47616
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
47617
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
47618
+ if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
47619
+ child = parent;
47620
+ parent = child.parent ?? null;
47621
+ }
47622
+ return false;
47623
+ };
47624
+ //#endregion
47625
+ //#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
47626
+ const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
47627
+ const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
47628
+ const namespaceObject = stripParenExpression(memberExpression.object);
47629
+ if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
47630
+ if (!isNodeOfType(namespaceObject, "Identifier")) return null;
47631
+ const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
47632
+ if (resolvedNamespaceSymbol?.kind !== "import") return null;
47633
+ const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
47634
+ return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
47635
+ };
47636
+ const resolveTanstackQueryHookName = (callExpression, scopes) => {
47637
+ const callee = stripParenExpression(callExpression.callee);
47638
+ if (isNodeOfType(callee, "Identifier")) {
47639
+ const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
47640
+ if (!resolvedSymbol) return null;
47641
+ if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
47642
+ const initializer = stripParenExpression(resolvedSymbol.initializer);
47643
+ return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
47644
+ }
47645
+ if (resolvedSymbol.kind !== "import") return null;
47646
+ const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
47647
+ if (importBinding === null) return null;
47648
+ if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
47649
+ return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
47650
+ }
47651
+ if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
47652
+ return null;
47653
+ };
47654
+ const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
47655
+ const unwrappedInitializer = stripParenExpression(initializer);
47656
+ if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
47657
+ if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
47658
+ const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
47659
+ if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
47660
+ const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
47661
+ if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
47662
+ return resolveTanstackQueryHookName(resolvedInitializer, scopes);
47663
+ };
47664
+ //#endregion
46571
47665
  //#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
47666
+ const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
47667
+ const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
47668
+ const resolveCalledFunction = (callee, context) => {
47669
+ const unwrappedCallee = stripParenExpression(callee);
47670
+ if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
47671
+ if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
47672
+ const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
47673
+ if (!symbol) return null;
47674
+ const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
47675
+ return candidate && isFunctionLike$2(candidate) ? candidate : null;
47676
+ };
47677
+ const hasSuspensionBefore = (functionNode, boundary, context) => {
47678
+ if (!isFunctionLike$2(functionNode)) return true;
47679
+ if (functionNode.generator) return true;
47680
+ const boundaryStart = getRangeStart(boundary);
47681
+ if (boundaryStart === null) return true;
47682
+ let hasSuspension = false;
47683
+ walkAst(functionNode, (node) => {
47684
+ if (node !== functionNode && isFunctionLike$2(node)) return false;
47685
+ if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
47686
+ const suspensionStart = getRangeStart(node);
47687
+ if (suspensionStart !== null && suspensionStart < boundaryStart) {
47688
+ hasSuspension = true;
47689
+ return false;
47690
+ }
47691
+ });
47692
+ return hasSuspension;
47693
+ };
47694
+ const isFunctionAncestor = (ancestor, functionNode) => {
47695
+ let enclosingFunction = findEnclosingFunction$1(functionNode);
47696
+ while (enclosingFunction) {
47697
+ if (enclosingFunction === ancestor) return true;
47698
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
47699
+ }
47700
+ return false;
47701
+ };
47702
+ const isUnconditionallyExecuted = (node, functionNode, context) => context.cfg.isUnconditionalFromEntry(node) && !isNodeConditionallyExecuted(node, functionNode) && !(isNodeOfType(node, "MemberExpression") && isNodeOfType(node.parent, "AssignmentExpression") && node.parent.left === node && (node.parent.operator === "&&=" || node.parent.operator === "||=" || node.parent.operator === "??="));
47703
+ const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
47704
+ if (visitedFunctions.has(callerFunction)) return false;
47705
+ visitedFunctions.add(callerFunction);
47706
+ let invokesTarget = false;
47707
+ walkAst(callerFunction, (node) => {
47708
+ if (node !== callerFunction && isFunctionLike$2(node)) return false;
47709
+ if (!isNodeOfType(node, "CallExpression")) return;
47710
+ if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
47711
+ const calledFunction = resolveCalledFunction(node.callee, context);
47712
+ if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
47713
+ invokesTarget = true;
47714
+ return false;
47715
+ }
47716
+ });
47717
+ return invokesTarget;
47718
+ };
47719
+ const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
47720
+ const boundaryFunction = findEnclosingFunction$1(boundary);
47721
+ const boundaryStart = getRangeStart(boundary);
47722
+ if (!boundaryFunction || boundaryStart === null) return false;
47723
+ let isInvokedBefore = false;
47724
+ walkAst(boundaryFunction, (node) => {
47725
+ if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
47726
+ if (!isNodeOfType(node, "CallExpression")) return;
47727
+ const callStart = getRangeStart(node);
47728
+ if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
47729
+ const calledFunction = resolveCalledFunction(node.callee, context);
47730
+ if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
47731
+ isInvokedBefore = true;
47732
+ return false;
47733
+ }
47734
+ });
47735
+ return isInvokedBefore;
47736
+ };
47737
+ const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
47738
+ const boundaryStart = getRangeStart(boundary);
47739
+ if (boundaryStart === null) return false;
47740
+ let isInvokedAfter = false;
47741
+ walkAst(callerFunction, (node) => {
47742
+ if (node !== callerFunction && isFunctionLike$2(node)) return false;
47743
+ if (!isNodeOfType(node, "CallExpression")) return;
47744
+ const callStart = getRangeStart(node);
47745
+ if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
47746
+ const calledFunction = resolveCalledFunction(node.callee, context);
47747
+ if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
47748
+ isInvokedAfter = true;
47749
+ return false;
47750
+ }
47751
+ });
47752
+ return isInvokedAfter;
47753
+ };
47754
+ const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
47755
+ const writeStart = getRangeStart(writeNode);
47756
+ const boundaryStart = getRangeStart(boundary);
47757
+ const writeFunction = findEnclosingFunction$1(writeNode);
47758
+ const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
47759
+ if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
47760
+ const boundaryFunction = findEnclosingFunction$1(boundary);
47761
+ const renderFunction = deferredExecutionFunction ? findEnclosingFunction$1(deferredExecutionFunction) : null;
47762
+ if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
47763
+ if (!writeFunction) return writeStart < boundaryStart;
47764
+ if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
47765
+ if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
47766
+ if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
47767
+ if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
47768
+ return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
47769
+ }
47770
+ return writeStart < boundaryStart;
47771
+ }
47772
+ if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
47773
+ return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
47774
+ };
47775
+ const getStaticStringValue = (node) => {
47776
+ const unwrappedNode = stripParenExpression(node);
47777
+ if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
47778
+ if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
47779
+ return null;
47780
+ };
47781
+ const isSameRefetchMember = (target, candidate, context) => {
47782
+ const unwrappedTarget = stripParenExpression(target);
47783
+ const unwrappedCandidate = stripParenExpression(candidate);
47784
+ if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
47785
+ const candidateTarget = stripParenExpression(unwrappedCandidate.object);
47786
+ if (!isNodeOfType(candidateTarget, "Identifier")) return false;
47787
+ const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
47788
+ const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
47789
+ return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
47790
+ };
47791
+ const getRefetchMutationTarget = (node, context) => {
47792
+ if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
47793
+ const parent = node.parent;
47794
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
47795
+ return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
47796
+ }
47797
+ if (!isNodeOfType(node, "CallExpression")) return null;
47798
+ const callee = stripParenExpression(node.callee);
47799
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
47800
+ const receiver = stripParenExpression(callee.object);
47801
+ if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
47802
+ const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
47803
+ const target = node.arguments[0];
47804
+ if (!target || isNodeOfType(target, "SpreadElement")) return null;
47805
+ if (methodName === "defineProperty") {
47806
+ const propertyKey = node.arguments[1];
47807
+ if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
47808
+ const descriptor = node.arguments[2];
47809
+ if (isNodeOfType(descriptor, "ObjectExpression")) {
47810
+ const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
47811
+ if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
47812
+ }
47813
+ return target;
47814
+ }
47815
+ if (methodName !== "assign") return null;
47816
+ let finalRefetchValue = null;
47817
+ for (const source of node.arguments.slice(1)) {
47818
+ if (!isNodeOfType(source, "ObjectExpression")) continue;
47819
+ for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
47820
+ }
47821
+ if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
47822
+ return target;
47823
+ };
47824
+ const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
47825
+ const unwrappedExpression = stripParenExpression(expression);
47826
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
47827
+ const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
47828
+ if (!resultSymbol) return false;
47829
+ const program = findProgramRoot(expression);
47830
+ if (!program) return true;
47831
+ if (getRangeStart(boundary) === null) return true;
47832
+ let hasWrite = false;
47833
+ walkAst(program, (node) => {
47834
+ const mutationTarget = getRefetchMutationTarget(node, context);
47835
+ if (!mutationTarget) return;
47836
+ if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
47837
+ const object = stripParenExpression(mutationTarget);
47838
+ if (!isNodeOfType(object, "Identifier")) return;
47839
+ if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
47840
+ hasWrite = true;
47841
+ return false;
47842
+ }
47843
+ });
47844
+ return hasWrite;
47845
+ };
47846
+ const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47847
+ const unwrappedExpression = stripParenExpression(expression);
47848
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
47849
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
47850
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
47851
+ if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
47852
+ visitedSymbolIds.add(symbol.id);
47853
+ const bindingProperty = symbol.bindingIdentifier.parent;
47854
+ if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
47855
+ const initializer = symbol.declarationNode.init;
47856
+ return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
47857
+ }
47858
+ return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
47859
+ };
47860
+ const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
47861
+ return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
47862
+ };
46572
47863
  const queryNoQueryInEffect = defineRule({
46573
47864
  id: "query-no-query-in-effect",
46574
47865
  title: "Query refetch inside useEffect",
@@ -46584,8 +47875,7 @@ const queryNoQueryInEffect = defineRule({
46584
47875
  walkAst(callback, (child) => {
46585
47876
  if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
46586
47877
  if (!isNodeOfType(child, "CallExpression")) return;
46587
- const callee = child.callee;
46588
- if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
47878
+ if (isTanstackRefetchCall(child, context, callback)) context.report({
46589
47879
  node: child,
46590
47880
  message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
46591
47881
  });
@@ -46594,28 +47884,6 @@ const queryNoQueryInEffect = defineRule({
46594
47884
  });
46595
47885
  //#endregion
46596
47886
  //#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
46597
- const resolveTanstackQueryHookName = (callExpression) => {
46598
- const callee = callExpression.callee;
46599
- if (isNodeOfType(callee, "Identifier")) {
46600
- const importBinding = getImportBindingForName(callExpression, callee.name);
46601
- if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
46602
- if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
46603
- return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
46604
- }
46605
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
46606
- const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
46607
- if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
46608
- }
46609
- return null;
46610
- };
46611
- const resolveHookNameFromInitializer = (initializer, scopes) => {
46612
- if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
46613
- if (!isNodeOfType(initializer, "Identifier")) return null;
46614
- const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
46615
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
46616
- if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
46617
- return resolveTanstackQueryHookName(resolvedSymbol.initializer);
46618
- };
46619
47887
  const queryNoRestDestructuring = defineRule({
46620
47888
  id: "query-no-rest-destructuring",
46621
47889
  title: "Rest destructuring on query result",
@@ -46627,7 +47895,7 @@ const queryNoRestDestructuring = defineRule({
46627
47895
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
46628
47896
  if (!node.init) return;
46629
47897
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
46630
- const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
47898
+ const hookName = resolveTanstackQueryHookNameFromInitializer(node.init, context.scopes);
46631
47899
  if (!hookName) return;
46632
47900
  context.report({
46633
47901
  node: node.id,
@@ -46762,8 +48030,8 @@ const queryStableQueryClient = defineRule({
46762
48030
  create: (context) => ({ NewExpression(node) {
46763
48031
  if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "QueryClient") return;
46764
48032
  if (isStableHookWrapperArgument(node)) return;
46765
- let enclosingFunction = findEnclosingFunction(node);
46766
- while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction(enclosingFunction);
48033
+ let enclosingFunction = findEnclosingFunction$1(node);
48034
+ while (enclosingFunction && isImmediatelyInvokedFunction(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
46767
48035
  if (!enclosingFunction) return;
46768
48036
  if (!isComponentFunction(enclosingFunction)) return;
46769
48037
  context.report({
@@ -46858,7 +48126,7 @@ const reactCompilerNoManualMemoization = defineRule({
46858
48126
  const comparatorArgument = node.arguments?.[1];
46859
48127
  if (comparatorArgument && !isNullishComparatorArgument(comparatorArgument)) return;
46860
48128
  } else {
46861
- const enclosingFunction = findEnclosingFunction(node);
48129
+ const enclosingFunction = findEnclosingFunction$1(node);
46862
48130
  if (!enclosingFunction || !isCompilerInferableFunction(enclosingFunction)) return;
46863
48131
  }
46864
48132
  const removalMessage = REMOVAL_MESSAGE_BY_REACT_API_NAME.get(apiName);
@@ -46871,7 +48139,7 @@ const reactCompilerNoManualMemoization = defineRule({
46871
48139
  });
46872
48140
  //#endregion
46873
48141
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
46874
- const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
48142
+ const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
46875
48143
  const reactInJsxScope = defineRule({
46876
48144
  id: "react-in-jsx-scope",
46877
48145
  title: "React not in scope for JSX",
@@ -46883,19 +48151,190 @@ const reactInJsxScope = defineRule({
46883
48151
  if (findVariableInitializer(node, "React")) return;
46884
48152
  context.report({
46885
48153
  node: node.name,
46886
- message: MESSAGE$6
48154
+ message: MESSAGE$7
46887
48155
  });
46888
48156
  },
46889
48157
  JSXFragment(node) {
46890
48158
  if (findVariableInitializer(node, "React")) return;
46891
48159
  context.report({
46892
48160
  node: node.openingFragment,
46893
- message: MESSAGE$6
48161
+ message: MESSAGE$7
46894
48162
  });
46895
48163
  }
46896
48164
  })
46897
48165
  });
46898
48166
  //#endregion
48167
+ //#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
48168
+ const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
48169
+ const REACT_MARKDOWN_MODULE = "react-markdown";
48170
+ const REHYPE_RAW_MODULE = "rehype-raw";
48171
+ const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
48172
+ const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
48173
+ const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
48174
+ const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
48175
+ const DEFAULT_EXPORT_NAMES = new Set(["default"]);
48176
+ const getImportDeclaration = (symbol) => {
48177
+ if (symbol.kind !== "import") return null;
48178
+ const importDeclaration = symbol.declarationNode.parent;
48179
+ return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
48180
+ };
48181
+ const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
48182
+ const isDefaultImportSymbol = (symbol, moduleName) => {
48183
+ if (!isImportFromModule(symbol, moduleName)) return false;
48184
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
48185
+ };
48186
+ const resolveImportedIdentifier = (node, scopes) => {
48187
+ if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
48188
+ const symbol = resolveConstIdentifierAlias(node, scopes);
48189
+ return symbol?.kind === "import" ? symbol : null;
48190
+ };
48191
+ const getStaticMemberParts = (node) => {
48192
+ if (isNodeOfType(node, "MemberExpression")) {
48193
+ if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
48194
+ return {
48195
+ object: node.object,
48196
+ propertyName: node.property.name
48197
+ };
48198
+ }
48199
+ if (isNodeOfType(node, "JSXMemberExpression")) {
48200
+ if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
48201
+ return {
48202
+ object: node.object,
48203
+ propertyName: node.property.name
48204
+ };
48205
+ }
48206
+ return null;
48207
+ };
48208
+ const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
48209
+ const memberParts = getStaticMemberParts(node);
48210
+ if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
48211
+ const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
48212
+ return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
48213
+ };
48214
+ const isReactMarkdownComponent = (node, scopes) => {
48215
+ if (isNodeOfType(node, "JSXIdentifier")) {
48216
+ const symbol = resolveImportedIdentifier(node, scopes);
48217
+ if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
48218
+ if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
48219
+ const importedName = getImportedName(symbol.declarationNode);
48220
+ return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
48221
+ }
48222
+ return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
48223
+ };
48224
+ const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
48225
+ const node = stripParenExpression(rawNode);
48226
+ if (isNodeOfType(node, "Identifier")) {
48227
+ const symbol = resolveConstIdentifierAlias(node, scopes);
48228
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
48229
+ if (isDefaultImportSymbol(symbol, moduleName)) return true;
48230
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
48231
+ visitedSymbolIds.add(symbol.id);
48232
+ return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
48233
+ }
48234
+ if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
48235
+ if (!isNodeOfType(node, "ArrayExpression")) return false;
48236
+ for (const element of node.elements) {
48237
+ if (!element || isNodeOfType(element, "SpreadElement")) continue;
48238
+ return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
48239
+ }
48240
+ return false;
48241
+ };
48242
+ const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
48243
+ const node = stripParenExpression(rawNode);
48244
+ if (isNodeOfType(node, "Identifier")) {
48245
+ const symbol = scopes.referenceFor(node)?.resolvedSymbol;
48246
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
48247
+ visitedSymbolIds.add(symbol.id);
48248
+ return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
48249
+ }
48250
+ if (!isNodeOfType(node, "ArrayExpression")) return null;
48251
+ const entries = [];
48252
+ for (const element of node.elements) {
48253
+ if (!element) continue;
48254
+ if (isNodeOfType(element, "SpreadElement")) {
48255
+ const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
48256
+ if (spreadEntries === null) return null;
48257
+ entries.push(...spreadEntries);
48258
+ continue;
48259
+ }
48260
+ entries.push(element);
48261
+ }
48262
+ return entries;
48263
+ };
48264
+ const findEffectiveExplicitAttribute = (attributes, attributeName) => {
48265
+ for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
48266
+ const attribute = attributes[attributeIndex];
48267
+ if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
48268
+ if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
48269
+ }
48270
+ return null;
48271
+ };
48272
+ const getAttributeExpression = (attribute) => {
48273
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
48274
+ return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
48275
+ };
48276
+ const isDomPurifyNamespace = (node, scopes) => {
48277
+ const symbol = resolveImportedIdentifier(node, scopes);
48278
+ if (!symbol) return false;
48279
+ const importDeclaration = getImportDeclaration(symbol);
48280
+ if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
48281
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
48282
+ };
48283
+ const isDomPurifySanitizeCall = (node, scopes) => {
48284
+ if (!isNodeOfType(node, "CallExpression")) return false;
48285
+ const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
48286
+ return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
48287
+ };
48288
+ const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
48289
+ const node = stripParenExpression(rawNode);
48290
+ if (isNodeOfType(node, "Literal")) return true;
48291
+ if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
48292
+ if (isDomPurifySanitizeCall(node, scopes)) return true;
48293
+ if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
48294
+ if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
48295
+ if (!isNodeOfType(node, "Identifier")) return false;
48296
+ const symbol = scopes.referenceFor(node)?.resolvedSymbol;
48297
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
48298
+ visitedSymbolIds.add(symbol.id);
48299
+ return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
48300
+ };
48301
+ const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
48302
+ const jsxElement = openingElement.parent;
48303
+ if (!isNodeOfType(jsxElement, "JSXElement")) return false;
48304
+ const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
48305
+ if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
48306
+ if (isNodeOfType(child, "JSXText")) return false;
48307
+ if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
48308
+ if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
48309
+ return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
48310
+ });
48311
+ const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
48312
+ if (!childrenAttribute) return false;
48313
+ const childrenExpression = getAttributeExpression(childrenAttribute);
48314
+ return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
48315
+ };
48316
+ const reactMarkdownUnsanitizedRawHtml = defineRule({
48317
+ id: "react-markdown-unsanitized-raw-html",
48318
+ title: "Unsanitized raw HTML in React Markdown",
48319
+ severity: "warn",
48320
+ recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
48321
+ create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
48322
+ if (!isReactMarkdownComponent(node.name, context.scopes)) return;
48323
+ const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
48324
+ if (!pluginsAttribute) return;
48325
+ const pluginsExpression = getAttributeExpression(pluginsAttribute);
48326
+ if (!pluginsExpression) return;
48327
+ const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
48328
+ if (pluginEntries === null) return;
48329
+ if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
48330
+ if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
48331
+ context.report({
48332
+ node,
48333
+ message: MESSAGE$6
48334
+ });
48335
+ } }))
48336
+ });
48337
+ //#endregion
46899
48338
  //#region src/plugin/utils/collect-react-redux-selector-aliases.ts
46900
48339
  const REACT_REDUX_MODULE = "react-redux";
46901
48340
  const collectReactReduxSelectorAliases = (programRoot) => {
@@ -49792,7 +51231,7 @@ const isReactNativeDimensionsCallee = (node, callee) => {
49792
51231
  };
49793
51232
  const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
49794
51233
  const isInsideStyleFactoryCallback = (node) => {
49795
- const enclosingFunction = findEnclosingFunction(node);
51234
+ const enclosingFunction = findEnclosingFunction$1(node);
49796
51235
  if (!enclosingFunction) return false;
49797
51236
  const callExpression = enclosingFunction.parent;
49798
51237
  if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) return false;
@@ -55323,22 +56762,6 @@ const isInsideClassComponent = (node) => {
55323
56762
  }
55324
56763
  return false;
55325
56764
  };
55326
- const hasShortCircuitAncestor = (descendant, ancestor) => {
55327
- let current = descendant.parent;
55328
- while (current && current !== ancestor) {
55329
- if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
55330
- if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
55331
- if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
55332
- current = current.parent ?? null;
55333
- }
55334
- return false;
55335
- };
55336
- const isWithinRange = (descendant, sibling) => {
55337
- const descendantSpan = descendant;
55338
- const siblingSpan = sibling;
55339
- if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
55340
- return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
55341
- };
55342
56765
  const isInsideTry = (descendant, ancestor) => {
55343
56766
  let current = descendant.parent;
55344
56767
  while (current && current !== ancestor) {
@@ -55530,7 +56953,7 @@ const rulesOfHooks = defineRule({
55530
56953
  });
55531
56954
  return;
55532
56955
  }
55533
- if (hasShortCircuitAncestor(node, enclosing.node)) {
56956
+ if (isNodeConditionallyExecuted(node, enclosing.node)) {
55534
56957
  context.report({
55535
56958
  node: node.callee,
55536
56959
  message: buildConditionalMessage(hookName)
@@ -56664,12 +58087,14 @@ const declarationAwaitsRequestScopedCall = (declaration) => {
56664
58087
  }
56665
58088
  return false;
56666
58089
  };
56667
- const declarationAwaitsGate = (declaration) => {
58090
+ const declarationAwaitsGate = (declaration, context) => {
56668
58091
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
56669
58092
  for (const declarator of declaration.declarations ?? []) {
56670
58093
  if (!isNodeOfType(declarator.init, "AwaitExpression")) continue;
56671
58094
  const argument = declarator.init.argument;
56672
58095
  if (!isNodeOfType(argument, "CallExpression")) continue;
58096
+ if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
58097
+ if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
56673
58098
  const calleeName = getCalleeName$2(argument);
56674
58099
  if (!calleeName) continue;
56675
58100
  if (isAuthGuardName(calleeName)) return true;
@@ -56697,7 +58122,7 @@ const serverSequentialIndependentAwait = defineRule({
56697
58122
  if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
56698
58123
  if (declarationAwaitsExistingPromise(nextStatement)) continue;
56699
58124
  if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
56700
- if (declarationAwaitsGate(currentStatement)) continue;
58125
+ if (declarationAwaitsGate(currentStatement, context)) continue;
56701
58126
  context.report({
56702
58127
  node: nextStatement,
56703
58128
  message: "This await doesn't use the previous result, so your users wait twice as long for nothing."
@@ -57683,7 +59108,7 @@ const tanstackStartNoNavigateInRender = defineRule({
57683
59108
  const isReturnedFromCustomHook = (functionNode) => {
57684
59109
  const parent = functionNode.parent;
57685
59110
  if (isNodeOfType(parent, "ReturnStatement")) {
57686
- const outerFunction = findEnclosingFunction(parent);
59111
+ const outerFunction = findEnclosingFunction$1(parent);
57687
59112
  const hookName = outerFunction ? getFunctionBindingName$1(outerFunction) : null;
57688
59113
  return Boolean(hookName && HOOK_NAME_PATTERN$3.test(hookName));
57689
59114
  }
@@ -57700,13 +59125,13 @@ const tanstackStartNoNavigateInRender = defineRule({
57700
59125
  return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
57701
59126
  };
57702
59127
  const isDeferredNavigateCall = (callNode) => {
57703
- let enclosingFunction = findEnclosingFunction(callNode);
59128
+ let enclosingFunction = findEnclosingFunction$1(callNode);
57704
59129
  while (enclosingFunction) {
57705
59130
  if (isDeferredCallbackPosition(enclosingFunction)) return true;
57706
59131
  if (isWiredAsEventHandler(enclosingFunction)) return true;
57707
59132
  if (isReturnedFromCustomHook(enclosingFunction)) return true;
57708
59133
  if (!isSynchronouslyInvokedAnonymousWrapper(enclosingFunction)) return false;
57709
- enclosingFunction = findEnclosingFunction(enclosingFunction);
59134
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
57710
59135
  }
57711
59136
  return false;
57712
59137
  };
@@ -62145,6 +63570,17 @@ const reactDoctorRules = [
62145
63570
  requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
62146
63571
  }
62147
63572
  },
63573
+ {
63574
+ key: "react-doctor/react-markdown-unsanitized-raw-html",
63575
+ id: "react-markdown-unsanitized-raw-html",
63576
+ source: "react-doctor",
63577
+ originallyExternal: false,
63578
+ rule: {
63579
+ ...reactMarkdownUnsanitizedRawHtml,
63580
+ framework: "global",
63581
+ category: "Security"
63582
+ }
63583
+ },
62148
63584
  {
62149
63585
  key: "react-doctor/redux-useselector-inline-derivation",
62150
63586
  id: "redux-useselector-inline-derivation",