oxlint-plugin-react-doctor 0.7.9-dev.1a30f2c → 0.7.9-dev.21043e0

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 +1854 -302
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { KEYS } from "eslint-visitor-keys";
2
2
  import * as path from "node:path";
3
+ import { parseSync, visitorKeys } from "oxc-parser";
3
4
  import * as fs from "node:fs";
4
5
  import { readFileSync } from "node:fs";
5
- import { parseSync, visitorKeys } from "oxc-parser";
6
6
  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;
@@ -327,7 +327,15 @@ const defineRule = (rule) => {
327
327
  let lastContentLineIndex;
328
328
  const buildLineStartOffsets = (content) => {
329
329
  const lineStartOffsets = [0];
330
- for (let newlineIndex = content.indexOf("\n"); newlineIndex !== -1; newlineIndex = content.indexOf("\n", newlineIndex + 1)) lineStartOffsets.push(newlineIndex + 1);
330
+ for (let characterIndex = 0; characterIndex < content.length; characterIndex += 1) {
331
+ const character = content[characterIndex];
332
+ if (character === "\r" && content[characterIndex + 1] === "\n") {
333
+ characterIndex += 1;
334
+ lineStartOffsets.push(characterIndex + 1);
335
+ continue;
336
+ }
337
+ if (character === "\r" || character === "\n" || character === "\u2028" || character === "\u2029") lineStartOffsets.push(characterIndex + 1);
338
+ }
331
339
  return lineStartOffsets;
332
340
  };
333
341
  const getLineStartOffsets = (content) => {
@@ -4342,7 +4350,8 @@ const SECRET_VALUE_PATTERNS = [
4342
4350
  ];
4343
4351
  const JWT_LITERAL_VALUE_PATTERN = /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}\b/;
4344
4352
  const PUBLIC_ENV_SECRET_NAME_PATTERN = /\b(?:NEXT_PUBLIC|VITE|REACT_APP|EXPO_PUBLIC)_[A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PRIVATE|DATABASE_URL|SERVICE_ROLE|AWS_ACCESS_KEY|AWS_SECRET)[A-Z0-9_]*\b/i;
4345
- const FULL_ENV_LEAK_CONTEXT_PATTERN = /\b(?:process\.env|import\.meta\.env|window\.__[A-Z0-9_]*ENV[A-Z0-9_]*__|__[A-Z0-9_]*ENV[A-Z0-9_]*__)\b/;
4353
+ const FULL_ENV_LEAK_CONTEXT_PATTERN = /\b(?:process\s*\.\s*env|import\s*\.\s*meta\s*\.\s*env|window\.__[A-Z0-9_]*ENV[A-Z0-9_]*__|__[A-Z0-9_]*ENV[A-Z0-9_]*__)\b/;
4354
+ const FULL_ENV_LEAK_COMMENT_TRIVIA_PATTERN = /\b(?:(?:process|window)\s*(?:\/[*/]|<!--|-->|\.\s*(?:\/[*/]|<!--|-->))|import\s*(?:\/[*/]|<!--|-->|\.\s*(?:\/[*/]|<!--|-->|meta\s*(?:\/[*/]|<!--|-->|\.\s*(?:\/[*/]|<!--|-->)))))/;
4346
4355
  const FULL_ENV_LEAK_SECRET_NAME_PATTERN = /\b(?:DATABASE_URL|AWS_SECRET_ACCESS_KEY|AWS_ACCESS_KEY_ID|MAILGUN_API_KEY|SALESFORCE_CLIENT_SECRET|OKTA_CLIENT_SECRET|SESSION_SECRET|COOKIE_SECRET|PRIVATE_KEY|SERVICE_ROLE)\b/;
4347
4356
  const TRUSTED_PUBLIC_SECRET_NAME_PATTERN = /(?:SENTRY_DSN|PUBLIC_KEY|PUBLISHABLE|ANON_KEY|POSTHOG_(?:PROJECT_)?TOKEN|POSTHOG_KEY|TLDRAW_LICENSE_KEY|CLERK_PUBLISHABLE_KEY|ALGOLIA_SEARCH_KEY|GC_API_KEY|GOOGLE_MAPS_API_KEY|MAPBOX_TOKEN|MIXPANEL_TOKEN|FACEBOOK_CLIENT_TOKEN|(?:NEXT_PUBLIC|VITE|REACT_APP|EXPO_PUBLIC)_(?:DISABLE|ENABLE|ALLOW|REQUIRE)_)|(?:TOKEN|SECRET|PASSWORD|PRIVATE)_(?:KIND|TYPE|URL|URI|ENDPOINT|HEADER|NAME)$/i;
4348
4357
  const PUBLIC_CLIENT_KEY_PATTERNS = [
@@ -4500,6 +4509,172 @@ const findSuspiciousPublicEnvSecretNamePattern = (content) => {
4500
4509
  //#region src/plugin/rules/security-scan/utils/has-full-env-leak-shape.ts
4501
4510
  const hasFullEnvLeakShape = (content) => FULL_ENV_LEAK_CONTEXT_PATTERN.test(content) && FULL_ENV_LEAK_SECRET_NAME_PATTERN.test(content);
4502
4511
  //#endregion
4512
+ //#region src/plugin/utils/attach-parent-references.ts
4513
+ const attachParentReferences = (root) => {
4514
+ const visit = (node, parent) => {
4515
+ const writableNode = node;
4516
+ writableNode.parent = parent;
4517
+ const nodeRecord = node;
4518
+ for (const key of Object.keys(nodeRecord)) {
4519
+ if (key === "parent") continue;
4520
+ const child = nodeRecord[key];
4521
+ if (Array.isArray(child)) {
4522
+ for (const item of child) if (isAstNode(item)) visit(item, node);
4523
+ } else if (isAstNode(child)) visit(child, node);
4524
+ }
4525
+ };
4526
+ visit(root, null);
4527
+ };
4528
+ //#endregion
4529
+ //#region src/plugin/utils/cross-file-probe-recorder.ts
4530
+ let activeProbeTrace = null;
4531
+ const recordExistenceProbe = (absolutePath) => {
4532
+ activeProbeTrace?.existencePaths.add(absolutePath);
4533
+ };
4534
+ const recordContentProbe = (absolutePath) => {
4535
+ activeProbeTrace?.contentPaths.add(absolutePath);
4536
+ };
4537
+ const isProbeRecorderActive = () => activeProbeTrace !== null;
4538
+ const collectCrossFileProbes = (collect) => {
4539
+ const previousTrace = activeProbeTrace;
4540
+ const trace = {
4541
+ existencePaths: /* @__PURE__ */ new Set(),
4542
+ contentPaths: /* @__PURE__ */ new Set()
4543
+ };
4544
+ activeProbeTrace = trace;
4545
+ try {
4546
+ collect();
4547
+ } finally {
4548
+ activeProbeTrace = previousTrace;
4549
+ }
4550
+ return trace;
4551
+ };
4552
+ //#endregion
4553
+ //#region src/plugin/utils/parse-source-file.ts
4554
+ const FILENAME_TO_LANG = {
4555
+ ".ts": "ts",
4556
+ ".tsx": "tsx",
4557
+ ".js": "js",
4558
+ ".jsx": "jsx",
4559
+ ".mjs": "js",
4560
+ ".cjs": "js",
4561
+ ".mts": "ts",
4562
+ ".cts": "ts"
4563
+ };
4564
+ const resolveLang = (filename) => {
4565
+ return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
4566
+ };
4567
+ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
4568
+ try {
4569
+ const result = parseSync(filename, sourceText, {
4570
+ astType: "ts",
4571
+ lang: resolveLang(filename)
4572
+ });
4573
+ if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
4574
+ const parsedProgram = result.program;
4575
+ if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
4576
+ return parsedProgram;
4577
+ } catch {
4578
+ return null;
4579
+ }
4580
+ };
4581
+ const parseCache = /* @__PURE__ */ new Map();
4582
+ const parseSourceFile = (absoluteFilePath) => {
4583
+ if (!(absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts"))) recordContentProbe(absoluteFilePath);
4584
+ let fileStat;
4585
+ try {
4586
+ fileStat = fs.statSync(absoluteFilePath);
4587
+ } catch {
4588
+ return null;
4589
+ }
4590
+ if (!fileStat.isFile()) return null;
4591
+ if (fileStat.size > 2e6) return null;
4592
+ const cached = parseCache.get(absoluteFilePath);
4593
+ if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return cached.program;
4594
+ if (absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts")) {
4595
+ parseCache.set(absoluteFilePath, {
4596
+ mtimeMs: fileStat.mtimeMs,
4597
+ size: fileStat.size,
4598
+ program: null
4599
+ });
4600
+ return null;
4601
+ }
4602
+ let sourceText;
4603
+ try {
4604
+ sourceText = fs.readFileSync(absoluteFilePath, "utf8");
4605
+ } catch {
4606
+ parseCache.set(absoluteFilePath, {
4607
+ mtimeMs: fileStat.mtimeMs,
4608
+ size: fileStat.size,
4609
+ program: null
4610
+ });
4611
+ return null;
4612
+ }
4613
+ const parsedProgram = parseSourceText({
4614
+ filename: absoluteFilePath,
4615
+ sourceText
4616
+ });
4617
+ parseCache.set(absoluteFilePath, {
4618
+ mtimeMs: fileStat.mtimeMs,
4619
+ size: fileStat.size,
4620
+ program: parsedProgram
4621
+ });
4622
+ return parsedProgram;
4623
+ };
4624
+ //#endregion
4625
+ //#region src/plugin/rules/security-scan/utils/mask-source-comments.ts
4626
+ const SOURCE_FILE_EXTENSION_PATTERN$1 = /\.(?:[cm]?[jt]sx?)$/i;
4627
+ const POSSIBLE_SOURCE_COMMENT_PATTERN = /\/\/|\/\*|<!--/;
4628
+ const LINE_TERMINATORS = new Set([
4629
+ "\r",
4630
+ "\n",
4631
+ "\u2028",
4632
+ "\u2029"
4633
+ ]);
4634
+ const hasPossibleAnnexBClosingComment = (content) => {
4635
+ let searchIndex = 0;
4636
+ while (searchIndex < content.length) {
4637
+ const closingCommentIndex = content.indexOf("-->", searchIndex);
4638
+ if (closingCommentIndex === -1) return false;
4639
+ let prefixIndex = closingCommentIndex - 1;
4640
+ while (prefixIndex >= 0 && !LINE_TERMINATORS.has(content[prefixIndex] ?? "")) {
4641
+ if (content[prefixIndex]?.trim() !== "") break;
4642
+ prefixIndex -= 1;
4643
+ }
4644
+ if (prefixIndex < 0 || LINE_TERMINATORS.has(content[prefixIndex] ?? "")) return true;
4645
+ searchIndex = closingCommentIndex + 3;
4646
+ }
4647
+ return false;
4648
+ };
4649
+ const maskSourceComments = (relativePath, content) => {
4650
+ if (!SOURCE_FILE_EXTENSION_PATTERN$1.test(relativePath)) return content;
4651
+ if (!content.startsWith("#!") && !POSSIBLE_SOURCE_COMMENT_PATTERN.test(content) && !hasPossibleAnnexBClosingComment(content)) return content;
4652
+ try {
4653
+ const result = parseSync(relativePath, content, {
4654
+ astType: "ts",
4655
+ lang: resolveLang(relativePath)
4656
+ });
4657
+ if (result.errors.some((parseError) => parseError.severity === "Error")) return void 0;
4658
+ const firstLineTerminatorIndex = content.search(/[\r\n\u2028\u2029]/);
4659
+ const ignoredRanges = [...content.startsWith("#!") ? [{
4660
+ start: 0,
4661
+ end: firstLineTerminatorIndex === -1 ? content.length : firstLineTerminatorIndex
4662
+ }] : [], ...result.comments];
4663
+ if (ignoredRanges.length === 0) return content;
4664
+ const contentParts = [];
4665
+ let previousEnd = 0;
4666
+ for (const ignoredRange of ignoredRanges) {
4667
+ contentParts.push(content.slice(previousEnd, ignoredRange.start));
4668
+ contentParts.push(content.slice(ignoredRange.start, ignoredRange.end).replace(/[^\r\n\u2028\u2029]/g, " "));
4669
+ previousEnd = ignoredRange.end;
4670
+ }
4671
+ contentParts.push(content.slice(previousEnd));
4672
+ return contentParts.join("");
4673
+ } catch {
4674
+ return;
4675
+ }
4676
+ };
4677
+ //#endregion
4503
4678
  //#region src/plugin/rules/security-scan/utils/scan-artifact-leak.ts
4504
4679
  const scanArtifactLeak = (file, findLeakPattern, message) => {
4505
4680
  if (DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath)) return [];
@@ -4515,12 +4690,39 @@ const scanArtifactLeak = (file, findLeakPattern, message) => {
4515
4690
  };
4516
4691
  //#endregion
4517
4692
  //#region src/plugin/rules/security-scan/artifact-env-leak.ts
4693
+ const ARTIFACT_ENV_LEAK_MESSAGE = "A browser artifact contains server-secret environment names or a full environment dump shape.";
4694
+ const findArtifactEnvLeakPattern = (content) => findSuspiciousPublicEnvSecretNamePattern(content) ?? (hasFullEnvLeakShape(content) ? FULL_ENV_LEAK_SECRET_NAME_PATTERN : void 0);
4518
4695
  const artifactEnvLeak = defineRule({
4519
4696
  id: "artifact-env-leak",
4520
4697
  title: "Server env leaked to browser artifact",
4521
4698
  severity: "error",
4522
4699
  recommendation: "Treat public env prefixes as publication, not secrecy; keep secret env vars server-only and rebuild after rotating leaked keys.",
4523
- scan: (file) => scanArtifactLeak(file, (content) => findSuspiciousPublicEnvSecretNamePattern(content) ?? (hasFullEnvLeakShape(content) ? FULL_ENV_LEAK_SECRET_NAME_PATTERN : void 0), "A browser artifact contains server-secret environment names or a full environment dump shape.")
4700
+ scan: (file) => {
4701
+ let isRawCandidateExact = false;
4702
+ const findRawCandidatePattern = (content) => {
4703
+ const suspiciousPublicNamePattern = findSuspiciousPublicEnvSecretNamePattern(content);
4704
+ if (suspiciousPublicNamePattern) {
4705
+ isRawCandidateExact = true;
4706
+ return suspiciousPublicNamePattern;
4707
+ }
4708
+ if (!FULL_ENV_LEAK_SECRET_NAME_PATTERN.test(content)) return void 0;
4709
+ if (FULL_ENV_LEAK_CONTEXT_PATTERN.test(content)) {
4710
+ isRawCandidateExact = true;
4711
+ return FULL_ENV_LEAK_SECRET_NAME_PATTERN;
4712
+ }
4713
+ return FULL_ENV_LEAK_COMMENT_TRIVIA_PATTERN.test(content) ? FULL_ENV_LEAK_SECRET_NAME_PATTERN : void 0;
4714
+ };
4715
+ const rawCandidateFindings = scanArtifactLeak(file, findRawCandidatePattern, ARTIFACT_ENV_LEAK_MESSAGE);
4716
+ if (rawCandidateFindings.length === 0) return rawCandidateFindings;
4717
+ const rawFindings = isRawCandidateExact ? rawCandidateFindings : scanArtifactLeak(file, findArtifactEnvLeakPattern, ARTIFACT_ENV_LEAK_MESSAGE);
4718
+ const executableContent = maskSourceComments(file.relativePath, file.content);
4719
+ if (executableContent === void 0) return rawCandidateFindings;
4720
+ if (executableContent === file.content) return rawFindings;
4721
+ return scanArtifactLeak({
4722
+ ...file,
4723
+ content: executableContent
4724
+ }, findArtifactEnvLeakPattern, ARTIFACT_ENV_LEAK_MESSAGE);
4725
+ }
4524
4726
  });
4525
4727
  //#endregion
4526
4728
  //#region src/plugin/rules/security-scan/artifact-secret-leak.ts
@@ -6534,6 +6736,28 @@ const asyncParallel = defineRule({
6534
6736
  }
6535
6737
  });
6536
6738
  //#endregion
6739
+ //#region src/plugin/utils/collect-function-return-statements.ts
6740
+ const collectFunctionReturnStatements = (functionNode) => {
6741
+ if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
6742
+ const returnStatements = [];
6743
+ walkAst(functionNode.body, (node) => {
6744
+ if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
6745
+ if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
6746
+ });
6747
+ return returnStatements;
6748
+ };
6749
+ //#endregion
6750
+ //#region src/plugin/utils/is-nullish-expression.ts
6751
+ const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
6752
+ //#endregion
6753
+ //#region src/plugin/utils/strip-this-parameter.ts
6754
+ const stripThisParameter = (parameters) => {
6755
+ const firstParameter = parameters[0];
6756
+ if (!firstParameter) return parameters;
6757
+ if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
6758
+ return parameters;
6759
+ };
6760
+ //#endregion
6537
6761
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6538
6762
  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.";
6539
6763
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
@@ -6545,12 +6769,8 @@ const STORAGE_GLOBALS = new Set([
6545
6769
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
6546
6770
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
6547
6771
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
6548
- const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
6549
- const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
6550
6772
  const isAuthCredentialKey = (key) => {
6551
- if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
6552
6773
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
6553
- if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
6554
6774
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
6555
6775
  return true;
6556
6776
  };
@@ -6559,11 +6779,63 @@ const isDirectWebStorageObject = (node) => {
6559
6779
  if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && STORAGE_GLOBALS.has(node.object.name) && isNodeOfType(node.property, "Identifier")) return STORAGE_NAMES.has(node.property.name);
6560
6780
  return false;
6561
6781
  };
6562
- const isWebStorageObject = (node) => {
6563
- if (isDirectWebStorageObject(node)) return true;
6564
- if (!isNodeOfType(node, "Identifier")) return false;
6565
- const binding = findVariableInitializer(node, node.name);
6566
- return binding?.initializer ? isDirectWebStorageObject(binding.initializer) : false;
6782
+ const immutableInitializer = (identifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
6783
+ if (visitedIdentifiers.has(identifier)) return null;
6784
+ visitedIdentifiers.add(identifier);
6785
+ const binding = findVariableInitializer(identifier, identifier.name);
6786
+ if (!binding?.initializer) return null;
6787
+ if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
6788
+ const declarator = binding.bindingIdentifier.parent;
6789
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
6790
+ const declaration = declarator.parent;
6791
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
6792
+ if (declaration.kind !== "const") return null;
6793
+ const initializer = stripParenExpression(binding.initializer);
6794
+ if (!isNodeOfType(initializer, "Identifier")) return initializer;
6795
+ return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
6796
+ };
6797
+ const isWebStorageFactoryResult = (node, visitedNodes) => {
6798
+ const expression = stripParenExpression(node);
6799
+ if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
6800
+ if (isNodeOfType(expression, "ConditionalExpression")) {
6801
+ const consequent = stripParenExpression(expression.consequent);
6802
+ const alternate = stripParenExpression(expression.alternate);
6803
+ return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
6804
+ }
6805
+ if (isNodeOfType(expression, "LogicalExpression")) {
6806
+ if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
6807
+ const left = stripParenExpression(expression.left);
6808
+ const right = stripParenExpression(expression.right);
6809
+ const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
6810
+ const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
6811
+ return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
6812
+ }
6813
+ return false;
6814
+ };
6815
+ const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
6816
+ const expression = stripParenExpression(node);
6817
+ if (visitedNodes.has(expression)) return false;
6818
+ visitedNodes.add(expression);
6819
+ if (isDirectWebStorageObject(expression)) return true;
6820
+ if (isNodeOfType(expression, "Identifier")) {
6821
+ const initializer = immutableInitializer(expression);
6822
+ return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
6823
+ }
6824
+ if (!isNodeOfType(expression, "CallExpression")) return false;
6825
+ const callee = stripParenExpression(expression.callee);
6826
+ if (!isNodeOfType(callee, "Identifier")) return false;
6827
+ const factory = immutableInitializer(callee);
6828
+ if (!isFunctionLike$1(factory)) return false;
6829
+ if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
6830
+ let didReturnWebStorage = false;
6831
+ for (const returnStatement of collectFunctionReturnStatements(factory)) {
6832
+ if (!returnStatement.argument) continue;
6833
+ const strippedReturn = stripParenExpression(returnStatement.argument);
6834
+ if (isNullishExpression(strippedReturn)) continue;
6835
+ if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
6836
+ didReturnWebStorage = true;
6837
+ }
6838
+ return didReturnWebStorage;
6567
6839
  };
6568
6840
  const resolveStaticKeyString = (node) => {
6569
6841
  if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
@@ -6583,6 +6855,56 @@ const staticMemberName = (member) => {
6583
6855
  if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
6584
6856
  return null;
6585
6857
  };
6858
+ const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
6859
+ const strippedExpression = stripParenExpression(expression);
6860
+ if (visitedNodes.has(strippedExpression)) return null;
6861
+ visitedNodes.add(strippedExpression);
6862
+ if (isNodeOfType(strippedExpression, "Identifier")) {
6863
+ const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
6864
+ const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
6865
+ if (directIndex !== -1) return directIndex;
6866
+ const initializer = immutableInitializer(strippedExpression);
6867
+ return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
6868
+ }
6869
+ if (canUnwrapSerialization && isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression") && !strippedExpression.callee.computed && isNodeOfType(strippedExpression.callee.object, "Identifier") && strippedExpression.callee.object.name === "JSON" && isNodeOfType(strippedExpression.callee.property, "Identifier") && strippedExpression.callee.property.name === "stringify") {
6870
+ const serializedArgument = strippedExpression.arguments[0];
6871
+ return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
6872
+ }
6873
+ return null;
6874
+ };
6875
+ const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
6876
+ const findStorageHelperSinks = (functionNode, scopes) => {
6877
+ const cachedSinks = storageHelperSinkCache.get(functionNode);
6878
+ if (cachedSinks) return cachedSinks;
6879
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
6880
+ storageHelperSinkCache.set(functionNode, []);
6881
+ return [];
6882
+ }
6883
+ const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
6884
+ const strippedParameter = stripParenExpression(parameter);
6885
+ const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
6886
+ return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
6887
+ });
6888
+ const helperSinks = [];
6889
+ walkAst(functionNode.body, (child) => {
6890
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
6891
+ if (!isNodeOfType(child, "CallExpression")) return;
6892
+ const callee = stripParenExpression(child.callee);
6893
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
6894
+ const keyExpression = child.arguments[0];
6895
+ const valueExpression = child.arguments[1];
6896
+ if (!keyExpression || !valueExpression) return;
6897
+ const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
6898
+ const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
6899
+ if (keyParameterIndex === null || valueParameterIndex === null) return;
6900
+ helperSinks.push({
6901
+ keyParameterIndex,
6902
+ valueParameterIndex
6903
+ });
6904
+ });
6905
+ storageHelperSinkCache.set(functionNode, helperSinks);
6906
+ return helperSinks;
6907
+ };
6586
6908
  const authTokenInWebStorage = defineRule({
6587
6909
  id: "auth-token-in-web-storage",
6588
6910
  title: "Auth token in web storage",
@@ -6590,14 +6912,23 @@ const authTokenInWebStorage = defineRule({
6590
6912
  recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
6591
6913
  create: skipNonProductionFiles((context) => ({
6592
6914
  CallExpression(node) {
6593
- const callee = node.callee;
6594
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
6595
- if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
6596
- if (!isWebStorageObject(stripParenExpression(callee.object))) return;
6597
- const keyArgument = node.arguments?.[0];
6598
- if (!keyArgument) return;
6599
- const keyString = resolveStaticKeyString(keyArgument);
6600
- if (keyString === null || !isAuthCredentialKey(keyString)) return;
6915
+ const callee = stripParenExpression(node.callee);
6916
+ const keyArguments = [];
6917
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "setItem" && isWebStorageObject(stripParenExpression(callee.object))) {
6918
+ const keyArgument = node.arguments[0];
6919
+ if (keyArgument) keyArguments.push(keyArgument);
6920
+ } else if (isNodeOfType(callee, "Identifier")) {
6921
+ const helperFunction = immutableInitializer(callee);
6922
+ const helperSinks = helperFunction ? findStorageHelperSinks(helperFunction, context.scopes) : [];
6923
+ for (const helperSink of helperSinks) {
6924
+ const keyArgument = node.arguments[helperSink.keyParameterIndex];
6925
+ if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
6926
+ }
6927
+ }
6928
+ if (!keyArguments.some((keyArgument) => {
6929
+ const keyString = resolveStaticKeyString(keyArgument);
6930
+ return keyString !== null && isAuthCredentialKey(keyString);
6931
+ })) return;
6601
6932
  context.report({
6602
6933
  node,
6603
6934
  message: MESSAGE$60
@@ -6845,9 +7176,6 @@ const isCreateElementCall = (node) => {
6845
7176
  return false;
6846
7177
  };
6847
7178
  //#endregion
6848
- //#region src/plugin/utils/is-nullish-expression.ts
6849
- const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
6850
- //#endregion
6851
7179
  //#region src/plugin/rules/react-builtins/button-has-type.ts
6852
7180
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
6853
7181
  const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
@@ -7309,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
7309
7637
  if (a.type !== b.type) return false;
7310
7638
  if (isNodeOfType(a, "ThisExpression")) return true;
7311
7639
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
7640
+ if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
7312
7641
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7313
7642
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7314
7643
  if (a.computed !== b.computed) return false;
@@ -8364,17 +8693,6 @@ const createMethodMutationAnalysis = (context) => {
8364
8693
  //#region src/plugin/utils/is-member-property.ts
8365
8694
  const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
8366
8695
  //#endregion
8367
- //#region src/plugin/utils/collect-function-return-statements.ts
8368
- const collectFunctionReturnStatements = (functionNode) => {
8369
- if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
8370
- const returnStatements = [];
8371
- walkAst(functionNode.body, (node) => {
8372
- if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
8373
- if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
8374
- });
8375
- return returnStatements;
8376
- };
8377
- //#endregion
8378
8696
  //#region src/plugin/utils/statement-always-exits.ts
8379
8697
  const statementAlwaysExits = (statement) => {
8380
8698
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -8641,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8641
8959
  if (!symbol || !isImportedFromReact(symbol)) return false;
8642
8960
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8643
8961
  };
8644
- const isReactNamespaceReceiver = (receiver, scopes, options) => {
8962
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8645
8963
  if (!isNodeOfType(receiver, "Identifier")) return false;
8646
8964
  if (isReactNamespaceImport(receiver, scopes)) return true;
8647
8965
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8654,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8654
8972
  for (const property of pattern.properties) {
8655
8973
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8656
8974
  const propertyName = getStaticPropertyKeyName(property);
8657
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8975
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8658
8976
  }
8659
8977
  return false;
8660
8978
  };
@@ -8678,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8678
8996
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8679
8997
  }
8680
8998
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8681
- return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
8999
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8682
9000
  };
8683
9001
  //#endregion
8684
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -9083,119 +9401,6 @@ const findReExportTargetsForName = (programRoot, exportedName) => {
9083
9401
  return exportAllTargets;
9084
9402
  };
9085
9403
  //#endregion
9086
- //#region src/plugin/utils/attach-parent-references.ts
9087
- const attachParentReferences = (root) => {
9088
- const visit = (node, parent) => {
9089
- const writableNode = node;
9090
- writableNode.parent = parent;
9091
- const nodeRecord = node;
9092
- for (const key of Object.keys(nodeRecord)) {
9093
- if (key === "parent") continue;
9094
- const child = nodeRecord[key];
9095
- if (Array.isArray(child)) {
9096
- for (const item of child) if (isAstNode(item)) visit(item, node);
9097
- } else if (isAstNode(child)) visit(child, node);
9098
- }
9099
- };
9100
- visit(root, null);
9101
- };
9102
- //#endregion
9103
- //#region src/plugin/utils/cross-file-probe-recorder.ts
9104
- let activeProbeTrace = null;
9105
- const recordExistenceProbe = (absolutePath) => {
9106
- activeProbeTrace?.existencePaths.add(absolutePath);
9107
- };
9108
- const recordContentProbe = (absolutePath) => {
9109
- activeProbeTrace?.contentPaths.add(absolutePath);
9110
- };
9111
- const isProbeRecorderActive = () => activeProbeTrace !== null;
9112
- const collectCrossFileProbes = (collect) => {
9113
- const previousTrace = activeProbeTrace;
9114
- const trace = {
9115
- existencePaths: /* @__PURE__ */ new Set(),
9116
- contentPaths: /* @__PURE__ */ new Set()
9117
- };
9118
- activeProbeTrace = trace;
9119
- try {
9120
- collect();
9121
- } finally {
9122
- activeProbeTrace = previousTrace;
9123
- }
9124
- return trace;
9125
- };
9126
- //#endregion
9127
- //#region src/plugin/utils/parse-source-file.ts
9128
- const FILENAME_TO_LANG = {
9129
- ".ts": "ts",
9130
- ".tsx": "tsx",
9131
- ".js": "js",
9132
- ".jsx": "jsx",
9133
- ".mjs": "js",
9134
- ".cjs": "js",
9135
- ".mts": "ts",
9136
- ".cts": "ts"
9137
- };
9138
- const resolveLang = (filename) => {
9139
- return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
9140
- };
9141
- const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
9142
- try {
9143
- const result = parseSync(filename, sourceText, {
9144
- astType: "ts",
9145
- lang: resolveLang(filename)
9146
- });
9147
- if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
9148
- const parsedProgram = result.program;
9149
- if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
9150
- return parsedProgram;
9151
- } catch {
9152
- return null;
9153
- }
9154
- };
9155
- const parseCache = /* @__PURE__ */ new Map();
9156
- const parseSourceFile = (absoluteFilePath) => {
9157
- if (!(absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts"))) recordContentProbe(absoluteFilePath);
9158
- let fileStat;
9159
- try {
9160
- fileStat = fs.statSync(absoluteFilePath);
9161
- } catch {
9162
- return null;
9163
- }
9164
- if (!fileStat.isFile()) return null;
9165
- if (fileStat.size > 2e6) return null;
9166
- const cached = parseCache.get(absoluteFilePath);
9167
- if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return cached.program;
9168
- if (absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts")) {
9169
- parseCache.set(absoluteFilePath, {
9170
- mtimeMs: fileStat.mtimeMs,
9171
- size: fileStat.size,
9172
- program: null
9173
- });
9174
- return null;
9175
- }
9176
- let sourceText;
9177
- try {
9178
- sourceText = fs.readFileSync(absoluteFilePath, "utf8");
9179
- } catch {
9180
- parseCache.set(absoluteFilePath, {
9181
- mtimeMs: fileStat.mtimeMs,
9182
- size: fileStat.size,
9183
- program: null
9184
- });
9185
- return null;
9186
- }
9187
- const parsedProgram = parseSourceText({
9188
- filename: absoluteFilePath,
9189
- sourceText
9190
- });
9191
- parseCache.set(absoluteFilePath, {
9192
- mtimeMs: fileStat.mtimeMs,
9193
- size: fileStat.size,
9194
- program: parsedProgram
9195
- });
9196
- return parsedProgram;
9197
- };
9198
- //#endregion
9199
9404
  //#region src/plugin/utils/resolve-relative-import-path.ts
9200
9405
  const MODULE_FILE_EXTENSIONS = [
9201
9406
  ".ts",
@@ -14662,6 +14867,29 @@ const getReleaseVerbName = (node) => {
14662
14867
  }
14663
14868
  return null;
14664
14869
  };
14870
+ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14871
+ const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14872
+ const usageFunction = findEnclosingFunction$1(usage.node);
14873
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
14874
+ const controllerKey = getListenerAbortControllerKey(usage, context);
14875
+ const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14876
+ if (controllerKey === null || refCurrentKey === null) return false;
14877
+ const usageFunctionBody = usageFunction.body;
14878
+ const previousAbortCalls = [];
14879
+ const ownershipAssignments = [];
14880
+ walkAst(usageFunctionBody, (child) => {
14881
+ if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
14882
+ if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
14883
+ ownershipAssignments.push(child);
14884
+ return;
14885
+ }
14886
+ if (!isNodeOfType(child, "CallExpression")) return;
14887
+ const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
14888
+ if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
14889
+ });
14890
+ const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14891
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14892
+ };
14665
14893
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14666
14894
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14667
14895
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14681,6 +14909,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14681
14909
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14682
14910
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
14683
14911
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14912
+ if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14684
14913
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
14685
14914
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14686
14915
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -14714,9 +14943,12 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14714
14943
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
14715
14944
  }
14716
14945
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
14717
- const releaseHandler = callNode.arguments?.[1];
14946
+ const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
14947
+ const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14718
14948
  if (!releaseHandler) return releaseVerbName === "off";
14719
- return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
14949
+ const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14950
+ const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
14951
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14720
14952
  }
14721
14953
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14722
14954
  return true;
@@ -14729,8 +14961,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14729
14961
  currentNode = parentNode;
14730
14962
  parentNode = currentNode.parent;
14731
14963
  }
14732
- if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
14733
- const effectCallback = findEnclosingFunction$1(parentNode);
14964
+ const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14734
14965
  const effectCall = effectCallback?.parent;
14735
14966
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
14736
14967
  };
@@ -14742,6 +14973,25 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
14742
14973
  if (!symbol) return false;
14743
14974
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
14744
14975
  };
14976
+ const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
14977
+ if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
14978
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
14979
+ const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
14980
+ if (!isNodeOfType(releaseCall, "CallExpression")) return false;
14981
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
14982
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
14983
+ const ownerFunction = findEnclosingFunction$1(releaseFunction);
14984
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
14985
+ const triggerRegistrations = [];
14986
+ walkAst(ownerFunction.body, (child) => {
14987
+ if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
14988
+ if (!isNodeOfType(child, "CallExpression")) return;
14989
+ const registrationDetails = getCallRegistrationDetails(child, context);
14990
+ if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
14991
+ });
14992
+ if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
14993
+ return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
14994
+ };
14745
14995
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14746
14996
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
14747
14997
  const releaseFunction = findEnclosingFunction$1(releaseNode);
@@ -14749,6 +14999,7 @@ const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14749
14999
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
14750
15000
  const usageFunction = findEnclosingFunction$1(usage.node);
14751
15001
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15002
+ if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
14752
15003
  return isPotentiallyReachableFunction(releaseFunction, context);
14753
15004
  };
14754
15005
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -15016,6 +15267,11 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
15016
15267
  parentNode = currentNode.parent;
15017
15268
  continue;
15018
15269
  }
15270
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
15271
+ currentNode = parentNode;
15272
+ parentNode = currentNode.parent;
15273
+ continue;
15274
+ }
15019
15275
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15020
15276
  const ownerFunction = findEnclosingFunction$1(resourceNode);
15021
15277
  const resourceSymbol = context.scopes.symbolFor(parentNode.id);
@@ -16877,7 +17133,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
16877
17133
  keys.add(depKey);
16878
17134
  continue;
16879
17135
  }
16880
- const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17136
+ const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
16881
17137
  if (identitySourceKeys) {
16882
17138
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
16883
17139
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -16960,6 +17216,161 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
16960
17216
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
16961
17217
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
16962
17218
  };
17219
+ const isPureDerivedExpression = (expression) => {
17220
+ const candidate = unwrapExpression$3(expression);
17221
+ if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17222
+ if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17223
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17224
+ if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17225
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17226
+ if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17227
+ return false;
17228
+ };
17229
+ const isPureDerivedStatement = (statement) => {
17230
+ if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17231
+ if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17232
+ if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17233
+ return false;
17234
+ };
17235
+ const isPureDerivedFunction = (functionNode) => {
17236
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17237
+ if (functionNode.async || functionNode.generator) return false;
17238
+ return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17239
+ };
17240
+ const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17241
+ if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17242
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17243
+ const callExpression = referenceRoot.parent;
17244
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17245
+ const functionNode = getFunctionValueNode(symbol);
17246
+ if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17247
+ const sourceKeys = /* @__PURE__ */ new Set();
17248
+ for (const capturedReference of closureCaptures(functionNode, scopes)) {
17249
+ const capturedSymbol = capturedReference.resolvedSymbol;
17250
+ if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17251
+ if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17252
+ const capturedKey = computeDepKey(capturedReference);
17253
+ if (!capturedKey) return null;
17254
+ if (capturedKey === capturedSymbol.name) {
17255
+ const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17256
+ if (nestedSourceKeys) {
17257
+ for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17258
+ continue;
17259
+ }
17260
+ }
17261
+ sourceKeys.add(capturedKey);
17262
+ }
17263
+ return sourceKeys.size > 0 ? sourceKeys : null;
17264
+ };
17265
+ const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17266
+ const sourceKeys = /* @__PURE__ */ new Set();
17267
+ for (const expression of expressions) {
17268
+ const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17269
+ if (!expressionSourceKeys) return null;
17270
+ for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17271
+ }
17272
+ return sourceKeys;
17273
+ };
17274
+ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17275
+ const candidate = unwrapExpression$3(expression);
17276
+ if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17277
+ if (isNodeOfType(candidate, "Identifier")) {
17278
+ if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17279
+ const sourceSymbol = scopes.symbolFor(candidate);
17280
+ if (!sourceSymbol) return null;
17281
+ if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17282
+ if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
17283
+ visitedSymbolIds.add(sourceSymbol.id);
17284
+ const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17285
+ visitedSymbolIds.delete(sourceSymbol.id);
17286
+ if (sourceKeys) return sourceKeys;
17287
+ }
17288
+ return new Set([sourceSymbol.name]);
17289
+ }
17290
+ if (isNodeOfType(candidate, "MemberExpression")) {
17291
+ if (hasComputedMemberExpression(candidate)) return null;
17292
+ const sourceKey = stringifyMemberChain(candidate);
17293
+ const rootIdentifier = getMemberRootIdentifier(candidate);
17294
+ const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17295
+ if (!sourceKey || !rootSymbol) return null;
17296
+ if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17297
+ return new Set([sourceKey]);
17298
+ }
17299
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17300
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17301
+ if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17302
+ candidate.test,
17303
+ candidate.consequent,
17304
+ candidate.alternate
17305
+ ], scopes, visitedSymbolIds);
17306
+ if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17307
+ if (isNodeOfType(candidate, "NewExpression")) {
17308
+ const callee = unwrapExpression$3(candidate.callee);
17309
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17310
+ const argumentsToAnalyze = [];
17311
+ for (const argument of candidate.arguments) {
17312
+ if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17313
+ argumentsToAnalyze.push(argument);
17314
+ }
17315
+ return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17316
+ }
17317
+ return null;
17318
+ };
17319
+ const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17320
+ const sourceKeys = /* @__PURE__ */ new Set();
17321
+ let currentNode = assignment;
17322
+ while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17323
+ const parentNode = currentNode.parent;
17324
+ if (isNodeOfType(parentNode, "IfStatement")) {
17325
+ if (parentNode.test === currentNode) return null;
17326
+ const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17327
+ if (!testSourceKeys) return null;
17328
+ for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17329
+ } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17330
+ currentNode = parentNode;
17331
+ }
17332
+ return currentNode.parent === boundaryFunction ? sourceKeys : null;
17333
+ };
17334
+ const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17335
+ const referenceRoot = findTransparentExpressionRoot(referenceNode);
17336
+ const callExpression = referenceRoot.parent;
17337
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17338
+ allowGlobalReactNamespace: true,
17339
+ allowUnboundBareCalls: true,
17340
+ resolveNamedAliases: true
17341
+ });
17342
+ };
17343
+ const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17344
+ if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17345
+ const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17346
+ if (!boundaryFunction) return null;
17347
+ const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17348
+ if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17349
+ const sourceKeys = /* @__PURE__ */ new Set();
17350
+ if (symbol.initializer) {
17351
+ const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17352
+ if (!initializerSourceKeys) return null;
17353
+ for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17354
+ }
17355
+ let writeCount = 0;
17356
+ for (const symbolReference of symbol.references) {
17357
+ if (symbolReference.flag === "read") {
17358
+ if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17359
+ continue;
17360
+ }
17361
+ if (symbolReference.flag !== "write") return null;
17362
+ const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17363
+ const assignment = referenceRoot.parent;
17364
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17365
+ const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17366
+ const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17367
+ if (!assignmentSourceKeys || !controlSourceKeys) return null;
17368
+ for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17369
+ for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17370
+ writeCount += 1;
17371
+ }
17372
+ return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17373
+ };
16963
17374
  const isUseCallbackResultDep = (node, scopes) => {
16964
17375
  const rootSymbol = getRootSymbol(node, scopes);
16965
17376
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -27251,7 +27662,11 @@ const mouseEventsHaveKeyEvents = defineRule({
27251
27662
  //#region src/plugin/utils/has-directive.ts
27252
27663
  const hasDirective = (programNode, directive) => {
27253
27664
  if (!isNodeOfType(programNode, "Program")) return false;
27254
- return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
27665
+ for (const statement of programNode.body) {
27666
+ if (!isNodeOfType(statement, "ExpressionStatement") || statement.directive === void 0) return false;
27667
+ if (statement.directive === directive) return true;
27668
+ }
27669
+ return false;
27255
27670
  };
27256
27671
  //#endregion
27257
27672
  //#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
@@ -29183,7 +29598,7 @@ const nextjsNoVercelOgImport = defineRule({
29183
29598
  //#endregion
29184
29599
  //#region src/plugin/rules/a11y/no-access-key.ts
29185
29600
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29186
- const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29601
+ const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29187
29602
  const noAccessKey = defineRule({
29188
29603
  id: "no-access-key",
29189
29604
  title: "accessKey attribute used",
@@ -29208,7 +29623,7 @@ const noAccessKey = defineRule({
29208
29623
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29209
29624
  const expression = attributeValue.expression;
29210
29625
  if (!expression || expression.type === "JSXEmptyExpression") return;
29211
- if (isUndefinedIdentifier(expression)) return;
29626
+ if (isUndefinedIdentifier$1(expression)) return;
29212
29627
  context.report({
29213
29628
  node: accessKey,
29214
29629
  message: MESSAGE$39
@@ -29973,6 +30388,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
29973
30388
  const importDeclaration = declarationNode.parent;
29974
30389
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
29975
30390
  }));
30391
+ const isReactNamespaceReceiver = (analysis, node) => {
30392
+ const receiver = stripParenExpression(node);
30393
+ if (!isNodeOfType(receiver, "Identifier")) return false;
30394
+ const namespaceReference = getRef(analysis, receiver);
30395
+ return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30396
+ };
29976
30397
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
29977
30398
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
29978
30399
  const callee = stripParenExpression(declarator.init.callee);
@@ -29981,24 +30402,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
29981
30402
  if (!reference?.resolved) return callee.name === hookName;
29982
30403
  return isReactNamedImportReference(reference, hookName);
29983
30404
  }
29984
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
29985
- const namespaceReference = getRef(analysis, callee.object);
29986
- if (!namespaceReference?.resolved) return callee.object.name === "React";
29987
- return isReactNamespaceImportReference(namespaceReference);
30405
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30406
+ return isReactNamespaceReceiver(analysis, callee.object);
29988
30407
  };
29989
30408
  const isHookCallee$1 = (analysis, node, hookName) => {
29990
30409
  if (!node) return false;
29991
30410
  if (isNodeOfType(node, "Identifier")) {
29992
30411
  if (node.name === hookName) return true;
29993
30412
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
29994
- const parent = node.parent;
29995
- if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30413
+ const receiverRoot = findTransparentExpressionRoot(node);
30414
+ const parent = receiverRoot.parent;
30415
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
29996
30416
  return false;
29997
30417
  }
29998
- if (isNodeOfType(node, "MemberExpression")) {
29999
- const receiver = stripParenExpression(node.object);
30000
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30001
- }
30418
+ if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30002
30419
  return false;
30003
30420
  };
30004
30421
  const isUseEffect = (node) => {
@@ -30422,7 +30839,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30422
30839
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30423
30840
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30424
30841
  };
30425
- const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
30842
+ const isSynchronousFunction = (functionNode) => {
30843
+ const functionMetadata = functionNode;
30844
+ return functionMetadata.async !== true && functionMetadata.generator !== true;
30845
+ };
30846
+ const findBindingVariable = (analysis, bindingIdentifier) => {
30847
+ for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
30848
+ return null;
30849
+ };
30850
+ const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
30851
+ if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
30852
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
30853
+ if (!bindingIdentifier) return null;
30854
+ const variable = findBindingVariable(analysis, bindingIdentifier);
30855
+ if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
30856
+ const definition = variable.defs[0];
30857
+ if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
30858
+ if (definition.type !== "Variable") return null;
30859
+ const declarator = definition.node;
30860
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
30861
+ if (declarator.init === functionNode) return variable;
30862
+ if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
30863
+ return null;
30864
+ };
30865
+ const getJsxEventValueAttribute = (identifier) => {
30866
+ const expression = findTransparentExpressionRoot(identifier);
30867
+ const expressionContainer = expression.parent;
30868
+ if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
30869
+ const attribute = expressionContainer.parent;
30870
+ if (!isNodeOfType(attribute, "JSXAttribute")) return null;
30871
+ const attributeName = getJsxAttributeName(attribute.name);
30872
+ return attributeName && isEventHandlerName(attributeName) ? attribute : null;
30873
+ };
30874
+ const getInlineJsxEventCallbackAttribute = (callExpression) => {
30875
+ const callbackFunction = findEnclosingFunction$1(callExpression);
30876
+ if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
30877
+ return getJsxEventValueAttribute(callbackFunction);
30878
+ };
30879
+ const isReactHookDependencyReference = (identifier) => {
30880
+ const expression = findTransparentExpressionRoot(identifier);
30881
+ const dependencyArray = expression.parent;
30882
+ if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
30883
+ const hookCall = dependencyArray.parent;
30884
+ if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
30885
+ const callee = hookCall.callee;
30886
+ if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
30887
+ return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
30888
+ };
30889
+ const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
30890
+ if (visitedVariables.has(functionVariable)) return false;
30891
+ const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
30892
+ const callExpressions = [];
30893
+ let hasDirectJsxEventReference = false;
30894
+ for (const reference of functionVariable.references) {
30895
+ if (reference.init) continue;
30896
+ const identifier = reference.identifier;
30897
+ if (reference.isWrite()) return false;
30898
+ const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
30899
+ if (jsxEventValueAttribute) {
30900
+ if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
30901
+ continue;
30902
+ }
30903
+ if (isReactHookDependencyReference(identifier)) continue;
30904
+ const callExpression = getCallExpr(reference);
30905
+ if (!callExpression) return false;
30906
+ const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
30907
+ if (jsxEventCallbackAttribute) {
30908
+ if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
30909
+ continue;
30910
+ }
30911
+ callExpressions.push(callExpression);
30912
+ }
30913
+ if (hasDirectJsxEventReference) return true;
30914
+ for (const callExpression of callExpressions) {
30915
+ if (!isNodeReachableWithinFunction(callExpression, context)) continue;
30916
+ const callerFunction = findEnclosingFunction$1(callExpression);
30917
+ if (!callerFunction || callerFunction === componentFunction) continue;
30918
+ const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
30919
+ if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
30920
+ }
30921
+ return false;
30922
+ };
30923
+ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30426
30924
  if (!setterRef.resolved) return false;
30427
30925
  const componentFunction = findEnclosingFunction$1(effectNode);
30428
30926
  if (!componentFunction) return false;
@@ -30431,6 +30929,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
30431
30929
  const identifier = reference.identifier;
30432
30930
  if (isAstDescendant(identifier, effectNode)) continue;
30433
30931
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
30932
+ if (!isNodeReachableWithinFunction(identifier, context)) continue;
30933
+ const writerFunction = findEnclosingFunction$1(identifier);
30934
+ if (!writerFunction || writerFunction === componentFunction) continue;
30935
+ const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
30936
+ if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
30434
30937
  }
30435
30938
  return false;
30436
30939
  };
@@ -31320,7 +31823,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31320
31823
  }
31321
31824
  return false;
31322
31825
  };
31323
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
31826
+ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31324
31827
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31325
31828
  if (frames.length === 0) return [];
31326
31829
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31350,7 +31853,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
31350
31853
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31351
31854
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31352
31855
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31353
- const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
31856
+ const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31354
31857
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31355
31858
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31356
31859
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31391,7 +31894,7 @@ const noAdjustStateOnPropChange = defineRule({
31391
31894
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31392
31895
  if (!dependencyReferences) return;
31393
31896
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31394
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31897
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31395
31898
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31396
31899
  context.report({
31397
31900
  node: fact.callExpression,
@@ -34078,6 +34581,7 @@ const noChainStateUpdates = defineRule({
34078
34581
  id: "no-chain-state-updates",
34079
34582
  title: "State updates chained through effects",
34080
34583
  severity: "warn",
34584
+ disabledWhen: ["react:18"],
34081
34585
  tags: ["test-noise"],
34082
34586
  recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
34083
34587
  create: (context) => ({ CallExpression(node) {
@@ -35964,7 +36468,7 @@ const noDerivedState = defineRule({
35964
36468
  if (!isUseEffect(node)) return;
35965
36469
  const analysis = getProgramAnalysis(node);
35966
36470
  if (!analysis) return;
35967
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36471
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
35968
36472
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
35969
36473
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
35970
36474
  }
@@ -35984,7 +36488,7 @@ const noDerivedStateEffect = defineRule({
35984
36488
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
35985
36489
  const analysis = getProgramAnalysis(node);
35986
36490
  if (!analysis) return;
35987
- if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36491
+ if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
35988
36492
  context.report({
35989
36493
  node,
35990
36494
  message: "You pay an extra render for state you can derive from other values."
@@ -36460,9 +36964,20 @@ const noDidMountSetState = defineRule({
36460
36964
  }
36461
36965
  });
36462
36966
  //#endregion
36967
+ //#region src/plugin/utils/find-enclosing-class.ts
36968
+ const findEnclosingClass = (node) => {
36969
+ let ancestor = node.parent;
36970
+ while (ancestor) {
36971
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
36972
+ ancestor = ancestor.parent ?? null;
36973
+ }
36974
+ return null;
36975
+ };
36976
+ //#endregion
36463
36977
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
36464
36978
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
36465
36979
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
36980
+ const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
36466
36981
  const EQUALITY_OPERATORS = new Set([
36467
36982
  "==",
36468
36983
  "===",
@@ -36474,6 +36989,8 @@ const FUNCTION_NODE_TYPES = new Set([
36474
36989
  "FunctionExpression",
36475
36990
  "ArrowFunctionExpression"
36476
36991
  ]);
36992
+ const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
36993
+ const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
36477
36994
  const isLifecycleMethodFunction = (node) => {
36478
36995
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
36479
36996
  const parent = node.parent;
@@ -36529,6 +37046,187 @@ const getStaticMemberName = (node) => {
36529
37046
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
36530
37047
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
36531
37048
  };
37049
+ const getMemberIdentity = (property) => {
37050
+ const propertyName = getPropertyKeyName$2(property);
37051
+ if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
37052
+ return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
37053
+ };
37054
+ const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
37055
+ if (!pattern) return;
37056
+ const unwrappedPattern = stripParenExpression(pattern);
37057
+ if (isNodeOfType(unwrappedPattern, "Identifier")) {
37058
+ previousSourcePaths.set(unwrappedPattern.name, {
37059
+ domain,
37060
+ members: [...members],
37061
+ source: "previous"
37062
+ });
37063
+ return;
37064
+ }
37065
+ if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
37066
+ collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
37067
+ return;
37068
+ }
37069
+ if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
37070
+ for (const property of unwrappedPattern.properties) {
37071
+ if (!isNodeOfType(property, "Property")) continue;
37072
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
37073
+ if (!propertyName) continue;
37074
+ collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
37075
+ }
37076
+ };
37077
+ const getStateSourcePath = (node, previousSourcePaths) => {
37078
+ let currentNode = stripParenExpression(node);
37079
+ const members = [];
37080
+ while (isNodeOfType(currentNode, "MemberExpression")) {
37081
+ const memberName = getStaticMemberName(currentNode);
37082
+ if (!memberName) return null;
37083
+ members.unshift(memberName);
37084
+ currentNode = stripParenExpression(currentNode.object);
37085
+ }
37086
+ if (isNodeOfType(currentNode, "ThisExpression")) {
37087
+ const [domain, ...pathMembers] = members;
37088
+ if (domain !== "props" && domain !== "state") return null;
37089
+ return {
37090
+ domain,
37091
+ members: pathMembers,
37092
+ source: "current"
37093
+ };
37094
+ }
37095
+ if (!isNodeOfType(currentNode, "Identifier")) return null;
37096
+ const previousSourcePath = previousSourcePaths.get(currentNode.name);
37097
+ return previousSourcePath ? {
37098
+ ...previousSourcePath,
37099
+ members: [...previousSourcePath.members, ...members]
37100
+ } : null;
37101
+ };
37102
+ const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
37103
+ const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
37104
+ const expression = stripParenExpression(test);
37105
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
37106
+ collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
37107
+ collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
37108
+ return;
37109
+ }
37110
+ if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
37111
+ const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
37112
+ const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
37113
+ if (Boolean(leftPath) === Boolean(rightPath)) return;
37114
+ const path = leftPath ?? rightPath;
37115
+ if (!path) return;
37116
+ comparisons.push({
37117
+ comparedValue: leftPath ? expression.right : expression.left,
37118
+ isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
37119
+ path
37120
+ });
37121
+ };
37122
+ const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
37123
+ const expression = stripParenExpression(test);
37124
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
37125
+ const comparisons = [];
37126
+ collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
37127
+ return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
37128
+ };
37129
+ const getThisFieldName = (node) => {
37130
+ const unwrappedNode = stripParenExpression(node);
37131
+ if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
37132
+ return getMemberIdentity(unwrappedNode.property);
37133
+ };
37134
+ const isUndefinedIdentifier = (node) => {
37135
+ const unwrappedNode = stripParenExpression(node);
37136
+ return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
37137
+ };
37138
+ const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
37139
+ const unwrappedNode = stripParenExpression(node);
37140
+ if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
37141
+ if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
37142
+ const left = stripParenExpression(unwrappedNode.left);
37143
+ return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
37144
+ };
37145
+ const getCallbackRefAssignedFields = (callback, scopes) => {
37146
+ const firstParameter = (callback.params ?? [])[0];
37147
+ if (!firstParameter) return /* @__PURE__ */ new Set();
37148
+ const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
37149
+ if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
37150
+ const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
37151
+ if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
37152
+ const body = callback.body;
37153
+ if (!body) return /* @__PURE__ */ new Set();
37154
+ const assignedFieldNames = /* @__PURE__ */ new Set();
37155
+ walkAst(body, (node) => {
37156
+ if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
37157
+ const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
37158
+ if (!assignmentTarget) return;
37159
+ const fieldName = getThisFieldName(assignmentTarget);
37160
+ if (!fieldName) return;
37161
+ if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
37162
+ assignedFieldNames.add(fieldName);
37163
+ return;
37164
+ }
37165
+ assignedFieldNames.delete(fieldName);
37166
+ });
37167
+ return assignedFieldNames;
37168
+ };
37169
+ const getClassMemberCallback = (classNode, memberName) => {
37170
+ const classBody = classNode.body?.body ?? [];
37171
+ for (const member of classBody) {
37172
+ if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
37173
+ if (member.static === true) continue;
37174
+ const key = member.key;
37175
+ if (getMemberIdentity(key) !== memberName) continue;
37176
+ const value = member.value;
37177
+ return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
37178
+ }
37179
+ return null;
37180
+ };
37181
+ const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
37182
+ const unwrappedExpression = stripParenExpression(expression);
37183
+ if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
37184
+ for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
37185
+ return;
37186
+ }
37187
+ const handlerName = getThisFieldName(unwrappedExpression);
37188
+ if (handlerName) {
37189
+ const callback = getClassMemberCallback(classNode, handlerName);
37190
+ if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
37191
+ return;
37192
+ }
37193
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
37194
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
37195
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
37196
+ return;
37197
+ }
37198
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37199
+ if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
37200
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
37201
+ }
37202
+ };
37203
+ const getCallbackRefFieldNames = (classNode, scopes) => {
37204
+ if (!classNode) return /* @__PURE__ */ new Set();
37205
+ const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
37206
+ if (cachedFieldNames) return cachedFieldNames;
37207
+ const fieldNames = /* @__PURE__ */ new Set();
37208
+ const classBody = classNode.body;
37209
+ if (classBody) walkAst(classBody, (node) => {
37210
+ if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
37211
+ if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
37212
+ collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
37213
+ });
37214
+ callbackRefFieldNamesByClass.set(classNode, fieldNames);
37215
+ return fieldNames;
37216
+ };
37217
+ const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
37218
+ const fieldNames = /* @__PURE__ */ new Set();
37219
+ const body = lifecycleFunction.body;
37220
+ if (!body) return fieldNames;
37221
+ walkAst(body, (node) => {
37222
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
37223
+ const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
37224
+ if (!target) return;
37225
+ const fieldName = getThisFieldName(target);
37226
+ if (fieldName) fieldNames.add(fieldName);
37227
+ });
37228
+ return fieldNames;
37229
+ };
36532
37230
  const getThisStateFieldName = (node) => {
36533
37231
  const unwrappedNode = stripParenExpression(node);
36534
37232
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -36546,15 +37244,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
36546
37244
  });
36547
37245
  return initializers;
36548
37246
  };
36549
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37247
+ const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36550
37248
  if (readsPostMountValue(node)) return true;
37249
+ const fieldName = getThisFieldName(node);
37250
+ if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
36551
37251
  const referencedNames = /* @__PURE__ */ new Set();
36552
37252
  collectReferenceIdentifierNames(node, referencedNames);
36553
37253
  for (const referencedName of referencedNames) {
36554
37254
  if (visitedNames.has(referencedName)) continue;
36555
37255
  const initializer = localInitializers.get(referencedName);
36556
37256
  if (!initializer) continue;
36557
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37257
+ if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36558
37258
  }
36559
37259
  return false;
36560
37260
  };
@@ -36568,50 +37268,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
36568
37268
  }
36569
37269
  return null;
36570
37270
  };
36571
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
36572
- let qualifies = false;
36573
- walkAst(test, (node) => {
36574
- if (qualifies) return false;
36575
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36576
- const leftFieldName = getThisStateFieldName(node.left);
36577
- const rightFieldName = getThisStateFieldName(node.right);
36578
- const fieldName = leftFieldName ?? rightFieldName;
36579
- const comparedValue = leftFieldName ? node.right : node.left;
36580
- if (!fieldName || !leftFieldName && !rightFieldName) return;
36581
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36582
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36583
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36584
- qualifies = true;
36585
- return false;
36586
- });
36587
- return qualifies;
36588
- };
36589
- const isDiffGuardTest = (test, paramNames, derivedNames) => {
36590
- if (referencesAnyName(test, paramNames)) return true;
36591
- let qualifies = false;
36592
- walkAst(test, (node) => {
36593
- if (qualifies) return false;
36594
- if (!isNodeOfType(node, "BinaryExpression")) return;
36595
- if (!EQUALITY_OPERATORS.has(node.operator)) return;
36596
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36597
- qualifies = true;
36598
- return false;
36599
- }
36600
- });
36601
- return qualifies;
37271
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
37272
+ const expression = stripParenExpression(test);
37273
+ if (isNodeOfType(expression, "LogicalExpression")) {
37274
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37275
+ const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37276
+ const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37277
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
37278
+ }
37279
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37280
+ const leftFieldName = getThisStateFieldName(expression.left);
37281
+ const rightFieldName = getThisStateFieldName(expression.right);
37282
+ const fieldName = leftFieldName ?? rightFieldName;
37283
+ const comparedValue = leftFieldName ? expression.right : expression.left;
37284
+ if (!fieldName) return false;
37285
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
37286
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
37287
+ return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
37288
+ };
37289
+ const containsPositiveStateFieldTest = (test, fieldName) => {
37290
+ const unwrappedTest = stripParenExpression(test);
37291
+ if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
37292
+ return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
36602
37293
  };
36603
- const isInsideDiffGuard = (setStateCall) => {
37294
+ const isConvergentUndefinedClearGuard = (test, setStateCall) => {
37295
+ if (!isNodeOfType(setStateCall, "CallExpression")) return false;
37296
+ const argument = setStateCall.arguments?.[0];
37297
+ if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
37298
+ for (const property of argument.properties ?? []) {
37299
+ if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
37300
+ const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
37301
+ if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
37302
+ }
37303
+ return false;
37304
+ };
37305
+ const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
37306
+ const expression = stripParenExpression(test);
37307
+ if (isNodeOfType(expression, "LogicalExpression")) {
37308
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37309
+ const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
37310
+ const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
37311
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
37312
+ }
37313
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37314
+ return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
37315
+ };
37316
+ const isInsideDiffGuard = (setStateCall, scopes) => {
36604
37317
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
36605
37318
  if (!lifecycleFunction) return false;
36606
37319
  const paramNames = /* @__PURE__ */ new Set();
36607
- for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37320
+ const parameters = lifecycleFunction.params ?? [];
37321
+ for (const param of parameters) collectPatternNames(param, paramNames);
37322
+ const previousSourcePaths = /* @__PURE__ */ new Map();
37323
+ const [previousPropsParameter, previousStateParameter] = parameters;
37324
+ collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
37325
+ collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
36608
37326
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
36609
37327
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37328
+ const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37329
+ const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
36610
37330
  let child = setStateCall;
36611
37331
  let ancestor = setStateCall.parent;
36612
37332
  while (ancestor && ancestor !== lifecycleFunction) {
36613
- const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
36614
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
37333
+ let guardTest = null;
37334
+ let isTruthfulBranch = true;
37335
+ if (isNodeOfType(ancestor, "IfStatement")) {
37336
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37337
+ else if (child === ancestor.alternate) {
37338
+ guardTest = ancestor.test;
37339
+ isTruthfulBranch = false;
37340
+ }
37341
+ } else if (isNodeOfType(ancestor, "ConditionalExpression")) {
37342
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37343
+ else if (child === ancestor.alternate) {
37344
+ guardTest = ancestor.test;
37345
+ isTruthfulBranch = false;
37346
+ }
37347
+ } else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
37348
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
36615
37349
  child = ancestor;
36616
37350
  ancestor = ancestor.parent ?? null;
36617
37351
  }
@@ -36633,7 +37367,7 @@ const noDidUpdateSetState = defineRule({
36633
37367
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
36634
37368
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
36635
37369
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
36636
- if (isInsideDiffGuard(node)) return;
37370
+ if (isInsideDiffGuard(node, context.scopes)) return;
36637
37371
  context.report({
36638
37372
  node: node.callee,
36639
37373
  message: MESSAGE$27
@@ -40355,41 +41089,223 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
40355
41089
  if (leftResult === false && rightResult === false) return false;
40356
41090
  return null;
40357
41091
  };
40358
- const readHydrationConditionResult = (expression, context, runtime) => {
41092
+ const readHydrationConditionResult = (expression, context, runtime, state) => {
40359
41093
  const unwrappedExpression = stripParenExpression(expression);
40360
41094
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40361
41095
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
40362
41096
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
40363
41097
  if (staticResult !== null) return staticResult;
41098
+ const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41099
+ const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41100
+ if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41101
+ state.visitedSymbolIds.add(expressionSymbol.id);
41102
+ const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41103
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41104
+ return result;
41105
+ }
41106
+ if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41107
+ state.visitedSymbolIds.add(expressionSymbol.id);
41108
+ const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41109
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41110
+ return result;
41111
+ }
41112
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41113
+ const callArguments = unwrappedExpression.arguments ?? [];
41114
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41115
+ allowGlobalReactNamespace: true,
41116
+ resolveNamedAliases: true
41117
+ })) {
41118
+ const callbackArgument = callArguments[0];
41119
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41120
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41121
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41122
+ }
41123
+ const callee = stripParenExpression(unwrappedExpression.callee);
41124
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
41125
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41126
+ if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41127
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41128
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41129
+ const parameter = helperFunction.params[parameterIndex];
41130
+ const argument = callArguments[parameterIndex];
41131
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41132
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41133
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41134
+ }
41135
+ return readHydrationFunctionResult(helperFunction, context, runtime, {
41136
+ ...state,
41137
+ parameterValuesBySymbolId
41138
+ });
41139
+ }
40364
41140
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
40365
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41141
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40366
41142
  return argumentResult === null ? null : !argumentResult;
40367
41143
  }
40368
41144
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40369
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41145
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41146
+ };
41147
+ const readHydrationStatementResult = (statement, context, runtime, state) => {
41148
+ if (isNodeOfType(statement, "ReturnStatement")) return {
41149
+ didReturn: true,
41150
+ value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41151
+ };
41152
+ if (isNodeOfType(statement, "BlockStatement")) {
41153
+ for (const childStatement of statement.body) {
41154
+ const result = readHydrationStatementResult(childStatement, context, runtime, state);
41155
+ if (result.didReturn) return result;
41156
+ if (statementAlwaysExits(childStatement)) break;
41157
+ }
41158
+ return {
41159
+ didReturn: false,
41160
+ value: null
41161
+ };
41162
+ }
41163
+ if (!isNodeOfType(statement, "IfStatement")) return {
41164
+ didReturn: false,
41165
+ value: null
41166
+ };
41167
+ const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41168
+ if (conditionResult !== null) {
41169
+ const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41170
+ return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41171
+ didReturn: false,
41172
+ value: null
41173
+ };
41174
+ }
41175
+ const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41176
+ const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41177
+ didReturn: false,
41178
+ value: null
41179
+ };
41180
+ return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41181
+ didReturn: consequentResult.didReturn || alternateResult.didReturn,
41182
+ value: null
41183
+ };
40370
41184
  };
40371
- const matchHydrationCondition = (expression, context) => {
41185
+ const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41186
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41187
+ state.visitedFunctionNodes.add(functionNode);
41188
+ const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41189
+ state.visitedFunctionNodes.delete(functionNode);
41190
+ return result;
41191
+ };
41192
+ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41193
+ const left = stripParenExpression(leftExpression);
41194
+ const right = stripParenExpression(rightExpression);
41195
+ if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41196
+ const leftSymbol = scopes.symbolFor(left);
41197
+ const rightSymbol = scopes.symbolFor(right);
41198
+ return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41199
+ }
41200
+ if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41201
+ if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41202
+ const rightArguments = right.arguments ?? [];
41203
+ return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41204
+ const rightArgument = rightArguments[index];
41205
+ return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41206
+ });
41207
+ }
41208
+ return true;
41209
+ };
41210
+ const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41211
+ if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41212
+ const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41213
+ const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41214
+ return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41215
+ };
41216
+ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41217
+ const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41218
+ return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41219
+ };
41220
+ const matchHydrationConditionInternal = (expression, context, state) => {
40372
41221
  const unwrappedExpression = stripParenExpression(expression);
40373
41222
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40374
41223
  if (predicateMatch) return {
40375
41224
  predicateMatch,
40376
41225
  predicateNode: unwrappedExpression
40377
41226
  };
40378
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
40379
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40380
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40381
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40382
- if (leftMatch && rightMatch) {
40383
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40384
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40385
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
41227
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
41228
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
41229
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41230
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41231
+ state.visitedSymbolIds.add(symbol.id);
41232
+ const match = matchHydrationConditionInternal(parameterValue, context, state);
41233
+ state.visitedSymbolIds.delete(symbol.id);
41234
+ return match;
41235
+ }
41236
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41237
+ state.visitedSymbolIds.add(symbol.id);
41238
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41239
+ state.visitedSymbolIds.delete(symbol.id);
41240
+ return match;
40386
41241
  }
41242
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41243
+ const callArguments = unwrappedExpression.arguments ?? [];
41244
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41245
+ allowGlobalReactNamespace: true,
41246
+ resolveNamedAliases: true
41247
+ })) {
41248
+ const callbackArgument = callArguments[0];
41249
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41250
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41251
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41252
+ }
41253
+ const callee = stripParenExpression(unwrappedExpression.callee);
41254
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41255
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41256
+ if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41257
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41258
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41259
+ const parameter = helperFunction.params[parameterIndex];
41260
+ const argument = callArguments[parameterIndex];
41261
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41262
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41263
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41264
+ }
41265
+ return matchHydrationFunctionResult(helperFunction, context, {
41266
+ ...state,
41267
+ parameterValuesBySymbolId
41268
+ });
41269
+ }
41270
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
41271
+ if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41272
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41273
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40387
41274
  const nestedMatch = leftMatch ?? rightMatch;
40388
41275
  if (!nestedMatch) return null;
40389
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40390
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40391
- return nestedMatch;
41276
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41277
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41278
+ return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
40392
41279
  };
41280
+ const matchHydrationReturningStatement = (statement, context, state) => {
41281
+ if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41282
+ if (isNodeOfType(statement, "IfStatement")) {
41283
+ const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41284
+ const consequentValues = getReturnedValues(statement.consequent);
41285
+ const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41286
+ if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41287
+ return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41288
+ }
41289
+ if (!isNodeOfType(statement, "BlockStatement")) return null;
41290
+ for (const childStatement of statement.body) {
41291
+ const match = matchHydrationReturningStatement(childStatement, context, state);
41292
+ if (match) return match;
41293
+ if (statementAlwaysExits(childStatement)) break;
41294
+ }
41295
+ return null;
41296
+ };
41297
+ const matchHydrationFunctionResult = (functionNode, context, state) => {
41298
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41299
+ state.visitedFunctionNodes.add(functionNode);
41300
+ const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41301
+ state.visitedFunctionNodes.delete(functionNode);
41302
+ return match;
41303
+ };
41304
+ const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41305
+ parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41306
+ visitedFunctionNodes: /* @__PURE__ */ new Set(),
41307
+ visitedSymbolIds: /* @__PURE__ */ new Set()
41308
+ });
40393
41309
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
40394
41310
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
40395
41311
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -40532,17 +41448,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
40532
41448
  const { predicateMatch, predicateNode } = conditionMatch;
40533
41449
  if (reportedNodes.has(predicateNode)) return;
40534
41450
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
40535
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41451
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40536
41452
  if (!componentOrHookNode) return;
40537
41453
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
40538
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41454
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40539
41455
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
40540
- const attribute = findEnclosingJsxAttribute(predicateNode);
41456
+ const attribute = findEnclosingJsxAttribute(conditionNode);
40541
41457
  if (!attribute || isEventHandlerAttribute(attribute)) return;
40542
41458
  }
40543
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40544
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40545
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41459
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41460
+ if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41461
+ const openingElement = findEnclosingJsxOpeningElement(conditionNode);
40546
41462
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
40547
41463
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
40548
41464
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -40924,7 +41840,7 @@ const noInitializeState = defineRule({
40924
41840
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
40925
41841
  const analysis = getProgramAnalysis(node);
40926
41842
  if (!analysis) return;
40927
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
41843
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
40928
41844
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
40929
41845
  const stateName = getStateName(fact.stateDeclarator);
40930
41846
  context.report({
@@ -44331,6 +45247,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
44331
45247
  "deserialize"
44332
45248
  ]);
44333
45249
  //#endregion
45250
+ //#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
45251
+ const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
45252
+ const callExpression = stripParenExpression(initializer);
45253
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
45254
+ const callbackArgument = callExpression.arguments[0];
45255
+ if (!callbackArgument) return null;
45256
+ if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
45257
+ return isReactApiCall(callExpression, "useCallback", scopes, {
45258
+ allowGlobalReactNamespace: true,
45259
+ allowUnboundBareCalls: true
45260
+ }) ? callbackArgument : null;
45261
+ };
45262
+ //#endregion
45263
+ //#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
45264
+ const getDeclarationKind$1 = (declarator) => {
45265
+ const declaration = declarator.parent;
45266
+ return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
45267
+ };
45268
+ const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45269
+ const mergeRequiredBranches = (leftNames, rightNames) => {
45270
+ if (!leftNames || !rightNames) return null;
45271
+ return new Set([...leftNames, ...rightNames]);
45272
+ };
45273
+ const getPropReferenceName = (analysis, identifier) => {
45274
+ if (!isNodeOfType(identifier, "Identifier")) return null;
45275
+ const reference = getRef(analysis, identifier);
45276
+ if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
45277
+ const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
45278
+ return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
45279
+ };
45280
+ const getSingleConstDeclarator = (reference) => {
45281
+ if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
45282
+ const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
45283
+ if (declarators.length !== 1) return null;
45284
+ const declarator = declarators[0];
45285
+ if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
45286
+ return declarator;
45287
+ };
45288
+ const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
45289
+ const unwrappedExpression = stripParenExpression(expression);
45290
+ if (isFunctionLike$1(unwrappedExpression)) {
45291
+ if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
45292
+ const callbackNames = /* @__PURE__ */ new Set();
45293
+ walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
45294
+ if (!isNodeOfType(child, "CallExpression")) return;
45295
+ const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
45296
+ if (!resolvedNames) return;
45297
+ for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
45298
+ });
45299
+ return callbackNames.size > 0 ? callbackNames : null;
45300
+ }
45301
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
45302
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
45303
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
45304
+ const propName = getPropReferenceName(analysis, unwrappedExpression);
45305
+ if (propName) return new Set([propName]);
45306
+ const reference = getRef(analysis, unwrappedExpression);
45307
+ if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
45308
+ const declarator = getSingleConstDeclarator(reference);
45309
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45310
+ visitedReferences.add(reference.resolved);
45311
+ const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
45312
+ const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
45313
+ allowGlobalReactNamespace: true,
45314
+ allowUnboundBareCalls: true
45315
+ }));
45316
+ return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
45317
+ }
45318
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
45319
+ const propertyName = getStaticMemberPropertyName(unwrappedExpression);
45320
+ if (!propertyName) return null;
45321
+ const receiver = stripParenExpression(unwrappedExpression.object);
45322
+ if (!isNodeOfType(receiver, "Identifier")) return null;
45323
+ const receiverReference = getRef(analysis, receiver);
45324
+ if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
45325
+ if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
45326
+ const declarator = getSingleConstDeclarator(receiverReference);
45327
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45328
+ visitedReferences.add(receiverReference.resolved);
45329
+ const initializer = stripParenExpression(declarator.init);
45330
+ if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
45331
+ if (!isReactApiCall(initializer, "useRef", scopes, {
45332
+ allowGlobalReactNamespace: true,
45333
+ allowUnboundBareCalls: true
45334
+ })) return null;
45335
+ const callbackArgument = initializer.arguments[0];
45336
+ if (!callbackArgument) return null;
45337
+ let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
45338
+ if (!callbackNames) return null;
45339
+ for (const candidateReference of receiverReference.resolved.references) {
45340
+ const candidateIdentifier = candidateReference.identifier;
45341
+ const candidateMember = candidateIdentifier.parent;
45342
+ if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
45343
+ const assignment = candidateMember.parent;
45344
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
45345
+ if (assignment.operator !== "=") return null;
45346
+ callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
45347
+ if (!callbackNames) return null;
45348
+ }
45349
+ return callbackNames;
45350
+ }
45351
+ if (!isNodeOfType(initializer, "ObjectExpression")) return null;
45352
+ const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
45353
+ if (!property || !isNodeOfType(property, "Property")) return null;
45354
+ return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
45355
+ };
45356
+ const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
45357
+ //#endregion
44334
45358
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
44335
45359
  const isUseStateIdentifier = (identifier) => {
44336
45360
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -44359,14 +45383,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
44359
45383
  "useStableCallback",
44360
45384
  "useCallbackRef"
44361
45385
  ]);
44362
- const getWrapperHookWrappedFunction = (initializer) => {
45386
+ const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
44363
45387
  if (!isNodeOfType(initializer, "CallExpression")) return null;
45388
+ const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
45389
+ if (transparentReactArgument) return transparentReactArgument;
44364
45390
  const callee = initializer.callee;
44365
45391
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
44366
45392
  if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
44367
45393
  const wrapped = initializer.arguments?.[0];
44368
- if (!wrapped || !isFunctionLike$1(wrapped)) return null;
44369
- return wrapped;
45394
+ if (!wrapped) return null;
45395
+ if (calleeName === "useEffectEvent") return null;
45396
+ if (isFunctionLike$1(wrapped)) return wrapped;
45397
+ return null;
44370
45398
  };
44371
45399
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
44372
45400
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
@@ -44376,16 +45404,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
44376
45404
  const innerParent = innerIdentifier.parent;
44377
45405
  return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
44378
45406
  });
44379
- const isDirectParentCallbackRef = (analysis, ref) => {
45407
+ const isDirectParentCallbackRef = (analysis, ref, scopes) => {
44380
45408
  if (isProp(analysis, ref)) return true;
45409
+ if (hasMutableBindingWrite$1(ref)) {
45410
+ if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
45411
+ const candidateIdentifier = candidateReference.identifier;
45412
+ const assignment = candidateIdentifier.parent;
45413
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
45414
+ const assignedReferences = getDownstreamRefs(analysis, assignment.right);
45415
+ return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
45416
+ })) return false;
45417
+ }
44381
45418
  return Boolean(ref.resolved?.defs.some((def) => {
44382
45419
  const node = def.node;
44383
45420
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44384
45421
  const initializer = unwrapChainExpression(node.init);
44385
- const wrappedFunction = getWrapperHookWrappedFunction(initializer);
45422
+ const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
44386
45423
  if (wrappedFunction) {
44387
45424
  if (wrappedFunction.async) return false;
44388
- return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45425
+ if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45426
+ const directName = getParentCallbackPropName(analysis, wrappedFunction);
45427
+ const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
45428
+ if (directName !== null) return true;
45429
+ return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
44389
45430
  }
44390
45431
  if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
44391
45432
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
@@ -44395,7 +45436,7 @@ const getDeclarationKind = (declarator) => {
44395
45436
  const declaration = declarator.parent;
44396
45437
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
44397
45438
  };
44398
- const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45439
+ const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44399
45440
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
44400
45441
  const unwrappedExpression = stripParenExpression(expression);
44401
45442
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -44407,7 +45448,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
44407
45448
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
44408
45449
  return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
44409
45450
  }
44410
- if (hasMutableBindingWrite(callbackReference)) return null;
45451
+ if (hasMutableBindingWrite$1(callbackReference)) return null;
44411
45452
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44412
45453
  if (definitions.length !== 1) return null;
44413
45454
  const declarator = definitions[0];
@@ -44473,7 +45514,7 @@ const getRefAliasDeclarator = (identifier) => {
44473
45514
  const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44474
45515
  if (!isNodeOfType(receiver, "Identifier")) return null;
44475
45516
  const receiverReference = getRef(analysis, receiver);
44476
- if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
45517
+ if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
44477
45518
  const variables = /* @__PURE__ */ new Set();
44478
45519
  let currentVariable = receiverReference.resolved;
44479
45520
  let refCall = null;
@@ -44489,7 +45530,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44489
45530
  }
44490
45531
  if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
44491
45532
  const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
44492
- if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
45533
+ if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
44493
45534
  currentVariable = upstreamReference.resolved;
44494
45535
  }
44495
45536
  if (!refCall) return null;
@@ -44564,7 +45605,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
44564
45605
  while (isNodeOfType(currentExpression, "Identifier")) {
44565
45606
  const currentReference = getRef(analysis, currentExpression);
44566
45607
  const currentVariable = currentReference?.resolved;
44567
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
45608
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
44568
45609
  visitedVariables.add(currentVariable);
44569
45610
  const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
44570
45611
  if (definitions.length !== 1) return false;
@@ -44578,11 +45619,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
44578
45619
  const propsExpression = stripParenExpression(propsSpread.argument);
44579
45620
  if (!isNodeOfType(propsExpression, "Identifier")) return false;
44580
45621
  const propsReference = getRef(analysis, propsExpression);
44581
- if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
45622
+ if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
44582
45623
  const contextExpression = stripParenExpression(contextSpread.argument);
44583
45624
  if (!isNodeOfType(contextExpression, "Identifier")) return false;
44584
45625
  const contextReference = getRef(analysis, contextExpression);
44585
- if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
45626
+ if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
44586
45627
  const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44587
45628
  if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
44588
45629
  const contextHook = stripParenExpression(contextInitializer.init.callee);
@@ -44596,7 +45637,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
44596
45637
  while (isNodeOfType(currentExpression, "Identifier")) {
44597
45638
  const currentReference = getRef(analysis, currentExpression);
44598
45639
  const currentVariable = currentReference?.resolved;
44599
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
45640
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
44600
45641
  visitedVariables.add(currentVariable);
44601
45642
  const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
44602
45643
  const bindingIdentifier = definition?.name;
@@ -44665,7 +45706,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44665
45706
  while (isNodeOfType(currentExpression, "Identifier")) {
44666
45707
  const callbackReference = getRef(analysis, currentExpression);
44667
45708
  const callbackVariable = callbackReference?.resolved;
44668
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
45709
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
44669
45710
  visitedVariables.add(callbackVariable);
44670
45711
  const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
44671
45712
  const declarator = definition?.node;
@@ -44685,10 +45726,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44685
45726
  if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
44686
45727
  return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
44687
45728
  };
44688
- const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
45729
+ const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
44689
45730
  const node = def.node;
44690
45731
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44691
- return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
45732
+ const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
45733
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
44692
45734
  }));
44693
45735
  const isHandlerBagArgument = (analysis, argument) => {
44694
45736
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -44707,14 +45749,25 @@ const isHandlerBagArgument = (analysis, argument) => {
44707
45749
  };
44708
45750
  const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
44709
45751
  const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
44710
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
45752
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
44711
45753
  "useIntersectionObserver",
44712
45754
  "useMatchMedia",
45755
+ "useMediaJobProgress",
44713
45756
  "useMediaQuery",
44714
45757
  "useResizeObserver",
44715
45758
  "useVisibility",
44716
45759
  "useWindowSize"
44717
45760
  ]);
45761
+ const isCallbackPropReference = (analysis, ref) => {
45762
+ if (!isProp(analysis, ref)) return false;
45763
+ const identifier = ref.identifier;
45764
+ if (!isNodeOfType(identifier, "Identifier")) return false;
45765
+ if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
45766
+ const member = identifier.parent;
45767
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
45768
+ const propertyName = getStaticMemberPropertyName(member);
45769
+ return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
45770
+ };
44718
45771
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
44719
45772
  const node = def.node;
44720
45773
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -44722,7 +45775,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
44722
45775
  if (!isNodeOfType(init, "CallExpression")) return false;
44723
45776
  const callee = init.callee;
44724
45777
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
44725
- return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
45778
+ return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44726
45779
  }));
44727
45780
  const isParentWiredHookResultArgument = (analysis, argument) => {
44728
45781
  if (!isNodeOfType(argument, "Identifier")) return false;
@@ -44735,19 +45788,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
44735
45788
  if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
44736
45789
  const parent = identifier.parent;
44737
45790
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
44738
- return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
45791
+ return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44739
45792
  };
44740
45793
  const isExternalSubscriptionHookRef = (ref) => {
44741
45794
  const identifier = ref.identifier;
44742
45795
  if (!isNodeOfType(identifier, "Identifier")) return false;
44743
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
45796
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
44744
45797
  return Boolean(ref.resolved?.defs.some((def) => {
44745
45798
  const node = def.node;
44746
45799
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44747
45800
  const initializer = stripParenExpression(node.init);
44748
45801
  if (!isNodeOfType(initializer, "CallExpression")) return false;
44749
45802
  const callee = stripParenExpression(initializer.callee);
44750
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
45803
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
44751
45804
  }));
44752
45805
  };
44753
45806
  const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
@@ -44783,16 +45836,22 @@ const noPassDataToParent = defineRule({
44783
45836
  const callExpr = getCallExpr(ref);
44784
45837
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
44785
45838
  const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
44786
- if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
44787
45839
  if (!isSynchronous(ref.identifier, effectFn)) continue;
44788
45840
  const calleeNode = unwrapChainExpression(callExpr.callee);
44789
45841
  const identifier = ref.identifier;
44790
- if (callbackRefProvenance) {
44791
- if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
45842
+ const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
45843
+ analysis,
45844
+ expression: calleeNode,
45845
+ scopes: context.scopes
45846
+ });
45847
+ const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
45848
+ if (isRefCall(analysis, ref) && !callbackPropNames) continue;
45849
+ if (callbackPropNames) {
45850
+ if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
44792
45851
  } else if (calleeNode === identifier) {
44793
45852
  const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
44794
45853
  if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
44795
- if (!isDirectParentCallbackRef(analysis, ref)) continue;
45854
+ if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
44796
45855
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
44797
45856
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
44798
45857
  if (!isWholePropsObjectReference(analysis, ref)) continue;
@@ -44800,10 +45859,10 @@ const noPassDataToParent = defineRule({
44800
45859
  } else continue;
44801
45860
  const methodName = getCallMethodName(calleeNode);
44802
45861
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
44803
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45862
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
44804
45863
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
44805
- if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
44806
- const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
45864
+ if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
45865
+ const isSetterNamedCallee = callbackPropNames ? [...callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
44807
45866
  const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
44808
45867
  const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
44809
45868
  if (isFunctionLike$1(argument)) {
@@ -44818,7 +45877,7 @@ const noPassDataToParent = defineRule({
44818
45877
  }
44819
45878
  return getDownstreamRefs(analysis, argument);
44820
45879
  }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
44821
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45880
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
44822
45881
  if (!argsUpstreamRefs.some((argRef) => {
44823
45882
  if (isUseStateIdentifier(argRef.identifier)) return false;
44824
45883
  if (isExternalSubscriptionHookRef(argRef)) return false;
@@ -44856,9 +45915,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
44856
45915
  return false;
44857
45916
  };
44858
45917
  //#endregion
45918
+ //#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
45919
+ const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
45920
+ "useCallbackRef",
45921
+ "useEffectEvent",
45922
+ "useEvent",
45923
+ "useEventCallback",
45924
+ "useLatest",
45925
+ "useMemoizedFn",
45926
+ "useStableCallback"
45927
+ ]);
45928
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
45929
+ "useIntersectionObserver",
45930
+ "useMatchMedia",
45931
+ "useMediaJobProgress",
45932
+ "useMediaQuery",
45933
+ "useResizeObserver",
45934
+ "useVisibility",
45935
+ "useWindowSize"
45936
+ ]);
45937
+ const getHookCalleeName = (initializer) => {
45938
+ const unwrappedInitializer = stripParenExpression(initializer);
45939
+ if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
45940
+ const callee = stripParenExpression(unwrappedInitializer.callee);
45941
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
45942
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
45943
+ return null;
45944
+ };
45945
+ const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
45946
+ const declarator = definition.node;
45947
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
45948
+ const calleeName = getHookCalleeName(declarator.init);
45949
+ if (!calleeName || !HOOK_NAME_PATTERN$3.test(calleeName) || BUILTIN_HOOK_NAMES.has(calleeName) || NON_STATE_CUSTOM_HOOK_NAMES.has(calleeName) || EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(calleeName)) return false;
45950
+ const initializer = stripParenExpression(declarator.init);
45951
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
45952
+ return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
45953
+ }));
45954
+ //#endregion
44859
45955
  //#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
44860
45956
  const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
44861
45957
  const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
45958
+ const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44862
45959
  const getCallCalleeName = (callExpr) => {
44863
45960
  if (!isNodeOfType(callExpr, "CallExpression")) return null;
44864
45961
  const callee = callExpr.callee;
@@ -44903,6 +46000,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
44903
46000
  stateRefs.push(ref);
44904
46001
  return;
44905
46002
  }
46003
+ if (isCustomHookStateResultReference(analysis, ref)) {
46004
+ stateRefs.push(ref);
46005
+ return;
46006
+ }
44906
46007
  for (const def of ref.resolved?.defs ?? []) {
44907
46008
  if (def.type === "ImportBinding" || def.type === "Parameter") continue;
44908
46009
  const defNode = def.node;
@@ -44932,6 +46033,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
44932
46033
  }
44933
46034
  return stateRefs;
44934
46035
  };
46036
+ const collectDirectCallStateRefs = (analysis, callExpression) => {
46037
+ const stateReferences = [];
46038
+ for (const argument of callExpression.arguments) {
46039
+ if (isFunctionLike$1(argument)) continue;
46040
+ for (const argumentReference of getDownstreamRefs(analysis, argument)) {
46041
+ if (resolveToFunction(argumentReference)) continue;
46042
+ collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
46043
+ }
46044
+ }
46045
+ return stateReferences;
46046
+ };
46047
+ const getTransparentWrapperPropReference = (analysis, reference, context) => {
46048
+ for (const definition of reference.resolved?.defs ?? []) {
46049
+ const declarator = definition.node;
46050
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
46051
+ const resultSymbol = context.scopes.symbolFor(declarator.id);
46052
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
46053
+ if (!callbackArgument) continue;
46054
+ const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
46055
+ const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
46056
+ if (callbackReference) return callbackReference;
46057
+ const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
46058
+ if (propReference) return propReference;
46059
+ }
46060
+ return null;
46061
+ };
44935
46062
  const isSetterNamedCallbackReceivingData = (callbackRef) => {
44936
46063
  const callExpr = getCallExpr(callbackRef);
44937
46064
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
@@ -44967,6 +46094,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
44967
46094
  const calleeName = getInitializerCalleeName(node.init);
44968
46095
  return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
44969
46096
  }));
46097
+ const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
46098
+ const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
46099
+ if (!helperFunction) return null;
46100
+ let ancestor = callExpression.parent;
46101
+ while (ancestor && ancestor !== effectFunction) {
46102
+ if (isFunctionLike$1(ancestor)) return null;
46103
+ ancestor = ancestor.parent;
46104
+ }
46105
+ return ancestor === effectFunction ? helperFunction : null;
46106
+ };
44970
46107
  const noPassLiveStateToParent = defineRule({
44971
46108
  id: "no-pass-live-state-to-parent",
44972
46109
  title: "Live state pushed to parent via effect",
@@ -44981,20 +46118,32 @@ const noPassLiveStateToParent = defineRule({
44981
46118
  if (!effectFnRefs) return;
44982
46119
  const effectFn = getEffectFn(analysis, node);
44983
46120
  if (!effectFn) return;
46121
+ const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
44984
46122
  for (const ref of effectFnRefs) {
44985
- const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
44986
- if (propCallbackRefs.length === 0) continue;
44987
- if (resolvesToLocalHookReturnBinding(ref)) continue;
44988
- if (!isSynchronous(ref.identifier, effectFn)) continue;
44989
46123
  const callExpr = getCallExpr(ref);
44990
- if (!callExpr) continue;
46124
+ if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
46125
+ const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
46126
+ const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
46127
+ const resolvedCallbackPropNames = getParentCallbackPropNames({
46128
+ analysis,
46129
+ expression: callExpr.callee,
46130
+ scopes: context.scopes
46131
+ });
46132
+ const callExpressionRoot = findTransparentExpressionRoot(callExpr);
46133
+ const notificationCallbackPropNames = Boolean(resolvedCallbackPropNames && callExpr.arguments.length > 0 && (!isCallResultCapturedToLocal(callExpr) || isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === effectFunctionBody) && [...resolvedCallbackPropNames].every((callbackPropName) => !DATA_FETCHING_CALLBACK_PATTERN.test(callbackPropName))) ? resolvedCallbackPropNames : null;
46134
+ if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
46135
+ const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
46136
+ const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
46137
+ if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
46138
+ if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
46139
+ if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
44991
46140
  if (isCallResultConsumedAsArgument(callExpr)) continue;
44992
46141
  const calleeNode = callExpr.callee;
44993
46142
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
44994
46143
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
44995
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
44996
- if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
44997
- const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
46144
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
46145
+ if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
46146
+ const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
44998
46147
  const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
44999
46148
  if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
45000
46149
  context.report({
@@ -45387,6 +46536,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
45387
46536
  if (!analysis) return true;
45388
46537
  const reference = getRef(analysis, element);
45389
46538
  if (!reference) return true;
46539
+ if (isCustomHookStateResultReference(analysis, reference)) return true;
45390
46540
  const upstreamReferences = getUpstreamRefs(analysis, reference);
45391
46541
  if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
45392
46542
  return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
@@ -45403,6 +46553,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
45403
46553
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
45404
46554
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
45405
46555
  };
46556
+ const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
46557
+ const callee = stripParenExpression(callExpression.callee);
46558
+ if (!isNodeOfType(callee, "Identifier")) return null;
46559
+ const binding = findVariableInitializer(callExpression, callee.name);
46560
+ if (!binding?.initializer) return null;
46561
+ const resultSymbol = context.scopes.symbolFor(callee);
46562
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
46563
+ if (!callbackArgument) return null;
46564
+ const callbackSource = stripParenExpression(callbackArgument);
46565
+ if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
46566
+ if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
46567
+ const receiver = stripParenExpression(callbackSource.object);
46568
+ const propertyName = getStaticPropertyName(callbackSource);
46569
+ if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
46570
+ return isPropName(receiver.name, receiver) ? propertyName : null;
46571
+ };
45406
46572
  const noPropCallbackInEffect = defineRule({
45407
46573
  id: "no-prop-callback-in-effect",
45408
46574
  title: "Parent kept in sync with a callback effect",
@@ -45436,9 +46602,16 @@ const noPropCallbackInEffect = defineRule({
45436
46602
  walkInsideStatementBlocks(callback.body, (child) => {
45437
46603
  if (!isNodeOfType(child, "CallExpression")) return;
45438
46604
  const directCallee = stripParenExpression(child.callee);
45439
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
46605
+ const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
46606
+ analysis,
46607
+ expression: directCallee,
46608
+ scopes: context.scopes
46609
+ }) : null;
46610
+ const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
45440
46611
  if (!calleeName) return;
45441
- if (!isResultDiscardedCall(child)) return;
46612
+ const callExpressionRoot = findTransparentExpressionRoot(child);
46613
+ const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
46614
+ if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
45442
46615
  if (reportedNodes.has(child)) return;
45443
46616
  reportedNodes.add(child);
45444
46617
  context.report({
@@ -46271,6 +47444,69 @@ const noRedundantShouldComponentUpdate = defineRule({
46271
47444
  }
46272
47445
  });
46273
47446
  //#endregion
47447
+ //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47448
+ const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47449
+ const expression = stripParenExpression(rawExpression);
47450
+ if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47451
+ if (isNodeOfType(expression, "ConditionalExpression")) {
47452
+ if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47453
+ return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47454
+ }
47455
+ if (isNodeOfType(expression, "LogicalExpression")) {
47456
+ if (isNodeOfType(expression.left, "Literal")) {
47457
+ const isLeftTruthy = Boolean(expression.left.value);
47458
+ if (expression.operator === "&&" && !isLeftTruthy) return [];
47459
+ if (expression.operator === "||" && isLeftTruthy) return [];
47460
+ if (expression.operator === "??" && expression.left.value !== null) return [];
47461
+ }
47462
+ if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
47463
+ return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
47464
+ }
47465
+ if (isNodeOfType(expression, "SequenceExpression")) {
47466
+ const finalExpression = expression.expressions.at(-1);
47467
+ return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
47468
+ }
47469
+ if (isNodeOfType(expression, "CallExpression")) {
47470
+ if (!isReactApiCall(expression, "useCallback", scopes)) return [];
47471
+ const callback = expression.arguments[0];
47472
+ return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
47473
+ }
47474
+ if (!isNodeOfType(expression, "Identifier")) return [];
47475
+ const symbol = scopes.symbolFor(expression);
47476
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
47477
+ if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
47478
+ const initializer = getDirectConstInitializer(symbol);
47479
+ if (!initializer) return [];
47480
+ return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
47481
+ };
47482
+ const functionReturnsCleanupFunction = (functionExpression, scopes) => {
47483
+ if (!isFunctionLike$1(functionExpression)) return false;
47484
+ if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
47485
+ return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
47486
+ };
47487
+ const callbackReturnsCleanupFunction = (callback, scopes) => {
47488
+ return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
47489
+ };
47490
+ const noRefCallbackCleanupBeforeReact19 = defineRule({
47491
+ id: "no-ref-callback-cleanup-before-react-19",
47492
+ title: "Ref cleanup requires React 19",
47493
+ requires: ["react:18"],
47494
+ disabledWhen: ["react:19"],
47495
+ severity: "warn",
47496
+ recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
47497
+ create: (context) => ({ JSXAttribute(node) {
47498
+ if (getJsxAttributeName(node.name) !== "ref") return;
47499
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
47500
+ const callback = node.value.expression;
47501
+ if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
47502
+ if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
47503
+ context.report({
47504
+ node,
47505
+ message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
47506
+ });
47507
+ } })
47508
+ });
47509
+ //#endregion
46274
47510
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
46275
47511
  const REPEATED_ANCESTOR_TYPES = new Set([
46276
47512
  "DoWhileStatement",
@@ -46866,7 +48102,7 @@ const doConditionsImplyFormula = (conditions, target) => {
46866
48102
  }
46867
48103
  return facts.didConflict || evaluateBooleanFormula$1(target, facts.assignments) === true;
46868
48104
  };
46869
- const getFunctionBindingSymbol = (functionNode, scopes) => {
48105
+ const getFunctionBindingSymbol$1 = (functionNode, scopes) => {
46870
48106
  if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) return scopes.symbolFor(functionNode.id);
46871
48107
  const parent = functionNode.parent;
46872
48108
  if ((isNodeOfType(functionNode, "ArrowFunctionExpression") || isNodeOfType(functionNode, "FunctionExpression")) && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return scopes.symbolFor(parent.id);
@@ -46899,7 +48135,7 @@ const isNodeEvaluatedDuringRender = (node, componentNode, scopes, visitedFunctio
46899
48135
  const synchronousCallbackCall = getSynchronousCallbackCall(functionNode);
46900
48136
  if (synchronousCallbackCall) return isNodeEvaluatedDuringRender(synchronousCallbackCall, componentNode, scopes, visitedFunctionSymbolIds);
46901
48137
  if (executesDuringRender(functionNode, scopes)) return isNodeEvaluatedDuringRender(functionNode.parent ?? functionNode, componentNode, scopes, visitedFunctionSymbolIds);
46902
- const functionSymbol = getFunctionBindingSymbol(functionNode, scopes);
48138
+ const functionSymbol = getFunctionBindingSymbol$1(functionNode, scopes);
46903
48139
  if (!functionSymbol || visitedFunctionSymbolIds.has(functionSymbol.id)) return false;
46904
48140
  visitedFunctionSymbolIds.add(functionSymbol.id);
46905
48141
  let callCount = 0;
@@ -46948,7 +48184,7 @@ const collectExposureConditions = (analysis, context, node, componentNode, prote
46948
48184
  parent = synchronousCallbackCall.parent;
46949
48185
  continue;
46950
48186
  }
46951
- const functionSymbol = getFunctionBindingSymbol(parent, context.scopes);
48187
+ const functionSymbol = getFunctionBindingSymbol$1(parent, context.scopes);
46952
48188
  if (functionSymbol?.references.length === 1) {
46953
48189
  const callExpression = isReferenceDirectlyCalled(functionSymbol.references[0].identifier);
46954
48190
  if (callExpression) {
@@ -47158,7 +48394,7 @@ const getSetterExposureConditions = (analysis, context, setterReference, compone
47158
48394
  const functionNode = findEnclosingFunction$1(setterReference.identifier);
47159
48395
  if (!functionNode) return null;
47160
48396
  if (isInlineJsxCallback(functionNode)) return [collectExposureConditions(analysis, context, functionNode, componentNode, protectedSymbolIds)];
47161
- const functionSymbol = getFunctionBindingSymbol(functionNode, context.scopes);
48397
+ const functionSymbol = getFunctionBindingSymbol$1(functionNode, context.scopes);
47162
48398
  if (!functionSymbol || functionSymbol.references.length === 0) return null;
47163
48399
  const conditionsByReference = [];
47164
48400
  for (const reference of functionSymbol.references) {
@@ -47546,7 +48782,7 @@ const isSelfReferentialSentinelValue = (variableName, literalValue) => literalVa
47546
48782
  const isIdentifierLikeKeyNameValue = (literalValue) => {
47547
48783
  const wordSegments = literalValue.replace(/^[_$\s]+|[_$\s]+$/g, "").split(/[_\-:./$]+/).filter((segment) => segment.length > 0);
47548
48784
  if (wordSegments.length < 2) return false;
47549
- return wordSegments.every((segment) => /^[a-z]+$/.test(segment));
48785
+ return wordSegments.every((segment) => /^[a-z]+(?:[A-Z][a-z]+)*$/.test(segment));
47550
48786
  };
47551
48787
  const FRAMEWORK_ENV_ADVICE = [
47552
48788
  [
@@ -47620,7 +48856,7 @@ const noSecretsInClientCode = defineRule({
47620
48856
  const isServerOnlyScope = isInsideServerOnlyScope(node);
47621
48857
  const trailingSuffix = getIdentifierTrailingWord(variableName);
47622
48858
  const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
47623
- if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && !isPublicUrlValue(literalValue) && !isPlaceholderValueForVariableHeuristic && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && literalValue.length > 24) {
48859
+ if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && !isPublicUrlValue(literalValue) && !isPlaceholderValueForVariableHeuristic && !isSelfReferentialSentinelValue(variableName, literalValue) && !isIdentifierLikeKeyNameValue(literalValue) && literalValue.length > 24) {
47624
48860
  context.report({
47625
48861
  node,
47626
48862
  message: `Hardcoding "${variableName}" in client code is a security vulnerability: the secret ships to the browser where anyone can read it.`
@@ -53031,12 +54267,6 @@ const isInsideEs6Component$1 = (methodDefinition) => {
53031
54267
  if (!owningClass) return false;
53032
54268
  return isPreactOrReactComponentClass(owningClass);
53033
54269
  };
53034
- const stripThisParameter = (params) => {
53035
- const first = params[0];
53036
- if (!first) return params;
53037
- if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
53038
- return params;
53039
- };
53040
54270
  const preactNoRenderArguments = defineRule({
53041
54271
  id: "preact-no-render-arguments",
53042
54272
  title: "render() reads props from arguments",
@@ -56238,8 +57468,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
56238
57468
  destructureIndex: 1
56239
57469
  });
56240
57470
  //#endregion
57471
+ //#region src/plugin/utils/unwrap-return-expression.ts
57472
+ const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57473
+ //#endregion
56241
57474
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
56242
57475
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57476
+ const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57477
+ const USE_STATE_ONLY = new Set(["useState"]);
57478
+ const REACT_API_CALL_OPTIONS = {
57479
+ allowGlobalReactNamespace: true,
57480
+ allowUnboundBareCalls: true,
57481
+ resolveNamedAliases: true
57482
+ };
57483
+ const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57484
+ let readsDerivedSymbol = false;
57485
+ walkAst(expression, (node) => {
57486
+ if (readsDerivedSymbol) return false;
57487
+ if (node !== expression && isFunctionLike$1(node)) return false;
57488
+ if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57489
+ });
57490
+ return readsDerivedSymbol;
57491
+ };
57492
+ const getStaticObjectPropertyName = (property) => {
57493
+ if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57494
+ if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57495
+ if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57496
+ return null;
57497
+ };
57498
+ const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57499
+ const isTransparentAssignmentTarget = (identifier) => {
57500
+ const expressionRoot = findTransparentExpressionRoot(identifier);
57501
+ const parent = expressionRoot.parent;
57502
+ return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57503
+ };
56243
57504
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
56244
57505
  let readsCurrent = false;
56245
57506
  walkAst(argument, (child) => {
@@ -56291,6 +57552,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
56291
57552
  });
56292
57553
  return referenceCount > 0 && !nonAriaReferenceFound;
56293
57554
  };
57555
+ const isGlobalWindowMember = (context, node, propertyName) => {
57556
+ const member = stripParenExpression(node);
57557
+ if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
57558
+ const receiver = stripParenExpression(member.object);
57559
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
57560
+ };
57561
+ const getDirectWindowWidthSetter = (context, statement) => {
57562
+ const call = unwrapDiscardedExpression(statement);
57563
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
57564
+ if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
57565
+ const argument = call.arguments[0];
57566
+ return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
57567
+ };
57568
+ const getResizeListenerHandler = (context, statement, methodName) => {
57569
+ const call = unwrapDiscardedExpression(statement);
57570
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
57571
+ if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
57572
+ const eventName = call.arguments[0];
57573
+ const handler = call.arguments[1];
57574
+ if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
57575
+ return isNodeOfType(handler, "Identifier") ? handler : null;
57576
+ };
57577
+ const getCleanupResizeHandler = (context, statement) => {
57578
+ if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
57579
+ const cleanupStatements = getCallbackStatements(statement.argument);
57580
+ if (cleanupStatements.length !== 1) return null;
57581
+ return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
57582
+ };
57583
+ const findExactViewportState = (context, componentFunction, setterCall) => {
57584
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
57585
+ const componentBody = componentFunction.body;
57586
+ if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
57587
+ const setterSymbol = context.scopes.symbolFor(setterCall.callee);
57588
+ if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
57589
+ const declarator = setterSymbol.declarationNode;
57590
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
57591
+ const stateIdentifier = declarator.id.elements?.[0];
57592
+ const setterIdentifier = declarator.id.elements?.[1];
57593
+ if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
57594
+ const initializer = declarator.init.arguments?.[0];
57595
+ if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
57596
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
57597
+ if (!stateSymbol) return null;
57598
+ const stateDerivedSymbolIds = new Set([stateSymbol.id]);
57599
+ let didAddDerivedSymbol = true;
57600
+ while (didAddDerivedSymbol) {
57601
+ didAddDerivedSymbol = false;
57602
+ for (const statement of componentBody.body ?? []) {
57603
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
57604
+ for (const candidateDeclarator of statement.declarations ?? []) {
57605
+ if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
57606
+ const candidateInitializer = stripParenExpression(candidateDeclarator.init);
57607
+ if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
57608
+ if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
57609
+ const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
57610
+ if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
57611
+ stateDerivedSymbolIds.add(candidateSymbol.id);
57612
+ didAddDerivedSymbol = true;
57613
+ }
57614
+ }
57615
+ }
57616
+ }
57617
+ const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
57618
+ const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57619
+ const symbol = context.scopes.symbolFor(identifier);
57620
+ if (!symbol) return false;
57621
+ if (visitedSymbolIds.has(symbol.id)) return true;
57622
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57623
+ nextVisitedSymbolIds.add(symbol.id);
57624
+ let hasUnknownReference = false;
57625
+ walkAst(componentBody, (node) => {
57626
+ if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
57627
+ const referenceRoot = findTransparentExpressionRoot(node);
57628
+ const parent = referenceRoot.parent;
57629
+ if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
57630
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
57631
+ hasUnknownReference = true;
57632
+ return false;
57633
+ });
57634
+ return !hasUnknownReference;
57635
+ };
57636
+ const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57637
+ const symbol = context.scopes.symbolFor(identifier);
57638
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
57639
+ const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
57640
+ if (cachedVisibility) return cachedVisibility;
57641
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
57642
+ if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
57643
+ const initializer = stripParenExpression(symbol.declarationNode.init);
57644
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57645
+ nextVisitedSymbolIds.add(symbol.id);
57646
+ if (isNodeOfType(initializer, "Identifier")) {
57647
+ const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
57648
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57649
+ return visibility;
57650
+ }
57651
+ if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
57652
+ let visibility = "non-visible";
57653
+ for (const property of initializer.properties ?? []) {
57654
+ const propertyName = getStaticObjectPropertyName(property);
57655
+ if (!isNodeOfType(property, "Property") || !propertyName) {
57656
+ visibility = "unknown";
57657
+ break;
57658
+ }
57659
+ if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
57660
+ }
57661
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57662
+ return visibility;
57663
+ };
57664
+ let hasNonAriaReference = false;
57665
+ walkAst(componentBody, (node) => {
57666
+ if (hasNonAriaReference) return false;
57667
+ if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
57668
+ if (findEnclosingFunction$1(node) !== componentFunction) return;
57669
+ const parent = node.parent;
57670
+ if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
57671
+ let cursor = parent;
57672
+ while (cursor && cursor !== componentBody) {
57673
+ if (isFunctionLike$1(cursor)) return;
57674
+ if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
57675
+ if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
57676
+ return;
57677
+ }
57678
+ if (isNodeOfType(cursor, "JSXAttribute")) {
57679
+ if (isEventHandlerAttribute(cursor)) return;
57680
+ if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
57681
+ return;
57682
+ }
57683
+ if (isNodeOfType(cursor, "ReturnStatement")) {
57684
+ hasNonAriaReference = true;
57685
+ return;
57686
+ }
57687
+ cursor = cursor.parent;
57688
+ }
57689
+ });
57690
+ return hasNonAriaReference ? stateIdentifier.name : null;
57691
+ };
57692
+ const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
57693
+ if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
57694
+ if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
57695
+ const statements = getCallbackStatements(callback);
57696
+ if (statements.length !== 4) return false;
57697
+ const handlerDeclaration = statements[0];
57698
+ if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
57699
+ const handlerDeclarator = handlerDeclaration.declarations[0];
57700
+ if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
57701
+ const handlerStatements = getCallbackStatements(handlerDeclarator.init);
57702
+ if (handlerStatements.length !== 1) return false;
57703
+ const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
57704
+ const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
57705
+ const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
57706
+ const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
57707
+ if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
57708
+ const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
57709
+ if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
57710
+ if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
57711
+ const componentFunction = findEnclosingFunction$1(effectCall);
57712
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
57713
+ return findExactViewportState(context, componentFunction, immediateSetter) !== null;
57714
+ };
56294
57715
  const renderingHydrationNoFlicker = defineRule({
56295
57716
  id: "rendering-hydration-no-flicker",
56296
57717
  title: "useEffect setState flashes on mount",
@@ -56303,7 +57724,14 @@ const renderingHydrationNoFlicker = defineRule({
56303
57724
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
56304
57725
  const callback = getEffectCallback(node);
56305
57726
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
56306
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
57727
+ if (isExactViewportSubscriptionEffect(context, node, callback)) {
57728
+ context.report({
57729
+ node,
57730
+ message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
57731
+ });
57732
+ return;
57733
+ }
57734
+ const bodyStatements = getCallbackStatements(callback);
56307
57735
  if (bodyStatements.length !== 1) return;
56308
57736
  const soleStatement = bodyStatements[0];
56309
57737
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -64945,6 +66373,109 @@ const isDeferrableSideEffectCall = (objectName, methodName) => {
64945
66373
  if (ANALYTICS_DEFERRABLE_OBJECTS.has(objectName)) return ANALYTICS_DEFERRABLE_METHODS.has(methodName);
64946
66374
  return false;
64947
66375
  };
66376
+ const NEXT_SERVER_SOURCE = "next/server";
66377
+ const NEXT_AFTER_EXPORT_NAMES = new Set(["after", "unstable_after"]);
66378
+ const isNextAfterImportSymbol = (symbol, contextNode) => {
66379
+ if (symbol.kind !== "import") return false;
66380
+ const importBinding = getImportBindingForName(contextNode, symbol.name);
66381
+ return Boolean(importBinding && importBinding.source === NEXT_SERVER_SOURCE && !importBinding.isNamespace && importBinding.exportedName && NEXT_AFTER_EXPORT_NAMES.has(importBinding.exportedName));
66382
+ };
66383
+ const isDirectObjectPatternBinding = (symbol) => {
66384
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
66385
+ if (!isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
66386
+ let bindingNode = symbol.bindingIdentifier;
66387
+ if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
66388
+ const property = bindingNode.parent;
66389
+ return Boolean(isNodeOfType(property, "Property") && property.value === bindingNode && property.parent === symbol.declarationNode.id);
66390
+ };
66391
+ const isNextServerNamespace = (expression, contextNode, scopes) => {
66392
+ let candidate = stripParenExpression(expression);
66393
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
66394
+ while (isNodeOfType(candidate, "Identifier")) {
66395
+ const symbol = scopes.symbolFor(candidate);
66396
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
66397
+ if (symbol.kind === "import") {
66398
+ const importBinding = getImportBindingForName(contextNode, symbol.name);
66399
+ return Boolean(importBinding?.source === NEXT_SERVER_SOURCE && importBinding.isNamespace);
66400
+ }
66401
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
66402
+ visitedSymbolIds.add(symbol.id);
66403
+ candidate = stripParenExpression(symbol.initializer);
66404
+ }
66405
+ return false;
66406
+ };
66407
+ const isNextAfterCallee = (callee, contextNode, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
66408
+ const candidate = stripParenExpression(callee);
66409
+ if (isNodeOfType(candidate, "MemberExpression")) {
66410
+ const propertyName = getStaticPropertyKeyName(candidate, { allowComputedString: true });
66411
+ return Boolean(propertyName && NEXT_AFTER_EXPORT_NAMES.has(propertyName) && isNextServerNamespace(candidate.object, contextNode, scopes));
66412
+ }
66413
+ if (!isNodeOfType(candidate, "Identifier")) return false;
66414
+ const symbol = scopes.symbolFor(candidate);
66415
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
66416
+ if (isNextAfterImportSymbol(symbol, contextNode)) return true;
66417
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
66418
+ if (symbol.kind === "const" && symbol.initializer && isDirectObjectPatternBinding(symbol) && destructuredPropertyName && NEXT_AFTER_EXPORT_NAMES.has(destructuredPropertyName)) return isNextServerNamespace(symbol.initializer, contextNode, scopes);
66419
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
66420
+ visitedSymbolIds.add(symbol.id);
66421
+ return isNextAfterCallee(symbol.initializer, contextNode, scopes, visitedSymbolIds);
66422
+ };
66423
+ const getDirectArgumentCall = (expression) => {
66424
+ const expressionRoot = findTransparentExpressionRoot(expression);
66425
+ const parent = expressionRoot.parent;
66426
+ if (!isNodeOfType(parent, "CallExpression")) return null;
66427
+ return parent.arguments[0] === expressionRoot ? parent : null;
66428
+ };
66429
+ const isScheduledByNextAfter = (expression, scopes) => {
66430
+ const callExpression = getDirectArgumentCall(expression);
66431
+ return Boolean(callExpression && isNextAfterCallee(callExpression.callee, callExpression, scopes));
66432
+ };
66433
+ const getFunctionBindingSymbol = (functionNode, scopes) => {
66434
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) return scopes.scopeFor(functionNode).symbols.find((symbol) => symbol.declarationNode === functionNode) ?? null;
66435
+ const functionRoot = findTransparentExpressionRoot(functionNode);
66436
+ const parent = functionRoot.parent;
66437
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== functionRoot || !isNodeOfType(parent.id, "Identifier")) return null;
66438
+ return scopes.symbolFor(parent.id);
66439
+ };
66440
+ const isDirectlyExported = (symbol) => {
66441
+ let declaration = symbol.declarationNode;
66442
+ if (isNodeOfType(declaration, "VariableDeclarator")) declaration = declaration.parent;
66443
+ return Boolean(declaration?.parent && (isNodeOfType(declaration.parent, "ExportNamedDeclaration") || isNodeOfType(declaration.parent, "ExportDefaultDeclaration")));
66444
+ };
66445
+ const isLexicallyInsideFunction = (node, functionNode) => {
66446
+ let enclosingFunction = findEnclosingFunction$1(node);
66447
+ while (enclosingFunction) {
66448
+ if (enclosingFunction === functionNode) return true;
66449
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
66450
+ }
66451
+ return false;
66452
+ };
66453
+ const isExclusivelyScheduledByNextAfter = (functionNode, scopes, visitedFunctionSymbolIds) => {
66454
+ if (isScheduledByNextAfter(functionNode, scopes)) return true;
66455
+ const functionSymbol = getFunctionBindingSymbol(functionNode, scopes);
66456
+ if (!functionSymbol || isDirectlyExported(functionSymbol) || visitedFunctionSymbolIds.has(functionSymbol.id)) return false;
66457
+ const nextVisitedFunctionSymbolIds = new Set(visitedFunctionSymbolIds).add(functionSymbol.id);
66458
+ let hasAfterUse = false;
66459
+ for (const reference of functionSymbol.references) {
66460
+ if (reference.flag !== "read") return false;
66461
+ if (isLexicallyInsideFunction(reference.identifier, functionNode)) continue;
66462
+ if (isScheduledByNextAfter(reference.identifier, scopes)) {
66463
+ hasAfterUse = true;
66464
+ continue;
66465
+ }
66466
+ if (!isInsideNextAfterCallback(reference.identifier, scopes, nextVisitedFunctionSymbolIds)) return false;
66467
+ hasAfterUse = true;
66468
+ }
66469
+ return hasAfterUse;
66470
+ };
66471
+ const isInsideNextAfterCallback = (node, scopes, visitedFunctionSymbolIds = /* @__PURE__ */ new Set()) => {
66472
+ let enclosingFunction = findEnclosingFunction$1(node);
66473
+ while (enclosingFunction) {
66474
+ if (isExclusivelyScheduledByNextAfter(enclosingFunction, scopes, visitedFunctionSymbolIds)) return true;
66475
+ enclosingFunction = findEnclosingFunction$1(enclosingFunction);
66476
+ }
66477
+ return false;
66478
+ };
64948
66479
  const serverAfterNonblocking = defineRule({
64949
66480
  id: "server-after-nonblocking",
64950
66481
  title: "Blocking side effect before response",
@@ -64979,6 +66510,7 @@ const serverAfterNonblocking = defineRule({
64979
66510
  if (!objectName) return;
64980
66511
  const methodName = node.callee.property.name;
64981
66512
  if (!isDeferrableSideEffectCall(objectName, methodName)) return;
66513
+ if (isInsideNextAfterCallback(node, context.scopes)) return;
64982
66514
  context.report({
64983
66515
  node,
64984
66516
  message: `${objectName}.${methodName}() runs before the response, so your users wait longer for it.`
@@ -66031,14 +67563,6 @@ const isStateKey = (key) => {
66031
67563
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
66032
67564
  return false;
66033
67565
  };
66034
- const findEnclosingClass = (node) => {
66035
- let ancestor = node.parent;
66036
- while (ancestor) {
66037
- if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66038
- ancestor = ancestor.parent ?? null;
66039
- }
66040
- return null;
66041
- };
66042
67566
  const isInConstructor = (node) => {
66043
67567
  let ancestor = node.parent;
66044
67568
  while (ancestor) {
@@ -66253,17 +67777,34 @@ const stylePropObject = defineRule({
66253
67777
  };
66254
67778
  }
66255
67779
  });
67780
+ //#endregion
67781
+ //#region src/plugin/rules/security-scan/utils/has-use-server-directive-in-content.ts
67782
+ const hasUseServerDirectiveInContent = (content, relativePath = "source.tsx") => {
67783
+ const programNode = parseSourceText({
67784
+ filename: relativePath,
67785
+ sourceText: content,
67786
+ shouldAttachParentReferences: false
67787
+ });
67788
+ return programNode === null ? false : hasDirective(programNode, "use server");
67789
+ };
67790
+ //#endregion
67791
+ //#region src/plugin/rules/security-scan/supabase-client-owned-authz-field.ts
67792
+ const scanSupabaseClientOwnedAuthzField = scanByPattern({
67793
+ shouldScan: (file) => isClientSourcePath(file.relativePath),
67794
+ pattern: /\b(?:ownerId|ownerID|creatorId|creatorID|userId|userID|uid|providerId|providerID|orgId|orgID|tenantId|tenantID|teamId|teamID|workspaceId|workspaceID|ghostOrg|role|roles|isAdmin|admin)\b/,
67795
+ requireAll: [/\b(?:supabase\b|\.from\s*\(\s*["'][^"']+["']\s*\))[\s\S]{0,700}\b(?:insert|upsert|update)\s*\(\s*(?:\{|\[?\s*\{)[\s\S]{0,700}\b(?:ownerId|creatorId|userId|orgId|tenantId|role|isAdmin)\b/i],
67796
+ message: "Client Supabase code appears to write user, tenant, owner, or role fields that should be enforced by RLS."
67797
+ });
66256
67798
  const supabaseClientOwnedAuthzField = defineRule({
66257
67799
  id: "supabase-client-owned-authz-field",
66258
67800
  title: "Client writes Supabase authorization field",
66259
67801
  severity: "error",
66260
67802
  recommendation: "Use RLS policies based on `auth.uid()` and server-owned membership rows; do not trust client-provided owner, org, or role columns.",
66261
- scan: scanByPattern({
66262
- shouldScan: (file) => isClientSourcePath(file.relativePath),
66263
- pattern: /\b(?:ownerId|ownerID|creatorId|creatorID|userId|userID|uid|providerId|providerID|orgId|orgID|tenantId|tenantID|teamId|teamID|workspaceId|workspaceID|ghostOrg|role|roles|isAdmin|admin)\b/,
66264
- requireAll: [/\b(?:supabase\b|\.from\s*\(\s*["'][^"']+["']\s*\))[\s\S]{0,700}\b(?:insert|upsert|update)\s*\(\s*(?:\{|\[?\s*\{)[\s\S]{0,700}\b(?:ownerId|creatorId|userId|orgId|tenantId|role|isAdmin)\b/i],
66265
- message: "Client Supabase code appears to write user, tenant, owner, or role fields that should be enforced by RLS."
66266
- })
67803
+ scan: (file) => {
67804
+ const findings = scanSupabaseClientOwnedAuthzField(file);
67805
+ if (findings.length === 0) return findings;
67806
+ return hasUseServerDirectiveInContent(file.content, file.relativePath) ? [] : findings;
67807
+ }
66267
67808
  });
66268
67809
  //#endregion
66269
67810
  //#region src/plugin/rules/security-scan/utils/is-supabase-migration-path.ts
@@ -70733,6 +72274,17 @@ const reactDoctorRules = [
70733
72274
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
70734
72275
  }
70735
72276
  },
72277
+ {
72278
+ key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72279
+ id: "no-ref-callback-cleanup-before-react-19",
72280
+ source: "react-doctor",
72281
+ originallyExternal: false,
72282
+ rule: {
72283
+ ...noRefCallbackCleanupBeforeReact19,
72284
+ framework: "global",
72285
+ category: "Bugs"
72286
+ }
72287
+ },
70736
72288
  {
70737
72289
  key: "react-doctor/no-ref-current-in-render",
70738
72290
  id: "no-ref-current-in-render",