oxlint-plugin-react-doctor 0.8.1-dev.7c9dbed → 0.8.1-dev.7eca0ee

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 +740 -4
  2. package/dist/index.js +2553 -302
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1953,13 +1953,13 @@ const getImportDeclaration = (node) => {
1953
1953
  }
1954
1954
  return null;
1955
1955
  };
1956
- const getImportSource = (declaration) => typeof declaration.source.value === "string" ? declaration.source.value : null;
1956
+ const getImportSource$1 = (declaration) => typeof declaration.source.value === "string" ? declaration.source.value : null;
1957
1957
  const isNamedImport = (symbol, importedName, moduleSources) => {
1958
1958
  if (symbol.kind !== "import") return false;
1959
1959
  const declaration = symbol.declarationNode;
1960
1960
  if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
1961
1961
  const importDeclaration = getImportDeclaration(declaration);
1962
- const source = importDeclaration ? getImportSource(importDeclaration) : null;
1962
+ const source = importDeclaration ? getImportSource$1(importDeclaration) : null;
1963
1963
  if (!source || !moduleSources.has(source)) return false;
1964
1964
  const imported = declaration.imported;
1965
1965
  return isNodeOfType(imported, "Identifier") && imported.name === importedName || isNodeOfType(imported, "Literal") && imported.value === importedName;
@@ -1968,7 +1968,7 @@ const isSatoriImport = (symbol) => {
1968
1968
  if (symbol.kind !== "import") return false;
1969
1969
  const declaration = symbol.declarationNode;
1970
1970
  const importDeclaration = getImportDeclaration(declaration);
1971
- if (!importDeclaration || getImportSource(importDeclaration) !== "satori") return false;
1971
+ if (!importDeclaration || getImportSource$1(importDeclaration) !== "satori") return false;
1972
1972
  if (isNodeOfType(declaration, "ImportDefaultSpecifier")) return true;
1973
1973
  if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
1974
1974
  const imported = declaration.imported;
@@ -1989,7 +1989,7 @@ const isGeneratedImageRendererCall = (node, scopes) => {
1989
1989
  const declaration = symbol.declarationNode;
1990
1990
  if (!isNodeOfType(declaration, "ImportNamespaceSpecifier")) return false;
1991
1991
  const importDeclaration = getImportDeclaration(declaration);
1992
- const source = importDeclaration ? getImportSource(importDeclaration) : null;
1992
+ const source = importDeclaration ? getImportSource$1(importDeclaration) : null;
1993
1993
  return Boolean(source && IMAGE_RESPONSE_MODULES.has(source));
1994
1994
  };
1995
1995
  //#endregion
@@ -3173,7 +3173,7 @@ const getJsxPropStaticStringValues = (attribute, scopes) => getJsxPropStaticStri
3173
3173
  const getJsxPropExhaustiveStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, null, true);
3174
3174
  //#endregion
3175
3175
  //#region src/plugin/rules/a11y/anchor-has-content.ts
3176
- const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
3176
+ const MESSAGE$66 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
3177
3177
  const hasLinkRole = (roleValue) => roleValue.trim().split(/\s+/).find((roleToken) => VALID_ARIA_ROLES.has(roleToken)) === "link";
3178
3178
  const isTransComponentsTemplate = (node) => {
3179
3179
  let current = node.parent;
@@ -3215,7 +3215,7 @@ const anchorHasContent = defineRule({
3215
3215
  if (isTransComponentsTemplate(node)) return;
3216
3216
  context.report({
3217
3217
  node: opening.name,
3218
- message: MESSAGE$62
3218
+ message: MESSAGE$66
3219
3219
  });
3220
3220
  } };
3221
3221
  }
@@ -3703,7 +3703,7 @@ const parseJsxValue = (value) => {
3703
3703
  };
3704
3704
  //#endregion
3705
3705
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
3706
- const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
3706
+ const MESSAGE$65 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
3707
3707
  const ariaActivedescendantHasTabindex = defineRule({
3708
3708
  id: "aria-activedescendant-has-tabindex",
3709
3709
  title: "aria-activedescendant missing tabindex",
@@ -3721,7 +3721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
3721
3721
  if (tabIndexValue === null || tabIndexValue >= -1) return;
3722
3722
  context.report({
3723
3723
  node: node.name,
3724
- message: MESSAGE$61
3724
+ message: MESSAGE$65
3725
3725
  });
3726
3726
  return;
3727
3727
  }
@@ -3729,7 +3729,7 @@ const ariaActivedescendantHasTabindex = defineRule({
3729
3729
  if (canContentEditableBeTabbable(node, context.scopes, context.settings)) return;
3730
3730
  context.report({
3731
3731
  node: node.name,
3732
- message: MESSAGE$61
3732
+ message: MESSAGE$65
3733
3733
  });
3734
3734
  } })
3735
3735
  });
@@ -6827,7 +6827,7 @@ const stripThisParameter = (parameters) => {
6827
6827
  };
6828
6828
  //#endregion
6829
6829
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6830
- 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.";
6830
+ const MESSAGE$64 = "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.";
6831
6831
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
6832
6832
  const STORAGE_GLOBALS = new Set([
6833
6833
  "window",
@@ -6999,7 +6999,7 @@ const authTokenInWebStorage = defineRule({
6999
6999
  })) return;
7000
7000
  context.report({
7001
7001
  node,
7002
- message: MESSAGE$60
7002
+ message: MESSAGE$64
7003
7003
  });
7004
7004
  },
7005
7005
  AssignmentExpression(node) {
@@ -7010,7 +7010,7 @@ const authTokenInWebStorage = defineRule({
7010
7010
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
7011
7011
  context.report({
7012
7012
  node: target,
7013
- message: MESSAGE$60
7013
+ message: MESSAGE$64
7014
7014
  });
7015
7015
  }
7016
7016
  }))
@@ -7149,7 +7149,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
7149
7149
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
7150
7150
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
7151
7151
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
7152
- const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
7152
+ const MESSAGE$63 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
7153
7153
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
7154
7154
  const scanWorkflowContent = (content) => {
7155
7155
  const lines = content.split("\n");
@@ -7194,7 +7194,7 @@ const scanWorkflowContent = (content) => {
7194
7194
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
7195
7195
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
7196
7196
  return [{
7197
- message: MESSAGE$59,
7197
+ message: MESSAGE$63,
7198
7198
  line: step.startLineIndex + installLineOffset + 1,
7199
7199
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
7200
7200
  }];
@@ -7204,7 +7204,7 @@ const scanWorkflowContent = (content) => {
7204
7204
  const scanNonWorkflowConfig = scanByPattern({
7205
7205
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
7206
7206
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
7207
- message: MESSAGE$59
7207
+ message: MESSAGE$63
7208
7208
  });
7209
7209
  const scan = (file) => {
7210
7210
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -7954,7 +7954,7 @@ const isPureEventBlockerHandler = (attribute) => {
7954
7954
  };
7955
7955
  //#endregion
7956
7956
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
7957
- const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
7957
+ const MESSAGE$62 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
7958
7958
  const KEY_HANDLERS = [
7959
7959
  "onKeyUp",
7960
7960
  "onKeyDown",
@@ -8165,7 +8165,7 @@ const clickEventsHaveKeyEvents = defineRule({
8165
8165
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
8166
8166
  context.report({
8167
8167
  node: node.name,
8168
- message: MESSAGE$58
8168
+ message: MESSAGE$62
8169
8169
  });
8170
8170
  } };
8171
8171
  }
@@ -10243,21 +10243,19 @@ const isReactComponentName = (name) => {
10243
10243
  return firstCharacter >= 65 && firstCharacter <= 90;
10244
10244
  };
10245
10245
  //#endregion
10246
- //#region src/plugin/rules/react-ui/utils/get-class-name-literal.ts
10247
- const getClassNameLiteral = (classAttribute) => {
10248
- if (!isNodeOfType(classAttribute, "JSXAttribute")) return null;
10249
- if (!classAttribute.value) return null;
10250
- if (isNodeOfType(classAttribute.value, "Literal") && typeof classAttribute.value.value === "string") return classAttribute.value.value;
10251
- if (isNodeOfType(classAttribute.value, "JSXExpressionContainer")) {
10252
- const expression = classAttribute.value.expression;
10253
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
10254
- if (isNodeOfType(expression, "TemplateLiteral") && expression.quasis?.length === 1) return expression.quasis[0].value?.raw ?? null;
10255
- }
10246
+ //#region src/plugin/utils/get-jsx-attribute-static-string.ts
10247
+ const getJsxAttributeStaticString = (attribute) => {
10248
+ if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return null;
10249
+ if (isNodeOfType(attribute.value, "Literal") && typeof attribute.value.value === "string") return attribute.value.value;
10250
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
10251
+ const expression = attribute.value.expression;
10252
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
10253
+ if (isNodeOfType(expression, "TemplateLiteral") && expression.expressions.length === 0 && expression.quasis.length === 1) return expression.quasis[0].value.raw;
10256
10254
  return null;
10257
10255
  };
10258
10256
  //#endregion
10259
10257
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
10260
- const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
10258
+ const MESSAGE$61 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
10261
10259
  const NON_OPERABLE_ELEMENTS = new Set([
10262
10260
  "td",
10263
10261
  "th",
@@ -10315,7 +10313,7 @@ const collectStaticTemplateClassTokens = (templateLiteral) => {
10315
10313
  const hasDisplayNoneClass = (opening) => {
10316
10314
  const classAttribute = hasJsxPropIgnoreCase(opening.attributes, "className") ?? hasJsxPropIgnoreCase(opening.attributes, "class");
10317
10315
  if (!classAttribute) return false;
10318
- const literalValue = getClassNameLiteral(classAttribute);
10316
+ const literalValue = getJsxAttributeStaticString(classAttribute);
10319
10317
  if (literalValue !== null) return literalValue.split(/\s+/).some(isDisplayNoneClassToken);
10320
10318
  if (classAttribute.value && isNodeOfType(classAttribute.value, "JSXExpressionContainer") && isNodeOfType(classAttribute.value.expression, "TemplateLiteral")) return collectStaticTemplateClassTokens(classAttribute.value.expression).some(isDisplayNoneClassToken);
10321
10319
  return false;
@@ -10787,7 +10785,7 @@ const controlHasAssociatedLabel = defineRule({
10787
10785
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
10788
10786
  context.report({
10789
10787
  node: candidate.opening,
10790
- message: MESSAGE$57
10788
+ message: MESSAGE$61
10791
10789
  });
10792
10790
  }
10793
10791
  }
@@ -12431,7 +12429,7 @@ const noRedundantPaddingAxes = defineRule({
12431
12429
  recommendation: "Collapse matching padding axes to `p-N` so duplicated classes do not make spacing harder to scan; keep split axes only when breakpoints differ.",
12432
12430
  create: (context) => ({ JSXAttribute(jsxAttribute) {
12433
12431
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
12434
- const classNameLiteral = getClassNameLiteral(jsxAttribute);
12432
+ const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
12435
12433
  if (!classNameLiteral) return;
12436
12434
  if (!classNameLiteral.includes("px-") || !classNameLiteral.includes("py-")) return;
12437
12435
  if (hasResponsivePrefix(classNameLiteral, "px") || hasResponsivePrefix(classNameLiteral, "py")) return;
@@ -12459,7 +12457,7 @@ const noRedundantSizeAxes = defineRule({
12459
12457
  return { JSXAttribute(jsxAttribute) {
12460
12458
  if (didReportInFile) return;
12461
12459
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
12462
- const classNameLiteral = getClassNameLiteral(jsxAttribute);
12460
+ const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
12463
12461
  if (!classNameLiteral) return;
12464
12462
  if (!classNameLiteral.includes("w-") || !classNameLiteral.includes("h-")) return;
12465
12463
  if (hasResponsivePrefix(classNameLiteral, "w") || hasResponsivePrefix(classNameLiteral, "h")) return;
@@ -12487,7 +12485,7 @@ const noSpaceOnFlexChildren = defineRule({
12487
12485
  recommendation: "Use `gap-*` on the flex or grid parent. `space-x-*` and `space-y-*` leave gaps when a child is hidden, miss spacing on wrapped lines, and don't flip in right-to-left layouts.",
12488
12486
  create: (context) => ({ JSXAttribute(jsxAttribute) {
12489
12487
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
12490
- const classNameLiteral = getClassNameLiteral(jsxAttribute);
12488
+ const classNameLiteral = getJsxAttributeStaticString(jsxAttribute);
12491
12489
  if (!classNameLiteral) return;
12492
12490
  if (!classNameLiteral.includes("space-")) return;
12493
12491
  const tokens = tokenizeClassName(classNameLiteral);
@@ -12594,7 +12592,7 @@ const noVagueButtonLabel = defineRule({
12594
12592
  });
12595
12593
  //#endregion
12596
12594
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
12597
- const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
12595
+ const MESSAGE$60 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
12598
12596
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
12599
12597
  const NAME_PROVIDING_ATTRIBUTES = [
12600
12598
  "aria-label",
@@ -12619,7 +12617,7 @@ const dialogHasAccessibleName = defineRule({
12619
12617
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
12620
12618
  context.report({
12621
12619
  node: node.name,
12622
- message: MESSAGE$56
12620
+ message: MESSAGE$60
12623
12621
  });
12624
12622
  } };
12625
12623
  }
@@ -12659,7 +12657,7 @@ const isEs6Component = (node) => {
12659
12657
  };
12660
12658
  //#endregion
12661
12659
  //#region src/plugin/rules/react-builtins/display-name.ts
12662
- const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
12660
+ const MESSAGE$59 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
12663
12661
  const DEFAULT_ADDITIONAL_HOCS = [
12664
12662
  "observer",
12665
12663
  "lazy",
@@ -12852,7 +12850,7 @@ const displayName = defineRule({
12852
12850
  const reportAt = (node) => {
12853
12851
  context.report({
12854
12852
  node,
12855
- message: MESSAGE$55
12853
+ message: MESSAGE$59
12856
12854
  });
12857
12855
  };
12858
12856
  return {
@@ -13803,17 +13801,18 @@ const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
13803
13801
  const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
13804
13802
  return Boolean(declarator && scopes.symbolFor(unwrappedNode)?.declarationNode === declarator && declarator.init && declarator.parent && isNodeOfType(declarator.parent, "VariableDeclaration") && declarator.parent.kind === "const" && isGlobalArrayFromMember(declarator.init, scopes));
13805
13803
  };
13806
- const executesDuringRender = (functionNode, scopes) => {
13807
- const parent = functionNode.parent;
13804
+ const executesDuringRender = (callbackExpression, scopes) => {
13805
+ const callbackRoot = findTransparentExpressionRoot(callbackExpression);
13806
+ const parent = callbackRoot.parent;
13808
13807
  if (isNodeOfType(parent, "NewExpression")) {
13809
13808
  const callee = stripParenExpression(parent.callee);
13810
- return Boolean(scopes && parent.arguments?.[0] === functionNode && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
13809
+ return Boolean(scopes && parent.arguments?.[0] === callbackRoot && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
13811
13810
  }
13812
13811
  if (!isNodeOfType(parent, "CallExpression")) return false;
13813
- if (parent.callee === functionNode) return true;
13814
- if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
13815
- if (scopes && parent.arguments?.[1] === functionNode && (isGlobalArrayFromMember(parent.callee, scopes) || isConstAliasOfGlobalArrayFrom(parent.callee, scopes))) return true;
13816
- return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
13812
+ if (parent.callee === callbackRoot) return true;
13813
+ if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === callbackRoot) return true;
13814
+ if (scopes && parent.arguments?.[1] === callbackRoot && (isGlobalArrayFromMember(parent.callee, scopes) || isConstAliasOfGlobalArrayFrom(parent.callee, scopes))) return true;
13815
+ return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === callbackRoot;
13817
13816
  };
13818
13817
  //#endregion
13819
13818
  //#region src/plugin/utils/find-render-phase-component-or-hook.ts
@@ -13940,9 +13939,10 @@ const doNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context, o
13940
13939
  //#region src/plugin/utils/get-function-binding-name.ts
13941
13940
  const getFunctionBindingIdentifier$1 = (functionNode) => {
13942
13941
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
13943
- const parent = functionNode.parent;
13942
+ const functionRoot = findTransparentExpressionRoot(functionNode);
13943
+ const parent = functionRoot.parent;
13944
13944
  if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
13945
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionNode && isNodeOfType(parent.left, "Identifier")) return parent.left;
13945
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
13946
13946
  if (isNodeOfType(parent, "CallExpression")) {
13947
13947
  const callParent = parent.parent;
13948
13948
  if (isNodeOfType(callParent, "VariableDeclarator") && isNodeOfType(callParent.id, "Identifier")) return callParent.id;
@@ -19264,7 +19264,7 @@ const forbidElements = defineRule({
19264
19264
  });
19265
19265
  //#endregion
19266
19266
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
19267
- const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
19267
+ const MESSAGE$58 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
19268
19268
  const forwardRefUsesRef = defineRule({
19269
19269
  id: "forward-ref-uses-ref",
19270
19270
  title: "forwardRef without ref parameter",
@@ -19287,7 +19287,7 @@ const forwardRefUsesRef = defineRule({
19287
19287
  if (isNodeOfType(onlyParam, "RestElement")) return;
19288
19288
  context.report({
19289
19289
  node: inner,
19290
- message: MESSAGE$54
19290
+ message: MESSAGE$58
19291
19291
  });
19292
19292
  } })
19293
19293
  });
@@ -19328,7 +19328,7 @@ const gitProviderUrlInjectionRisk = defineRule({
19328
19328
  });
19329
19329
  //#endregion
19330
19330
  //#region src/plugin/rules/a11y/heading-has-content.ts
19331
- const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
19331
+ const MESSAGE$57 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
19332
19332
  const DEFAULT_HEADING_TAGS = [
19333
19333
  "h1",
19334
19334
  "h2",
@@ -19362,7 +19362,7 @@ const headingHasContent = defineRule({
19362
19362
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
19363
19363
  context.report({
19364
19364
  node,
19365
- message: MESSAGE$53
19365
+ message: MESSAGE$57
19366
19366
  });
19367
19367
  } };
19368
19368
  }
@@ -19530,7 +19530,7 @@ const hooksNoNanInDeps = defineRule({
19530
19530
  });
19531
19531
  //#endregion
19532
19532
  //#region src/plugin/rules/a11y/html-has-lang.ts
19533
- const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19533
+ const MESSAGE$56 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19534
19534
  const resolveSettings$38 = (settings) => {
19535
19535
  const reactDoctor = settings?.["react-doctor"];
19536
19536
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -19577,13 +19577,13 @@ const htmlHasLang = defineRule({
19577
19577
  if (!lang) {
19578
19578
  context.report({
19579
19579
  node: node.name,
19580
- message: MESSAGE$52
19580
+ message: MESSAGE$56
19581
19581
  });
19582
19582
  return;
19583
19583
  }
19584
19584
  if (evaluateLang(lang.value) === "empty") context.report({
19585
19585
  node: lang,
19586
- message: MESSAGE$52
19586
+ message: MESSAGE$56
19587
19587
  });
19588
19588
  } };
19589
19589
  }
@@ -19856,7 +19856,7 @@ const isJsxFragmentElement = (node, scopes) => {
19856
19856
  };
19857
19857
  //#endregion
19858
19858
  //#region src/plugin/rules/a11y/iframe-has-title.ts
19859
- const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
19859
+ const MESSAGE$55 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
19860
19860
  const isStaticallyAriaHidden = (openingElement) => {
19861
19861
  const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
19862
19862
  if (!ariaHiddenAttribute) return false;
@@ -19945,14 +19945,14 @@ const iframeHasTitle = defineRule({
19945
19945
  if (!titleAttr) {
19946
19946
  if (hasSpread || tag === "iframe") context.report({
19947
19947
  node: node.name,
19948
- message: MESSAGE$51
19948
+ message: MESSAGE$55
19949
19949
  });
19950
19950
  return;
19951
19951
  }
19952
19952
  const verdict = evaluateTitleValue(titleAttr.value);
19953
19953
  if (verdict === "missing" || verdict === "empty") context.report({
19954
19954
  node: titleAttr,
19955
- message: MESSAGE$51
19955
+ message: MESSAGE$55
19956
19956
  });
19957
19957
  } })
19958
19958
  });
@@ -20077,7 +20077,7 @@ const iframeMissingSandbox = defineRule({
20077
20077
  });
20078
20078
  //#endregion
20079
20079
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
20080
- const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
20080
+ const MESSAGE$54 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
20081
20081
  const DEFAULT_COMPONENTS = ["img"];
20082
20082
  const DEFAULT_REDUNDANT_WORDS = [
20083
20083
  "image",
@@ -20142,7 +20142,7 @@ const imgRedundantAlt = defineRule({
20142
20142
  if (!altAttribute) return;
20143
20143
  if (altValueRedundant(altAttribute, settings.words)) context.report({
20144
20144
  node: altAttribute,
20145
- message: MESSAGE$50
20145
+ message: MESSAGE$54
20146
20146
  });
20147
20147
  } };
20148
20148
  }
@@ -20674,7 +20674,7 @@ const collectHandlerReferencedNames = (root) => {
20674
20674
  //#endregion
20675
20675
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
20676
20676
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
20677
- const COMPONENT_NAME_PATTERN = /^[A-Z]/;
20677
+ const COMPONENT_NAME_PATTERN$1 = /^[A-Z]/;
20678
20678
  const isImportedSelectAtom = (callExpression) => {
20679
20679
  if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
20680
20680
  const localName = callExpression.callee.name;
@@ -20710,14 +20710,14 @@ const getHandlerNamedBindingName = (functionNode) => {
20710
20710
  const containingFunctionIsComponentOrHook = (functionNode) => {
20711
20711
  if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
20712
20712
  const declaredName = functionNode.id.name;
20713
- return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20713
+ return COMPONENT_NAME_PATTERN$1.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20714
20714
  }
20715
20715
  let cursor = functionNode.parent ?? null;
20716
20716
  while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
20717
20717
  if (!cursor) return false;
20718
20718
  if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
20719
20719
  if (!isNodeOfType(cursor.id, "Identifier")) return false;
20720
- return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
20720
+ return COMPONENT_NAME_PATTERN$1.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
20721
20721
  };
20722
20722
  const isBindingInvokedOnRenderPath = (root, bindingName) => {
20723
20723
  let didFindRenderPathInvocation = false;
@@ -21795,7 +21795,7 @@ const isDiscardedProbeInsideTry = (node) => {
21795
21795
  return false;
21796
21796
  };
21797
21797
  const isComponentOrHookName = (name) => /^[A-Z]/.test(name) || /^use[A-Z0-9]/.test(name);
21798
- const getFunctionName = (functionNode) => {
21798
+ const getFunctionName$1 = (functionNode) => {
21799
21799
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
21800
21800
  const holder = functionNode.parent;
21801
21801
  if (holder && isNodeOfType(holder, "VariableDeclarator") && isNodeOfType(holder.id, "Identifier")) return holder.id.name;
@@ -21804,7 +21804,7 @@ const getFunctionName = (functionNode) => {
21804
21804
  const isUncacheableOptionsMergeUtility = (node) => {
21805
21805
  const enclosingFunction = findEnclosingFunction$1(node);
21806
21806
  if (!enclosingFunction || !isFunctionLike$1(enclosingFunction)) return false;
21807
- const functionName = getFunctionName(enclosingFunction);
21807
+ const functionName = getFunctionName$1(enclosingFunction);
21808
21808
  if (!functionName || isComponentOrHookName(functionName)) return false;
21809
21809
  const parameterNames = /* @__PURE__ */ new Set();
21810
21810
  for (const parameter of enclosingFunction.params ?? []) collectPatternNames(parameter, parameterNames);
@@ -22106,7 +22106,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
22106
22106
  if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
22107
22107
  return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
22108
22108
  };
22109
- const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
22109
+ const MESSAGE$53 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
22110
22110
  const jsHoistRegexp = defineRule({
22111
22111
  id: "js-hoist-regexp",
22112
22112
  title: "RegExp built inside a loop",
@@ -22123,7 +22123,7 @@ const jsHoistRegexp = defineRule({
22123
22123
  if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
22124
22124
  context.report({
22125
22125
  node,
22126
- message: MESSAGE$49
22126
+ message: MESSAGE$53
22127
22127
  });
22128
22128
  };
22129
22129
  return createLoopAwareVisitors({
@@ -24890,7 +24890,7 @@ const jsxMaxDepth = defineRule({
24890
24890
  });
24891
24891
  //#endregion
24892
24892
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
24893
- const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
24893
+ const MESSAGE$52 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
24894
24894
  const LITERAL_TEXT_TAGS = new Set([
24895
24895
  "code",
24896
24896
  "pre",
@@ -24960,7 +24960,7 @@ const jsxNoCommentTextnodes = defineRule({
24960
24960
  if (isDeliberateStyledCommentToken(node)) return;
24961
24961
  context.report({
24962
24962
  node,
24963
- message: MESSAGE$48
24963
+ message: MESSAGE$52
24964
24964
  });
24965
24965
  } })
24966
24966
  });
@@ -24991,7 +24991,7 @@ const isInsideFunctionScope = (node) => {
24991
24991
  };
24992
24992
  //#endregion
24993
24993
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
24994
- const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
24994
+ const MESSAGE$51 = "Every reader of this context redraws on each render because you build its `value` inline.";
24995
24995
  const CONTEXT_MODULES$1 = [
24996
24996
  "react",
24997
24997
  "use-context-selector",
@@ -25094,7 +25094,7 @@ const jsxNoConstructedContextValues = defineRule({
25094
25094
  if (!isConstructedValue(innerExpression)) continue;
25095
25095
  context.report({
25096
25096
  node: attribute,
25097
- message: MESSAGE$47
25097
+ message: MESSAGE$51
25098
25098
  });
25099
25099
  }
25100
25100
  }
@@ -25960,7 +25960,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
25960
25960
  ];
25961
25961
  //#endregion
25962
25962
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
25963
- const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
25963
+ const MESSAGE$50 = "This child redraws every render because the prop gets a brand new array each time.";
25964
25964
  const isDataArrayPropName = (propName) => {
25965
25965
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
25966
25966
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -26047,7 +26047,7 @@ const jsxNoNewArrayAsProp = defineRule({
26047
26047
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
26048
26048
  context.report({
26049
26049
  node,
26050
- message: MESSAGE$46
26050
+ message: MESSAGE$50
26051
26051
  });
26052
26052
  }
26053
26053
  };
@@ -26305,7 +26305,7 @@ const SAFE_RECEIVER_NAMES = new Set([
26305
26305
  ]);
26306
26306
  //#endregion
26307
26307
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
26308
- const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
26308
+ const MESSAGE$49 = "This child redraws every render because the prop gets a brand new function each time.";
26309
26309
  const isAccessorPredicateName = (propName) => {
26310
26310
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
26311
26311
  if (propName.length <= prefix.length) continue;
@@ -26512,7 +26512,7 @@ const jsxNoNewFunctionAsProp = defineRule({
26512
26512
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
26513
26513
  context.report({
26514
26514
  node,
26515
- message: MESSAGE$45
26515
+ message: MESSAGE$49
26516
26516
  });
26517
26517
  }
26518
26518
  };
@@ -26732,7 +26732,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
26732
26732
  ];
26733
26733
  //#endregion
26734
26734
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
26735
- const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
26735
+ const MESSAGE$48 = "This child redraws every render because the prop gets a brand new object each time.";
26736
26736
  const isConfigObjectPropName = (propName) => {
26737
26737
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
26738
26738
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -26821,7 +26821,7 @@ const jsxNoNewObjectAsProp = defineRule({
26821
26821
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
26822
26822
  context.report({
26823
26823
  node,
26824
- message: MESSAGE$44
26824
+ message: MESSAGE$48
26825
26825
  });
26826
26826
  }
26827
26827
  };
@@ -26829,7 +26829,7 @@ const jsxNoNewObjectAsProp = defineRule({
26829
26829
  });
26830
26830
  //#endregion
26831
26831
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26832
- const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26832
+ const MESSAGE$47 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26833
26833
  const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
26834
26834
  const resolveSettings$28 = (settings) => {
26835
26835
  const reactDoctor = settings?.["react-doctor"];
@@ -26867,7 +26867,7 @@ const jsxNoScriptUrl = defineRule({
26867
26867
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
26868
26868
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
26869
26869
  node: attribute,
26870
- message: MESSAGE$43
26870
+ message: MESSAGE$47
26871
26871
  });
26872
26872
  }
26873
26873
  } };
@@ -27210,7 +27210,7 @@ const jsxPropsNoSpreadMulti = defineRule({
27210
27210
  });
27211
27211
  //#endregion
27212
27212
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
27213
- const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
27213
+ const MESSAGE$46 = "You can't tell what props reach this element when you spread them.";
27214
27214
  const resolveSettings$25 = (settings) => {
27215
27215
  const reactDoctor = settings?.["react-doctor"];
27216
27216
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -27253,7 +27253,7 @@ const jsxPropsNoSpreading = defineRule({
27253
27253
  }
27254
27254
  context.report({
27255
27255
  node: attribute,
27256
- message: MESSAGE$42
27256
+ message: MESSAGE$46
27257
27257
  });
27258
27258
  didReportInFile = true;
27259
27259
  return;
@@ -27529,7 +27529,7 @@ const labelHasAssociatedControl = defineRule({
27529
27529
  });
27530
27530
  //#endregion
27531
27531
  //#region src/plugin/rules/a11y/lang.ts
27532
- const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
27532
+ const MESSAGE$45 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
27533
27533
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
27534
27534
  "aa",
27535
27535
  "ab",
@@ -28009,7 +28009,7 @@ const lang = defineRule({
28009
28009
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
28010
28010
  context.report({
28011
28011
  node: langAttr,
28012
- message: MESSAGE$41
28012
+ message: MESSAGE$45
28013
28013
  });
28014
28014
  return;
28015
28015
  }
@@ -28018,7 +28018,7 @@ const lang = defineRule({
28018
28018
  if (value === null) return;
28019
28019
  if (!isValidLangTag(value)) context.report({
28020
28020
  node: langAttr,
28021
- message: MESSAGE$41
28021
+ message: MESSAGE$45
28022
28022
  });
28023
28023
  } })
28024
28024
  });
@@ -28063,13 +28063,13 @@ const mdxSsrExecutionRisk = defineRule({
28063
28063
  recommendation: "Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.",
28064
28064
  scan: scanByPattern({
28065
28065
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
28066
- pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
28066
+ pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo\b|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
28067
28067
  message: "MDX/markdown rendering code may evaluate user or repository content during SSR or static generation."
28068
28068
  })
28069
28069
  });
28070
28070
  //#endregion
28071
28071
  //#region src/plugin/rules/a11y/media-has-caption.ts
28072
- const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
28072
+ const MESSAGE$44 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
28073
28073
  const DEFAULT_AUDIO = ["audio"];
28074
28074
  const DEFAULT_VIDEO = ["video"];
28075
28075
  const DEFAULT_TRACK = ["track"];
@@ -28159,7 +28159,7 @@ const mediaHasCaption = defineRule({
28159
28159
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
28160
28160
  context.report({
28161
28161
  node: node.name,
28162
- message: MESSAGE$40
28162
+ message: MESSAGE$44
28163
28163
  });
28164
28164
  return;
28165
28165
  }
@@ -28178,12 +28178,553 @@ const mediaHasCaption = defineRule({
28178
28178
  return kindValue.value.toLowerCase() === "captions";
28179
28179
  })) context.report({
28180
28180
  node: node.name,
28181
- message: MESSAGE$40
28181
+ message: MESSAGE$44
28182
28182
  });
28183
28183
  } };
28184
28184
  }
28185
28185
  });
28186
28186
  //#endregion
28187
+ //#region src/plugin/utils/get-class-binding-symbol.ts
28188
+ const getClassBindingSymbol = (classNode, scopes) => {
28189
+ if (isNodeOfType(classNode.id, "Identifier")) return scopes.symbolFor(classNode.id);
28190
+ const parent = classNode.parent;
28191
+ return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? scopes.symbolFor(parent.id) : null;
28192
+ };
28193
+ //#endregion
28194
+ //#region src/plugin/utils/mobx-rule-gates.ts
28195
+ const MOBX_RULE_GATES = {
28196
+ "mobx-reaction-disposer-discarded": { requires: ["mobx:4"] },
28197
+ "mobx-no-make-auto-observable-in-inheritance": { requires: ["mobx:6"] },
28198
+ "mobx-no-computed-side-effects": { requires: ["mobx:4"] },
28199
+ "mobx-async-action-requires-action": { requires: ["mobx:4"] },
28200
+ "mobx-no-observer-wrapped-memo": { requires: [
28201
+ "mobx:4",
28202
+ "mobx-react-binding",
28203
+ "react"
28204
+ ] },
28205
+ "mobx-make-observable-unconditional": { requires: ["mobx:6"] },
28206
+ "mobx-legacy-decorator-needs-make-observable": { requires: ["mobx:6"] },
28207
+ "mobx-initialize-before-make-auto-observable": { requires: ["mobx:6"] },
28208
+ "mobx-observable-read-needs-observer": {
28209
+ requires: [
28210
+ "mobx:4",
28211
+ "mobx-react-binding",
28212
+ "react"
28213
+ ],
28214
+ disabledWhen: ["mobx-react-observer"]
28215
+ },
28216
+ "mobx-observer-before-inject": { requires: [
28217
+ "mobx:4",
28218
+ "mobx-react",
28219
+ "react"
28220
+ ] },
28221
+ "mobx-reaction-requires-observable": { requires: ["mobx:4"] },
28222
+ "mobx-no-invalid-observable-override": { requires: ["mobx:6"] },
28223
+ "mobx-no-observable-prop-to-untracked-child": { requires: [
28224
+ "mobx:4",
28225
+ "mobx-react-binding",
28226
+ "react"
28227
+ ] },
28228
+ "mobx-no-stale-observable-snapshot-after-await": { requires: ["mobx:4"] },
28229
+ "mobx-no-reaction-comparison-value-mutation": { requires: ["mobx:4"] },
28230
+ "mobx-observer-class-no-should-component-update": { requires: [
28231
+ "mobx:4",
28232
+ "mobx-react",
28233
+ "react"
28234
+ ] },
28235
+ "mobx-enable-static-rendering-for-ssr": { requires: [
28236
+ "mobx:4",
28237
+ "mobx-react-binding",
28238
+ "react",
28239
+ "ssr"
28240
+ ] },
28241
+ "mobx-no-rest-destructure-observable": { requires: [
28242
+ "mobx:4",
28243
+ "mobx-react-binding",
28244
+ "react"
28245
+ ] },
28246
+ "mobx-computed-depends-on-non-observable": { requires: ["mobx:4"] },
28247
+ "mobx-no-keepalive-computed-without-disposal": { requires: ["mobx:4"] }
28248
+ };
28249
+ //#endregion
28250
+ //#region src/plugin/utils/resolve-imported-api-reference.ts
28251
+ const resolveImportSymbol = (symbol) => {
28252
+ const importDeclaration = getImportDeclarationForSymbol(symbol);
28253
+ if (!importDeclaration || typeof importDeclaration.source.value !== "string") return null;
28254
+ if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return {
28255
+ source: importDeclaration.source.value,
28256
+ importedName: null,
28257
+ isNamespace: true
28258
+ };
28259
+ if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
28260
+ source: importDeclaration.source.value,
28261
+ importedName: "default",
28262
+ isNamespace: false
28263
+ };
28264
+ const importedName = getImportedName(symbol.declarationNode);
28265
+ return importedName ? {
28266
+ source: importDeclaration.source.value,
28267
+ importedName,
28268
+ isNamespace: false
28269
+ } : null;
28270
+ };
28271
+ const resolveIdentifierReference = (identifier, scopes, visitedSymbolIds) => {
28272
+ const symbol = scopes.symbolFor(identifier);
28273
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
28274
+ if (symbol.kind === "import") return resolveImportSymbol(symbol);
28275
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
28276
+ visitedSymbolIds.add(symbol.id);
28277
+ if (isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) {
28278
+ const receiver = resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
28279
+ if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
28280
+ for (const property of symbol.declarationNode.id.properties) {
28281
+ if (!isNodeOfType(property, "Property")) continue;
28282
+ if ((isNodeOfType(property.value, "AssignmentPattern") ? property.value.left : property.value) !== symbol.bindingIdentifier) continue;
28283
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
28284
+ return propertyName ? {
28285
+ source: receiver.source,
28286
+ importedName: propertyName,
28287
+ isNamespace: false
28288
+ } : null;
28289
+ }
28290
+ return null;
28291
+ }
28292
+ return resolveImportedApiReference(symbol.initializer, scopes, visitedSymbolIds);
28293
+ };
28294
+ const resolveImportedApiReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
28295
+ const unwrappedExpression = stripParenExpression(expression);
28296
+ if (isNodeOfType(unwrappedExpression, "Identifier")) return resolveIdentifierReference(unwrappedExpression, scopes, visitedSymbolIds);
28297
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
28298
+ const propertyName = getStaticPropertyName(unwrappedExpression);
28299
+ if (!propertyName) return null;
28300
+ const receiver = resolveImportedApiReference(unwrappedExpression.object, scopes, visitedSymbolIds);
28301
+ if (!receiver || !receiver.isNamespace && receiver.importedName !== "default") return null;
28302
+ return {
28303
+ source: receiver.source,
28304
+ importedName: propertyName,
28305
+ isNamespace: false
28306
+ };
28307
+ };
28308
+ //#endregion
28309
+ //#region src/plugin/rules/mobx/mobx-no-make-auto-observable-in-inheritance.ts
28310
+ const MESSAGE$43 = "MobX does not support `makeAutoObservable(this)` in inherited classes. Use composition or explicit `makeObservable` annotations.";
28311
+ const getEnclosingConstructorClass = (node) => {
28312
+ let ancestor = node.parent;
28313
+ while (ancestor) {
28314
+ if (!isFunctionLike$1(ancestor)) {
28315
+ ancestor = ancestor.parent ?? null;
28316
+ continue;
28317
+ }
28318
+ const methodDefinition = ancestor.parent;
28319
+ if (!isNodeOfType(methodDefinition, "MethodDefinition") || methodDefinition.kind !== "constructor") return null;
28320
+ const classNode = methodDefinition.parent?.parent;
28321
+ return isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression") ? classNode : null;
28322
+ }
28323
+ return null;
28324
+ };
28325
+ const isNonNullSuperclass = (classNode) => {
28326
+ if (!classNode.superClass) return false;
28327
+ const superClass = stripParenExpression(classNode.superClass);
28328
+ return !(isNodeOfType(superClass, "Literal") && superClass.value === null);
28329
+ };
28330
+ const subclassedClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
28331
+ const getSubclassedClassSymbolIds = (node, scopes) => {
28332
+ const cached = subclassedClassSymbolIdsByAnalysis.get(scopes);
28333
+ if (cached) return cached;
28334
+ const symbolIds = /* @__PURE__ */ new Set();
28335
+ const program = findProgramRoot(node);
28336
+ if (program) walkAst(program, (candidate) => {
28337
+ if (!isNodeOfType(candidate, "ClassDeclaration") && !isNodeOfType(candidate, "ClassExpression") || !isNonNullSuperclass(candidate)) return;
28338
+ const superClassExpression = candidate.superClass;
28339
+ if (!superClassExpression) return;
28340
+ const superClass = stripParenExpression(superClassExpression);
28341
+ if (!isNodeOfType(superClass, "Identifier")) return;
28342
+ const symbol = resolveConstIdentifierAlias(superClass, scopes);
28343
+ if (symbol?.kind === "class" || symbol?.kind === "const" && isNodeOfType(symbol.initializer, "ClassExpression")) symbolIds.add(symbol.id);
28344
+ });
28345
+ subclassedClassSymbolIdsByAnalysis.set(scopes, symbolIds);
28346
+ return symbolIds;
28347
+ };
28348
+ const isMakeAutoObservableCall = (callExpression, scopes) => {
28349
+ const reference = resolveImportedApiReference(callExpression.callee, scopes);
28350
+ return reference?.source === "mobx" && reference.importedName === "makeAutoObservable";
28351
+ };
28352
+ const mobxNoMakeAutoObservableInInheritance = defineRule({
28353
+ id: "mobx-no-make-auto-observable-in-inheritance",
28354
+ title: "Unsupported MobX auto-observable inheritance",
28355
+ severity: "error",
28356
+ category: "Bugs",
28357
+ requires: MOBX_RULE_GATES["mobx-no-make-auto-observable-in-inheritance"].requires,
28358
+ recommendation: "Replace inheritance with composition, or annotate inherited members explicitly with `makeObservable`.",
28359
+ create: (context) => ({ CallExpression(callExpression) {
28360
+ if (!isMakeAutoObservableCall(callExpression, context.scopes)) return;
28361
+ const target = callExpression.arguments[0];
28362
+ if (!target || !isNodeOfType(stripParenExpression(target), "ThisExpression")) return;
28363
+ const classNode = getEnclosingConstructorClass(callExpression);
28364
+ if (!classNode) return;
28365
+ const classSymbol = getClassBindingSymbol(classNode, context.scopes);
28366
+ const isSubclassed = Boolean(classSymbol && getSubclassedClassSymbolIds(callExpression, context.scopes).has(classSymbol.id));
28367
+ if (!isNonNullSuperclass(classNode) && !isSubclassed) return;
28368
+ context.report({
28369
+ node: callExpression,
28370
+ message: MESSAGE$43
28371
+ });
28372
+ } })
28373
+ });
28374
+ //#endregion
28375
+ //#region src/plugin/rules/mobx/mobx-no-observer-wrapped-memo.ts
28376
+ const OBSERVER_MODULES = new Set(["mobx-react", "mobx-react-lite"]);
28377
+ const MESSAGE$42 = "`observer` cannot wrap an already memoized or observed component. Apply `observer` first, then place `memo` outside only if needed.";
28378
+ const isObserverCall = (callExpression, scopes) => {
28379
+ const reference = resolveImportedApiReference(callExpression.callee, scopes);
28380
+ return Boolean(reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source));
28381
+ };
28382
+ const hasInvalidInnerWrapper = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
28383
+ const unwrappedExpression = stripParenExpression(expression);
28384
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
28385
+ const symbol = scopes.symbolFor(unwrappedExpression);
28386
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
28387
+ visitedSymbolIds.add(symbol.id);
28388
+ return hasInvalidInnerWrapper(symbol.initializer, scopes, visitedSymbolIds);
28389
+ }
28390
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
28391
+ const reference = resolveImportedApiReference(unwrappedExpression.callee, scopes);
28392
+ if (reference?.importedName === "observer" && OBSERVER_MODULES.has(reference.source)) return true;
28393
+ return reference?.source === "react" && reference.importedName === "memo";
28394
+ };
28395
+ const mobxNoObserverWrappedMemo = defineRule({
28396
+ id: "mobx-no-observer-wrapped-memo",
28397
+ title: "Invalid MobX observer wrapper order",
28398
+ severity: "error",
28399
+ category: "Bugs",
28400
+ requires: MOBX_RULE_GATES["mobx-no-observer-wrapped-memo"].requires,
28401
+ recommendation: "Pass the component directly to `observer`, or apply React `memo` outside the resulting observer component.",
28402
+ create: (context) => ({ CallExpression(callExpression) {
28403
+ if (!isObserverCall(callExpression, context.scopes)) return;
28404
+ const componentArgument = callExpression.arguments[0];
28405
+ if (!componentArgument) return;
28406
+ if (!hasInvalidInnerWrapper(componentArgument, context.scopes)) return;
28407
+ context.report({
28408
+ node: callExpression,
28409
+ message: MESSAGE$42
28410
+ });
28411
+ } })
28412
+ });
28413
+ //#endregion
28414
+ //#region src/plugin/utils/is-result-discarded-call.ts
28415
+ const isResultDiscardedCall = (callExpression) => {
28416
+ let node = callExpression;
28417
+ let parent = node.parent;
28418
+ while (parent) {
28419
+ if (isNodeOfType(parent, "ExpressionStatement")) return true;
28420
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
28421
+ if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
28422
+ if (isNodeOfType(parent, "ChainExpression")) {
28423
+ node = parent;
28424
+ parent = node.parent;
28425
+ continue;
28426
+ }
28427
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
28428
+ node = parent;
28429
+ parent = node.parent;
28430
+ continue;
28431
+ }
28432
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
28433
+ node = parent;
28434
+ parent = node.parent;
28435
+ continue;
28436
+ }
28437
+ if (isNodeOfType(parent, "SequenceExpression")) {
28438
+ const expressions = parent.expressions ?? [];
28439
+ if (expressions[expressions.length - 1] !== node) return true;
28440
+ node = parent;
28441
+ parent = node.parent;
28442
+ continue;
28443
+ }
28444
+ return false;
28445
+ }
28446
+ return false;
28447
+ };
28448
+ //#endregion
28449
+ //#region src/plugin/utils/resolve-stable-options-object.ts
28450
+ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, referenceNode = expression, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
28451
+ const unwrappedExpression = stripParenExpression(expression);
28452
+ if (isNodeOfType(unwrappedExpression, "ObjectExpression")) return unwrappedExpression;
28453
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
28454
+ const symbol = scopes.symbolFor(unwrappedExpression);
28455
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, referenceNode, scopes) || observedPropertyNames.some((propertyName) => hasPossibleStaticPropertyWriteBefore(unwrappedExpression, propertyName, referenceNode, scopes))) return null;
28456
+ visitedSymbolIds.add(symbol.id);
28457
+ return resolveStableOptionsObject(symbol.initializer, observedPropertyNames, scopes, referenceNode, visitedSymbolIds);
28458
+ };
28459
+ //#endregion
28460
+ //#region src/plugin/rules/mobx/mobx-reaction-disposer-discarded.ts
28461
+ const MESSAGE$41 = "This MobX reaction discards its disposer and can outlive its owner. Store and dispose it during teardown, or provide an AbortSignal.";
28462
+ const LEAKING_SUBSCRIPTION_NAMES = new Set(["reaction", "autorun"]);
28463
+ const OPTIONS_ARGUMENT_INDEX = {
28464
+ autorun: 1,
28465
+ reaction: 2
28466
+ };
28467
+ const DISPOSER_COERCION_NAMES = new Set([
28468
+ "Boolean",
28469
+ "Number",
28470
+ "String"
28471
+ ]);
28472
+ const NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES = new Set([
28473
+ "Array",
28474
+ "Boolean",
28475
+ "JSON",
28476
+ "Math",
28477
+ "Number",
28478
+ "Object",
28479
+ "String",
28480
+ "console"
28481
+ ]);
28482
+ const NON_OBSERVING_IMPORTED_METHOD_NAMES = new Set([
28483
+ "add",
28484
+ "clear",
28485
+ "delete",
28486
+ "remove",
28487
+ "save",
28488
+ "set",
28489
+ "update",
28490
+ "write"
28491
+ ]);
28492
+ const REACTION_PARAMETER_INDEX = {
28493
+ autorun: 0,
28494
+ reaction: 2
28495
+ };
28496
+ const REACTION_CALLBACK_INDEX = {
28497
+ autorun: 0,
28498
+ reaction: 1
28499
+ };
28500
+ const OBSERVATION_CALLBACK_INDEX = {
28501
+ autorun: 0,
28502
+ reaction: 0
28503
+ };
28504
+ const PROCESS_LIFETIME_WIRING_NAME_PATTERN = /^(?:register.*(?:reactions?|autoruns?)|init.*(?:stores?|reactions?|autoruns?)|setup.*(?:stores?|reactions?|autoruns?)|bootstrap(?:app(?:lication)?|stores?|reactions?|autoruns?))$/i;
28505
+ const resolveLeakingSubscriptionName = (callExpression, scopes) => {
28506
+ const reference = resolveImportedApiReference(callExpression.callee, scopes);
28507
+ if (reference?.source !== "mobx" || !reference.importedName || !LEAKING_SUBSCRIPTION_NAMES.has(reference.importedName)) return null;
28508
+ return reference.importedName;
28509
+ };
28510
+ const isEvaluatedAtModuleScope = (node) => {
28511
+ let ancestor = node.parent;
28512
+ while (ancestor) {
28513
+ if (isNodeOfType(ancestor, "PropertyDefinition") || isNodeOfType(ancestor, "AccessorProperty")) {
28514
+ if (!ancestor.static) return false;
28515
+ ancestor = ancestor.parent ?? null;
28516
+ continue;
28517
+ }
28518
+ if (isNodeOfType(ancestor, "StaticBlock")) {
28519
+ ancestor = ancestor.parent ?? null;
28520
+ continue;
28521
+ }
28522
+ if (isFunctionLike$1(ancestor)) {
28523
+ const functionRoot = findTransparentExpressionRoot(ancestor);
28524
+ const invocation = functionRoot.parent;
28525
+ if (isNodeOfType(invocation, "CallExpression") && stripParenExpression(invocation.callee) === functionRoot) {
28526
+ ancestor = invocation.parent ?? null;
28527
+ continue;
28528
+ }
28529
+ return false;
28530
+ }
28531
+ ancestor = ancestor.parent ?? null;
28532
+ }
28533
+ return true;
28534
+ };
28535
+ const getFunctionName = (functionNode) => {
28536
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id.name;
28537
+ const parent = functionNode.parent;
28538
+ return isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier") ? parent.id.name : null;
28539
+ };
28540
+ const isModuleScopedFunction = (functionNode) => {
28541
+ let ancestor = functionNode.parent;
28542
+ while (ancestor) {
28543
+ if (isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "ClassBody")) return false;
28544
+ if (isNodeOfType(ancestor, "Program")) return true;
28545
+ ancestor = ancestor.parent ?? null;
28546
+ }
28547
+ return false;
28548
+ };
28549
+ const getDirectCalls = (functionNode, scopes) => {
28550
+ const bindingIdentifier = isNodeOfType(functionNode, "FunctionDeclaration") ? functionNode.id : isNodeOfType(functionNode.parent, "VariableDeclarator") ? functionNode.parent.id : null;
28551
+ if (!bindingIdentifier || !isNodeOfType(bindingIdentifier, "Identifier")) return [];
28552
+ const symbol = scopes.scopeFor(functionNode).symbolsByName.get(bindingIdentifier.name);
28553
+ if (!symbol) return [];
28554
+ const calls = [];
28555
+ const program = findProgramRoot(functionNode);
28556
+ if (!program) return calls;
28557
+ walkAst(program, (candidate) => {
28558
+ if (!isNodeOfType(candidate, "CallExpression")) return;
28559
+ const callee = stripParenExpression(candidate.callee);
28560
+ if (!isNodeOfType(callee, "Identifier")) return;
28561
+ if (resolveConstIdentifierAlias(callee, scopes)?.id === symbol.id) calls.push(candidate);
28562
+ });
28563
+ return calls;
28564
+ };
28565
+ const processLifetimeClassSymbolIdsByAnalysis = /* @__PURE__ */ new WeakMap();
28566
+ const getProcessLifetimeClassSymbolIds = (node, scopes) => {
28567
+ const cached = processLifetimeClassSymbolIdsByAnalysis.get(scopes);
28568
+ if (cached) return cached;
28569
+ const instantiationScopeBySymbolId = /* @__PURE__ */ new Map();
28570
+ const program = findProgramRoot(node);
28571
+ if (program) walkAst(program, (candidate) => {
28572
+ if (!isNodeOfType(candidate, "NewExpression")) return;
28573
+ const callee = stripParenExpression(candidate.callee);
28574
+ const symbol = isNodeOfType(callee, "Identifier") ? resolveConstIdentifierAlias(callee, scopes) : null;
28575
+ if (!symbol) return;
28576
+ const wasModuleOnly = instantiationScopeBySymbolId.get(symbol.id) ?? true;
28577
+ instantiationScopeBySymbolId.set(symbol.id, wasModuleOnly && isEvaluatedAtModuleScope(candidate));
28578
+ });
28579
+ const processLifetimeSymbolIds = /* @__PURE__ */ new Set();
28580
+ for (const [symbolId, isModuleOnly] of instantiationScopeBySymbolId) if (isModuleOnly) processLifetimeSymbolIds.add(symbolId);
28581
+ processLifetimeClassSymbolIdsByAnalysis.set(scopes, processLifetimeSymbolIds);
28582
+ return processLifetimeSymbolIds;
28583
+ };
28584
+ const isProcessLifetimeWiring = (node, scopes) => {
28585
+ let ancestor = node.parent;
28586
+ while (ancestor) {
28587
+ if (!isFunctionLike$1(ancestor)) {
28588
+ ancestor = ancestor.parent ?? null;
28589
+ continue;
28590
+ }
28591
+ const functionName = getFunctionName(ancestor);
28592
+ if (functionName && PROCESS_LIFETIME_WIRING_NAME_PATTERN.test(functionName) && isModuleScopedFunction(ancestor)) {
28593
+ const calls = getDirectCalls(ancestor, scopes);
28594
+ return calls.length > 0 && calls.every(isEvaluatedAtModuleScope);
28595
+ }
28596
+ const methodDefinition = ancestor.parent;
28597
+ if (isNodeOfType(methodDefinition, "MethodDefinition") && methodDefinition.kind === "constructor") {
28598
+ let classNode = methodDefinition.parent?.parent;
28599
+ if (isNodeOfType(classNode, "ClassDeclaration") || isNodeOfType(classNode, "ClassExpression")) {
28600
+ const classRoot = findTransparentExpressionRoot(classNode);
28601
+ const classInstantiation = classRoot.parent;
28602
+ if (isNodeOfType(classNode, "ClassExpression") && isNodeOfType(classInstantiation, "NewExpression") && stripParenExpression(classInstantiation.callee) === classRoot) return isEvaluatedAtModuleScope(classInstantiation);
28603
+ const classSymbol = getClassBindingSymbol(classNode, scopes);
28604
+ return Boolean(classSymbol && getProcessLifetimeClassSymbolIds(node, scopes).has(classSymbol.id));
28605
+ }
28606
+ }
28607
+ return false;
28608
+ }
28609
+ return false;
28610
+ };
28611
+ const mayCarryAbortSignal = (optionsArgument, scopes) => {
28612
+ if (!optionsArgument) return false;
28613
+ const options = resolveStableOptionsObject(optionsArgument, ["signal"], scopes);
28614
+ if (!options) return true;
28615
+ return options.properties.some((property) => {
28616
+ if (!isNodeOfType(property, "Property")) return true;
28617
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
28618
+ if (propertyName === null) return true;
28619
+ if (propertyName !== "signal") return false;
28620
+ const value = property.value;
28621
+ if (isNodeOfType(value, "Identifier") && value.name === "undefined") return false;
28622
+ if (isNodeOfType(value, "Literal") && value.value == null) return false;
28623
+ return !(isNodeOfType(value, "UnaryExpression") && value.operator === "void");
28624
+ });
28625
+ };
28626
+ const callbackDisposesReaction = (callExpression, subscriptionName, scopes) => {
28627
+ const callbackArgument = callExpression.arguments[REACTION_CALLBACK_INDEX[subscriptionName]];
28628
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
28629
+ const callback = resolveExactLocalFunction(callbackArgument, scopes);
28630
+ if (!callback || !isFunctionLike$1(callback)) return false;
28631
+ const reactionParameter = callback.params?.[REACTION_PARAMETER_INDEX[subscriptionName]];
28632
+ if (!reactionParameter || !isNodeOfType(reactionParameter, "Identifier")) return false;
28633
+ const reactionSymbol = scopes.symbolFor(reactionParameter);
28634
+ if (!reactionSymbol) return false;
28635
+ let doesDisposeReaction = false;
28636
+ walkAst(callback, (candidate) => {
28637
+ if (doesDisposeReaction || !isNodeOfType(candidate, "CallExpression")) return;
28638
+ const callee = stripParenExpression(candidate.callee);
28639
+ if (!isNodeOfType(callee, "MemberExpression")) return;
28640
+ const receiver = stripParenExpression(callee.object);
28641
+ if (isNodeOfType(receiver, "Identifier") && scopes.symbolFor(receiver)?.id === reactionSymbol.id && getStaticPropertyKeyName(callee, { allowComputedString: true }) === "dispose") doesDisposeReaction = true;
28642
+ });
28643
+ return doesDisposeReaction;
28644
+ };
28645
+ const getMemberReceiverRoot = (memberExpression) => {
28646
+ let receiver = stripParenExpression(memberExpression);
28647
+ while (isNodeOfType(receiver, "MemberExpression")) receiver = stripParenExpression(receiver.object);
28648
+ return receiver;
28649
+ };
28650
+ const observesOnlyInstanceRootedState = (callExpression, subscriptionName, scopes) => {
28651
+ const callbackArgument = callExpression.arguments[OBSERVATION_CALLBACK_INDEX[subscriptionName]];
28652
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return false;
28653
+ const callback = resolveExactLocalFunction(callbackArgument, scopes);
28654
+ if (!callback || !isFunctionLike$1(callback)) return false;
28655
+ let observesExternalState = false;
28656
+ walkAst(callback, (candidate) => {
28657
+ if (candidate !== callback && isFunctionLike$1(candidate)) return false;
28658
+ if (observesExternalState || !isNodeOfType(candidate, "MemberExpression")) return;
28659
+ const receiver = getMemberReceiverRoot(candidate);
28660
+ const candidateRoot = findTransparentExpressionRoot(candidate);
28661
+ const parent = candidateRoot.parent;
28662
+ const isDirectMethodCall = isNodeOfType(parent, "CallExpression") && parent.callee === candidateRoot;
28663
+ if (isNodeOfType(receiver, "ThisExpression")) {
28664
+ if (isDirectMethodCall) observesExternalState = true;
28665
+ return;
28666
+ }
28667
+ if (!isNodeOfType(receiver, "Identifier")) {
28668
+ observesExternalState = true;
28669
+ return;
28670
+ }
28671
+ const receiverSymbol = scopes.symbolFor(receiver);
28672
+ if (receiverSymbol && isAstDescendant(receiverSymbol.declarationNode, callback) || scopes.isGlobalReference(receiver) && NON_OBSERVABLE_GLOBAL_RECEIVER_NAMES.has(receiver.name)) return;
28673
+ if (receiverSymbol?.kind === "import" && isDirectMethodCall && NON_OBSERVING_IMPORTED_METHOD_NAMES.has(getStaticPropertyKeyName(candidate, { allowComputedString: true }) ?? "")) return;
28674
+ observesExternalState = true;
28675
+ });
28676
+ return !observesExternalState;
28677
+ };
28678
+ const isForwardedFromConciseArrow = (callExpression) => {
28679
+ let expressionRoot = findTransparentExpressionRoot(callExpression);
28680
+ let parent = expressionRoot.parent;
28681
+ while (parent) {
28682
+ if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === expressionRoot) return true;
28683
+ if (isNodeOfType(parent, "LogicalExpression") && (parent.right === expressionRoot || parent.left === expressionRoot && parent.operator !== "&&") || isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === expressionRoot || parent.alternate === expressionRoot) || isNodeOfType(parent, "SequenceExpression") && parent.expressions[parent.expressions.length - 1] === expressionRoot) {
28684
+ expressionRoot = findTransparentExpressionRoot(parent);
28685
+ parent = expressionRoot.parent;
28686
+ continue;
28687
+ }
28688
+ return false;
28689
+ }
28690
+ return false;
28691
+ };
28692
+ const isDisposerDiscarded = (callExpression) => {
28693
+ if (isForwardedFromConciseArrow(callExpression)) return false;
28694
+ const expressionRoot = findTransparentExpressionRoot(callExpression);
28695
+ if (isResultDiscardedCall(callExpression)) return true;
28696
+ const parent = expressionRoot.parent;
28697
+ if (!parent) return false;
28698
+ if (isNodeOfType(parent, "UnaryExpression") || isNodeOfType(parent, "BinaryExpression")) return true;
28699
+ if ((isNodeOfType(parent, "IfStatement") || isNodeOfType(parent, "WhileStatement") || isNodeOfType(parent, "DoWhileStatement") || isNodeOfType(parent, "ForStatement")) && parent.test === expressionRoot) return true;
28700
+ if (isNodeOfType(parent, "ConditionalExpression") && parent.test === expressionRoot || isNodeOfType(parent, "SwitchStatement") && parent.discriminant === expressionRoot) return true;
28701
+ if (isNodeOfType(parent, "LogicalExpression") && parent.left === expressionRoot) return parent.operator === "&&" || isResultDiscardedCall(parent);
28702
+ const callee = isNodeOfType(parent, "CallExpression") ? stripParenExpression(parent.callee) : null;
28703
+ return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments.some((argument) => argument === expressionRoot) && isNodeOfType(callee, "Identifier") && DISPOSER_COERCION_NAMES.has(callee.name));
28704
+ };
28705
+ const mobxReactionDisposerDiscarded = defineRule({
28706
+ id: "mobx-reaction-disposer-discarded",
28707
+ title: "MobX reaction disposer discarded",
28708
+ severity: "warn",
28709
+ category: "Bugs",
28710
+ requires: MOBX_RULE_GATES["mobx-reaction-disposer-discarded"].requires,
28711
+ recommendation: "Keep the disposer returned by `reaction` or `autorun` and invoke it during teardown, or provide an AbortSignal.",
28712
+ create: (context) => ({ CallExpression(callExpression) {
28713
+ const subscriptionName = resolveLeakingSubscriptionName(callExpression, context.scopes);
28714
+ if (!subscriptionName || !isDisposerDiscarded(callExpression)) return;
28715
+ if (isEvaluatedAtModuleScope(callExpression)) return;
28716
+ if (isProcessLifetimeWiring(callExpression, context.scopes)) return;
28717
+ if (callbackDisposesReaction(callExpression, subscriptionName, context.scopes)) return;
28718
+ if (observesOnlyInstanceRootedState(callExpression, subscriptionName, context.scopes)) return;
28719
+ const optionsArgument = callExpression.arguments[OPTIONS_ARGUMENT_INDEX[subscriptionName]];
28720
+ if (mayCarryAbortSignal(optionsArgument, context.scopes)) return;
28721
+ context.report({
28722
+ node: callExpression,
28723
+ message: MESSAGE$41
28724
+ });
28725
+ } })
28726
+ });
28727
+ //#endregion
28187
28728
  //#region src/plugin/utils/has-jsx-prop.ts
28188
28729
  const hasJsxProp = (attributes, targetProp) => {
28189
28730
  for (const attribute of attributes) {
@@ -28974,11 +29515,11 @@ const hasEmailTemplateImport = (programRoot) => {
28974
29515
  return found;
28975
29516
  };
28976
29517
  //#endregion
28977
- //#region src/plugin/utils/build-generated-image-project-index.ts
28978
- const GENERATED_IMAGE_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
28979
- const GENERATED_IMAGE_DECLARATION_FILE_PATTERN = /\.d\.[cm]?[jt]s$/i;
28980
- const GENERATED_IMAGE_MDX_FILE_PATTERN = /\.mdx$/i;
28981
- const GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES = new Set([
29518
+ //#region src/plugin/utils/build-source-project-index.ts
29519
+ const SOURCE_PROJECT_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
29520
+ const SOURCE_PROJECT_DECLARATION_FILE_PATTERN = /\.d\.[cm]?[jt]s$/i;
29521
+ const SOURCE_PROJECT_MDX_FILE_PATTERN = /\.mdx$/i;
29522
+ const SOURCE_PROJECT_IGNORED_DIRECTORY_NAMES = new Set([
28982
29523
  ".angular",
28983
29524
  ".astro",
28984
29525
  ".cache",
@@ -28999,12 +29540,12 @@ const GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES = new Set([
28999
29540
  "out",
29000
29541
  "storybook-static"
29001
29542
  ]);
29002
- const generatedImageScopeCache = /* @__PURE__ */ new WeakMap();
29003
- const getGeneratedImageModuleScopes = (programNode) => {
29004
- const cachedScopes = generatedImageScopeCache.get(programNode);
29543
+ const sourceProjectScopeCache = /* @__PURE__ */ new WeakMap();
29544
+ const getSourceProjectModuleScopes = (programNode) => {
29545
+ const cachedScopes = sourceProjectScopeCache.get(programNode);
29005
29546
  if (cachedScopes) return cachedScopes;
29006
29547
  const scopes = analyzeScopes(programNode);
29007
- generatedImageScopeCache.set(programNode, scopes);
29548
+ sourceProjectScopeCache.set(programNode, scopes);
29008
29549
  return scopes;
29009
29550
  };
29010
29551
  const listProductionSourceFiles = (rootDirectory) => {
@@ -29022,7 +29563,7 @@ const listProductionSourceFiles = (rootDirectory) => {
29022
29563
  }
29023
29564
  for (const entry of entries) {
29024
29565
  const absolutePath = path.join(currentDirectory, entry.name);
29025
- const isIgnoredDirectoryName = GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".") && entry.name !== ".dumi" && entry.name !== ".storybook";
29566
+ const isIgnoredDirectoryName = SOURCE_PROJECT_IGNORED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".") && entry.name !== ".dumi" && entry.name !== ".storybook";
29026
29567
  if (entry.isSymbolicLink() && isIgnoredDirectoryName) continue;
29027
29568
  if (entry.isSymbolicLink()) return null;
29028
29569
  if (entry.isDirectory()) {
@@ -29031,12 +29572,12 @@ const listProductionSourceFiles = (rootDirectory) => {
29031
29572
  continue;
29032
29573
  }
29033
29574
  if (!entry.isFile() || isTestlikeFilename(absolutePath)) continue;
29034
- if (GENERATED_IMAGE_MDX_FILE_PATTERN.test(entry.name)) {
29575
+ if (SOURCE_PROJECT_MDX_FILE_PATTERN.test(entry.name)) {
29035
29576
  hasOpaqueMdxConsumerSurface = true;
29036
29577
  continue;
29037
29578
  }
29038
- if (!GENERATED_IMAGE_SOURCE_FILE_PATTERN.test(entry.name)) continue;
29039
- if (GENERATED_IMAGE_DECLARATION_FILE_PATTERN.test(entry.name)) continue;
29579
+ if (!SOURCE_PROJECT_FILE_PATTERN.test(entry.name)) continue;
29580
+ if (SOURCE_PROJECT_DECLARATION_FILE_PATTERN.test(entry.name)) continue;
29040
29581
  sourceFilePaths.push(normalizeFilename(absolutePath));
29041
29582
  }
29042
29583
  }
@@ -29081,7 +29622,7 @@ const indexModuleSources = (module, consumerModulesByFilePath, resolvedSourcePat
29081
29622
  consumerModulesByFilePath.set(normalizedSourcePath, consumerModules);
29082
29623
  });
29083
29624
  };
29084
- const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, currentProgramNode, currentScopes) => {
29625
+ const buildSourceProjectIndex = (rootDirectory, currentFilePath, currentProgramNode, currentScopes) => {
29085
29626
  const productionSourceFiles = listProductionSourceFiles(rootDirectory);
29086
29627
  if (!productionSourceFiles) return null;
29087
29628
  const modulesByFilePath = /* @__PURE__ */ new Map();
@@ -29099,7 +29640,7 @@ const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, current
29099
29640
  modulesByFilePath.set(filePath, {
29100
29641
  filePath,
29101
29642
  programNode: parsedProgram,
29102
- scopes: getGeneratedImageModuleScopes(parsedProgram)
29643
+ scopes: getSourceProjectModuleScopes(parsedProgram)
29103
29644
  });
29104
29645
  }
29105
29646
  const consumerModulesByFilePath = /* @__PURE__ */ new Map();
@@ -29115,6 +29656,51 @@ const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, current
29115
29656
  };
29116
29657
  };
29117
29658
  //#endregion
29659
+ //#region src/plugin/utils/get-direct-function-binding-identifier.ts
29660
+ const getDirectFunctionBindingIdentifier = (functionNode) => {
29661
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
29662
+ const functionValueRoot = findTransparentExpressionRoot(functionNode);
29663
+ const parent = functionValueRoot.parent;
29664
+ return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
29665
+ };
29666
+ //#endregion
29667
+ //#region src/plugin/utils/get-function-export-names.ts
29668
+ const getExportedSpecifierName$1 = (specifier) => {
29669
+ const exported = specifier.exported;
29670
+ if (isNodeOfType(exported, "Identifier")) return exported.name;
29671
+ return isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null;
29672
+ };
29673
+ const getLocalSpecifierName = (specifier) => {
29674
+ const local = specifier.local;
29675
+ if (isNodeOfType(local, "Identifier")) return local.name;
29676
+ return isNodeOfType(local, "Literal") && typeof local.value === "string" ? local.value : null;
29677
+ };
29678
+ const getFunctionExportNames = (programNode, functionNode) => {
29679
+ const functionValueRoot = findTransparentExpressionRoot(functionNode);
29680
+ const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
29681
+ const exportedNames = /* @__PURE__ */ new Set();
29682
+ for (const statement of programNode.body) {
29683
+ if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
29684
+ if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
29685
+ continue;
29686
+ }
29687
+ if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
29688
+ const declaration = statement.declaration;
29689
+ if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
29690
+ if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
29691
+ for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
29692
+ }
29693
+ if (!bindingName || statement.source) continue;
29694
+ for (const specifier of statement.specifiers) {
29695
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
29696
+ if (getLocalSpecifierName(specifier) !== bindingName) continue;
29697
+ const exportedName = getExportedSpecifierName$1(specifier);
29698
+ if (exportedName) exportedNames.add(exportedName);
29699
+ }
29700
+ }
29701
+ return [...exportedNames];
29702
+ };
29703
+ //#endregion
29118
29704
  //#region src/plugin/utils/read-nearest-package-manifest.ts
29119
29705
  const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
29120
29706
  const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
@@ -29199,37 +29785,6 @@ const getImportSpecifierName = (specifier) => {
29199
29785
  if (isNodeOfType(imported, "Identifier")) return imported.name;
29200
29786
  return isNodeOfType(imported, "Literal") && typeof imported.value === "string" ? imported.value : null;
29201
29787
  };
29202
- const getDirectFunctionBindingIdentifier = (functionNode) => {
29203
- if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
29204
- const functionValueRoot = findTransparentExpressionRoot(functionNode);
29205
- const parent = functionValueRoot.parent;
29206
- return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
29207
- };
29208
- const getExportNamesForFunction = (programNode, functionNode) => {
29209
- const functionValueRoot = findTransparentExpressionRoot(functionNode);
29210
- const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
29211
- const exportedNames = /* @__PURE__ */ new Set();
29212
- for (const statement of programNode.body) {
29213
- if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
29214
- if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
29215
- continue;
29216
- }
29217
- if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
29218
- const declaration = statement.declaration;
29219
- if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
29220
- if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
29221
- for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
29222
- }
29223
- if (!bindingName || statement.source) continue;
29224
- for (const specifier of statement.specifiers) {
29225
- if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
29226
- if (getImportedSpecifierName(specifier) !== bindingName) continue;
29227
- const exportedName = getExportedSpecifierName(specifier);
29228
- if (exportedName) exportedNames.add(exportedName);
29229
- }
29230
- }
29231
- return [...exportedNames];
29232
- };
29233
29788
  const isTransparentGeneratedImageValueFlow = (expression, target) => {
29234
29789
  let current = findTransparentExpressionRoot(expression);
29235
29790
  while (current !== target) {
@@ -29288,7 +29843,7 @@ const classifyInvokedExpression = (module, expression, state) => {
29288
29843
  }
29289
29844
  const forwardingFunction = getForwardingFunction(expression);
29290
29845
  if (!forwardingFunction) return false;
29291
- const exportedNames = getExportNamesForFunction(module.programNode, forwardingFunction);
29846
+ const exportedNames = getFunctionExportNames(module.programNode, forwardingFunction);
29292
29847
  if (exportedNames.length === 0) return false;
29293
29848
  for (const exportedName of exportedNames) enqueueExport(state, module.filePath, exportedName);
29294
29849
  return true;
@@ -29421,9 +29976,9 @@ const createExportedJsxGeneratedImageOwnershipAnalyzer = (context) => {
29421
29976
  const programNode = findProgramRoot(jsxNode);
29422
29977
  const enclosingFunction = findEnclosingFunction$1(jsxNode);
29423
29978
  if (!programNode || !enclosingFunction) return false;
29424
- const initialExportNames = getExportNamesForFunction(programNode, enclosingFunction);
29979
+ const initialExportNames = getFunctionExportNames(programNode, enclosingFunction);
29425
29980
  if (initialExportNames.length === 0) return false;
29426
- if (projectIndex === void 0) projectIndex = buildGeneratedImageProjectIndex(rootDirectory, filename, programNode, context.scopes);
29981
+ if (projectIndex === void 0) projectIndex = buildSourceProjectIndex(rootDirectory, filename, programNode, context.scopes);
29427
29982
  if (!projectIndex || projectIndex.hasOpaqueMdxConsumerSurface) return false;
29428
29983
  const state = {
29429
29984
  projectIndex,
@@ -30231,7 +30786,7 @@ const nextjsNoVercelOgImport = defineRule({
30231
30786
  });
30232
30787
  //#endregion
30233
30788
  //#region src/plugin/rules/a11y/no-access-key.ts
30234
- const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
30789
+ const MESSAGE$40 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
30235
30790
  const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
30236
30791
  const noAccessKey = defineRule({
30237
30792
  id: "no-access-key",
@@ -30250,7 +30805,7 @@ const noAccessKey = defineRule({
30250
30805
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
30251
30806
  context.report({
30252
30807
  node: accessKey,
30253
- message: MESSAGE$39
30808
+ message: MESSAGE$40
30254
30809
  });
30255
30810
  return;
30256
30811
  }
@@ -30260,7 +30815,7 @@ const noAccessKey = defineRule({
30260
30815
  if (isUndefinedIdentifier$1(expression)) return;
30261
30816
  context.report({
30262
30817
  node: accessKey,
30263
- message: MESSAGE$39
30818
+ message: MESSAGE$40
30264
30819
  });
30265
30820
  }
30266
30821
  } };
@@ -32603,7 +33158,7 @@ const noAdjustStateOnPropChange = defineRule({
32603
33158
  });
32604
33159
  //#endregion
32605
33160
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
32606
- const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
33161
+ const MESSAGE$39 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
32607
33162
  const ALWAYS_FOCUSABLE_TAGS = new Set([
32608
33163
  "button",
32609
33164
  "embed",
@@ -32715,7 +33270,7 @@ const noAriaHiddenOnFocusable = defineRule({
32715
33270
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
32716
33271
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
32717
33272
  node: ariaHidden,
32718
- message: MESSAGE$38
33273
+ message: MESSAGE$39
32719
33274
  });
32720
33275
  } })
32721
33276
  });
@@ -33762,7 +34317,7 @@ const isAllLiteralArrayExpression = (node) => {
33762
34317
  };
33763
34318
  //#endregion
33764
34319
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
33765
- const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
34320
+ const MESSAGE$38 = "Your users can see & submit the wrong data when this list reorders.";
33766
34321
  const SECOND_INDEX_METHODS = new Set([
33767
34322
  "every",
33768
34323
  "filter",
@@ -33881,14 +34436,14 @@ const noArrayIndexKey = defineRule({
33881
34436
  if (propName !== "key") continue;
33882
34437
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
33883
34438
  node: property,
33884
- message: MESSAGE$37
34439
+ message: MESSAGE$38
33885
34440
  });
33886
34441
  }
33887
34442
  } })
33888
34443
  });
33889
34444
  //#endregion
33890
34445
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
33891
- const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
34446
+ const MESSAGE$37 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
33892
34447
  const noAsyncEffectCallback = defineRule({
33893
34448
  id: "no-async-effect-callback",
33894
34449
  title: "Async effect callback",
@@ -33906,13 +34461,13 @@ const noAsyncEffectCallback = defineRule({
33906
34461
  if (!callback.async) return;
33907
34462
  context.report({
33908
34463
  node: callback,
33909
- message: MESSAGE$36
34464
+ message: MESSAGE$37
33910
34465
  });
33911
34466
  } })
33912
34467
  });
33913
34468
  //#endregion
33914
34469
  //#region src/plugin/rules/a11y/no-autofocus.ts
33915
- const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
34470
+ const MESSAGE$36 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
33916
34471
  const resolveSettings$21 = (settings) => {
33917
34472
  const reactDoctor = settings?.["react-doctor"];
33918
34473
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -34009,7 +34564,7 @@ const noAutofocus = defineRule({
34009
34564
  if (isConditionallyRendered(node)) return;
34010
34565
  context.report({
34011
34566
  node: autoFocusAttribute,
34012
- message: MESSAGE$35
34567
+ message: MESSAGE$36
34013
34568
  });
34014
34569
  } };
34015
34570
  }
@@ -35338,7 +35893,7 @@ const noChainStateUpdates = defineRule({
35338
35893
  });
35339
35894
  //#endregion
35340
35895
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
35341
- const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
35896
+ const MESSAGE$35 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
35342
35897
  const noChildrenProp = defineRule({
35343
35898
  id: "no-children-prop",
35344
35899
  title: "Children passed as a prop",
@@ -35350,7 +35905,7 @@ const noChildrenProp = defineRule({
35350
35905
  if (node.name.name !== "children") return;
35351
35906
  context.report({
35352
35907
  node: node.name,
35353
- message: MESSAGE$34
35908
+ message: MESSAGE$35
35354
35909
  });
35355
35910
  },
35356
35911
  CallExpression(node) {
@@ -35363,7 +35918,7 @@ const noChildrenProp = defineRule({
35363
35918
  const propertyKey = property.key;
35364
35919
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
35365
35920
  node: propertyKey,
35366
- message: MESSAGE$34
35921
+ message: MESSAGE$35
35367
35922
  });
35368
35923
  }
35369
35924
  }
@@ -35371,7 +35926,7 @@ const noChildrenProp = defineRule({
35371
35926
  });
35372
35927
  //#endregion
35373
35928
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
35374
- const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
35929
+ const MESSAGE$34 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
35375
35930
  const noCloneElement = defineRule({
35376
35931
  id: "no-clone-element",
35377
35932
  title: "cloneElement makes child props fragile",
@@ -35384,7 +35939,7 @@ const noCloneElement = defineRule({
35384
35939
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
35385
35940
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
35386
35941
  node: callee,
35387
- message: MESSAGE$33
35942
+ message: MESSAGE$34
35388
35943
  });
35389
35944
  return;
35390
35945
  }
@@ -35397,7 +35952,7 @@ const noCloneElement = defineRule({
35397
35952
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
35398
35953
  context.report({
35399
35954
  node: callee,
35400
- message: MESSAGE$33
35955
+ message: MESSAGE$34
35401
35956
  });
35402
35957
  }
35403
35958
  } })
@@ -35417,7 +35972,7 @@ const getCallMethodName = (callee) => {
35417
35972
  };
35418
35973
  //#endregion
35419
35974
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
35420
- const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
35975
+ const MESSAGE$33 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
35421
35976
  const CONTEXT_MODULES = [
35422
35977
  "react",
35423
35978
  "use-context-selector",
@@ -35465,7 +36020,7 @@ const noCreateContextInRender = defineRule({
35465
36020
  if (!componentOrHookName) return;
35466
36021
  context.report({
35467
36022
  node,
35468
- message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
36023
+ message: `${MESSAGE$33} (called inside "${componentOrHookName}")`
35469
36024
  });
35470
36025
  } })
35471
36026
  });
@@ -36212,7 +36767,7 @@ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes)
36212
36767
  };
36213
36768
  //#endregion
36214
36769
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
36215
- const MESSAGE$31 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
36770
+ const MESSAGE$32 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
36216
36771
  const isUseMemoCallbackArgument = (functionNode, scopes) => {
36217
36772
  const parent = functionNode.parent;
36218
36773
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -36266,7 +36821,7 @@ const noCreateRefInFunctionComponent = defineRule({
36266
36821
  if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
36267
36822
  context.report({
36268
36823
  node,
36269
- message: MESSAGE$31
36824
+ message: MESSAGE$32
36270
36825
  });
36271
36826
  } })
36272
36827
  });
@@ -36406,7 +36961,7 @@ const noCreateStoreInRender = defineRule({
36406
36961
  });
36407
36962
  //#endregion
36408
36963
  //#region src/plugin/rules/react-builtins/no-danger.ts
36409
- const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
36964
+ const MESSAGE$31 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
36410
36965
  const noDanger = defineRule({
36411
36966
  id: "no-danger",
36412
36967
  title: "Raw HTML injection can run unsafe markup",
@@ -36420,7 +36975,7 @@ const noDanger = defineRule({
36420
36975
  if (!propAttribute) return;
36421
36976
  context.report({
36422
36977
  node: propAttribute.name,
36423
- message: MESSAGE$30
36978
+ message: MESSAGE$31
36424
36979
  });
36425
36980
  },
36426
36981
  CallExpression(node) {
@@ -36432,7 +36987,7 @@ const noDanger = defineRule({
36432
36987
  const propertyKey = property.key;
36433
36988
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
36434
36989
  node: propertyKey,
36435
- message: MESSAGE$30
36990
+ message: MESSAGE$31
36436
36991
  });
36437
36992
  }
36438
36993
  }
@@ -36455,7 +37010,7 @@ const isMeaningfulJsxChild = (child) => {
36455
37010
  };
36456
37011
  //#endregion
36457
37012
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
36458
- const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
37013
+ const MESSAGE$30 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
36459
37014
  const mergePropsShape = (target, source) => {
36460
37015
  target.hasDangerously ||= source.hasDangerously;
36461
37016
  target.hasChildren ||= source.hasChildren;
@@ -36530,7 +37085,7 @@ const noDangerWithChildren = defineRule({
36530
37085
  if (!hasChildrenProp && !hasNestedChildren) return;
36531
37086
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
36532
37087
  node: opening,
36533
- message: MESSAGE$29
37088
+ message: MESSAGE$30
36534
37089
  });
36535
37090
  },
36536
37091
  CallExpression(node) {
@@ -36543,7 +37098,7 @@ const noDangerWithChildren = defineRule({
36543
37098
  const positionalChildren = node.arguments.slice(2);
36544
37099
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
36545
37100
  node,
36546
- message: MESSAGE$29
37101
+ message: MESSAGE$30
36547
37102
  });
36548
37103
  }
36549
37104
  })
@@ -37518,7 +38073,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
37518
38073
  //#endregion
37519
38074
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
37520
38075
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
37521
- const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
38076
+ const MESSAGE$29 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
37522
38077
  const getEnclosingLifecycleFunction = (setStateCall) => {
37523
38078
  let ancestor = setStateCall.parent;
37524
38079
  while (ancestor) {
@@ -37645,7 +38200,7 @@ const noDidMountSetState = defineRule({
37645
38200
  }
37646
38201
  context.report({
37647
38202
  node: node.callee,
37648
- message: MESSAGE$28
38203
+ message: MESSAGE$29
37649
38204
  });
37650
38205
  } };
37651
38206
  }
@@ -37663,7 +38218,7 @@ const findEnclosingClass = (node) => {
37663
38218
  //#endregion
37664
38219
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
37665
38220
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
37666
- const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
38221
+ const MESSAGE$28 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37667
38222
  const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
37668
38223
  const EQUALITY_OPERATORS = new Set([
37669
38224
  "==",
@@ -38057,7 +38612,7 @@ const noDidUpdateSetState = defineRule({
38057
38612
  if (isInsideDiffGuard(node, context.scopes)) return;
38058
38613
  context.report({
38059
38614
  node: node.callee,
38060
- message: MESSAGE$27
38615
+ message: MESSAGE$28
38061
38616
  });
38062
38617
  } };
38063
38618
  }
@@ -38080,7 +38635,7 @@ const isStateMemberExpression = (node) => {
38080
38635
  };
38081
38636
  //#endregion
38082
38637
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
38083
- const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
38638
+ const MESSAGE$27 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
38084
38639
  const shouldIgnoreMutation = (node) => {
38085
38640
  let isConstructor = false;
38086
38641
  let isInsideCallExpression = false;
@@ -38102,7 +38657,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
38102
38657
  if (shouldIgnoreMutation(reportNode)) return;
38103
38658
  context.report({
38104
38659
  node: reportNode,
38105
- message: MESSAGE$26
38660
+ message: MESSAGE$27
38106
38661
  });
38107
38662
  };
38108
38663
  const noDirectMutationState = defineRule({
@@ -38453,7 +39008,7 @@ const noDocumentStartViewTransition = defineRule({
38453
39008
  });
38454
39009
  //#endregion
38455
39010
  //#region src/plugin/rules/js-performance/no-document-write.ts
38456
- const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
39011
+ const MESSAGE$26 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
38457
39012
  const WRITE_METHODS = new Set(["write", "writeln"]);
38458
39013
  const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
38459
39014
  const noDocumentWrite = defineRule({
@@ -38470,7 +39025,7 @@ const noDocumentWrite = defineRule({
38470
39025
  if (methodName === null || !WRITE_METHODS.has(methodName)) return;
38471
39026
  context.report({
38472
39027
  node,
38473
- message: MESSAGE$25
39028
+ message: MESSAGE$26
38474
39029
  });
38475
39030
  } })
38476
39031
  });
@@ -41518,7 +42073,7 @@ const IMPORT_INITIALIZER_TYPES = new Set([
41518
42073
  "ImportNamespaceSpecifier"
41519
42074
  ]);
41520
42075
  const CANCELLATION_FLAG_NAME_PATTERN = /cancel|ignore/i;
41521
- const PROMISE_CONTINUATION_METHOD_NAMES = new Set([
42076
+ const PROMISE_CONTINUATION_METHOD_NAMES$1 = new Set([
41522
42077
  "then",
41523
42078
  "catch",
41524
42079
  "finally"
@@ -41731,18 +42286,10 @@ const isCompletionSinkAfterCancellationEarlyExit = (completionSink, cancellation
41731
42286
  return false;
41732
42287
  };
41733
42288
  const isCompletionSinkGuardedByCancellationFlag = (completionSink, cancellationFlagKey, context) => isCompletionSinkInsideCancellationGuard(completionSink, cancellationFlagKey, context) || isCompletionSinkAfterCancellationEarlyExit(completionSink, cancellationFlagKey, context);
41734
- const isDescendantOf = (node, ancestor) => {
41735
- let currentNode = node;
41736
- while (currentNode) {
41737
- if (currentNode === ancestor) return true;
41738
- currentNode = currentNode.parent ?? null;
41739
- }
41740
- return false;
41741
- };
41742
42289
  const isPromiseContinuationForRequest = (functionNode, request) => {
41743
42290
  const callNode = functionNode.parent;
41744
- if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((callArgument) => callArgument === functionNode) || !isNodeOfType(callNode.callee, "MemberExpression") || callNode.callee.computed || !isNodeOfType(callNode.callee.property, "Identifier") || !PROMISE_CONTINUATION_METHOD_NAMES.has(callNode.callee.property.name)) return false;
41745
- return isDescendantOf(request, callNode.callee.object);
42291
+ if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((callArgument) => callArgument === functionNode) || !isNodeOfType(callNode.callee, "MemberExpression") || callNode.callee.computed || !isNodeOfType(callNode.callee.property, "Identifier") || !PROMISE_CONTINUATION_METHOD_NAMES$1.has(callNode.callee.property.name)) return false;
42292
+ return isAstDescendant(request, callNode.callee.object);
41746
42293
  };
41747
42294
  const isAwaitedInFunction = (request, functionNode) => {
41748
42295
  let currentNode = request.parent;
@@ -41810,7 +42357,7 @@ const ALLOWED_NAMESPACES = new Set([
41810
42357
  "ReactDOM",
41811
42358
  "ReactDom"
41812
42359
  ]);
41813
- const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
42360
+ const MESSAGE$25 = "`findDOMNode` crashes your app in React 19 because it was removed.";
41814
42361
  const noFindDomNode = defineRule({
41815
42362
  id: "no-find-dom-node",
41816
42363
  title: "findDOMNode breaks component encapsulation",
@@ -41821,7 +42368,7 @@ const noFindDomNode = defineRule({
41821
42368
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
41822
42369
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
41823
42370
  node: callee,
41824
- message: MESSAGE$24
42371
+ message: MESSAGE$25
41825
42372
  });
41826
42373
  return;
41827
42374
  }
@@ -41832,7 +42379,7 @@ const noFindDomNode = defineRule({
41832
42379
  if (callee.property.name !== "findDOMNode") return;
41833
42380
  context.report({
41834
42381
  node: callee.property,
41835
- message: MESSAGE$24
42382
+ message: MESSAGE$25
41836
42383
  });
41837
42384
  }
41838
42385
  } })
@@ -42970,7 +43517,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
42970
43517
  });
42971
43518
  //#endregion
42972
43519
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
42973
- const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
43520
+ const MESSAGE$24 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
42974
43521
  const noImgLazyWithHighFetchpriority = defineRule({
42975
43522
  id: "no-img-lazy-with-high-fetchpriority",
42976
43523
  title: "Lazy image with high fetchPriority",
@@ -42984,7 +43531,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
42984
43531
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
42985
43532
  context.report({
42986
43533
  node: node.name,
42987
- message: MESSAGE$23
43534
+ message: MESSAGE$24
42988
43535
  });
42989
43536
  } })
42990
43537
  });
@@ -43173,7 +43720,7 @@ const noImpureStateUpdater = defineRule({
43173
43720
  });
43174
43721
  //#endregion
43175
43722
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
43176
- const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
43723
+ const MESSAGE$23 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
43177
43724
  const REACT_USE_REF_OPTIONS = {
43178
43725
  allowGlobalReactNamespace: false,
43179
43726
  allowUnboundBareCalls: false
@@ -43274,7 +43821,7 @@ const noIndeterminateAttribute = defineRule({
43274
43821
  if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
43275
43822
  context.report({
43276
43823
  node: indeterminateAttribute,
43277
- message: MESSAGE$22
43824
+ message: MESSAGE$23
43278
43825
  });
43279
43826
  },
43280
43827
  CallExpression(node) {
@@ -43283,7 +43830,7 @@ const noIndeterminateAttribute = defineRule({
43283
43830
  if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
43284
43831
  context.report({
43285
43832
  node,
43286
- message: MESSAGE$22
43833
+ message: MESSAGE$23
43287
43834
  });
43288
43835
  }
43289
43836
  };
@@ -43571,7 +44118,7 @@ const noIsMounted = defineRule({
43571
44118
  });
43572
44119
  //#endregion
43573
44120
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
43574
- const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
44121
+ const MESSAGE$22 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
43575
44122
  const isJsonMethodCall = (node, method) => {
43576
44123
  if (!isNodeOfType(node, "CallExpression")) return false;
43577
44124
  const callee = node.callee;
@@ -43635,13 +44182,13 @@ const noJsonParseStringifyClone = defineRule({
43635
44182
  if (isCatchParameterRoundTrip(firstArgument)) return;
43636
44183
  context.report({
43637
44184
  node,
43638
- message: MESSAGE$21
44185
+ message: MESSAGE$22
43639
44186
  });
43640
44187
  } })
43641
44188
  });
43642
44189
  //#endregion
43643
44190
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
43644
- const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
44191
+ const MESSAGE$21 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
43645
44192
  const isJsxElementTypeReference = (node) => {
43646
44193
  if (!isNodeOfType(node, "TSTypeReference")) return false;
43647
44194
  const typeName = node.typeName;
@@ -43693,7 +44240,7 @@ const noJsxElementType = defineRule({
43693
44240
  if (isJsxImported) return;
43694
44241
  for (const typeAnnotation of flaggedAnnotations) context.report({
43695
44242
  node: typeAnnotation,
43696
- message: MESSAGE$20
44243
+ message: MESSAGE$21
43697
44244
  });
43698
44245
  }
43699
44246
  };
@@ -44974,7 +45521,7 @@ const noMoment = defineRule({
44974
45521
  });
44975
45522
  //#endregion
44976
45523
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
44977
- const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
45524
+ const MESSAGE$20 = "This file declares several components, so each component is harder to find, test, and change.";
44978
45525
  const resolveSettings$16 = (settings) => {
44979
45526
  const reactDoctor = settings?.["react-doctor"];
44980
45527
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -45326,7 +45873,7 @@ const noMultiComp = defineRule({
45326
45873
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
45327
45874
  for (const component of flagged.slice(1)) context.report({
45328
45875
  node: component.reportNode,
45329
- message: MESSAGE$19
45876
+ message: MESSAGE$20
45330
45877
  });
45331
45878
  } };
45332
45879
  }
@@ -45414,41 +45961,6 @@ const noMutableInDeps = defineRule({
45414
45961
  }
45415
45962
  });
45416
45963
  //#endregion
45417
- //#region src/plugin/utils/is-result-discarded-call.ts
45418
- const isResultDiscardedCall = (callExpression) => {
45419
- let node = callExpression;
45420
- let parent = node.parent;
45421
- while (parent) {
45422
- if (isNodeOfType(parent, "ExpressionStatement")) return true;
45423
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
45424
- if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
45425
- if (isNodeOfType(parent, "ChainExpression")) {
45426
- node = parent;
45427
- parent = node.parent;
45428
- continue;
45429
- }
45430
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
45431
- node = parent;
45432
- parent = node.parent;
45433
- continue;
45434
- }
45435
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
45436
- node = parent;
45437
- parent = node.parent;
45438
- continue;
45439
- }
45440
- if (isNodeOfType(parent, "SequenceExpression")) {
45441
- const expressions = parent.expressions ?? [];
45442
- if (expressions[expressions.length - 1] !== node) return true;
45443
- node = parent;
45444
- parent = node.parent;
45445
- continue;
45446
- }
45447
- return false;
45448
- }
45449
- return false;
45450
- };
45451
- //#endregion
45452
45964
  //#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
45453
45965
  const LODASH_MUTATOR_NAMES = new Set([
45454
45966
  "set",
@@ -45539,7 +46051,7 @@ const resolveReducerFunction = (node, currentFilename) => {
45539
46051
  };
45540
46052
  //#endregion
45541
46053
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
45542
- const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
46054
+ const MESSAGE$19 = "This reducer changes state in place, so your update is silently skipped.";
45543
46055
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
45544
46056
  "copyWithin",
45545
46057
  "fill",
@@ -45743,7 +46255,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
45743
46255
  reportedNodes.add(options.crossFileConsumerCallSite);
45744
46256
  context.report({
45745
46257
  node: options.crossFileConsumerCallSite,
45746
- message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
46258
+ message: `${MESSAGE$19} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
45747
46259
  });
45748
46260
  return;
45749
46261
  }
@@ -45752,7 +46264,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
45752
46264
  reportedNodes.add(mutation.node);
45753
46265
  context.report({
45754
46266
  node: mutation.node,
45755
- message: MESSAGE$18
46267
+ message: MESSAGE$19
45756
46268
  });
45757
46269
  }
45758
46270
  };
@@ -46159,7 +46671,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
46159
46671
  });
46160
46672
  //#endregion
46161
46673
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
46162
- const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
46674
+ const MESSAGE$18 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
46163
46675
  const KEYBOARD_HANDLER_PROP_NAMES = [
46164
46676
  "onKeyDown",
46165
46677
  "onKeyUp",
@@ -46278,7 +46790,7 @@ const noNoninteractiveTabindex = defineRule({
46278
46790
  if (numeric === null) {
46279
46791
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
46280
46792
  node: tabIndex,
46281
- message: MESSAGE$17
46793
+ message: MESSAGE$18
46282
46794
  });
46283
46795
  return;
46284
46796
  }
@@ -46300,7 +46812,7 @@ const noNoninteractiveTabindex = defineRule({
46300
46812
  if (!roleAttribute) {
46301
46813
  context.report({
46302
46814
  node: tabIndex,
46303
- message: MESSAGE$17
46815
+ message: MESSAGE$18
46304
46816
  });
46305
46817
  return;
46306
46818
  }
@@ -46314,7 +46826,7 @@ const noNoninteractiveTabindex = defineRule({
46314
46826
  }
46315
46827
  context.report({
46316
46828
  node: tabIndex,
46317
- message: MESSAGE$17
46829
+ message: MESSAGE$18
46318
46830
  });
46319
46831
  } };
46320
46832
  }
@@ -48520,7 +49032,7 @@ const noRandomKey = defineRule({
48520
49032
  });
48521
49033
  //#endregion
48522
49034
  //#region src/plugin/rules/react-builtins/no-react-children.ts
48523
- const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
49035
+ const MESSAGE$17 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
48524
49036
  const isChildrenIdentifier = (node, contextNode) => {
48525
49037
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
48526
49038
  return isImportedFromModule(contextNode, "Children", "react");
@@ -48546,13 +49058,13 @@ const noReactChildren = defineRule({
48546
49058
  if (isChildrenIdentifier(memberObject, node)) {
48547
49059
  context.report({
48548
49060
  node: calleeOuter,
48549
- message: MESSAGE$16
49061
+ message: MESSAGE$17
48550
49062
  });
48551
49063
  return;
48552
49064
  }
48553
49065
  if (isReactNamespaceMember(memberObject, node)) context.report({
48554
49066
  node: calleeOuter,
48555
- message: MESSAGE$16
49067
+ message: MESSAGE$17
48556
49068
  });
48557
49069
  } })
48558
49070
  });
@@ -49434,7 +49946,7 @@ const noRenderPropChildren = defineRule({
49434
49946
  });
49435
49947
  //#endregion
49436
49948
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
49437
- const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
49949
+ const MESSAGE$16 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
49438
49950
  const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
49439
49951
  const isUsedAsReturnValue = (parent) => {
49440
49952
  if (!parent) return false;
@@ -49452,7 +49964,7 @@ const noRenderReturnValue = defineRule({
49452
49964
  if (!isUsedAsReturnValue(node.parent)) return;
49453
49965
  context.report({
49454
49966
  node: node.callee,
49455
- message: MESSAGE$15
49967
+ message: MESSAGE$16
49456
49968
  });
49457
49969
  } })
49458
49970
  });
@@ -50879,7 +51391,7 @@ const noSelfUpdatingEffect = defineRule({
50879
51391
  });
50880
51392
  //#endregion
50881
51393
  //#region src/plugin/rules/react-builtins/no-set-state.ts
50882
- const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
51394
+ const MESSAGE$15 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
50883
51395
  const noSetState = defineRule({
50884
51396
  id: "no-set-state",
50885
51397
  title: "Local class state forbidden",
@@ -50894,7 +51406,7 @@ const noSetState = defineRule({
50894
51406
  if (!getParentComponent(node)) return;
50895
51407
  context.report({
50896
51408
  node: node.callee,
50897
- message: MESSAGE$14
51409
+ message: MESSAGE$15
50898
51410
  });
50899
51411
  } })
50900
51412
  });
@@ -51264,7 +51776,7 @@ const isAbstractRole = (openingElement, settings) => {
51264
51776
  };
51265
51777
  //#endregion
51266
51778
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
51267
- const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
51779
+ const MESSAGE$14 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
51268
51780
  const DEFAULT_HANDLERS = [
51269
51781
  "onClick",
51270
51782
  "onMouseDown",
@@ -51379,7 +51891,7 @@ const noStaticElementInteractions = defineRule({
51379
51891
  if (!roleAttribute || !roleAttribute.value) {
51380
51892
  context.report({
51381
51893
  node: node.name,
51382
- message: MESSAGE$13
51894
+ message: MESSAGE$14
51383
51895
  });
51384
51896
  return;
51385
51897
  }
@@ -51389,7 +51901,7 @@ const noStaticElementInteractions = defineRule({
51389
51901
  if (isRecognizedRoleString(attributeValue.value)) return;
51390
51902
  context.report({
51391
51903
  node: node.name,
51392
- message: MESSAGE$13
51904
+ message: MESSAGE$14
51393
51905
  });
51394
51906
  return;
51395
51907
  }
@@ -51397,7 +51909,7 @@ const noStaticElementInteractions = defineRule({
51397
51909
  if (!settings.allowExpressionValues) {
51398
51910
  context.report({
51399
51911
  node: node.name,
51400
- message: MESSAGE$13
51912
+ message: MESSAGE$14
51401
51913
  });
51402
51914
  return;
51403
51915
  }
@@ -51405,7 +51917,7 @@ const noStaticElementInteractions = defineRule({
51405
51917
  if (isStaticNullishExpression(expression)) {
51406
51918
  context.report({
51407
51919
  node: node.name,
51408
- message: MESSAGE$13
51920
+ message: MESSAGE$14
51409
51921
  });
51410
51922
  return;
51411
51923
  }
@@ -51415,7 +51927,7 @@ const noStaticElementInteractions = defineRule({
51415
51927
  if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
51416
51928
  context.report({
51417
51929
  node: node.name,
51418
- message: MESSAGE$13
51930
+ message: MESSAGE$14
51419
51931
  });
51420
51932
  return;
51421
51933
  }
@@ -51423,7 +51935,7 @@ const noStaticElementInteractions = defineRule({
51423
51935
  }
51424
51936
  context.report({
51425
51937
  node: node.name,
51426
- message: MESSAGE$13
51938
+ message: MESSAGE$14
51427
51939
  });
51428
51940
  } };
51429
51941
  }
@@ -51529,7 +52041,7 @@ const noStringRefs = defineRule({
51529
52041
  });
51530
52042
  //#endregion
51531
52043
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
51532
- const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
52044
+ const MESSAGE$13 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
51533
52045
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
51534
52046
  const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
51535
52047
  const noSyncXhr = defineRule({
@@ -51550,7 +52062,7 @@ const noSyncXhr = defineRule({
51550
52062
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
51551
52063
  context.report({
51552
52064
  node,
51553
- message: MESSAGE$12
52065
+ message: MESSAGE$13
51554
52066
  });
51555
52067
  };
51556
52068
  return {
@@ -51575,7 +52087,7 @@ const noSyncXhr = defineRule({
51575
52087
  });
51576
52088
  //#endregion
51577
52089
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
51578
- const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
52090
+ const MESSAGE$12 = "This value is `undefined` because function components have no `this`.";
51579
52091
  const isInsideClassMethod = (node, customClassFactoryNames) => {
51580
52092
  let ancestor = node.parent;
51581
52093
  while (ancestor) {
@@ -51663,7 +52175,7 @@ const noThisInSfc = defineRule({
51663
52175
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
51664
52176
  context.report({
51665
52177
  node,
51666
- message: MESSAGE$11
52178
+ message: MESSAGE$12
51667
52179
  });
51668
52180
  } };
51669
52181
  }
@@ -53686,7 +54198,7 @@ const isElementTypeJsxAttribute = (node) => {
53686
54198
  const attributeName = node.name.name;
53687
54199
  return ELEMENT_TYPE_PROP_NAMES.has(attributeName.toLowerCase()) || attributeName.endsWith("Component");
53688
54200
  };
53689
- const isReactUseMemoCallback = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
54201
+ const isReactUseMemoCallback$1 = (call, valueNode, scopes) => call.arguments[0] === valueNode && isReactApiCall(call, "useMemo", scopes, {
53690
54202
  allowGlobalReactNamespace: true,
53691
54203
  resolveNamedAliases: true
53692
54204
  });
@@ -53710,7 +54222,7 @@ const isRenderFlowingReadReference = (identifier, scopes, visitedSymbols = /* @_
53710
54222
  case "ArrowFunctionExpression": return false;
53711
54223
  case "CallExpression":
53712
54224
  if (parent.callee === valueNode) return false;
53713
- if (isReactUseMemoCallback(parent, valueNode, scopes)) return false;
54225
+ if (isReactUseMemoCallback$1(parent, valueNode, scopes)) return false;
53714
54226
  if (isReactCreateElementCall(parent, scopes)) return true;
53715
54227
  valueNode = parent;
53716
54228
  parent = parent.parent;
@@ -54033,7 +54545,7 @@ const noWideLetterSpacing = defineRule({
54033
54545
  //#endregion
54034
54546
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
54035
54547
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
54036
- const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
54548
+ const MESSAGE$11 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
54037
54549
  const resolveSettings$7 = (settings) => {
54038
54550
  const reactDoctor = settings?.["react-doctor"];
54039
54551
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -54067,7 +54579,7 @@ const noWillUpdateSetState = defineRule({
54067
54579
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
54068
54580
  context.report({
54069
54581
  node: node.callee,
54070
- message: MESSAGE$10
54582
+ message: MESSAGE$11
54071
54583
  });
54072
54584
  } };
54073
54585
  }
@@ -55867,7 +56379,7 @@ const preactNoRenderArguments = defineRule({
55867
56379
  });
55868
56380
  //#endregion
55869
56381
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
55870
- const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
56382
+ const MESSAGE$10 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
55871
56383
  const preactPreferOndblclick = defineRule({
55872
56384
  id: "preact-prefer-ondblclick",
55873
56385
  title: "onDoubleClick instead of onDblClick",
@@ -55882,7 +56394,7 @@ const preactPreferOndblclick = defineRule({
55882
56394
  if (!onDoubleClickAttribute) return;
55883
56395
  context.report({
55884
56396
  node: onDoubleClickAttribute,
55885
- message: MESSAGE$9
56397
+ message: MESSAGE$10
55886
56398
  });
55887
56399
  } })
55888
56400
  });
@@ -56122,7 +56634,7 @@ const preferExplicitVariants = defineRule({
56122
56634
  });
56123
56635
  //#endregion
56124
56636
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
56125
- const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
56637
+ const MESSAGE$9 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
56126
56638
  const resolveSettings$4 = (settings) => {
56127
56639
  const reactDoctor = settings?.["react-doctor"];
56128
56640
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -56161,7 +56673,7 @@ const preferFunctionComponent = defineRule({
56161
56673
  const reportNode = node.id ?? node;
56162
56674
  context.report({
56163
56675
  node: reportNode,
56164
- message: MESSAGE$8
56676
+ message: MESSAGE$9
56165
56677
  });
56166
56678
  };
56167
56679
  return {
@@ -57482,7 +57994,8 @@ const preferUseReducer = defineRule({
57482
57994
  //#endregion
57483
57995
  //#region src/plugin/rules/security-scan/utils/is-public-debug-artifact-path.ts
57484
57996
  const LOCALE_DIRECTORY_PATTERN = /(?:^|\/)(?:locales?|i18n|lang|langs|translations?)\//i;
57485
- const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && /(?:^|\/)(?:\.env(?:\.[^/]*)?|[^/]*(?:debug|crash|trace|stack|report|dump|phpinfo)[^/]*\.(?:txt|log|json|html?)|[^/]+\.log)$/i.test(relativePath);
57997
+ const PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN = /(?:^|\/)(?:\.env(?:\.[^/]*)?|(?:debug|crash|trace|stack[-_]?trace|report|dump|phpinfo)(?:[-_.][^/]*)?\.(?:txt|log|json|html?)|[^/]+\.log)$/i;
57998
+ const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN.test(relativePath);
57486
57999
  //#endregion
57487
58000
  //#region src/plugin/rules/security-scan/public-debug-artifact.ts
57488
58001
  const publicDebugArtifact = defineRule({
@@ -58361,7 +58874,7 @@ const reactCompilerNoManualMemoization = defineRule({
58361
58874
  });
58362
58875
  //#endregion
58363
58876
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
58364
- const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
58877
+ const MESSAGE$8 = "This JSX crashes because `React` isn't in scope.";
58365
58878
  const reactInJsxScope = defineRule({
58366
58879
  id: "react-in-jsx-scope",
58367
58880
  title: "React not in scope for JSX",
@@ -58373,21 +58886,21 @@ const reactInJsxScope = defineRule({
58373
58886
  if (findVariableInitializer(node, "React")) return;
58374
58887
  context.report({
58375
58888
  node: node.name,
58376
- message: MESSAGE$7
58889
+ message: MESSAGE$8
58377
58890
  });
58378
58891
  },
58379
58892
  JSXFragment(node) {
58380
58893
  if (findVariableInitializer(node, "React")) return;
58381
58894
  context.report({
58382
58895
  node: node.openingFragment,
58383
- message: MESSAGE$7
58896
+ message: MESSAGE$8
58384
58897
  });
58385
58898
  }
58386
58899
  })
58387
58900
  });
58388
58901
  //#endregion
58389
58902
  //#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
58390
- const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
58903
+ const MESSAGE$7 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
58391
58904
  const REACT_MARKDOWN_MODULE = "react-markdown";
58392
58905
  const REHYPE_RAW_MODULE = "rehype-raw";
58393
58906
  const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
@@ -58539,7 +59052,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
58539
59052
  if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
58540
59053
  context.report({
58541
59054
  node,
58542
- message: MESSAGE$6
59055
+ message: MESSAGE$7
58543
59056
  });
58544
59057
  } }))
58545
59058
  });
@@ -58595,7 +59108,7 @@ const inlineUseSelectorFunction = (callNode, aliases) => {
58595
59108
  };
58596
59109
  //#endregion
58597
59110
  //#region src/plugin/rules/state-and-effects/redux-useselector-inline-derivation.ts
58598
- const ALLOCATING_ARRAY_METHODS = new Set([
59111
+ const ALLOCATING_ARRAY_METHODS$1 = new Set([
58599
59112
  "filter",
58600
59113
  "map",
58601
59114
  "flatMap",
@@ -58631,7 +59144,7 @@ const getAllocatingCallSiteDescription = (expression) => {
58631
59144
  method: methodName
58632
59145
  };
58633
59146
  }
58634
- if (ALLOCATING_ARRAY_METHODS.has(methodName)) return {
59147
+ if (ALLOCATING_ARRAY_METHODS$1.has(methodName)) return {
58635
59148
  kind: "method",
58636
59149
  method: methodName
58637
59150
  };
@@ -58694,7 +59207,7 @@ const reduxUseselectorInlineDerivation = defineRule({
58694
59207
  });
58695
59208
  //#endregion
58696
59209
  //#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
58697
- const MESSAGE$5 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
59210
+ const MESSAGE$6 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
58698
59211
  const isConciseBodyReturningCollection = (functionNode) => {
58699
59212
  if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
58700
59213
  const rawBody = functionNode.body;
@@ -58729,13 +59242,567 @@ const reduxUseselectorReturnsNewCollection = defineRule({
58729
59242
  if (!isConciseBodyReturningCollection(selectorArgument)) return;
58730
59243
  context.report({
58731
59244
  node,
58732
- message: MESSAGE$5
59245
+ message: MESSAGE$6
58733
59246
  });
58734
59247
  }
58735
59248
  };
58736
59249
  }
58737
59250
  });
58738
59251
  //#endregion
59252
+ //#region src/plugin/utils/is-remotion-module-source.ts
59253
+ const isRemotionModuleSource = (moduleSource) => moduleSource === "remotion" || moduleSource.startsWith("@remotion/");
59254
+ //#endregion
59255
+ //#region src/plugin/utils/resolve-remotion-api.ts
59256
+ const resolveRemotionApi = (referenceNode, scopes) => {
59257
+ const candidate = stripParenExpression(referenceNode);
59258
+ if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXIdentifier")) {
59259
+ const symbol = scopes.symbolFor(candidate);
59260
+ if (symbol?.kind !== "import") return null;
59261
+ const importBinding = getImportBindingForName(candidate, symbol.name);
59262
+ if (!importBinding || importBinding.isNamespace || importBinding.exportedName === null || !isRemotionModuleSource(importBinding.source)) return null;
59263
+ return {
59264
+ apiName: importBinding.exportedName,
59265
+ moduleSource: importBinding.source
59266
+ };
59267
+ }
59268
+ if (isNodeOfType(candidate, "MemberExpression")) {
59269
+ const apiName = getStaticPropertyKeyName(candidate, { allowComputedString: true });
59270
+ const namespaceObject = stripParenExpression(candidate.object);
59271
+ if (!apiName || !isNodeOfType(namespaceObject, "Identifier")) return null;
59272
+ const symbol = scopes.symbolFor(namespaceObject);
59273
+ if (symbol?.kind !== "import") return null;
59274
+ const importBinding = getImportBindingForName(namespaceObject, symbol.name);
59275
+ if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
59276
+ return {
59277
+ apiName,
59278
+ moduleSource: importBinding.source
59279
+ };
59280
+ }
59281
+ if (!isNodeOfType(candidate, "JSXMemberExpression") || !isNodeOfType(candidate.object, "JSXIdentifier") || !isNodeOfType(candidate.property, "JSXIdentifier")) return null;
59282
+ const symbol = scopes.symbolFor(candidate.object);
59283
+ if (symbol?.kind !== "import") return null;
59284
+ const importBinding = getImportBindingForName(candidate.object, symbol.name);
59285
+ if (!importBinding?.isNamespace || !isRemotionModuleSource(importBinding.source)) return null;
59286
+ return {
59287
+ apiName: candidate.property.name,
59288
+ moduleSource: importBinding.source
59289
+ };
59290
+ };
59291
+ //#endregion
59292
+ //#region src/plugin/utils/create-remotion-composition-ownership-analyzer.ts
59293
+ const compositionAttributeFunctionCacheBySettings = /* @__PURE__ */ new WeakMap();
59294
+ const getFunctionExportKeys = (filePath, programNode, functionNode) => getFunctionExportNames(programNode, functionNode).map((exportedName) => `${normalizeFilename(filePath)}\0${exportedName}`);
59295
+ const resolveImportedCompositionFunction = (expression, module) => {
59296
+ const unwrappedExpression = stripParenExpression(expression);
59297
+ let importReference;
59298
+ let exportedName;
59299
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
59300
+ const symbol = module.scopes.symbolFor(unwrappedExpression);
59301
+ if (symbol?.kind !== "import") return null;
59302
+ importReference = unwrappedExpression;
59303
+ exportedName = getImportBindingForName(unwrappedExpression, symbol.name)?.exportedName ?? null;
59304
+ } else if (isNodeOfType(unwrappedExpression, "MemberExpression") && isNodeOfType(stripParenExpression(unwrappedExpression.object), "Identifier")) {
59305
+ const namespaceObject = stripParenExpression(unwrappedExpression.object);
59306
+ if (!isNodeOfType(namespaceObject, "Identifier")) return null;
59307
+ const symbol = module.scopes.symbolFor(namespaceObject);
59308
+ if (symbol?.kind !== "import") return null;
59309
+ if (!getImportBindingForName(namespaceObject, symbol.name)?.isNamespace) return null;
59310
+ importReference = namespaceObject;
59311
+ exportedName = getStaticPropertyKeyName(unwrappedExpression, { allowComputedString: true });
59312
+ } else return null;
59313
+ if (!exportedName) return null;
59314
+ const importBinding = getImportBindingForName(importReference, importReference.name);
59315
+ if (!importBinding) return null;
59316
+ return resolveCrossFileFunctionExportWithFilePath(module.filePath, importBinding.source, exportedName);
59317
+ };
59318
+ const collectCompositionAttributeFunctionKeys = (context, currentProgram, attributeName) => {
59319
+ const filename = context.filename ? normalizeFilename(context.filename) : "";
59320
+ const rootDirectorySetting = getReactDoctorStringSetting(context.settings, "rootDirectory");
59321
+ const rootDirectory = rootDirectorySetting ? normalizeFilename(rootDirectorySetting).replace(/\/$/, "") : "";
59322
+ if (!filename || !rootDirectory || filename !== rootDirectory && !filename.startsWith(`${rootDirectory}/`)) return null;
59323
+ const projectIndex = buildSourceProjectIndex(rootDirectory, filename, currentProgram, context.scopes);
59324
+ if (!projectIndex) return null;
59325
+ const functionKeys = /* @__PURE__ */ new Set();
59326
+ for (const module of projectIndex.modulesByFilePath.values()) walkAst(module.programNode, (candidate) => {
59327
+ if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
59328
+ const apiBinding = resolveRemotionApi(candidate.name, module.scopes);
59329
+ if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
59330
+ const functionAttribute = findJsxAttribute(candidate.attributes, attributeName);
59331
+ if (!functionAttribute?.value || !isNodeOfType(functionAttribute.value, "JSXExpressionContainer") || !functionAttribute.value.expression) return;
59332
+ const resolvedFunction = resolveImportedCompositionFunction(functionAttribute.value.expression, module);
59333
+ if (!resolvedFunction || !isNodeOfType(resolvedFunction.programNode, "Program")) return;
59334
+ for (const functionKey of getFunctionExportKeys(resolvedFunction.filePath, resolvedFunction.programNode, resolvedFunction.functionNode)) functionKeys.add(functionKey);
59335
+ });
59336
+ return functionKeys;
59337
+ };
59338
+ const createRemotionCompositionAttributeOwnershipAnalyzer = (context, attributeName) => {
59339
+ const settings = context.settings;
59340
+ return (functionNode) => {
59341
+ const currentProgram = findProgramRoot(functionNode);
59342
+ if (!currentProgram || !isNodeOfType(currentProgram, "Program") || !context.filename || !settings) return false;
59343
+ const currentFunctionKeys = getFunctionExportKeys(context.filename, currentProgram, functionNode);
59344
+ if (currentFunctionKeys.length === 0) return false;
59345
+ let cacheByAttributeName = compositionAttributeFunctionCacheBySettings.get(settings);
59346
+ if (!cacheByAttributeName) {
59347
+ cacheByAttributeName = /* @__PURE__ */ new Map();
59348
+ compositionAttributeFunctionCacheBySettings.set(settings, cacheByAttributeName);
59349
+ }
59350
+ let cache = cacheByAttributeName.get(attributeName);
59351
+ if (!cache) {
59352
+ cache = { failedFilenames: /* @__PURE__ */ new Set() };
59353
+ cacheByAttributeName.set(attributeName, cache);
59354
+ }
59355
+ let functionKeys = cache.functionKeys;
59356
+ if (!functionKeys) {
59357
+ const filename = normalizeFilename(context.filename);
59358
+ if (cache.failedFilenames.has(filename)) return false;
59359
+ const collectedFunctionKeys = collectCompositionAttributeFunctionKeys(context, currentProgram, attributeName);
59360
+ if (!collectedFunctionKeys) {
59361
+ cache.failedFilenames.add(filename);
59362
+ return false;
59363
+ }
59364
+ functionKeys = collectedFunctionKeys;
59365
+ cache.functionKeys = collectedFunctionKeys;
59366
+ }
59367
+ return currentFunctionKeys.some((functionKey) => functionKeys.has(functionKey));
59368
+ };
59369
+ };
59370
+ const createRemotionCompositionOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "component");
59371
+ const createRemotionMetadataOwnershipAnalyzer = (context) => createRemotionCompositionAttributeOwnershipAnalyzer(context, "calculateMetadata");
59372
+ //#endregion
59373
+ //#region src/plugin/rules/correctness/remotion-calculate-metadata-fetch-signal.ts
59374
+ const getParameterIdentifier = (parameter) => {
59375
+ if (isNodeOfType(parameter, "Identifier")) return parameter;
59376
+ if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
59377
+ return null;
59378
+ };
59379
+ const getAbortSignalBinding = (functionNode) => {
59380
+ if (!isFunctionLike$1(functionNode)) return {
59381
+ parameterIdentifier: null,
59382
+ signalIdentifier: null
59383
+ };
59384
+ const firstParameter = functionNode.params[0];
59385
+ const parameterIdentifier = getParameterIdentifier(firstParameter);
59386
+ if (parameterIdentifier) return {
59387
+ parameterIdentifier,
59388
+ signalIdentifier: null
59389
+ };
59390
+ if (!isNodeOfType(firstParameter, "ObjectPattern")) return {
59391
+ parameterIdentifier: null,
59392
+ signalIdentifier: null
59393
+ };
59394
+ for (const property of firstParameter.properties) {
59395
+ if (!isNodeOfType(property, "Property")) continue;
59396
+ if (getStaticPropertyKeyName(property) !== "abortSignal") continue;
59397
+ const signalIdentifier = getParameterIdentifier(property.value);
59398
+ if (signalIdentifier) return {
59399
+ parameterIdentifier: null,
59400
+ signalIdentifier
59401
+ };
59402
+ }
59403
+ return {
59404
+ parameterIdentifier: null,
59405
+ signalIdentifier: null
59406
+ };
59407
+ };
59408
+ const identifiersResolveToSameSymbol = (leftIdentifier, rightIdentifier, scopes) => {
59409
+ if (!isNodeOfType(leftIdentifier, "Identifier") || !isNodeOfType(rightIdentifier, "Identifier")) return false;
59410
+ const leftSymbol = scopes.symbolFor(leftIdentifier);
59411
+ return Boolean(leftSymbol && leftSymbol === scopes.symbolFor(rightIdentifier));
59412
+ };
59413
+ const isMetadataAbortSignal = (expression, binding, scopes) => {
59414
+ const candidate = stripParenExpression(expression);
59415
+ if (binding.signalIdentifier && identifiersResolveToSameSymbol(candidate, binding.signalIdentifier, scopes)) return true;
59416
+ return Boolean(binding.parameterIdentifier && isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "abortSignal" && identifiersResolveToSameSymbol(candidate.object, binding.parameterIdentifier, scopes));
59417
+ };
59418
+ const fetchUsesMetadataAbortSignal = (fetchCall, binding, scopes) => {
59419
+ const optionsArgument = fetchCall.arguments[1];
59420
+ if (!optionsArgument) return false;
59421
+ const options = stripParenExpression(optionsArgument);
59422
+ if (!isNodeOfType(options, "ObjectExpression")) return null;
59423
+ if (options.properties.some((property) => isNodeOfType(property, "SpreadElement"))) return null;
59424
+ for (let propertyIndex = options.properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
59425
+ const property = options.properties[propertyIndex];
59426
+ if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "signal") return isMetadataAbortSignal(property.value, binding, scopes);
59427
+ }
59428
+ return false;
59429
+ };
59430
+ const reportFetchesWithoutAbortSignal = (functionNode, context) => {
59431
+ const binding = getAbortSignalBinding(functionNode);
59432
+ walkAst(functionNode, (candidate) => {
59433
+ if (candidate !== functionNode && isFunctionLike$1(candidate)) return false;
59434
+ if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "Identifier") || candidate.callee.name !== "fetch" || !context.scopes.isGlobalReference(candidate.callee) || fetchUsesMetadataAbortSignal(candidate, binding, context.scopes) !== false) return;
59435
+ context.report({
59436
+ node: candidate,
59437
+ message: "Pass Remotion's `abortSignal` to this fetch with `{signal: abortSignal}` so superseded metadata requests are cancelled."
59438
+ });
59439
+ });
59440
+ };
59441
+ const isCalculateMetadataFunctionType = (variableDeclarator) => {
59442
+ if (!isNodeOfType(variableDeclarator.id, "Identifier")) return false;
59443
+ const annotation = variableDeclarator.id.typeAnnotation;
59444
+ if (!isNodeOfType(annotation, "TSTypeAnnotation") || !isNodeOfType(annotation.typeAnnotation, "TSTypeReference")) return false;
59445
+ const typeName = annotation.typeAnnotation.typeName;
59446
+ if (!isNodeOfType(typeName, "Identifier")) return false;
59447
+ const apiBinding = getImportBindingForName(typeName, typeName.name);
59448
+ return Boolean(apiBinding?.exportedName === "CalculateMetadataFunction" && apiBinding.source === "remotion");
59449
+ };
59450
+ const remotionCalculateMetadataFetchSignal = defineRule({
59451
+ id: "remotion-calculate-metadata-fetch-signal",
59452
+ title: "calculateMetadata fetch ignores abortSignal",
59453
+ tags: ["react-jsx-only"],
59454
+ requires: ["remotion:4"],
59455
+ severity: "error",
59456
+ recommendation: "Destructure `abortSignal` from the calculateMetadata argument and pass it to direct fetch calls as `{signal: abortSignal}`.",
59457
+ create: (context) => {
59458
+ const analyzedFunctions = /* @__PURE__ */ new WeakSet();
59459
+ const isOwnedByCalculateMetadata = createRemotionMetadataOwnershipAnalyzer(context);
59460
+ const analyzeFunction = (functionNode) => {
59461
+ if (!functionNode || analyzedFunctions.has(functionNode)) return;
59462
+ analyzedFunctions.add(functionNode);
59463
+ reportFetchesWithoutAbortSignal(functionNode, context);
59464
+ };
59465
+ return {
59466
+ CallExpression(node) {
59467
+ if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "fetch" || !context.scopes.isGlobalReference(node.callee)) return;
59468
+ const functionNode = findEnclosingFunction$1(node);
59469
+ if (functionNode && isOwnedByCalculateMetadata(functionNode)) analyzeFunction(functionNode);
59470
+ },
59471
+ JSXOpeningElement(node) {
59472
+ const apiBinding = resolveRemotionApi(node.name, context.scopes);
59473
+ if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
59474
+ const calculateMetadataAttribute = findJsxAttribute(node.attributes, "calculateMetadata");
59475
+ if (!calculateMetadataAttribute?.value || !isNodeOfType(calculateMetadataAttribute.value, "JSXExpressionContainer") || !calculateMetadataAttribute.value.expression) return;
59476
+ analyzeFunction(resolveExactLocalFunction(calculateMetadataAttribute.value.expression, context.scopes));
59477
+ },
59478
+ VariableDeclarator(node) {
59479
+ if (!node.init || !isCalculateMetadataFunctionType(node)) return;
59480
+ analyzeFunction(resolveExactLocalFunction(node.init, context.scopes));
59481
+ }
59482
+ };
59483
+ }
59484
+ });
59485
+ //#endregion
59486
+ //#region src/plugin/utils/create-remotion-render-evidence-checker.ts
59487
+ const REMOTION_RENDER_CALL_NAMES = new Set([
59488
+ "continueRender",
59489
+ "delayRender",
59490
+ "getInputProps",
59491
+ "random",
59492
+ "spring",
59493
+ "useCurrentFrame",
59494
+ "useDelayRender",
59495
+ "useVideoConfig"
59496
+ ]);
59497
+ const REMOTION_RENDER_COMPONENT_MODULE_BY_NAME = new Map([
59498
+ ["Audio", "@remotion/media"],
59499
+ ["Freeze", "remotion"],
59500
+ ["IFrame", "remotion"],
59501
+ ["Img", "remotion"],
59502
+ ["Loop", "remotion"],
59503
+ ["OffthreadVideo", "remotion"],
59504
+ ["Sequence", "remotion"],
59505
+ ["Series", "remotion"],
59506
+ ["Video", "@remotion/media"]
59507
+ ]);
59508
+ const createRemotionRenderEvidenceChecker = (context) => {
59509
+ const scopes = context.scopes;
59510
+ const evidenceByFunction = /* @__PURE__ */ new WeakMap();
59511
+ const registeredCompositionFunctions = /* @__PURE__ */ new WeakSet();
59512
+ const inspectedPrograms = /* @__PURE__ */ new WeakSet();
59513
+ const isOwnedByRegisteredComposition = createRemotionCompositionOwnershipAnalyzer(context);
59514
+ const collectRegisteredCompositionFunctions = (functionNode) => {
59515
+ const program = findProgramRoot(functionNode);
59516
+ if (!program || inspectedPrograms.has(program)) return;
59517
+ inspectedPrograms.add(program);
59518
+ walkAst(program, (candidate) => {
59519
+ if (!isNodeOfType(candidate, "JSXOpeningElement")) return;
59520
+ const apiBinding = resolveRemotionApi(candidate.name, scopes);
59521
+ if (apiBinding?.apiName !== "Composition" || apiBinding.moduleSource !== "remotion") return;
59522
+ const componentAttribute = findJsxAttribute(candidate.attributes, "component");
59523
+ if (!componentAttribute?.value || !isNodeOfType(componentAttribute.value, "JSXExpressionContainer") || !componentAttribute.value.expression) return;
59524
+ const registeredFunction = resolveExactLocalFunction(componentAttribute.value.expression, scopes);
59525
+ if (registeredFunction) registeredCompositionFunctions.add(registeredFunction);
59526
+ });
59527
+ };
59528
+ const functionUsesRemotionRenderApi = (functionNode) => {
59529
+ let hasEvidence = false;
59530
+ walkAst(functionNode, (candidate) => {
59531
+ if (hasEvidence) return false;
59532
+ if (!isNodeOfType(candidate, "CallExpression") && !isNodeOfType(candidate, "JSXOpeningElement")) return;
59533
+ const apiBinding = resolveRemotionApi(isNodeOfType(candidate, "CallExpression") ? candidate.callee : candidate.name, scopes);
59534
+ if (isNodeOfType(candidate, "CallExpression") && apiBinding?.moduleSource === "remotion" && REMOTION_RENDER_CALL_NAMES.has(apiBinding.apiName) || isNodeOfType(candidate, "JSXOpeningElement") && apiBinding !== null && REMOTION_RENDER_COMPONENT_MODULE_BY_NAME.get(apiBinding.apiName) === apiBinding.moduleSource) {
59535
+ hasEvidence = true;
59536
+ return false;
59537
+ }
59538
+ });
59539
+ return hasEvidence;
59540
+ };
59541
+ return { functionHasEvidence: (functionNode) => {
59542
+ const cachedEvidence = evidenceByFunction.get(functionNode);
59543
+ if (cachedEvidence !== void 0) return cachedEvidence;
59544
+ collectRegisteredCompositionFunctions(functionNode);
59545
+ const hasEvidence = registeredCompositionFunctions.has(functionNode) || functionUsesRemotionRenderApi(functionNode) || isOwnedByRegisteredComposition(functionNode);
59546
+ evidenceByFunction.set(functionNode, hasEvidence);
59547
+ return hasEvidence;
59548
+ } };
59549
+ };
59550
+ //#endregion
59551
+ //#region src/plugin/rules/correctness/remotion-deterministic-randomness.ts
59552
+ const isGlobalMathObject = (node, scopes) => {
59553
+ const candidate = stripParenExpression(node);
59554
+ if (isNodeOfType(candidate, "Identifier")) return candidate.name === "Math" && scopes.isGlobalReference(candidate);
59555
+ return Boolean(isNodeOfType(candidate, "MemberExpression") && getStaticPropertyKeyName(candidate, { allowComputedString: true }) === "Math" && isNodeOfType(candidate.object, "Identifier") && candidate.object.name === "globalThis" && scopes.isGlobalReference(candidate.object));
59556
+ };
59557
+ const remotionDeterministicRandomness = defineRule({
59558
+ id: "remotion-deterministic-randomness",
59559
+ title: "Randomness changes between rendered frames",
59560
+ tags: ["react-jsx-only"],
59561
+ requires: ["remotion:4"],
59562
+ severity: "error",
59563
+ recommendation: "Use Remotion's seeded `random(seed)` helper so the same frame produces the same value in every render tab.",
59564
+ create: (context) => {
59565
+ const renderEvidence = createRemotionRenderEvidenceChecker(context);
59566
+ return { CallExpression(node) {
59567
+ const callee = stripParenExpression(node.callee);
59568
+ if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyKeyName(callee, { allowComputedString: true }) !== "random" || !isGlobalMathObject(callee.object, context.scopes)) return;
59569
+ const componentOrHook = findRenderPhaseComponentOrHook(node, context.scopes) ?? findEnclosingFunction$1(node);
59570
+ if (!componentOrHook || !renderEvidence.functionHasEvidence(componentOrHook)) return;
59571
+ const displayName = componentOrHookDisplayNameForFunction(componentOrHook);
59572
+ if (displayName && !isReactHookName(displayName) && !functionHasReactComponentEvidence(componentOrHook, context.scopes, context.cfg)) return;
59573
+ context.report({
59574
+ node,
59575
+ message: "`Math.random()` can return a different value in each parallel Remotion render tab, so the same frame is not deterministic. Use `random(seed)` from `remotion` instead."
59576
+ });
59577
+ } };
59578
+ }
59579
+ });
59580
+ //#endregion
59581
+ //#region src/plugin/utils/create-remotion-css-time-rule-visitors.ts
59582
+ const createRemotionCssTimeRuleVisitors = (context, options) => {
59583
+ const renderEvidence = createRemotionRenderEvidenceChecker(context);
59584
+ return {
59585
+ Property(node) {
59586
+ if (!options.stylePropertyNames.has(getStaticPropertyKeyName(node) ?? "")) return;
59587
+ const styleObject = node.parent;
59588
+ const expressionContainer = styleObject?.parent;
59589
+ const styleAttribute = expressionContainer?.parent;
59590
+ if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
59591
+ const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
59592
+ if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
59593
+ context.report({
59594
+ node,
59595
+ message: options.styleMessage
59596
+ });
59597
+ },
59598
+ JSXAttribute(node) {
59599
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "className") return;
59600
+ const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
59601
+ if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
59602
+ const className = getJsxAttributeStaticString(node);
59603
+ if (!className) return;
59604
+ if (className.split(/\s+/).filter(Boolean).some((classToken) => options.classTokenIsForbidden(classToken.split(":").at(-1) ?? ""))) context.report({
59605
+ node,
59606
+ message: options.classMessage
59607
+ });
59608
+ }
59609
+ };
59610
+ };
59611
+ //#endregion
59612
+ //#region src/plugin/rules/correctness/remotion-no-css-animation.ts
59613
+ const ANIMATION_STYLE_PROPERTY_NAMES = new Set(["animation", "animationName"]);
59614
+ const remotionNoCssAnimation = defineRule({
59615
+ id: "remotion-no-css-animation",
59616
+ title: "CSS animation is not frame-driven",
59617
+ tags: ["react-jsx-only"],
59618
+ requires: ["remotion:4"],
59619
+ severity: "error",
59620
+ recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
59621
+ create: (context) => createRemotionCssTimeRuleVisitors(context, {
59622
+ classTokenIsForbidden: (classToken) => classToken.startsWith("animate-") && classToken !== "animate-none",
59623
+ classMessage: "Tailwind animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
59624
+ styleMessage: "CSS animations advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
59625
+ stylePropertyNames: ANIMATION_STYLE_PROPERTY_NAMES
59626
+ })
59627
+ });
59628
+ //#endregion
59629
+ //#region src/plugin/rules/correctness/remotion-no-css-transition.ts
59630
+ const TRANSITION_STYLE_PROPERTY_NAMES = new Set(["transition", "transitionProperty"]);
59631
+ const remotionNoCssTransition = defineRule({
59632
+ id: "remotion-no-css-transition",
59633
+ title: "CSS transition is not frame-driven",
59634
+ tags: ["react-jsx-only"],
59635
+ requires: ["remotion:4"],
59636
+ severity: "error",
59637
+ recommendation: "Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",
59638
+ create: (context) => createRemotionCssTimeRuleVisitors(context, {
59639
+ classTokenIsForbidden: (classToken) => (classToken === "transition" || classToken.startsWith("transition-")) && classToken !== "transition-none",
59640
+ classMessage: "Tailwind transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
59641
+ styleMessage: "CSS transitions advance on browser time, so Remotion can capture inconsistent frames. Drive the property from `useCurrentFrame()` instead.",
59642
+ stylePropertyNames: TRANSITION_STYLE_PROPERTY_NAMES
59643
+ })
59644
+ });
59645
+ //#endregion
59646
+ //#region src/plugin/rules/correctness/remotion-no-css-url-assets.ts
59647
+ const CSS_URL_ASSET_PROPERTY_NAMES = new Set([
59648
+ "backgroundImage",
59649
+ "maskImage",
59650
+ "WebkitMaskImage"
59651
+ ]);
59652
+ const CSS_URL_PATTERN = /\burl\(\s*(["']?)([^"')]+)\1\s*\)/i;
59653
+ const isEmbeddedAssetSource = (assetSource) => assetSource.startsWith("data:") || assetSource.startsWith("#");
59654
+ const getStaticStringExpression = (node) => {
59655
+ if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
59656
+ if (isNodeOfType(node, "TemplateLiteral") && node.expressions.length === 0 && node.quasis.length === 1) return node.quasis[0].value.raw;
59657
+ return null;
59658
+ };
59659
+ const isReactUseMemoCallback = (node, scopes) => {
59660
+ const parent = node.parent;
59661
+ return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === node && isReactApiCall(parent, "useMemo", scopes, { resolveNamedAliases: true }));
59662
+ };
59663
+ const componentPreloadsStaticImage = (componentNode, assetSource, scopes) => {
59664
+ let hasPreload = false;
59665
+ walkAst(componentNode, (child) => {
59666
+ if (hasPreload) return false;
59667
+ if (child !== componentNode && isFunctionLike$1(child) && !isReactUseMemoCallback(child, scopes)) return false;
59668
+ if (!isNodeOfType(child, "JSXOpeningElement")) return;
59669
+ const apiBinding = resolveRemotionApi(child.name, scopes);
59670
+ if (apiBinding?.apiName !== "Img" || apiBinding.moduleSource !== "remotion") return;
59671
+ const sourceAttribute = findJsxAttribute(child.attributes, "src");
59672
+ if (sourceAttribute && getJsxAttributeStaticString(sourceAttribute) === assetSource) {
59673
+ hasPreload = true;
59674
+ return false;
59675
+ }
59676
+ });
59677
+ return hasPreload;
59678
+ };
59679
+ const remotionNoCssUrlAssets = defineRule({
59680
+ id: "remotion-no-css-url-assets",
59681
+ title: "CSS URL asset can flicker in Remotion",
59682
+ tags: ["react-jsx-only"],
59683
+ requires: ["remotion:4"],
59684
+ severity: "error",
59685
+ recommendation: "Render the asset with `Img` inside an `AbsoluteFill`, or preload the same source with a hidden `Img` when a CSS mask is required.",
59686
+ create: (context) => {
59687
+ const renderEvidence = createRemotionRenderEvidenceChecker(context);
59688
+ return { Property(node) {
59689
+ if (!CSS_URL_ASSET_PROPERTY_NAMES.has(getStaticPropertyKeyName(node) ?? "")) return;
59690
+ const styleObject = node.parent;
59691
+ const expressionContainer = styleObject?.parent;
59692
+ const styleAttribute = expressionContainer?.parent;
59693
+ if (!isNodeOfType(styleObject, "ObjectExpression") || !isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== styleObject || !isNodeOfType(styleAttribute, "JSXAttribute") || !isNodeOfType(styleAttribute.name, "JSXIdentifier") || styleAttribute.name.name !== "style") return;
59694
+ const cssValue = getStaticStringExpression(node.value);
59695
+ const urlMatch = cssValue ? CSS_URL_PATTERN.exec(cssValue) : null;
59696
+ if (!urlMatch) return;
59697
+ const assetSource = urlMatch[2].trim();
59698
+ if (isEmbeddedAssetSource(assetSource)) return;
59699
+ const componentNode = findRenderPhaseComponentOrHook(node, context.scopes);
59700
+ if (!componentNode || !renderEvidence.functionHasEvidence(componentNode) || componentPreloadsStaticImage(componentNode, assetSource, context.scopes)) return;
59701
+ context.report({
59702
+ node,
59703
+ message: "Remotion cannot detect when a CSS `url()` asset has loaded, so the rendered frame can flicker. Render or preload the source with <Img> instead."
59704
+ });
59705
+ } };
59706
+ }
59707
+ });
59708
+ //#endregion
59709
+ //#region src/plugin/rules/correctness/remotion-no-module-scope-delay-render.ts
59710
+ const remotionNoModuleScopeDelayRender = defineRule({
59711
+ id: "remotion-no-module-scope-delay-render",
59712
+ title: "Module-scoped delayRender blocks every composition",
59713
+ requires: ["remotion:4"],
59714
+ severity: "error",
59715
+ recommendation: "Create the handle once inside the component. Use `useDelayRender()` on Remotion 4.0.342 or newer, or lazy `useState(() => delayRender())` on earlier versions.",
59716
+ create: (context) => ({ CallExpression(node) {
59717
+ const apiBinding = resolveRemotionApi(node.callee, context.scopes);
59718
+ if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || findEnclosingFunction$1(node)) return;
59719
+ context.report({
59720
+ node,
59721
+ message: "A module-scoped `delayRender()` handle blocks all compositions and composition discovery. Move it inside the component and create it once with `useDelayRender()` or a lazy `useState` initializer."
59722
+ });
59723
+ } })
59724
+ });
59725
+ //#endregion
59726
+ //#region src/plugin/rules/correctness/remotion-no-native-media-elements.ts
59727
+ const REMOTION_MEDIA_REPLACEMENT_BY_TAG = new Map([
59728
+ ["audio", "`Audio` from `@remotion/media`"],
59729
+ ["iframe", "`IFrame` from `remotion`"],
59730
+ ["img", "`Img` from `remotion`"],
59731
+ ["video", "`Video` from `@remotion/media`"]
59732
+ ]);
59733
+ const remotionNoNativeMediaElements = defineRule({
59734
+ id: "remotion-no-native-media-elements",
59735
+ title: "Native media element bypasses Remotion loading",
59736
+ tags: ["react-jsx-only"],
59737
+ requires: ["remotion:4"],
59738
+ severity: "error",
59739
+ recommendation: "Use Remotion's media components so rendering waits for assets and seeks media to the requested frame.",
59740
+ create: (context) => {
59741
+ const renderEvidence = createRemotionRenderEvidenceChecker(context);
59742
+ return { JSXOpeningElement(node) {
59743
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
59744
+ const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
59745
+ if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
59746
+ const replacement = REMOTION_MEDIA_REPLACEMENT_BY_TAG.get(node.name.name);
59747
+ if (!replacement) return;
59748
+ context.report({
59749
+ node,
59750
+ message: `Native <${node.name.name}> does not let Remotion reliably wait for and synchronize the asset. Use ${replacement} instead.`
59751
+ });
59752
+ } };
59753
+ }
59754
+ });
59755
+ //#endregion
59756
+ //#region src/plugin/rules/correctness/remotion-no-next-image.ts
59757
+ const remotionNoNextImage = defineRule({
59758
+ id: "remotion-no-next-image",
59759
+ title: "Next.js Image can flicker in Remotion",
59760
+ tags: ["react-jsx-only"],
59761
+ requires: ["remotion:4"],
59762
+ severity: "error",
59763
+ recommendation: "Use `Img` from `remotion`, which delays rendering until the image is loaded.",
59764
+ create: (context) => {
59765
+ const renderEvidence = createRemotionRenderEvidenceChecker(context);
59766
+ return { JSXOpeningElement(node) {
59767
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
59768
+ const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
59769
+ if (!renderFunction || !renderEvidence.functionHasEvidence(renderFunction)) return;
59770
+ const symbol = context.scopes.symbolFor(node.name);
59771
+ if (symbol?.kind !== "import") return;
59772
+ const importBinding = getImportBindingForName(node.name, symbol.name);
59773
+ if (importBinding?.source !== "next/image" || importBinding.exportedName !== "default") return;
59774
+ context.report({
59775
+ node,
59776
+ message: "Next.js <Image> does not expose a reliable loading signal to Remotion, so rendered frames can flicker. Use <Img> from `remotion` instead."
59777
+ });
59778
+ } };
59779
+ }
59780
+ });
59781
+ //#endregion
59782
+ //#region src/plugin/rules/correctness/remotion-stable-delay-render-handle.ts
59783
+ const isUseStateLazyInitializer = (node, scopes) => {
59784
+ const enclosingFunction = findEnclosingFunction$1(node);
59785
+ if (!enclosingFunction) return false;
59786
+ const parent = enclosingFunction.parent;
59787
+ return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments[0] === enclosingFunction && isReactApiCall(parent, "useState", scopes, { resolveNamedAliases: true }));
59788
+ };
59789
+ const remotionStableDelayRenderHandle = defineRule({
59790
+ id: "remotion-stable-delay-render-handle",
59791
+ title: "delayRender handle is recreated during render",
59792
+ tags: ["react-jsx-only"],
59793
+ requires: ["remotion:4"],
59794
+ severity: "error",
59795
+ recommendation: "Prefer `useDelayRender()`, or initialize `delayRender()` once with `useState(() => delayRender())`.",
59796
+ create: (context) => ({ CallExpression(node) {
59797
+ const apiBinding = resolveRemotionApi(node.callee, context.scopes);
59798
+ if (apiBinding?.apiName !== "delayRender" || apiBinding.moduleSource !== "remotion" || !findRenderPhaseComponentOrHook(node, context.scopes) || isUseStateLazyInitializer(node, context.scopes)) return;
59799
+ context.report({
59800
+ node,
59801
+ message: "Calling `delayRender()` during every component render creates another outstanding handle and can make rendering time out. Use `useDelayRender()` or a lazy `useState` initializer."
59802
+ });
59803
+ } })
59804
+ });
59805
+ //#endregion
58739
59806
  //#region src/plugin/rules/performance/rendering-animate-svg-wrapper.ts
58740
59807
  const renderingAnimateSvgWrapper = defineRule({
58741
59808
  id: "rendering-animate-svg-wrapper",
@@ -59856,7 +60923,7 @@ const functionBodyHasReturnWithValue = (functionNode) => {
59856
60923
  //#endregion
59857
60924
  //#region src/plugin/rules/react-builtins/require-render-return.ts
59858
60925
  const RENDER_METHOD_NAME = "render";
59859
- const MESSAGE$4 = "Your users see nothing because this `render` method returns nothing.";
60926
+ const MESSAGE$5 = "Your users see nothing because this `render` method returns nothing.";
59860
60927
  const isStaticRenderKey = (key) => {
59861
60928
  if (isNodeOfType(key, "Identifier")) return key.name === RENDER_METHOD_NAME;
59862
60929
  if (isNodeOfType(key, "Literal")) return key.value === RENDER_METHOD_NAME;
@@ -59916,7 +60983,7 @@ const requireRenderReturn = defineRule({
59916
60983
  if (functionBodyHasReturnWithValue(functionNode)) return;
59917
60984
  context.report({
59918
60985
  node: host.reportNode,
59919
- message: MESSAGE$4
60986
+ message: MESSAGE$5
59920
60987
  });
59921
60988
  };
59922
60989
  return {
@@ -68468,6 +69535,7 @@ const countOwnScopeHookCalls = (functionNode, scopes, settings, countedFunctionN
68468
69535
  };
68469
69536
  const MIN_HOOK_CALLS_FOR_RENDER_SCOPE = 2;
68470
69537
  const RENDER_SCOPE_FACTORY_NAME_PATTERN = /^_?(?:init|create|make|build)(?:[A-Z0-9_]|$)/;
69538
+ const HOOK_NAMESPACE_NAME_PATTERN = /Hooks?$/;
68471
69539
  const isLocalNonHookFunctionCallee = (call, scopes, settings, countedFunctionNodes) => {
68472
69540
  const callee = call.callee;
68473
69541
  if (!isNodeOfType(callee, "Identifier")) return false;
@@ -68492,6 +69560,15 @@ const isPackageImportedNonReactHookCallee = (call) => {
68492
69560
  if (PATH_ALIAS_IMPORT_PATTERN.test(importSource)) return false;
68493
69561
  return !isReactEcosystemImportSource(importSource);
68494
69562
  };
69563
+ const isDefaultImportedClassApiMemberCallee = (call) => {
69564
+ const callee = call.callee;
69565
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
69566
+ if (!isNodeOfType(callee.object, "Identifier")) return false;
69567
+ if (HOOK_NAMESPACE_NAME_PATTERN.test(callee.object.name)) return false;
69568
+ const importBinding = getImportBindingForName(call, callee.object.name);
69569
+ if (!importBinding || importBinding.exportedName !== "default") return false;
69570
+ return !REACT_RUNTIME_MODULE_SOURCES.has(importBinding.source);
69571
+ };
68495
69572
  const isProjectOwnedMdxComponentsGetter = (call) => {
68496
69573
  const callee = call.callee;
68497
69574
  if (!isNodeOfType(callee, "Identifier") || callee.name !== "useMDXComponents") return false;
@@ -68641,6 +69718,7 @@ const rulesOfHooks = defineRule({
68641
69718
  return;
68642
69719
  }
68643
69720
  if (isInsideClassComponent(node)) {
69721
+ if (isPackageImportedNonReactHookCallee(node) || isDefaultImportedClassApiMemberCallee(node)) return;
68644
69722
  context.report({
68645
69723
  node: node.callee,
68646
69724
  message: buildClassComponentMessage(hookName)
@@ -68747,7 +69825,7 @@ const rulesOfHooks = defineRule({
68747
69825
  });
68748
69826
  //#endregion
68749
69827
  //#region src/plugin/rules/a11y/scope.ts
68750
- const MESSAGE$3 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
69828
+ const MESSAGE$4 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
68751
69829
  const scope = defineRule({
68752
69830
  id: "scope",
68753
69831
  title: "scope attribute on non-th element",
@@ -68762,7 +69840,7 @@ const scope = defineRule({
68762
69840
  if (!HTML_TAGS.has(tag)) return;
68763
69841
  if (tag !== "th") context.report({
68764
69842
  node: scopeAttribute,
68765
- message: MESSAGE$3
69843
+ message: MESSAGE$4
68766
69844
  });
68767
69845
  } })
68768
69846
  });
@@ -68773,13 +69851,13 @@ const secretInFallback = defineRule({
68773
69851
  recommendation: "Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",
68774
69852
  scan: scanByPattern({
68775
69853
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
68776
- pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
69854
+ pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!test test test test test test test test test test test junk["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
68777
69855
  message: "A secret env var has a hardcoded string fallback: the literal is a committed secret and the app fails open (uses it) when the variable is unset."
68778
69856
  })
68779
69857
  });
68780
69858
  //#endregion
68781
69859
  //#region src/plugin/rules/react-builtins/self-closing-comp.ts
68782
- const MESSAGE$2 = "This tag has no children, so the closing tag adds noise without changing output.";
69860
+ const MESSAGE$3 = "This tag has no children, so the closing tag adds noise without changing output.";
68783
69861
  const resolveSettings$2 = (settings) => {
68784
69862
  const reactDoctor = settings?.["react-doctor"];
68785
69863
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
@@ -68818,7 +69896,7 @@ const selfClosingComp = defineRule({
68818
69896
  if (isHtmlTag && !settings.html) return;
68819
69897
  context.report({
68820
69898
  node: node.openingElement,
68821
- message: MESSAGE$2
69899
+ message: MESSAGE$3
68822
69900
  });
68823
69901
  } };
68824
69902
  }
@@ -69043,7 +70121,30 @@ const isAuthGuardName = (calleeName) => {
69043
70121
  //#endregion
69044
70122
  //#region src/plugin/utils/is-non-privileged-server-action.ts
69045
70123
  const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
69046
- const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
70124
+ const LOCALE_PREFERENCE_VERB_TOKENS = new Set(["set", "update"]);
70125
+ const LOCALE_PREFERENCE_NOUN_TOKENS = new Set(["language", "locale"]);
70126
+ const COOKIE_DELETION_METHOD_NAME = "delete";
70127
+ const isCacheOrNavigationCall = (node) => {
70128
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return false;
70129
+ return NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
70130
+ };
70131
+ const isCallerScopedLocalePreferenceCall = (node) => {
70132
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.arguments?.length !== 1) return false;
70133
+ const nameTokens = tokenizeIdentifierWords(node.callee.name);
70134
+ if (!nameTokens.some((token) => LOCALE_PREFERENCE_VERB_TOKENS.has(token))) return false;
70135
+ if (!nameTokens.some((token) => LOCALE_PREFERENCE_NOUN_TOKENS.has(token))) return false;
70136
+ const importSource = getImportSourceForName(node, node.callee.name)?.toLowerCase();
70137
+ return Boolean(importSource?.includes("i18n") || importSource?.includes("locale"));
70138
+ };
70139
+ const isCallerScopedCookieCall = (node, cookieBindingNames) => {
70140
+ if (!isNodeOfType(node, "CallExpression")) return false;
70141
+ if (isNodeOfType(node.callee, "Identifier")) return node.callee.name === "cookies" && getImportSourceForName(node, node.callee.name) === "next/headers";
70142
+ if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== COOKIE_DELETION_METHOD_NAME) return false;
70143
+ const receiver = stripParenExpression(node.callee.object);
70144
+ if (isNodeOfType(receiver, "Identifier")) return cookieBindingNames.has(receiver.name);
70145
+ return isCookiesOrAwaitedCookiesCall(receiver) && getImportSourceForName(receiver, "cookies") === "next/headers";
70146
+ };
70147
+ const isKnownNonDataEffectCall = (node, cookieBindingNames) => isCacheOrNavigationCall(node) || isCallerScopedLocalePreferenceCall(node) || isCallerScopedCookieCall(node, cookieBindingNames);
69047
70148
  const unwrapExpression = (node) => {
69048
70149
  let current = node;
69049
70150
  while (current) {
@@ -69059,10 +70160,10 @@ const unwrapExpression = (node) => {
69059
70160
  }
69060
70161
  return null;
69061
70162
  };
69062
- const isDataExposingValue = (node) => {
70163
+ const isDataExposingValue = (node, cookieBindingNames) => {
69063
70164
  const value = unwrapExpression(node);
69064
70165
  if (!value) return false;
69065
- if (isCacheOrNavigationCall(value)) return false;
70166
+ if (isKnownNonDataEffectCall(value, cookieBindingNames)) return false;
69066
70167
  return !isLiteralOnlyExpression(value);
69067
70168
  };
69068
70169
  const isLiteralOnlyExpression = (node) => {
@@ -69080,22 +70181,23 @@ const getReturnedOrThrownArgument = (node) => {
69080
70181
  if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
69081
70182
  return null;
69082
70183
  };
69083
- const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
69084
- const isPrivilegedEffect = (node) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node);
70184
+ const isDataExposingReturnOrThrow = (node, cookieBindingNames) => isDataExposingValue(getReturnedOrThrownArgument(node), cookieBindingNames);
70185
+ const isPrivilegedEffect = (node, cookieBindingNames) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node, cookieBindingNames);
69085
70186
  const isNonPrivilegedServerAction = (functionNode) => {
69086
70187
  const functionBody = functionNode.body;
69087
70188
  if (!functionBody) return false;
69088
- if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
70189
+ const cookieBindingNames = getImportSourceForName(functionBody, "cookies") === "next/headers" ? collectLocallyScopedCookieBindings(functionBody) : /* @__PURE__ */ new Set();
70190
+ if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody, cookieBindingNames)) return false;
69089
70191
  let hasNonDataEffectCall = false;
69090
70192
  let hasPrivilegedEffect = false;
69091
70193
  walkAst(functionBody, (child) => {
69092
70194
  if (hasPrivilegedEffect) return false;
69093
70195
  if (child !== functionBody && isFunctionLike$1(child)) return false;
69094
- if (isCacheOrNavigationCall(child)) {
70196
+ if (isKnownNonDataEffectCall(child, cookieBindingNames)) {
69095
70197
  hasNonDataEffectCall = true;
69096
70198
  return;
69097
70199
  }
69098
- if (isPrivilegedEffect(child)) {
70200
+ if (isPrivilegedEffect(child, cookieBindingNames)) {
69099
70201
  hasPrivilegedEffect = true;
69100
70202
  return false;
69101
70203
  }
@@ -69174,6 +70276,34 @@ const getAuthScanRoots = (functionNode) => {
69174
70276
  if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
69175
70277
  return [bodyNode];
69176
70278
  };
70279
+ const COMPONENT_NAME_PATTERN = /^[A-Z]/;
70280
+ const TEST_APP_SOURCE_PATH_PATTERN = /(?:^|[/\\])test[/\\](?:app|src)[/\\]/;
70281
+ const containsJsxOutsideNestedFunctions = (rootNode) => {
70282
+ let containsJsx = false;
70283
+ walkAst(rootNode, (child) => {
70284
+ if (containsJsx) return false;
70285
+ if (child !== rootNode && isFunctionLike$1(child)) return false;
70286
+ if (isNodeOfType(child, "JSXElement") || isNodeOfType(child, "JSXFragment")) {
70287
+ containsJsx = true;
70288
+ return false;
70289
+ }
70290
+ });
70291
+ return containsJsx;
70292
+ };
70293
+ const isComponentLikeServerExport = (candidate) => {
70294
+ if (!COMPONENT_NAME_PATTERN.test(candidate.displayName)) return false;
70295
+ const functionBody = candidate.functionNode.body;
70296
+ if (!isNodeOfType(functionBody, "BlockStatement")) return containsJsxOutsideNestedFunctions(functionBody);
70297
+ let hasReturnedJsx = false;
70298
+ walkAst(functionBody, (child) => {
70299
+ if (hasReturnedJsx) return false;
70300
+ if (child !== functionBody && isFunctionLike$1(child)) return false;
70301
+ if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
70302
+ hasReturnedJsx = containsJsxOutsideNestedFunctions(child.argument);
70303
+ return false;
70304
+ });
70305
+ return hasReturnedJsx;
70306
+ };
69177
70307
  const CREDENTIAL_MERGE_TAIL_TOKENS = {
69178
70308
  sign: new Set([
69179
70309
  "in",
@@ -69238,6 +70368,7 @@ const isCredentialEstablishingActionName = (actionName) => {
69238
70368
  const hasPublicNameToken = (actionName) => tokenizeIdentifierWords(actionName).includes("public");
69239
70369
  const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
69240
70370
  if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
70371
+ if (isComponentLikeServerExport(candidate)) return;
69241
70372
  if (isCredentialEstablishingActionName(candidate.displayName)) return;
69242
70373
  if (hasPublicNameToken(candidate.displayName)) return;
69243
70374
  if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
@@ -69275,18 +70406,42 @@ const serverAuthActions = defineRule({
69275
70406
  title: "Unauthenticated server action can be called directly",
69276
70407
  severity: "error",
69277
70408
  recommendation: "Check auth before touching data because exported server actions can be called directly by unauthenticated clients.",
69278
- create: (context) => {
70409
+ create: skipNonProductionFiles((context) => {
70410
+ const shouldSkipTestAppSource = Boolean(context.filename && TEST_APP_SOURCE_PATH_PATTERN.test(context.filename));
69279
70411
  let fileHasUseServerDirective = false;
70412
+ let programNode = null;
70413
+ const inspectedFunctions = /* @__PURE__ */ new Set();
69280
70414
  const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
69281
70415
  const allowedFunctionNames = customAuthFunctionNames.length > 0 ? new Set([...AUTH_FUNCTION_NAMES, ...customAuthFunctionNames]) : AUTH_FUNCTION_NAMES;
69282
- const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
70416
+ const inspect = (candidate) => {
70417
+ if (inspectedFunctions.has(candidate.functionNode)) return;
70418
+ inspectedFunctions.add(candidate.functionNode);
70419
+ inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
70420
+ };
69283
70421
  return {
69284
- Program(programNode) {
69285
- fileHasUseServerDirective = hasDirective(programNode, "use server");
70422
+ Program(currentProgramNode) {
70423
+ programNode = currentProgramNode;
70424
+ fileHasUseServerDirective = hasDirective(currentProgramNode, "use server");
69286
70425
  },
69287
70426
  ExportNamedDeclaration(node) {
70427
+ if (shouldSkipTestAppSource) return;
69288
70428
  const declaration = node.declaration;
69289
- if (!declaration) return;
70429
+ if (!declaration) {
70430
+ if (!programNode || node.source || node.exportKind === "type") return;
70431
+ for (const specifier of node.specifiers ?? []) {
70432
+ if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
70433
+ const exportedName = isNodeOfType(specifier.exported, "Identifier") ? specifier.exported.name : isNodeOfType(specifier.exported, "Literal") && typeof specifier.exported.value === "string" ? specifier.exported.value : null;
70434
+ if (!exportedName) continue;
70435
+ const exportedValue = findExportedValue(programNode, exportedName);
70436
+ if (!isAsyncFunctionLikeNode(exportedValue)) continue;
70437
+ inspect({
70438
+ functionNode: exportedValue,
70439
+ displayName: isNodeOfType(specifier.local, "Identifier") ? specifier.local.name : exportedName,
70440
+ reportNode: specifier.local ?? specifier
70441
+ });
70442
+ }
70443
+ return;
70444
+ }
69290
70445
  if (isAsyncFunctionLikeNode(declaration)) {
69291
70446
  if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
69292
70447
  inspect({
@@ -69299,11 +70454,23 @@ const serverAuthActions = defineRule({
69299
70454
  if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
69300
70455
  },
69301
70456
  ExportDefaultDeclaration(node) {
69302
- const candidate = getCandidateFromDefaultDeclaration(node);
70457
+ if (shouldSkipTestAppSource) return;
70458
+ const directCandidate = getCandidateFromDefaultDeclaration(node);
70459
+ if (directCandidate) {
70460
+ inspect(directCandidate);
70461
+ return;
70462
+ }
70463
+ if (!programNode) return;
70464
+ const resolvedDefaultExport = findExportedValue(programNode, "default");
70465
+ const candidate = isAsyncFunctionLikeNode(resolvedDefaultExport) ? {
70466
+ functionNode: resolvedDefaultExport,
70467
+ displayName: isNodeOfType(node.declaration, "Identifier") ? node.declaration.name : "default",
70468
+ reportNode: node.declaration ?? node
70469
+ } : null;
69303
70470
  if (candidate) inspect(candidate);
69304
70471
  }
69305
70472
  };
69306
- }
70473
+ })
69307
70474
  });
69308
70475
  //#endregion
69309
70476
  //#region src/plugin/rules/server/server-cache-with-object-literal.ts
@@ -69383,6 +70550,7 @@ const serverDedupProps = defineRule({
69383
70550
  for (const attr of node.attributes ?? []) {
69384
70551
  if (!isNodeOfType(attr, "JSXAttribute")) continue;
69385
70552
  if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
70553
+ if (attr.name.name === "key") continue;
69386
70554
  if (!isNodeOfType(attr.value, "JSXExpressionContainer")) continue;
69387
70555
  const expression = attr.value.expression;
69388
70556
  if (!expression) continue;
@@ -70112,7 +71280,7 @@ const collectJsxRuntimeImports = (program) => {
70112
71280
  };
70113
71281
  //#endregion
70114
71282
  //#region src/plugin/rules/react-builtins/style-prop-object.ts
70115
- const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
71283
+ const MESSAGE$2 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
70116
71284
  const resolveSettings = (settings) => {
70117
71285
  const reactDoctor = settings?.["react-doctor"];
70118
71286
  return { allow: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stylePropObject ?? {} : {}).allow ?? [] };
@@ -70219,7 +71387,7 @@ const stylePropObject = defineRule({
70219
71387
  if (isNodeOfType(value, "Literal") && typeof value.value === "string") {
70220
71388
  context.report({
70221
71389
  node: attribute,
70222
- message: MESSAGE$1
71390
+ message: MESSAGE$2
70223
71391
  });
70224
71392
  return;
70225
71393
  }
@@ -70227,7 +71395,7 @@ const stylePropObject = defineRule({
70227
71395
  const innerExpression = value.expression;
70228
71396
  if (innerExpression && innerExpression.type !== "JSXEmptyExpression" && isInvalidStyleExpression(innerExpression)) context.report({
70229
71397
  node: attribute,
70230
- message: MESSAGE$1
71398
+ message: MESSAGE$2
70231
71399
  });
70232
71400
  }
70233
71401
  }
@@ -70250,7 +71418,7 @@ const stylePropObject = defineRule({
70250
71418
  if (!(isNodeOfType(key, "Identifier") && key.name === "style" || isNodeOfType(key, "Literal") && key.value === "style")) continue;
70251
71419
  if (isInvalidStyleExpression(property.value)) context.report({
70252
71420
  node: property,
70253
- message: MESSAGE$1
71421
+ message: MESSAGE$2
70254
71422
  });
70255
71423
  }
70256
71424
  }
@@ -70586,7 +71754,7 @@ const svgFilterClickjackingRisk = defineRule({
70586
71754
  });
70587
71755
  //#endregion
70588
71756
  //#region src/plugin/rules/a11y/tabindex-no-positive.ts
70589
- const MESSAGE = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
71757
+ const MESSAGE$1 = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
70590
71758
  const tabindexNoPositive = defineRule({
70591
71759
  id: "tabindex-no-positive",
70592
71760
  title: "Positive tabindex value",
@@ -70606,7 +71774,7 @@ const tabindexNoPositive = defineRule({
70606
71774
  } else if (tabIndex.value && isNodeOfType(tabIndex.value, "JSXExpressionContainer")) numericValue = parseJsxValue(tabIndex.value);
70607
71775
  if (numericValue !== null && numericValue > 0) context.report({
70608
71776
  node: tabIndex,
70609
- message: MESSAGE
71777
+ message: MESSAGE$1
70610
71778
  });
70611
71779
  } })
70612
71780
  });
@@ -71512,6 +72680,445 @@ const useLazyMotion = defineRule({
71512
72680
  } })
71513
72681
  });
71514
72682
  //#endregion
72683
+ //#region src/plugin/rules/valtio/valtio-no-proxy-read-in-render.ts
72684
+ const VALTIO_REACT_MODULE_SOURCES$1 = new Set(["valtio", "valtio/react"]);
72685
+ const REACT_DEPENDENCY_ARRAY_HOOK_NAMES = new Set([
72686
+ "useCallback",
72687
+ "useEffect",
72688
+ "useInsertionEffect",
72689
+ "useLayoutEffect",
72690
+ "useMemo"
72691
+ ]);
72692
+ const MESSAGE = "Read this Valtio value from the useSnapshot result during render. Keep proxy reads for callbacks so render uses the tracked, consistent snapshot.";
72693
+ const isValtioImportSymbol = (symbol) => {
72694
+ if (symbol.kind !== "import") return false;
72695
+ const importDeclaration = symbol.declarationNode.parent;
72696
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && VALTIO_REACT_MODULE_SOURCES$1.has(importDeclaration.source.value));
72697
+ };
72698
+ const isValtioUseSnapshotCall = (node, scopes) => {
72699
+ if (!isNodeOfType(node, "CallExpression")) return false;
72700
+ const callee = stripParenExpression(node.callee);
72701
+ if (isNodeOfType(callee, "Identifier")) {
72702
+ const symbol = resolveConstIdentifierAlias(callee, scopes);
72703
+ return Boolean(symbol && isValtioImportSymbol(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot");
72704
+ }
72705
+ if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "useSnapshot") return false;
72706
+ const receiver = stripParenExpression(callee.object);
72707
+ if (!isNodeOfType(receiver, "Identifier")) return false;
72708
+ const namespaceSymbol = resolveConstIdentifierAlias(receiver, scopes);
72709
+ return Boolean(namespaceSymbol && isValtioImportSymbol(namespaceSymbol) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
72710
+ };
72711
+ const collectPatternBindingIdentifiers = (pattern) => {
72712
+ if (isNodeOfType(pattern, "Identifier")) return [pattern];
72713
+ if (isNodeOfType(pattern, "AssignmentPattern")) return collectPatternBindingIdentifiers(pattern.left);
72714
+ if (isNodeOfType(pattern, "RestElement")) return collectPatternBindingIdentifiers(pattern.argument);
72715
+ if (isNodeOfType(pattern, "ArrayPattern")) return pattern.elements.flatMap((element) => element ? collectPatternBindingIdentifiers(element) : []);
72716
+ if (isNodeOfType(pattern, "ObjectPattern")) return pattern.properties.flatMap((property) => {
72717
+ if (isNodeOfType(property, "Property")) return collectPatternBindingIdentifiers(property.value);
72718
+ return isNodeOfType(property, "RestElement") ? collectPatternBindingIdentifiers(property.argument) : [];
72719
+ });
72720
+ return [];
72721
+ };
72722
+ const collectAssignmentWriteTargets = (target) => {
72723
+ const candidate = stripParenExpression(target);
72724
+ if (isNodeOfType(candidate, "AssignmentPattern")) return collectAssignmentWriteTargets(candidate.left);
72725
+ if (isNodeOfType(candidate, "RestElement")) return collectAssignmentWriteTargets(candidate.argument);
72726
+ if (isNodeOfType(candidate, "ArrayPattern")) return candidate.elements.flatMap((element) => element ? collectAssignmentWriteTargets(element) : []);
72727
+ if (isNodeOfType(candidate, "ObjectPattern")) return candidate.properties.flatMap((property) => isNodeOfType(property, "Property") ? collectAssignmentWriteTargets(property.value) : collectAssignmentWriteTargets(property.argument));
72728
+ return [candidate];
72729
+ };
72730
+ const findPatternPathToBinding = (pattern, bindingIdentifier) => {
72731
+ if (pattern === bindingIdentifier) return [];
72732
+ if (isNodeOfType(pattern, "AssignmentPattern")) return findPatternPathToBinding(pattern.left, bindingIdentifier);
72733
+ if (isNodeOfType(pattern, "RestElement")) return null;
72734
+ if (isNodeOfType(pattern, "ArrayPattern")) {
72735
+ for (const [elementIndex, element] of pattern.elements.entries()) {
72736
+ if (!element) continue;
72737
+ const nestedPath = findPatternPathToBinding(element, bindingIdentifier);
72738
+ if (nestedPath) return [String(elementIndex), ...nestedPath];
72739
+ }
72740
+ return null;
72741
+ }
72742
+ if (!isNodeOfType(pattern, "ObjectPattern")) return null;
72743
+ for (const property of pattern.properties) {
72744
+ if (!isNodeOfType(property, "Property")) continue;
72745
+ const nestedPath = findPatternPathToBinding(property.value, bindingIdentifier);
72746
+ if (!nestedPath) continue;
72747
+ const propertyName = getStaticPropertyKeyName(property);
72748
+ return propertyName === null ? null : [propertyName, ...nestedPath];
72749
+ }
72750
+ return null;
72751
+ };
72752
+ const resolveProxyPath = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), allowedAliasSymbolIds = null, collectedAliasCaptures = null) => {
72753
+ const candidate = stripParenExpression(expression);
72754
+ if (isNodeOfType(candidate, "MemberExpression")) {
72755
+ const receiverPath = resolveProxyPath(candidate.object, scopes, visitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
72756
+ if (!receiverPath) return null;
72757
+ return {
72758
+ ...receiverPath,
72759
+ properties: [...receiverPath.properties, getStaticPropertyName(candidate)]
72760
+ };
72761
+ }
72762
+ if (!isNodeOfType(candidate, "Identifier")) return null;
72763
+ const symbol = scopes.symbolFor(candidate);
72764
+ if (!symbol) return scopes.isGlobalReference(candidate) ? {
72765
+ rootKey: `global:${candidate.name}`,
72766
+ properties: []
72767
+ } : null;
72768
+ if (visitedSymbolIds.has(symbol.id)) return null;
72769
+ if (symbol.kind === "const" && symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator")) {
72770
+ const directAlias = symbol.declarationNode.id === symbol.bindingIdentifier;
72771
+ const initializerNode = directAlias ? symbol.initializer : symbol.declarationNode.init;
72772
+ if (!initializerNode) return {
72773
+ rootKey: `symbol:${symbol.id}`,
72774
+ properties: []
72775
+ };
72776
+ const initializer = stripParenExpression(initializerNode);
72777
+ const canResolveDirectAlias = directAlias && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"));
72778
+ const patternPath = directAlias ? null : findPatternPathToBinding(symbol.declarationNode.id, symbol.bindingIdentifier);
72779
+ if ((canResolveDirectAlias || patternPath) && (allowedAliasSymbolIds === null || allowedAliasSymbolIds.has(symbol.id))) {
72780
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
72781
+ nextVisitedSymbolIds.add(symbol.id);
72782
+ const initializerPath = resolveProxyPath(initializer, scopes, nextVisitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
72783
+ if (initializerPath) {
72784
+ const aliasPath = {
72785
+ ...initializerPath,
72786
+ properties: [...initializerPath.properties, ...patternPath ?? []]
72787
+ };
72788
+ const declarationEnd = symbol.declarationNode.range?.[1];
72789
+ if (typeof declarationEnd === "number") collectedAliasCaptures?.set(symbol.id, {
72790
+ declarationEnd,
72791
+ path: aliasPath
72792
+ });
72793
+ return aliasPath;
72794
+ }
72795
+ }
72796
+ }
72797
+ return {
72798
+ rootKey: `symbol:${symbol.id}`,
72799
+ properties: []
72800
+ };
72801
+ };
72802
+ const isScopeWithin = (candidateScope, ancestorScope) => {
72803
+ let currentScope = candidateScope;
72804
+ while (currentScope) {
72805
+ if (currentScope === ancestorScope) return true;
72806
+ currentScope = currentScope.parent;
72807
+ }
72808
+ return false;
72809
+ };
72810
+ const isPathPrefix = (prefix, candidate) => {
72811
+ if (prefix.rootKey !== candidate.rootKey || prefix.properties.length > candidate.properties.length) return false;
72812
+ return prefix.properties.every((propertyName, propertyIndex) => propertyName !== null && propertyName === candidate.properties[propertyIndex]);
72813
+ };
72814
+ const findOutermostMemberRead = (identifier) => {
72815
+ let currentExpression = findTransparentExpressionRoot(identifier);
72816
+ for (;;) {
72817
+ const parent = currentExpression.parent;
72818
+ if (!isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== stripParenExpression(currentExpression)) return currentExpression;
72819
+ currentExpression = findTransparentExpressionRoot(parent);
72820
+ }
72821
+ };
72822
+ const isReadPositionWithinAssignmentTarget = (expression) => {
72823
+ let currentNode = expression;
72824
+ let parentNode = currentNode.parent;
72825
+ while (parentNode) {
72826
+ if (isNodeOfType(parentNode, "MemberExpression") && parentNode.computed && parentNode.property === currentNode || isNodeOfType(parentNode, "Property") && parentNode.computed && parentNode.key === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.right === currentNode) return true;
72827
+ if (isNodeOfType(parentNode, "AssignmentExpression") || isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete" || isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return false;
72828
+ currentNode = parentNode;
72829
+ parentNode = currentNode.parent;
72830
+ }
72831
+ return false;
72832
+ };
72833
+ const isSnapshotArgument = (expression, scopes) => {
72834
+ const expressionRoot = findTransparentExpressionRoot(expression);
72835
+ const parent = expressionRoot.parent;
72836
+ return Boolean(parent && isNodeOfType(parent, "CallExpression") && isValtioUseSnapshotCall(parent, scopes) && parent.arguments[0] === expressionRoot);
72837
+ };
72838
+ const isStableProxyDependency = (expression, readPath, matchingTarget, scopes) => {
72839
+ if (readPath.properties.length !== matchingTarget.path.properties.length || !isPathPrefix(matchingTarget.path, readPath)) return false;
72840
+ const dependencyArray = findTransparentExpressionRoot(expression).parent;
72841
+ if (!isNodeOfType(dependencyArray, "ArrayExpression")) return false;
72842
+ const hookCall = dependencyArray.parent;
72843
+ return Boolean(isNodeOfType(hookCall, "CallExpression") && hookCall.arguments[1] === dependencyArray && isReactApiCall(hookCall, REACT_DEPENDENCY_ARRAY_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }));
72844
+ };
72845
+ const getSnapshotTarget = (callExpression, context) => {
72846
+ if (!isValtioUseSnapshotCall(callExpression, context.scopes)) return null;
72847
+ const proxyArgument = callExpression.arguments[0];
72848
+ if (!proxyArgument || isNodeOfType(proxyArgument, "SpreadElement")) return null;
72849
+ const path = resolveProxyPath(proxyArgument, context.scopes);
72850
+ if (!path || path.properties.some((propertyName) => propertyName === null)) return null;
72851
+ const callExpressionRoot = findTransparentExpressionRoot(callExpression);
72852
+ const declarator = callExpressionRoot.parent;
72853
+ if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== callExpressionRoot || !isNodeOfType(declarator.parent, "VariableDeclaration")) return null;
72854
+ const ownerFunction = findRenderPhaseComponentOrHook(callExpression, context.scopes);
72855
+ if (!ownerFunction) return null;
72856
+ const snapshotBindingScopes = collectPatternBindingIdentifiers(declarator.id).flatMap((bindingIdentifier) => {
72857
+ const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
72858
+ return bindingSymbol ? [bindingSymbol.scope] : [];
72859
+ });
72860
+ const declarationEnd = declarator.range?.[1];
72861
+ if (snapshotBindingScopes.length === 0 || typeof declarationEnd !== "number") return null;
72862
+ return {
72863
+ declarationEnd,
72864
+ ownerFunction,
72865
+ path,
72866
+ snapshotBindingScopes
72867
+ };
72868
+ };
72869
+ const wasTargetReplacedBeforeRead = (target, readPosition, writeTargets, readAliasCaptures, context) => writeTargets.some((writeTarget) => {
72870
+ const writeNode = findTransparentExpressionRoot(writeTarget).parent;
72871
+ const writePosition = writeNode?.range?.[0];
72872
+ if (!writeNode || typeof writePosition !== "number" || writePosition <= target.declarationEnd || writePosition >= readPosition || findRenderPhaseComponentOrHook(writeNode, context.scopes) !== target.ownerFunction) return false;
72873
+ const writePath = resolveProxyPath(writeTarget, context.scopes, /* @__PURE__ */ new Set(), null);
72874
+ if (!writePath || !isPathPrefix(writePath, target.path)) return false;
72875
+ return ![...readAliasCaptures.values()].some((aliasCapture) => aliasCapture.declarationEnd < writePosition && isPathPrefix(writePath, aliasCapture.path));
72876
+ });
72877
+ const valtioNoProxyReadInRender = defineRule({
72878
+ id: "valtio-no-proxy-read-in-render",
72879
+ title: "Valtio proxy read during render",
72880
+ severity: "warn",
72881
+ recommendation: "Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.",
72882
+ requires: ["valtio:1"],
72883
+ create: (context) => {
72884
+ const snapshotTargets = [];
72885
+ const identifierCandidates = [];
72886
+ const writeTargets = [];
72887
+ return {
72888
+ CallExpression(node) {
72889
+ const snapshotTarget = getSnapshotTarget(node, context);
72890
+ if (snapshotTarget) snapshotTargets.push(snapshotTarget);
72891
+ },
72892
+ Identifier(node) {
72893
+ if (context.scopes.referenceFor(node)) identifierCandidates.push(node);
72894
+ },
72895
+ AssignmentExpression(node) {
72896
+ writeTargets.push(...collectAssignmentWriteTargets(node.left));
72897
+ },
72898
+ UpdateExpression(node) {
72899
+ writeTargets.push(node.argument);
72900
+ },
72901
+ "Program:exit"() {
72902
+ const reportedExpressions = /* @__PURE__ */ new Set();
72903
+ for (const identifier of identifierCandidates) {
72904
+ const readExpression = findOutermostMemberRead(identifier);
72905
+ if (reportedExpressions.has(readExpression) || isWithinAssignmentTarget(readExpression) && !isReadPositionWithinAssignmentTarget(readExpression) || isSnapshotArgument(readExpression, context.scopes)) continue;
72906
+ const readAliasCaptures = /* @__PURE__ */ new Map();
72907
+ const readPath = resolveProxyPath(readExpression, context.scopes, /* @__PURE__ */ new Set(), null, readAliasCaptures);
72908
+ const readPosition = readExpression.range?.[0];
72909
+ const ownerFunction = findRenderPhaseComponentOrHook(readExpression, context.scopes);
72910
+ if (!readPath || typeof readPosition !== "number" || !ownerFunction) continue;
72911
+ const readScope = context.scopes.scopeFor(readExpression);
72912
+ const matchingTarget = snapshotTargets.find((target) => target.ownerFunction === ownerFunction && target.declarationEnd < readPosition && target.snapshotBindingScopes.some((bindingScope) => isScopeWithin(readScope, bindingScope)) && isPathPrefix(target.path, readPath) && (readAliasCaptures.size === 0 || [...readAliasCaptures.values()].some((aliasCapture) => isPathPrefix(aliasCapture.path, target.path) || isPathPrefix(target.path, aliasCapture.path) && readPath.properties.length > aliasCapture.path.properties.length)) && !wasTargetReplacedBeforeRead(target, readPosition, writeTargets, readAliasCaptures, context));
72913
+ if (!matchingTarget || isStableProxyDependency(readExpression, readPath, matchingTarget, context.scopes)) continue;
72914
+ reportedExpressions.add(readExpression);
72915
+ context.report({
72916
+ node: readExpression,
72917
+ message: MESSAGE
72918
+ });
72919
+ }
72920
+ }
72921
+ };
72922
+ }
72923
+ });
72924
+ //#endregion
72925
+ //#region src/plugin/rules/valtio/valtio-no-snapshot-in-callback.ts
72926
+ const VALTIO_REACT_MODULE_SOURCES = new Set(["valtio", "valtio/react"]);
72927
+ const DEFERRED_REACT_HOOK_NAMES = new Set([
72928
+ "useEffect",
72929
+ "useInsertionEffect",
72930
+ "useLayoutEffect"
72931
+ ]);
72932
+ const PROMISE_CONTINUATION_METHOD_NAMES = new Set([
72933
+ "catch",
72934
+ "finally",
72935
+ "then"
72936
+ ]);
72937
+ const DEFERRED_CONSTRUCTOR_NAMES = new Set([
72938
+ "IntersectionObserver",
72939
+ "MutationObserver",
72940
+ "PerformanceObserver",
72941
+ "ResizeObserver"
72942
+ ]);
72943
+ const isValtioImport = (symbol) => {
72944
+ const source = getImportDeclarationForSymbol(symbol)?.source.value;
72945
+ return typeof source === "string" && VALTIO_REACT_MODULE_SOURCES.has(source);
72946
+ };
72947
+ const resolvesToValtioNamespace = (expression, scopes, visitedSymbolIds) => {
72948
+ const candidate = stripParenExpression(expression);
72949
+ if (!isNodeOfType(candidate, "Identifier")) return false;
72950
+ const symbol = scopes.symbolFor(candidate);
72951
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
72952
+ if (symbol.kind === "import" && isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") && isValtioImport(symbol)) return true;
72953
+ const initializer = getDirectUnreassignedInitializer(symbol);
72954
+ if (!initializer) return false;
72955
+ visitedSymbolIds.add(symbol.id);
72956
+ return resolvesToValtioNamespace(initializer, scopes, visitedSymbolIds);
72957
+ };
72958
+ const isValtioUseSnapshotCallee = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
72959
+ const candidate = stripParenExpression(expression);
72960
+ if (isNodeOfType(candidate, "Identifier")) {
72961
+ const symbol = scopes.symbolFor(candidate);
72962
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
72963
+ if (symbol.kind === "import" && isValtioImport(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot") return true;
72964
+ const initializer = getDirectUnreassignedInitializer(symbol);
72965
+ if (!initializer) return false;
72966
+ visitedSymbolIds.add(symbol.id);
72967
+ return isValtioUseSnapshotCallee(initializer, scopes, visitedSymbolIds);
72968
+ }
72969
+ if (!isNodeOfType(candidate, "MemberExpression") || getStaticPropertyName(candidate) !== "useSnapshot") return false;
72970
+ return resolvesToValtioNamespace(candidate.object, scopes, visitedSymbolIds);
72971
+ };
72972
+ const getSnapshotOriginCall = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
72973
+ const candidate = stripParenExpression(expression);
72974
+ if (isNodeOfType(candidate, "CallExpression")) return isValtioUseSnapshotCallee(candidate.callee, context.scopes) ? candidate : null;
72975
+ if (!isNodeOfType(candidate, "Identifier")) return null;
72976
+ const symbol = context.scopes.symbolFor(candidate);
72977
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
72978
+ const initializer = getDirectUnreassignedInitializer(symbol);
72979
+ if (!initializer) return null;
72980
+ visitedSymbolIds.add(symbol.id);
72981
+ return getSnapshotOriginCall(initializer, context, visitedSymbolIds);
72982
+ };
72983
+ const isJsxEventHandlerValue = (expression) => {
72984
+ const expressionContainer = expression.parent;
72985
+ if (!isNodeOfType(expressionContainer, "JSXExpressionContainer")) return false;
72986
+ const attribute = expressionContainer.parent;
72987
+ return Boolean(isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && REACT_HANDLER_PROP_PATTERN.test(attribute.name.name));
72988
+ };
72989
+ const isGlobalDeferredFunctionCall = (callExpression, context) => {
72990
+ const callee = stripParenExpression(callExpression.callee);
72991
+ if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) return context.scopes.isGlobalReference(callee);
72992
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
72993
+ const methodName = getStaticPropertyName(callee);
72994
+ if (!methodName || !TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(methodName)) return false;
72995
+ const receiver = stripParenExpression(callee.object);
72996
+ return Boolean(isNodeOfType(receiver, "Identifier") && (receiver.name === "globalThis" || receiver.name === "window") && context.scopes.isGlobalReference(receiver));
72997
+ };
72998
+ const isDeferredCallbackArgument = (expression, context) => {
72999
+ const parent = expression.parent;
73000
+ if (isNodeOfType(parent, "NewExpression")) {
73001
+ const callee = stripParenExpression(parent.callee);
73002
+ return Boolean(parent.arguments?.[0] === expression && isNodeOfType(callee, "Identifier") && DEFERRED_CONSTRUCTOR_NAMES.has(callee.name) && context.scopes.isGlobalReference(callee));
73003
+ }
73004
+ if (!isNodeOfType(parent, "CallExpression")) return false;
73005
+ if (!(parent.arguments ?? []).some((argument) => argument === expression)) return false;
73006
+ if (parent.arguments?.[0] === expression && isReactApiCall(parent, DEFERRED_REACT_HOOK_NAMES, context.scopes, {
73007
+ allowGlobalReactNamespace: true,
73008
+ resolveNamedAliases: true
73009
+ })) return true;
73010
+ if (parent.arguments?.[0] === expression && isGlobalDeferredFunctionCall(parent, context)) return true;
73011
+ const callee = stripParenExpression(parent.callee);
73012
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
73013
+ const methodName = getStaticPropertyName(callee);
73014
+ return Boolean(methodName && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || PROMISE_CONTINUATION_METHOD_NAMES.has(methodName)));
73015
+ };
73016
+ const isReactEffectCallbackValue = (expression, context) => {
73017
+ const expressionRoot = findTransparentExpressionRoot(expression);
73018
+ const parent = expressionRoot.parent;
73019
+ return Boolean(isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === expressionRoot && isReactApiCall(parent, DEFERRED_REACT_HOOK_NAMES, context.scopes, {
73020
+ allowGlobalReactNamespace: true,
73021
+ resolveNamedAliases: true
73022
+ }));
73023
+ };
73024
+ const isSymbolUsedAsReactEffectCallback = (symbol, context, visitedSymbolIds) => {
73025
+ if (visitedSymbolIds.has(symbol.id)) return false;
73026
+ visitedSymbolIds.add(symbol.id);
73027
+ return symbol.references.some((reference) => {
73028
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
73029
+ if (isReactEffectCallbackValue(referenceRoot, context)) return true;
73030
+ const aliasSymbol = getConstAliasSymbol(referenceRoot, context);
73031
+ return Boolean(aliasSymbol && isSymbolUsedAsReactEffectCallback(aliasSymbol, context, visitedSymbolIds));
73032
+ });
73033
+ };
73034
+ const isFunctionUsedAsReactEffectCallback = (functionNode, context) => {
73035
+ if (isReactEffectCallbackValue(functionNode, context)) return true;
73036
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
73037
+ const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
73038
+ return Boolean(symbol && isSymbolUsedAsReactEffectCallback(symbol, context, /* @__PURE__ */ new Set()));
73039
+ };
73040
+ const isReactEffectCleanupValue = (expression, context) => {
73041
+ const expressionRoot = findTransparentExpressionRoot(expression);
73042
+ const returnStatement = expressionRoot.parent;
73043
+ if (isNodeOfType(returnStatement, "ArrowFunctionExpression") && returnStatement.body === expressionRoot) return isFunctionUsedAsReactEffectCallback(returnStatement, context);
73044
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== expressionRoot) return false;
73045
+ const effectCallback = findEnclosingFunction$1(returnStatement);
73046
+ return Boolean(effectCallback && isFunctionUsedAsReactEffectCallback(effectCallback, context));
73047
+ };
73048
+ const isDeferredCallbackValue = (expression, context) => isJsxEventHandlerValue(expression) || isDeferredCallbackArgument(expression, context) || isReactEffectCleanupValue(expression, context);
73049
+ const getConstAliasSymbol = (expression, context) => {
73050
+ const expressionRoot = findTransparentExpressionRoot(expression);
73051
+ const declarator = expressionRoot.parent;
73052
+ if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== expressionRoot || !isNodeOfType(declarator.id, "Identifier")) return null;
73053
+ const aliasSymbol = context.scopes.symbolFor(declarator.id);
73054
+ return aliasSymbol?.kind === "const" ? aliasSymbol : null;
73055
+ };
73056
+ const isCallTarget = (expression) => {
73057
+ const parent = expression.parent;
73058
+ return isNodeOfType(parent, "CallExpression") && parent.callee === expression ? parent : null;
73059
+ };
73060
+ const getSynchronousCallbackInvocation = (expression, context) => {
73061
+ if (!executesDuringRender(expression, context.scopes)) return null;
73062
+ const parent = expression.parent;
73063
+ return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
73064
+ };
73065
+ const isNodeExecutedFromDeferredCallback = (node, snapshotOwner, context, visitedFunctionSymbolIds) => {
73066
+ const enclosingFunction = findEnclosingFunction$1(node);
73067
+ if (!enclosingFunction || enclosingFunction === snapshotOwner) return false;
73068
+ return isFunctionExecutedAsDeferredCallback(enclosingFunction, snapshotOwner, context, visitedFunctionSymbolIds);
73069
+ };
73070
+ const isSymbolExecutedAsDeferredCallback = (symbol, snapshotOwner, context, visitedFunctionSymbolIds) => {
73071
+ if (visitedFunctionSymbolIds.has(symbol.id)) return false;
73072
+ visitedFunctionSymbolIds.add(symbol.id);
73073
+ return symbol.references.some((reference) => {
73074
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
73075
+ if (isDeferredCallbackValue(referenceRoot, context)) return true;
73076
+ const aliasSymbol = getConstAliasSymbol(referenceRoot, context);
73077
+ if (aliasSymbol && isSymbolExecutedAsDeferredCallback(aliasSymbol, snapshotOwner, context, visitedFunctionSymbolIds)) return true;
73078
+ const callTarget = isCallTarget(referenceRoot);
73079
+ if (callTarget) return isNodeExecutedFromDeferredCallback(callTarget, snapshotOwner, context, visitedFunctionSymbolIds);
73080
+ const callbackInvocation = getSynchronousCallbackInvocation(referenceRoot, context);
73081
+ return Boolean(callbackInvocation && isNodeExecutedFromDeferredCallback(callbackInvocation, snapshotOwner, context, visitedFunctionSymbolIds));
73082
+ });
73083
+ };
73084
+ const isFunctionExecutedAsDeferredCallback = (functionNode, snapshotOwner, context, visitedFunctionSymbolIds) => {
73085
+ const functionRoot = findTransparentExpressionRoot(functionNode);
73086
+ if (isDeferredCallbackValue(functionRoot, context)) return true;
73087
+ const synchronousInvocation = getSynchronousCallbackInvocation(functionRoot, context);
73088
+ if (synchronousInvocation) return isNodeExecutedFromDeferredCallback(synchronousInvocation, snapshotOwner, context, visitedFunctionSymbolIds);
73089
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
73090
+ if (!bindingIdentifier) return false;
73091
+ const symbol = context.scopes.symbolFor(bindingIdentifier);
73092
+ return Boolean(symbol && isSymbolExecutedAsDeferredCallback(symbol, snapshotOwner, context, visitedFunctionSymbolIds));
73093
+ };
73094
+ const isDirectSnapshotAliasInitializer = (identifier) => {
73095
+ const expressionRoot = findTransparentExpressionRoot(identifier);
73096
+ const parent = expressionRoot.parent;
73097
+ return Boolean(isNodeOfType(parent, "VariableDeclarator") && parent.init === expressionRoot && isNodeOfType(parent.id, "Identifier"));
73098
+ };
73099
+ const valtioNoSnapshotInCallback = defineRule({
73100
+ id: "valtio-no-snapshot-in-callback",
73101
+ title: "Valtio snapshot read in a callback",
73102
+ severity: "warn",
73103
+ requires: ["valtio", "valtio:1"],
73104
+ recommendation: "Read from the original Valtio proxy inside callbacks. `useSnapshot()` results are for render reads; callback reads can become tracked render dependencies and cause extra re-renders.",
73105
+ create: (context) => ({ Identifier(node) {
73106
+ const reference = context.scopes.referenceFor(node);
73107
+ if (!reference || reference.flag === "write") return;
73108
+ const snapshotOriginCall = getSnapshotOriginCall(node, context);
73109
+ if (!snapshotOriginCall || isDirectSnapshotAliasInitializer(node)) return;
73110
+ const snapshotOwner = findEnclosingFunction$1(snapshotOriginCall);
73111
+ if (!snapshotOwner) return;
73112
+ const referenceOwner = findEnclosingFunction$1(node);
73113
+ if (!referenceOwner || referenceOwner === snapshotOwner) return;
73114
+ if (!isFunctionExecutedAsDeferredCallback(referenceOwner, snapshotOwner, context, /* @__PURE__ */ new Set())) return;
73115
+ context.report({
73116
+ node,
73117
+ message: "This callback reads a Valtio snapshot. Read the original proxy instead so callback-only fields do not become tracked render dependencies."
73118
+ });
73119
+ } })
73120
+ });
73121
+ //#endregion
71515
73122
  //#region src/plugin/rules/react-builtins/void-dom-elements-no-children.ts
71516
73123
  const VOID_DOM_ELEMENTS = new Set([
71517
73124
  "area",
@@ -72045,6 +73652,453 @@ const zodV4PreferTopLevelStringFormats = defineRule({
72045
73652
  })
72046
73653
  });
72047
73654
  //#endregion
73655
+ //#region src/plugin/utils/function-returns-collection-at-path.ts
73656
+ const resolveImmutableInitializer = (expression, scopes, visitedSymbolIds) => {
73657
+ const candidate = stripParenExpression(expression);
73658
+ if (!isNodeOfType(candidate, "Identifier")) return candidate;
73659
+ const symbol = scopes.symbolFor(candidate);
73660
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return candidate;
73661
+ visitedSymbolIds.add(symbol.id);
73662
+ return resolveImmutableInitializer(symbol.initializer, scopes, visitedSymbolIds);
73663
+ };
73664
+ const expressionAtPropertyPath = (expression, propertyPath, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
73665
+ const candidate = resolveImmutableInitializer(expression, scopes, visitedSymbolIds);
73666
+ const [propertyName, ...remainingPath] = propertyPath;
73667
+ if (!propertyName) return candidate;
73668
+ if (!isNodeOfType(candidate, "ObjectExpression")) return null;
73669
+ for (const property of candidate.properties) {
73670
+ if (!isNodeOfType(property, "Property")) continue;
73671
+ if (getStaticPropertyKeyName(property) !== propertyName) continue;
73672
+ return expressionAtPropertyPath(property.value, remainingPath, scopes, visitedSymbolIds);
73673
+ }
73674
+ return null;
73675
+ };
73676
+ const isGlobalConstructor = (expression, constructorNames, scopes) => {
73677
+ const candidate = stripParenExpression(expression);
73678
+ return isNodeOfType(candidate, "Identifier") && constructorNames.has(candidate.name) && scopes.isGlobalReference(candidate);
73679
+ };
73680
+ const isCollectionExpression = (expression, collectionKind, scopes) => {
73681
+ const candidate = stripParenExpression(expression);
73682
+ if (collectionKind === "array" && isNodeOfType(candidate, "ArrayExpression")) return true;
73683
+ if (!isNodeOfType(candidate, "NewExpression")) return false;
73684
+ return isGlobalConstructor(candidate.callee, collectionKind === "array" ? new Set(["Array"]) : new Set(["Map", "Set"]), scopes);
73685
+ };
73686
+ const functionReturnsCollectionAtPath = ({ collectionKind, functionNode, propertyPath, scopes }) => {
73687
+ if (!isFunctionLike$1(functionNode)) return false;
73688
+ const returnedExpressions = isNodeOfType(functionNode.body, "BlockStatement") ? collectFunctionReturnStatements(functionNode).flatMap((returnStatement) => returnStatement.argument ? [returnStatement.argument] : []) : [functionNode.body];
73689
+ return returnedExpressions.length > 0 && returnedExpressions.every((returnedExpression) => {
73690
+ const collectionExpression = expressionAtPropertyPath(returnedExpression, propertyPath, scopes);
73691
+ return Boolean(collectionExpression && isCollectionExpression(collectionExpression, collectionKind, scopes));
73692
+ });
73693
+ };
73694
+ //#endregion
73695
+ //#region src/plugin/utils/resolve-zustand-api.ts
73696
+ const ZUSTAND_APIS_BY_MODULE = new Map([
73697
+ ["zustand", new Set([
73698
+ "create",
73699
+ "createStore",
73700
+ "useStore"
73701
+ ])],
73702
+ ["zustand/vanilla", new Set(["createStore"])],
73703
+ ["zustand/react", new Set(["useStore"])],
73704
+ ["zustand/traditional", new Set(["createWithEqualityFn", "useStoreWithEqualityFn"])],
73705
+ ["zustand/shallow", new Set(["shallow", "useShallow"])],
73706
+ ["zustand/react/shallow", new Set(["useShallow"])],
73707
+ ["zustand/middleware", new Set([
73708
+ "combine",
73709
+ "createJSONStorage",
73710
+ "devtools",
73711
+ "persist",
73712
+ "redux",
73713
+ "subscribeWithSelector"
73714
+ ])],
73715
+ ["zustand/middleware/immer", new Set(["immer"])]
73716
+ ]);
73717
+ const STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX = new Map([
73718
+ ["combine", 1],
73719
+ ["devtools", 0],
73720
+ ["immer", 0],
73721
+ ["persist", 0],
73722
+ ["subscribeWithSelector", 0]
73723
+ ]);
73724
+ const toZustandApiName = (value) => {
73725
+ switch (value) {
73726
+ case "combine":
73727
+ case "create":
73728
+ case "createJSONStorage":
73729
+ case "createStore":
73730
+ case "createWithEqualityFn":
73731
+ case "devtools":
73732
+ case "immer":
73733
+ case "persist":
73734
+ case "redux":
73735
+ case "shallow":
73736
+ case "subscribeWithSelector":
73737
+ case "useShallow":
73738
+ case "useStore":
73739
+ case "useStoreWithEqualityFn": return value;
73740
+ default: return null;
73741
+ }
73742
+ };
73743
+ const getImportSource = (symbol) => {
73744
+ const declaration = getImportDeclarationForSymbol(symbol);
73745
+ if (!declaration || isTypeOnlyImport(declaration) || isNodeOfType(symbol.declarationNode, "ImportSpecifier") && symbol.declarationNode.importKind === "type") return null;
73746
+ return typeof declaration.source.value === "string" ? declaration.source.value : null;
73747
+ };
73748
+ const bindingFromImportSymbol = (symbol) => {
73749
+ const moduleSource = getImportSource(symbol);
73750
+ const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
73751
+ if (!moduleSource || !supportedApiNames) return null;
73752
+ if (moduleSource === "zustand" && isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return {
73753
+ apiName: "create",
73754
+ moduleSource
73755
+ };
73756
+ const importedName = getImportedName(symbol.declarationNode);
73757
+ const apiName = importedName ? toZustandApiName(importedName) : null;
73758
+ return apiName && supportedApiNames.has(apiName) ? {
73759
+ apiName,
73760
+ moduleSource
73761
+ } : null;
73762
+ };
73763
+ const namespaceImportSource = (expression, scopes) => {
73764
+ if (!isNodeOfType(expression, "Identifier")) return null;
73765
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
73766
+ if (!symbol || !isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) return null;
73767
+ return getImportSource(symbol);
73768
+ };
73769
+ const resolveZustandApiBinding = (expression, scopes) => {
73770
+ const candidate = stripParenExpression(expression);
73771
+ if (isNodeOfType(candidate, "Identifier")) {
73772
+ const symbol = resolveConstIdentifierAlias(candidate, scopes);
73773
+ if (!symbol) return null;
73774
+ if (symbol.kind === "import") return bindingFromImportSymbol(symbol);
73775
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
73776
+ return resolveZustandApiBinding(symbol.initializer, scopes);
73777
+ }
73778
+ if (!isNodeOfType(candidate, "MemberExpression")) return null;
73779
+ const propertyName = getStaticPropertyName(candidate);
73780
+ const moduleSource = namespaceImportSource(stripParenExpression(candidate.object), scopes);
73781
+ const supportedApiNames = moduleSource ? ZUSTAND_APIS_BY_MODULE.get(moduleSource) : null;
73782
+ const apiName = propertyName ? toZustandApiName(propertyName) : null;
73783
+ return apiName && moduleSource && supportedApiNames?.has(apiName) ? {
73784
+ apiName,
73785
+ moduleSource
73786
+ } : null;
73787
+ };
73788
+ const resolveStateCreator = (expression, scopes, middlewareNames, visitedExpressions = /* @__PURE__ */ new Set()) => {
73789
+ const candidate = stripParenExpression(expression);
73790
+ if (visitedExpressions.has(candidate)) return null;
73791
+ visitedExpressions.add(candidate);
73792
+ const creatorFunction = resolveExactLocalFunction(expression, scopes);
73793
+ if (isFunctionLike$1(creatorFunction)) return creatorFunction;
73794
+ if (!isNodeOfType(candidate, "CallExpression")) return null;
73795
+ const middleware = resolveZustandApiBinding(candidate.callee, scopes);
73796
+ const creatorArgumentIndex = middleware ? STATE_CREATOR_MIDDLEWARE_ARGUMENT_INDEX.get(middleware.apiName) : null;
73797
+ if (!middleware || creatorArgumentIndex === void 0 || creatorArgumentIndex === null) return null;
73798
+ const creatorArgument = candidate.arguments[creatorArgumentIndex];
73799
+ if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
73800
+ middlewareNames.add(middleware.apiName);
73801
+ return resolveStateCreator(creatorArgument, scopes, middlewareNames, visitedExpressions);
73802
+ };
73803
+ const isStoreFactoryApi = (binding) => binding?.apiName === "create" || binding?.apiName === "createStore" || binding?.apiName === "createWithEqualityFn";
73804
+ const resolveCurriedStoreFactoryBinding = (expression, scopes) => {
73805
+ const candidate = stripParenExpression(expression);
73806
+ if (isNodeOfType(candidate, "Identifier")) {
73807
+ const symbol = resolveConstIdentifierAlias(candidate, scopes);
73808
+ if (symbol?.kind !== "const" || !symbol.initializer) return null;
73809
+ return resolveCurriedStoreFactoryBinding(symbol.initializer, scopes);
73810
+ }
73811
+ if (!isNodeOfType(candidate, "CallExpression") || candidate.arguments.length > 0) return null;
73812
+ const factoryBinding = resolveZustandApiBinding(candidate.callee, scopes);
73813
+ return isStoreFactoryApi(factoryBinding) ? factoryBinding : null;
73814
+ };
73815
+ const resolveZustandStoreCreator = (callExpression, scopes) => {
73816
+ const factoryCall = resolveZustandStoreFactoryCall(callExpression, scopes);
73817
+ if (!factoryCall) return null;
73818
+ const middlewareNames = /* @__PURE__ */ new Set();
73819
+ const creatorFunction = resolveStateCreator(factoryCall.creatorArgument, scopes, middlewareNames);
73820
+ if (!creatorFunction) return null;
73821
+ return {
73822
+ creatorFunction,
73823
+ factoryApiName: factoryCall.factoryApiName,
73824
+ middlewareNames
73825
+ };
73826
+ };
73827
+ const resolveZustandStoreFactoryCall = (callExpression, scopes) => {
73828
+ let factoryBinding = resolveZustandApiBinding(callExpression.callee, scopes);
73829
+ if (!isStoreFactoryApi(factoryBinding)) {
73830
+ factoryBinding = resolveCurriedStoreFactoryBinding(callExpression.callee, scopes);
73831
+ if (!isStoreFactoryApi(factoryBinding)) return null;
73832
+ }
73833
+ const creatorArgument = callExpression.arguments[0];
73834
+ if (!creatorArgument || isNodeOfType(creatorArgument, "SpreadElement")) return null;
73835
+ return {
73836
+ callExpression,
73837
+ creatorArgument,
73838
+ factoryApiName: factoryBinding.apiName
73839
+ };
73840
+ };
73841
+ //#endregion
73842
+ //#region src/plugin/rules/state-and-effects/zustand-no-fresh-selector-result.ts
73843
+ const ALLOCATING_ARRAY_METHODS = new Set([
73844
+ "filter",
73845
+ "flat",
73846
+ "flatMap",
73847
+ "map",
73848
+ "toReversed",
73849
+ "toSorted",
73850
+ "toSpliced",
73851
+ "with"
73852
+ ]);
73853
+ const SAME_REFERENCE_ARRAY_METHODS = new Set(["reverse", "sort"]);
73854
+ const ALLOCATING_NAMESPACE_METHOD_KINDS = new Map([["Array", new Map([["from", "array"], ["of", "array"]])], ["Object", new Map([
73855
+ ["create", "object"],
73856
+ ["entries", "array"],
73857
+ ["fromEntries", "object"],
73858
+ ["keys", "array"],
73859
+ ["values", "array"]
73860
+ ])]]);
73861
+ const isNullishEqualityArgument = (argument, scopes) => {
73862
+ const candidate = stripParenExpression(argument);
73863
+ return isNodeOfType(candidate, "Identifier") && candidate.name === "undefined" && scopes.isGlobalReference(candidate) || isNodeOfType(candidate, "Literal") && candidate.value === null || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "void";
73864
+ };
73865
+ const hasExplicitEqualityArgument = (argumentsList, equalityArgumentIndex, scopes) => {
73866
+ const equalityArgument = argumentsList[equalityArgumentIndex];
73867
+ if (!equalityArgument) return false;
73868
+ if (isNodeOfType(equalityArgument, "SpreadElement")) return true;
73869
+ return !isNullishEqualityArgument(equalityArgument, scopes);
73870
+ };
73871
+ const resolveZustandStoreCreation = (expression, scopes) => {
73872
+ const candidate = stripParenExpression(expression);
73873
+ if (!isNodeOfType(candidate, "CallExpression")) return null;
73874
+ const factoryCall = resolveZustandStoreFactoryCall(candidate, scopes);
73875
+ if (!factoryCall || factoryCall.factoryApiName !== "create" && factoryCall.factoryApiName !== "createWithEqualityFn") return null;
73876
+ return {
73877
+ creatorFunction: resolveZustandStoreCreator(candidate, scopes)?.creatorFunction ?? null,
73878
+ hasDefaultEquality: factoryCall.factoryApiName === "createWithEqualityFn" && hasExplicitEqualityArgument(candidate.arguments, 1, scopes),
73879
+ supportsEqualityArgument: factoryCall.factoryApiName === "createWithEqualityFn"
73880
+ };
73881
+ };
73882
+ const resolveZustandBoundStore = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
73883
+ const candidate = stripParenExpression(expression);
73884
+ if (!isNodeOfType(candidate, "Identifier")) return null;
73885
+ const symbol = scopes.symbolFor(candidate);
73886
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
73887
+ visitedSymbolIds.add(symbol.id);
73888
+ return resolveZustandStoreCreation(symbol.initializer, scopes) ?? resolveZustandBoundStore(symbol.initializer, scopes, visitedSymbolIds);
73889
+ };
73890
+ const getZustandSelectorCall = (callExpression, scopes) => {
73891
+ const apiBinding = resolveZustandApiBinding(callExpression.callee, scopes);
73892
+ if (apiBinding?.apiName === "useStore" || apiBinding?.apiName === "useStoreWithEqualityFn") {
73893
+ const selector = callExpression.arguments[1];
73894
+ if (!selector || isNodeOfType(selector, "SpreadElement")) return null;
73895
+ if (apiBinding.apiName === "useStoreWithEqualityFn" && hasExplicitEqualityArgument(callExpression.arguments, 2, scopes)) return null;
73896
+ return {
73897
+ selector,
73898
+ storeCreatorFunction: null
73899
+ };
73900
+ }
73901
+ const boundStore = resolveZustandBoundStore(callExpression.callee, scopes);
73902
+ const selector = callExpression.arguments[0];
73903
+ if (!boundStore || !selector || isNodeOfType(selector, "SpreadElement")) return null;
73904
+ if (boundStore.hasDefaultEquality || boundStore.supportsEqualityArgument && hasExplicitEqualityArgument(callExpression.arguments, 1, scopes)) return null;
73905
+ return {
73906
+ selector,
73907
+ storeCreatorFunction: boundStore.creatorFunction
73908
+ };
73909
+ };
73910
+ const isUseShallowCall = (callExpression, scopes) => resolveZustandApiBinding(callExpression.callee, scopes)?.apiName === "useShallow";
73911
+ const resolveSelectorFunction = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
73912
+ const candidate = stripParenExpression(expression);
73913
+ if (isFunctionLike$1(candidate)) return candidate;
73914
+ if (isNodeOfType(candidate, "CallExpression")) {
73915
+ if (isUseShallowCall(candidate, scopes)) return null;
73916
+ if (!isReactApiCall(candidate, "useCallback", scopes, {
73917
+ allowGlobalReactNamespace: true,
73918
+ resolveNamedAliases: true
73919
+ })) return null;
73920
+ const callback = candidate.arguments[0];
73921
+ if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
73922
+ return resolveSelectorFunction(callback, scopes, visitedSymbolIds);
73923
+ }
73924
+ if (!isNodeOfType(candidate, "Identifier")) return null;
73925
+ const symbol = scopes.symbolFor(candidate);
73926
+ if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
73927
+ if (symbol.kind === "function" && isFunctionLike$1(symbol.declarationNode)) return symbol.declarationNode;
73928
+ if (symbol.kind !== "const" || !symbol.initializer) return null;
73929
+ visitedSymbolIds.add(symbol.id);
73930
+ return resolveSelectorFunction(symbol.initializer, scopes, visitedSymbolIds);
73931
+ };
73932
+ const selectorStatePropertyPath = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
73933
+ const candidate = stripParenExpression(expression);
73934
+ if (isNodeOfType(candidate, "Identifier")) {
73935
+ const symbol = analysis.scopes.symbolFor(candidate);
73936
+ const selectorParameter = isFunctionLike$1(analysis.selectorFunction) ? analysis.selectorFunction.params[0] : null;
73937
+ if (selectorParameter && isNodeOfType(selectorParameter, "Identifier") && symbol?.id === analysis.scopes.symbolFor(selectorParameter)?.id) return [];
73938
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
73939
+ visitedSymbolIds.add(symbol.id);
73940
+ return selectorStatePropertyPath(symbol.initializer, analysis, visitedSymbolIds);
73941
+ }
73942
+ if (!isNodeOfType(candidate, "MemberExpression")) return null;
73943
+ const propertyName = getStaticPropertyName(candidate);
73944
+ const objectPath = selectorStatePropertyPath(candidate.object, analysis, visitedSymbolIds);
73945
+ return propertyName && objectPath ? [...objectPath, propertyName] : null;
73946
+ };
73947
+ const freshResultFromAllocatingCall = (expression, analysis, visitedSymbolIds) => {
73948
+ const callee = stripParenExpression(expression.callee);
73949
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
73950
+ const methodName = getStaticPropertyName(callee);
73951
+ if (!methodName) return null;
73952
+ const receiver = stripParenExpression(callee.object);
73953
+ if (isNodeOfType(receiver, "Identifier") && analysis.scopes.isGlobalReference(receiver)) {
73954
+ const resultKind = ALLOCATING_NAMESPACE_METHOD_KINDS.get(receiver.name)?.get(methodName);
73955
+ if (resultKind) return {
73956
+ kind: resultKind,
73957
+ node: expression
73958
+ };
73959
+ if (receiver.name === "Object" && methodName === "assign") {
73960
+ const target = expression.arguments[0];
73961
+ if (!target || isNodeOfType(target, "SpreadElement")) return null;
73962
+ const freshTarget = resolveFreshSelectorResult(target, analysis, new Set(visitedSymbolIds));
73963
+ return freshTarget ? {
73964
+ kind: freshTarget.kind,
73965
+ node: expression
73966
+ } : null;
73967
+ }
73968
+ }
73969
+ if (ALLOCATING_ARRAY_METHODS.has(methodName)) {
73970
+ if (resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds))?.kind === "array") return {
73971
+ kind: "array",
73972
+ node: expression
73973
+ };
73974
+ const receiverPath = selectorStatePropertyPath(receiver, analysis);
73975
+ if (receiverPath && analysis.storeCreatorFunction && functionReturnsCollectionAtPath({
73976
+ collectionKind: "array",
73977
+ functionNode: analysis.storeCreatorFunction,
73978
+ propertyPath: receiverPath,
73979
+ scopes: analysis.scopes
73980
+ })) return {
73981
+ kind: "array",
73982
+ node: expression
73983
+ };
73984
+ return null;
73985
+ }
73986
+ if (!SAME_REFERENCE_ARRAY_METHODS.has(methodName)) return null;
73987
+ return resolveFreshSelectorResult(receiver, analysis, new Set(visitedSymbolIds)) ? {
73988
+ kind: "array",
73989
+ node: expression
73990
+ } : null;
73991
+ };
73992
+ const resolveFreshSelectorResult = (expression, analysis, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
73993
+ const freshRenderValue = resolveFreshRenderValue(expression, analysis.scopes);
73994
+ if (freshRenderValue?.kind === "array" || freshRenderValue?.kind === "function" || freshRenderValue?.kind === "instance" || freshRenderValue?.kind === "object") return {
73995
+ kind: freshRenderValue.kind,
73996
+ node: expression
73997
+ };
73998
+ const candidate = stripParenExpression(expression);
73999
+ if (isNodeOfType(candidate, "CallExpression")) return freshResultFromAllocatingCall(candidate, analysis, visitedSymbolIds);
74000
+ if (isNodeOfType(candidate, "ConditionalExpression")) return resolveFreshSelectorResult(candidate.consequent, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.alternate, analysis, new Set(visitedSymbolIds));
74001
+ if (isNodeOfType(candidate, "LogicalExpression")) {
74002
+ if (candidate.operator === "&&") return resolveFreshSelectorResult(candidate.right, analysis, visitedSymbolIds);
74003
+ return resolveFreshSelectorResult(candidate.left, analysis, new Set(visitedSymbolIds)) ?? resolveFreshSelectorResult(candidate.right, analysis, new Set(visitedSymbolIds));
74004
+ }
74005
+ if (isNodeOfType(candidate, "SequenceExpression")) {
74006
+ const returnedExpression = candidate.expressions[candidate.expressions.length - 1];
74007
+ return returnedExpression ? resolveFreshSelectorResult(returnedExpression, analysis, visitedSymbolIds) : null;
74008
+ }
74009
+ if (!isNodeOfType(candidate, "Identifier")) return null;
74010
+ const symbol = analysis.scopes.symbolFor(candidate);
74011
+ if (symbol?.kind !== "const" || symbol.scope.kind === "module" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
74012
+ visitedSymbolIds.add(symbol.id);
74013
+ return resolveFreshSelectorResult(symbol.initializer, analysis, visitedSymbolIds);
74014
+ };
74015
+ const findFreshSelectorReturn = (selectorFunction, scopes, storeCreatorFunction) => {
74016
+ if (!isFunctionLike$1(selectorFunction) || !selectorFunction.body) return null;
74017
+ const analysis = {
74018
+ scopes,
74019
+ selectorFunction,
74020
+ storeCreatorFunction
74021
+ };
74022
+ if (!isNodeOfType(selectorFunction.body, "BlockStatement")) return resolveFreshSelectorResult(selectorFunction.body, analysis);
74023
+ let freshResult = null;
74024
+ walkAst(selectorFunction.body, (candidate) => {
74025
+ if (freshResult) return false;
74026
+ if (candidate !== selectorFunction.body && isFunctionLike$1(candidate)) return false;
74027
+ if (!isNodeOfType(candidate, "ReturnStatement") || !candidate.argument) return;
74028
+ freshResult = resolveFreshSelectorResult(candidate.argument, analysis);
74029
+ return freshResult ? false : void 0;
74030
+ });
74031
+ return freshResult;
74032
+ };
74033
+ const zustandNoFreshSelectorResult = defineRule({
74034
+ id: "zustand-no-fresh-selector-result",
74035
+ title: "Zustand selector returns a fresh value",
74036
+ severity: "error",
74037
+ category: "Performance",
74038
+ requires: ["zustand", "zustand:5"],
74039
+ recommendation: "Select a stable store field, split the selector, or wrap a collection selector with `useShallow`.",
74040
+ create: (context) => ({ CallExpression(node) {
74041
+ const selectorCall = getZustandSelectorCall(node, context.scopes);
74042
+ if (!selectorCall) return;
74043
+ const selectorFunction = resolveSelectorFunction(selectorCall.selector, context.scopes);
74044
+ if (!selectorFunction) return;
74045
+ const freshResult = findFreshSelectorReturn(selectorFunction, context.scopes, selectorCall.storeCreatorFunction);
74046
+ if (!freshResult) return;
74047
+ context.report({
74048
+ node: freshResult.node,
74049
+ message: "This Zustand selector creates a new reference whenever the store is read, so Object.is never sees a stable snapshot and Zustand v5 can repeatedly render or hit maximum update depth. Select a stable field or use `useShallow`."
74050
+ });
74051
+ } })
74052
+ });
74053
+ //#endregion
74054
+ //#region src/plugin/rules/state-and-effects/zustand-no-whole-store-destructure.ts
74055
+ const BOUND_STORE_FACTORY_APIS = new Set(["create", "createWithEqualityFn"]);
74056
+ const VANILLA_STORE_FACTORY_APIS = new Set(["createStore"]);
74057
+ const isStoreFactoryValue = (rawValue, factoryApiNames, scopes) => {
74058
+ const value = stripParenExpression(rawValue);
74059
+ if (!isNodeOfType(value, "CallExpression")) return false;
74060
+ const factoryCall = resolveZustandStoreFactoryCall(value, scopes);
74061
+ return Boolean(factoryCall && factoryApiNames.has(factoryCall.factoryApiName));
74062
+ };
74063
+ const isStoreValue = (rawValue, factoryApiNames, scopes) => {
74064
+ const value = stripParenExpression(rawValue);
74065
+ if (isStoreFactoryValue(value, factoryApiNames, scopes)) return true;
74066
+ if (!isNodeOfType(value, "Identifier")) return false;
74067
+ const symbol = resolveConstIdentifierAlias(value, scopes);
74068
+ return Boolean(symbol?.kind === "const" && symbol.initializer && isStoreFactoryValue(symbol.initializer, factoryApiNames, scopes));
74069
+ };
74070
+ const isBoundStoreCall = (node, scopes) => {
74071
+ const callee = stripParenExpression(node.callee);
74072
+ return isNodeOfType(callee, "Identifier") && isStoreValue(callee, BOUND_STORE_FACTORY_APIS, scopes);
74073
+ };
74074
+ const isVanillaStoreHookCall = (node, scopes) => {
74075
+ if (node.arguments.length !== 1) return false;
74076
+ const hook = resolveZustandApiBinding(node.callee, scopes);
74077
+ if (hook?.apiName !== "useStore" && hook?.apiName !== "useStoreWithEqualityFn") return false;
74078
+ return isStoreValue(node.arguments[0], VANILLA_STORE_FACTORY_APIS, scopes);
74079
+ };
74080
+ const isDirectReactRenderCall = (node, context) => {
74081
+ const renderFunction = findRenderPhaseComponentOrHook(node, context.scopes);
74082
+ if (!renderFunction || findEnclosingFunction$1(node) !== renderFunction) return false;
74083
+ const displayName = componentOrHookDisplayNameForFunction(renderFunction);
74084
+ return Boolean(displayName && (isReactHookName(displayName) || functionHasReactComponentEvidence(renderFunction, context.scopes, context.cfg)));
74085
+ };
74086
+ const zustandNoWholeStoreDestructure = defineRule({
74087
+ id: "zustand-no-whole-store-destructure",
74088
+ title: "Whole Zustand store subscribed during render",
74089
+ severity: "warn",
74090
+ requires: ["zustand:1"],
74091
+ recommendation: "Pass a selector to the Zustand hook so this component rerenders only when the state it reads changes.",
74092
+ create: (context) => ({ CallExpression(node) {
74093
+ if (!isDirectReactRenderCall(node, context)) return;
74094
+ if (!(node.arguments.length === 0 && isBoundStoreCall(node, context.scopes)) && !isVanillaStoreHookCall(node, context.scopes)) return;
74095
+ context.report({
74096
+ node,
74097
+ message: "This hook subscribes to the whole Zustand store, so every store update rerenders this component. Pass a selector for the state it reads."
74098
+ });
74099
+ } })
74100
+ });
74101
+ //#endregion
72048
74102
  //#region src/plugin/rule-registry.ts
72049
74103
  const reactDoctorRules = [
72050
74104
  {
@@ -73359,6 +75413,39 @@ const reactDoctorRules = [
73359
75413
  requires: [...new Set(["react", ...mediaHasCaption.requires ?? []])]
73360
75414
  }
73361
75415
  },
75416
+ {
75417
+ key: "react-doctor/mobx-no-make-auto-observable-in-inheritance",
75418
+ id: "mobx-no-make-auto-observable-in-inheritance",
75419
+ source: "react-doctor",
75420
+ originallyExternal: false,
75421
+ rule: {
75422
+ ...mobxNoMakeAutoObservableInInheritance,
75423
+ framework: "global",
75424
+ category: "Bugs"
75425
+ }
75426
+ },
75427
+ {
75428
+ key: "react-doctor/mobx-no-observer-wrapped-memo",
75429
+ id: "mobx-no-observer-wrapped-memo",
75430
+ source: "react-doctor",
75431
+ originallyExternal: false,
75432
+ rule: {
75433
+ ...mobxNoObserverWrappedMemo,
75434
+ framework: "global",
75435
+ category: "Bugs"
75436
+ }
75437
+ },
75438
+ {
75439
+ key: "react-doctor/mobx-reaction-disposer-discarded",
75440
+ id: "mobx-reaction-disposer-discarded",
75441
+ source: "react-doctor",
75442
+ originallyExternal: false,
75443
+ rule: {
75444
+ ...mobxReactionDisposerDiscarded,
75445
+ framework: "global",
75446
+ category: "Bugs"
75447
+ }
75448
+ },
73362
75449
  {
73363
75450
  key: "react-doctor/mouse-events-have-key-events",
73364
75451
  id: "mouse-events-have-key-events",
@@ -75541,6 +77628,105 @@ const reactDoctorRules = [
75541
77628
  requires: [...new Set(["react", ...reduxUseselectorReturnsNewCollection.requires ?? []])]
75542
77629
  }
75543
77630
  },
77631
+ {
77632
+ key: "react-doctor/remotion-calculate-metadata-fetch-signal",
77633
+ id: "remotion-calculate-metadata-fetch-signal",
77634
+ source: "react-doctor",
77635
+ originallyExternal: false,
77636
+ rule: {
77637
+ ...remotionCalculateMetadataFetchSignal,
77638
+ framework: "global",
77639
+ category: "Bugs"
77640
+ }
77641
+ },
77642
+ {
77643
+ key: "react-doctor/remotion-deterministic-randomness",
77644
+ id: "remotion-deterministic-randomness",
77645
+ source: "react-doctor",
77646
+ originallyExternal: false,
77647
+ rule: {
77648
+ ...remotionDeterministicRandomness,
77649
+ framework: "global",
77650
+ category: "Bugs"
77651
+ }
77652
+ },
77653
+ {
77654
+ key: "react-doctor/remotion-no-css-animation",
77655
+ id: "remotion-no-css-animation",
77656
+ source: "react-doctor",
77657
+ originallyExternal: false,
77658
+ rule: {
77659
+ ...remotionNoCssAnimation,
77660
+ framework: "global",
77661
+ category: "Bugs"
77662
+ }
77663
+ },
77664
+ {
77665
+ key: "react-doctor/remotion-no-css-transition",
77666
+ id: "remotion-no-css-transition",
77667
+ source: "react-doctor",
77668
+ originallyExternal: false,
77669
+ rule: {
77670
+ ...remotionNoCssTransition,
77671
+ framework: "global",
77672
+ category: "Bugs"
77673
+ }
77674
+ },
77675
+ {
77676
+ key: "react-doctor/remotion-no-css-url-assets",
77677
+ id: "remotion-no-css-url-assets",
77678
+ source: "react-doctor",
77679
+ originallyExternal: false,
77680
+ rule: {
77681
+ ...remotionNoCssUrlAssets,
77682
+ framework: "global",
77683
+ category: "Bugs"
77684
+ }
77685
+ },
77686
+ {
77687
+ key: "react-doctor/remotion-no-module-scope-delay-render",
77688
+ id: "remotion-no-module-scope-delay-render",
77689
+ source: "react-doctor",
77690
+ originallyExternal: false,
77691
+ rule: {
77692
+ ...remotionNoModuleScopeDelayRender,
77693
+ framework: "global",
77694
+ category: "Bugs"
77695
+ }
77696
+ },
77697
+ {
77698
+ key: "react-doctor/remotion-no-native-media-elements",
77699
+ id: "remotion-no-native-media-elements",
77700
+ source: "react-doctor",
77701
+ originallyExternal: false,
77702
+ rule: {
77703
+ ...remotionNoNativeMediaElements,
77704
+ framework: "global",
77705
+ category: "Bugs"
77706
+ }
77707
+ },
77708
+ {
77709
+ key: "react-doctor/remotion-no-next-image",
77710
+ id: "remotion-no-next-image",
77711
+ source: "react-doctor",
77712
+ originallyExternal: false,
77713
+ rule: {
77714
+ ...remotionNoNextImage,
77715
+ framework: "global",
77716
+ category: "Bugs"
77717
+ }
77718
+ },
77719
+ {
77720
+ key: "react-doctor/remotion-stable-delay-render-handle",
77721
+ id: "remotion-stable-delay-render-handle",
77722
+ source: "react-doctor",
77723
+ originallyExternal: false,
77724
+ rule: {
77725
+ ...remotionStableDelayRenderHandle,
77726
+ framework: "global",
77727
+ category: "Bugs"
77728
+ }
77729
+ },
75544
77730
  {
75545
77731
  key: "react-doctor/rendering-animate-svg-wrapper",
75546
77732
  id: "rendering-animate-svg-wrapper",
@@ -76664,6 +78850,30 @@ const reactDoctorRules = [
76664
78850
  category: "Performance"
76665
78851
  }
76666
78852
  },
78853
+ {
78854
+ key: "react-doctor/valtio-no-proxy-read-in-render",
78855
+ id: "valtio-no-proxy-read-in-render",
78856
+ source: "react-doctor",
78857
+ originallyExternal: false,
78858
+ rule: {
78859
+ ...valtioNoProxyReadInRender,
78860
+ framework: "global",
78861
+ category: "Bugs",
78862
+ requires: [...new Set(["react", ...valtioNoProxyReadInRender.requires ?? []])]
78863
+ }
78864
+ },
78865
+ {
78866
+ key: "react-doctor/valtio-no-snapshot-in-callback",
78867
+ id: "valtio-no-snapshot-in-callback",
78868
+ source: "react-doctor",
78869
+ originallyExternal: false,
78870
+ rule: {
78871
+ ...valtioNoSnapshotInCallback,
78872
+ framework: "global",
78873
+ category: "Bugs",
78874
+ requires: [...new Set(["react", ...valtioNoSnapshotInCallback.requires ?? []])]
78875
+ }
78876
+ },
76667
78877
  {
76668
78878
  key: "react-doctor/void-dom-elements-no-children",
76669
78879
  id: "void-dom-elements-no-children",
@@ -76731,6 +78941,30 @@ const reactDoctorRules = [
76731
78941
  framework: "global",
76732
78942
  category: "Maintainability"
76733
78943
  }
78944
+ },
78945
+ {
78946
+ key: "react-doctor/zustand-no-fresh-selector-result",
78947
+ id: "zustand-no-fresh-selector-result",
78948
+ source: "react-doctor",
78949
+ originallyExternal: false,
78950
+ rule: {
78951
+ ...zustandNoFreshSelectorResult,
78952
+ framework: "global",
78953
+ category: "Performance",
78954
+ requires: [...new Set(["react", ...zustandNoFreshSelectorResult.requires ?? []])]
78955
+ }
78956
+ },
78957
+ {
78958
+ key: "react-doctor/zustand-no-whole-store-destructure",
78959
+ id: "zustand-no-whole-store-destructure",
78960
+ source: "react-doctor",
78961
+ originallyExternal: false,
78962
+ rule: {
78963
+ ...zustandNoWholeStoreDestructure,
78964
+ framework: "global",
78965
+ category: "Bugs",
78966
+ requires: [...new Set(["react", ...zustandNoWholeStoreDestructure.requires ?? []])]
78967
+ }
76734
78968
  }
76735
78969
  ];
76736
78970
  const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
@@ -77000,6 +79234,13 @@ const CROSS_FILE_RULE_IDS = new Set([
77000
79234
  "no-unguarded-browser-global-in-render-or-hook-init",
77001
79235
  "prefer-dynamic-import",
77002
79236
  "rendering-hydration-mismatch-time",
79237
+ "remotion-calculate-metadata-fetch-signal",
79238
+ "remotion-deterministic-randomness",
79239
+ "remotion-no-css-animation",
79240
+ "remotion-no-css-transition",
79241
+ "remotion-no-css-url-assets",
79242
+ "remotion-no-native-media-elements",
79243
+ "remotion-no-next-image",
77003
79244
  "rerender-memo-with-default-value",
77004
79245
  "rn-no-legacy-shadow-styles",
77005
79246
  "rn-no-raw-text",
@@ -77217,7 +79458,17 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
77217
79458
  * `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
77218
79459
  * partition), forcing a conscious classification.
77219
79460
  */
77220
- const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["nextjs-no-img-element", "only-export-components"]);
79461
+ const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set([
79462
+ "nextjs-no-img-element",
79463
+ "only-export-components",
79464
+ "remotion-calculate-metadata-fetch-signal",
79465
+ "remotion-deterministic-randomness",
79466
+ "remotion-no-css-animation",
79467
+ "remotion-no-css-transition",
79468
+ "remotion-no-css-url-assets",
79469
+ "remotion-no-native-media-elements",
79470
+ "remotion-no-next-image"
79471
+ ]);
77221
79472
  /**
77222
79473
  * Runs the collectors for `ruleIds` over one file and returns every
77223
79474
  * filesystem probe they made — the file's cross-file dependency set.