oxlint-plugin-react-doctor 0.6.2-dev.da3b19c → 0.6.2-dev.fc75a3e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +446 -605
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -378,18 +378,13 @@ const isBrowserArtifactPath = (relativePath, isGeneratedBundle) => {
378
378
  const isConfigOrCiPath = (relativePath) => /(?:^|\/)(?:package\.json|Dockerfile|docker-compose\.ya?ml|\.github\/workflows\/[^/]+\.ya?ml|vercel\.json|next\.config\.[cm]?[jt]s|netlify\.toml)$/i.test(relativePath);
379
379
  //#endregion
380
380
  //#region src/plugin/rules/security-scan/utils/is-production-file-path.ts
381
- const classificationCacheByPattern = /* @__PURE__ */ new Map();
382
381
  const isProductionFilePath = (relativePath, sourceFilePattern) => {
383
- let classificationByPath = classificationCacheByPattern.get(sourceFilePattern);
384
- if (!classificationByPath) {
385
- classificationByPath = /* @__PURE__ */ new Map();
386
- classificationCacheByPattern.set(sourceFilePattern, classificationByPath);
387
- }
388
- const cached = classificationByPath.get(relativePath);
389
- if (cached !== void 0) return cached;
390
- const isProduction = sourceFilePattern.test(relativePath) && !TEST_CONTEXT_PATTERN.test(relativePath) && !BUILD_SCRIPT_CONTEXT_PATTERN.test(relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(relativePath) && !GENERATED_SOURCE_CONTEXT_PATTERN.test(relativePath);
391
- classificationByPath.set(relativePath, isProduction);
392
- return isProduction;
382
+ if (!sourceFilePattern.test(relativePath)) return false;
383
+ if (TEST_CONTEXT_PATTERN.test(relativePath)) return false;
384
+ if (BUILD_SCRIPT_CONTEXT_PATTERN.test(relativePath)) return false;
385
+ if (DOCUMENTATION_CONTEXT_PATTERN.test(relativePath)) return false;
386
+ if (GENERATED_SOURCE_CONTEXT_PATTERN.test(relativePath)) return false;
387
+ return true;
393
388
  };
394
389
  //#endregion
395
390
  //#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
@@ -788,6 +783,24 @@ const collectChildComponentNames = (element, into) => {
788
783
  into.add(name);
789
784
  });
790
785
  };
786
+ const findSameFileComponentBody = (programRoot, componentName) => {
787
+ let foundBody = null;
788
+ walkAst(programRoot, (node) => {
789
+ if (foundBody) return false;
790
+ if (isNodeOfType(node, "FunctionDeclaration") && node.id && node.id.name === componentName) {
791
+ foundBody = node.body;
792
+ return false;
793
+ }
794
+ if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.id.name === componentName) {
795
+ const initializer = node.init;
796
+ if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) {
797
+ foundBody = initializer.body;
798
+ return false;
799
+ }
800
+ }
801
+ });
802
+ return foundBody;
803
+ };
791
804
  const countEffectHookCalls = (body) => {
792
805
  if (!body) return 0;
793
806
  let count = 0;
@@ -797,36 +810,6 @@ const countEffectHookCalls = (body) => {
797
810
  });
798
811
  return count;
799
812
  };
800
- const componentEffectIndexCache = /* @__PURE__ */ new WeakMap();
801
- const getComponentEffectIndex = (programRoot) => {
802
- const cached = componentEffectIndexCache.get(programRoot);
803
- if (cached) return cached;
804
- const bodyByName = /* @__PURE__ */ new Map();
805
- walkAst(programRoot, (node) => {
806
- if (isNodeOfType(node, "FunctionDeclaration") && node.id && !bodyByName.has(node.id.name)) {
807
- bodyByName.set(node.id.name, node.body);
808
- return;
809
- }
810
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && !bodyByName.has(node.id.name)) {
811
- const initializer = node.init;
812
- if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) bodyByName.set(node.id.name, initializer.body);
813
- }
814
- });
815
- const index = {
816
- bodyByName,
817
- effectCountByName: /* @__PURE__ */ new Map()
818
- };
819
- componentEffectIndexCache.set(programRoot, index);
820
- return index;
821
- };
822
- const getSameFileComponentEffectCount = (programRoot, componentName) => {
823
- const index = getComponentEffectIndex(programRoot);
824
- const cachedCount = index.effectCountByName.get(componentName);
825
- if (cachedCount !== void 0) return cachedCount;
826
- const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
827
- index.effectCountByName.set(componentName, count);
828
- return count;
829
- };
830
813
  const activityWrapsEffectHeavySubtree = defineRule({
831
814
  id: "activity-wraps-effect-heavy-subtree",
832
815
  title: "Activity wraps an effect-heavy subtree",
@@ -883,7 +866,9 @@ const activityWrapsEffectHeavySubtree = defineRule({
883
866
  let totalEffects = 0;
884
867
  const effectfulChildren = [];
885
868
  for (const componentName of childComponentNames) {
886
- const effectCount = getSameFileComponentEffectCount(programRoot, componentName);
869
+ const body = findSameFileComponentBody(programRoot, componentName);
870
+ if (!body) continue;
871
+ const effectCount = countEffectHookCalls(body);
887
872
  if (effectCount === 0) continue;
888
873
  totalEffects += effectCount;
889
874
  effectfulChildren.push(`<${componentName}>`);
@@ -1473,12 +1458,6 @@ const getElementType = (openingElement, settings) => {
1473
1458
  return elementType;
1474
1459
  };
1475
1460
  //#endregion
1476
- //#region src/plugin/utils/has-jsx-a11y-settings.ts
1477
- const hasJsxA11ySettings = (settings) => {
1478
- const block = settings?.["jsx-a11y"];
1479
- return typeof block === "object" && block !== null;
1480
- };
1481
- //#endregion
1482
1461
  //#region src/plugin/utils/find-import-source-for-name.ts
1483
1462
  const collectFromProgram = (programRoot) => {
1484
1463
  const lookup = /* @__PURE__ */ new Map();
@@ -1682,7 +1661,7 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
1682
1661
  };
1683
1662
  //#endregion
1684
1663
  //#region src/plugin/utils/normalize-filename.ts
1685
- const normalizeFilename = (filename) => filename.includes("\\") ? filename.replaceAll("\\", "/") : filename;
1664
+ const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
1686
1665
  //#endregion
1687
1666
  //#region src/plugin/utils/is-generated-image-render-context.ts
1688
1667
  const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
@@ -2031,12 +2010,7 @@ const altText = defineRule({
2031
2010
  const objectAliases = new Set(settings.object ?? []);
2032
2011
  const areaAliases = new Set(settings.area ?? []);
2033
2012
  const inputImageAliases = new Set(settings["input[type=\"image\"]"] ?? []);
2034
- const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
2035
2013
  return { JSXOpeningElement(node) {
2036
- if (!fileHasJsxA11ySettings && isNodeOfType(node.name, "JSXIdentifier")) {
2037
- const rawName = node.name.name;
2038
- if (rawName !== "img" && rawName !== "object" && rawName !== "area" && rawName.toLowerCase() !== "input" && !imgAliases.has(rawName) && !objectAliases.has(rawName) && !areaAliases.has(rawName) && !inputImageAliases.has(rawName)) return;
2039
- }
2040
2014
  if (isGeneratedImageRenderContext(context, node)) return;
2041
2015
  const tag = getElementType(node, context.settings);
2042
2016
  if (checkImg && (tag === "img" || imgAliases.has(tag))) {
@@ -2219,9 +2193,7 @@ const anchorIsValid = defineRule({
2219
2193
  category: "Accessibility",
2220
2194
  create: (context) => {
2221
2195
  const settings = resolveSettings$50(context.settings);
2222
- const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
2223
2196
  return { JSXOpeningElement(node) {
2224
- if (!fileHasJsxA11ySettings && (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "a")) return;
2225
2197
  if (getElementType(node, context.settings) !== "a") return;
2226
2198
  let hrefAttribute;
2227
2199
  for (const attributeName of settings.hrefAttributeNames) {
@@ -3647,9 +3619,8 @@ const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
3647
3619
  const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3648
3620
  //#endregion
3649
3621
  //#region src/plugin/rules/security-scan/utils/find-suspicious-public-env-secret-name.ts
3650
- const PUBLIC_ENV_SECRET_NAME_GLOBAL_PATTERN = new RegExp(PUBLIC_ENV_SECRET_NAME_PATTERN.source, "gi");
3651
3622
  const findSuspiciousPublicEnvSecretNamePattern = (content) => {
3652
- for (const match of content.matchAll(PUBLIC_ENV_SECRET_NAME_GLOBAL_PATTERN)) {
3623
+ for (const match of content.matchAll(new RegExp(PUBLIC_ENV_SECRET_NAME_PATTERN.source, "gi"))) {
3653
3624
  const value = match[0] ?? "";
3654
3625
  if (!TRUSTED_PUBLIC_SECRET_NAME_PATTERN.test(value)) return new RegExp(escapeRegExp(value));
3655
3626
  }
@@ -4116,56 +4087,62 @@ const collectPatternIdentifiers = (pattern, target) => {
4116
4087
  for (const element of pattern.elements ?? []) if (element) collectPatternIdentifiers(element, target);
4117
4088
  } else if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternIdentifiers(pattern.left, target);
4118
4089
  };
4090
+ const collectAssignedIdentifiers = (block) => {
4091
+ const assigned = /* @__PURE__ */ new Set();
4092
+ walkAst(block, (child) => {
4093
+ if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
4094
+ if (isNodeOfType(child, "AssignmentExpression") && child.left) collectPatternIdentifiers(child.left, assigned);
4095
+ });
4096
+ return assigned;
4097
+ };
4098
+ const collectAwaitedArgIdentifiers = (block) => {
4099
+ const referenced = /* @__PURE__ */ new Set();
4100
+ walkAst(block, (child) => {
4101
+ if (isInlineFunctionExpression(child) || isNodeOfType(child, "FunctionDeclaration")) return false;
4102
+ if (!isNodeOfType(child, "AwaitExpression") || !child.argument) return;
4103
+ collectReferenceIdentifierNames(child.argument, referenced);
4104
+ });
4105
+ return referenced;
4106
+ };
4119
4107
  const ARRAY_MUTATION_METHOD_NAMES = new Set([
4120
4108
  "push",
4121
4109
  "unshift",
4122
4110
  "splice"
4123
4111
  ]);
4124
- const addDerivedBindings = (block, names) => {
4125
- const declaratorBindings = [];
4112
+ const collectMutatedArrayNames = (block) => {
4113
+ const mutated = /* @__PURE__ */ new Set();
4126
4114
  walkAst(block, (child) => {
4127
4115
  if (child !== block && isFunctionLike$1(child)) return false;
4128
- if (!isNodeOfType(child, "VariableDeclarator") || !child.init) return;
4129
- if (!isNodeOfType(child.id, "Identifier")) return;
4130
- const referencedNames = /* @__PURE__ */ new Set();
4131
- collectReferenceIdentifierNames(child.init, referencedNames);
4132
- declaratorBindings.push({
4133
- declaredName: child.id.name,
4134
- referencedNames
4135
- });
4116
+ if (!isNodeOfType(child, "CallExpression")) return;
4117
+ const callee = child.callee;
4118
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && ARRAY_MUTATION_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) mutated.add(callee.object.name);
4136
4119
  });
4120
+ return mutated;
4121
+ };
4122
+ const addDerivedBindings = (block, names) => {
4137
4123
  let didGrow = true;
4138
4124
  while (didGrow) {
4139
4125
  didGrow = false;
4140
- for (const { declaredName, referencedNames } of declaratorBindings) {
4141
- if (names.has(declaredName)) continue;
4142
- for (const referenced of referencedNames) if (names.has(referenced)) {
4143
- names.add(declaredName);
4126
+ walkAst(block, (child) => {
4127
+ if (child !== block && isFunctionLike$1(child)) return false;
4128
+ if (!isNodeOfType(child, "VariableDeclarator") || !child.init) return;
4129
+ if (!isNodeOfType(child.id, "Identifier") || names.has(child.id.name)) return;
4130
+ const initReferences = /* @__PURE__ */ new Set();
4131
+ collectReferenceIdentifierNames(child.init, initReferences);
4132
+ for (const referenced of initReferences) if (names.has(referenced)) {
4133
+ names.add(child.id.name);
4144
4134
  didGrow = true;
4145
4135
  break;
4146
4136
  }
4147
- }
4137
+ });
4148
4138
  }
4149
4139
  };
4150
4140
  const hasLoopCarriedDependency = (block) => {
4151
- const carried = /* @__PURE__ */ new Set();
4152
- const awaitedReferences = /* @__PURE__ */ new Set();
4153
- walkAst(block, (child) => {
4154
- if (child !== block && isFunctionLike$1(child)) return false;
4155
- if (isNodeOfType(child, "AssignmentExpression") && child.left) {
4156
- collectPatternIdentifiers(child.left, carried);
4157
- return;
4158
- }
4159
- if (isNodeOfType(child, "AwaitExpression") && child.argument) {
4160
- collectReferenceIdentifierNames(child.argument, awaitedReferences);
4161
- return;
4162
- }
4163
- if (!isNodeOfType(child, "CallExpression")) return;
4164
- const callee = child.callee;
4165
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && ARRAY_MUTATION_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) carried.add(callee.object.name);
4166
- });
4141
+ const carried = collectAssignedIdentifiers(block);
4142
+ for (const name of collectMutatedArrayNames(block)) carried.add(name);
4167
4143
  if (carried.size === 0) return false;
4168
4144
  addDerivedBindings(block, carried);
4145
+ const awaitedReferences = collectAwaitedArgIdentifiers(block);
4169
4146
  for (const name of carried) if (awaitedReferences.has(name)) return true;
4170
4147
  return false;
4171
4148
  };
@@ -4843,8 +4820,7 @@ const FORM_CONTROL_TAGS = new Set([
4843
4820
  ]);
4844
4821
  const resolveSettings$48 = (settings) => {
4845
4822
  const reactDoctor = settings?.["react-doctor"];
4846
- const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {};
4847
- return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
4823
+ return { inputComponents: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {}).inputComponents ?? [] };
4848
4824
  };
4849
4825
  const autocompleteValid = defineRule({
4850
4826
  id: "autocomplete-valid",
@@ -4857,7 +4833,7 @@ const autocompleteValid = defineRule({
4857
4833
  const settings = resolveSettings$48(context.settings);
4858
4834
  return { JSXOpeningElement: (node) => {
4859
4835
  const tag = getElementType(node, context.settings);
4860
- if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.has(tag)) return;
4836
+ if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.includes(tag)) return;
4861
4837
  const attribute = hasJsxPropIgnoreCase(node.attributes, "autoComplete");
4862
4838
  if (!attribute) return;
4863
4839
  const value = getJsxPropStringValue(attribute);
@@ -4901,8 +4877,9 @@ const isCreateElementCall = (node) => {
4901
4877
  const callee = node.callee;
4902
4878
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createElement";
4903
4879
  if (isNodeOfType(callee, "MemberExpression")) {
4904
- if (!(callee.computed ? isNodeOfType(callee.property, "Literal") && callee.property.value === "createElement" : isNodeOfType(callee.property, "Identifier") && callee.property.name === "createElement")) return false;
4905
- return !memberChainContainsDocument(callee.object);
4880
+ if (memberChainContainsDocument(callee.object)) return false;
4881
+ if (callee.computed) return isNodeOfType(callee.property, "Literal") && callee.property.value === "createElement";
4882
+ return isNodeOfType(callee.property, "Identifier") && callee.property.name === "createElement";
4906
4883
  }
4907
4884
  return false;
4908
4885
  };
@@ -5719,20 +5696,12 @@ const getTemplateInterpolations = (valueTail) => {
5719
5696
  return interpolations === null ? "" : interpolations.join(" ");
5720
5697
  };
5721
5698
  const isInertParseTarget = (target, fileContent) => {
5722
- const fileHasCreateElement = fileContent.includes("createElement");
5723
- const fileHasIsolatedDocument = fileContent.includes("createHTMLDocument");
5724
- if (!fileHasCreateElement && !fileHasIsolatedDocument) return false;
5725
5699
  const escapedTarget = escapeRegExp(target);
5726
5700
  const escapedRoot = escapeRegExp(target.split(".")[0] ?? target);
5727
5701
  if (new RegExp(`\\b${escapedRoot}\\s*=\\s*[^\\n;]*(?:getElementById|querySelector|getElementsBy|\\.current\\b|document\\.(?:body|head|documentElement))`).test(fileContent)) return false;
5728
- if (fileHasCreateElement) {
5729
- if (new RegExp(`${escapedTarget}\\s*=\\s*document\\.createElement\\(\\s*["'\`]template["'\`]`).test(fileContent)) return true;
5730
- if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateElement\\(\\s*["'\`](?:style|textarea)["'\`]`).test(fileContent)) return true;
5731
- }
5732
- if (fileHasIsolatedDocument) {
5733
- if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateHTMLDocument\\s*\\(`).test(fileContent)) return true;
5734
- }
5735
- if (!fileHasCreateElement) return false;
5702
+ if (new RegExp(`${escapedTarget}\\s*=\\s*document\\.createElement\\(\\s*["'\`]template["'\`]`).test(fileContent)) return true;
5703
+ if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateElement\\(\\s*["'\`](?:style|textarea)["'\`]`).test(fileContent)) return true;
5704
+ if (new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateHTMLDocument\\s*\\(`).test(fileContent)) return true;
5736
5705
  if (!new RegExp(`${escapedRoot}\\s*=\\s*[^\\n;]*\\bcreateElement\\s*\\(`).test(fileContent)) return false;
5737
5706
  const attachedToLiveTreePattern = new RegExp(`${LIVE_DOM_ATTACH_PATTERN.source}[^)]*\\b${escapedRoot}\\b`);
5738
5707
  const returnedAsNodePattern = new RegExp(`\\breturn\\b[^\\n]*\\b${escapedRoot}\\b(?!\\s*\\.\\s*(?:textContent|innerText|innerHTML|outerHTML))`);
@@ -5825,9 +5794,10 @@ const dangerousHtmlSink = defineRule({
5825
5794
  const valueIdentifier = valueExpression.match(/^[\w$]+/)?.[0];
5826
5795
  if (valueIdentifier !== void 0) {
5827
5796
  const escapedIdentifier = escapeRegExp(valueIdentifier);
5828
- if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i").test(file.content)) continue;
5829
- if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i").test(file.content)) continue;
5830
- if (new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`).test(file.content)) continue;
5797
+ const fromSerializer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SERIALIZER_ASSIGNMENT_PATTERN.source}`, "i");
5798
+ const fromSanitizer = new RegExp(`\\b${escapedIdentifier}\\b\\s*${SANITIZED_ASSIGNMENT_PATTERN.source}`, "i");
5799
+ const fromDomContent = new RegExp(`\\b${escapedIdentifier}\\b\\s*${DOM_CONTENT_ASSIGNMENT_PATTERN.source}`);
5800
+ if (fromSerializer.test(file.content) || fromSanitizer.test(file.content) || fromDomContent.test(file.content)) continue;
5831
5801
  }
5832
5802
  }
5833
5803
  const sinkTargetMatch = INNERHTML_TARGET_PATTERN.exec(line);
@@ -5976,7 +5946,6 @@ const noRedundantPaddingAxes = defineRule({
5976
5946
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
5977
5947
  const classNameLiteral = getClassNameLiteral(jsxAttribute);
5978
5948
  if (!classNameLiteral) return;
5979
- if (!classNameLiteral.includes("px-") || !classNameLiteral.includes("py-")) return;
5980
5949
  if (hasResponsivePrefix(classNameLiteral, "px") || hasResponsivePrefix(classNameLiteral, "py")) return;
5981
5950
  const matchedPairs = collectAxisShorthandPairs(classNameLiteral, PADDING_HORIZONTAL_AXIS_PATTERN, PADDING_VERTICAL_AXIS_PATTERN);
5982
5951
  if (matchedPairs.length === 0) return;
@@ -6001,7 +5970,6 @@ const noRedundantSizeAxes = defineRule({
6001
5970
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
6002
5971
  const classNameLiteral = getClassNameLiteral(jsxAttribute);
6003
5972
  if (!classNameLiteral) return;
6004
- if (!classNameLiteral.includes("w-") || !classNameLiteral.includes("h-")) return;
6005
5973
  if (hasResponsivePrefix(classNameLiteral, "w") || hasResponsivePrefix(classNameLiteral, "h")) return;
6006
5974
  const matchedPairs = collectAxisShorthandPairs(classNameLiteral, SIZE_WIDTH_AXIS_PATTERN, SIZE_HEIGHT_AXIS_PATTERN);
6007
5975
  if (matchedPairs.length === 0) return;
@@ -6026,7 +5994,6 @@ const noSpaceOnFlexChildren = defineRule({
6026
5994
  if (!isNodeOfType(jsxAttribute.name, "JSXIdentifier") || jsxAttribute.name.name !== "className") return;
6027
5995
  const classNameLiteral = getClassNameLiteral(jsxAttribute);
6028
5996
  if (!classNameLiteral) return;
6029
- if (!classNameLiteral.includes("space-")) return;
6030
5997
  const tokens = tokenizeClassName(classNameLiteral);
6031
5998
  let hasFlexOrGridLayout = false;
6032
5999
  for (const token of tokens) {
@@ -6218,10 +6185,7 @@ const isReactVersionAtLeast$1 = (version, major, minor) => {
6218
6185
  const actualMinor = Number(match[2]);
6219
6186
  return actualMajor > major || actualMajor === major && actualMinor >= minor;
6220
6187
  };
6221
- const containsJsxCache = /* @__PURE__ */ new WeakMap();
6222
6188
  const containsJsx$1 = (root) => {
6223
- const cached = containsJsxCache.get(root);
6224
- if (cached !== void 0) return cached;
6225
6189
  let found = false;
6226
6190
  const visit = (node) => {
6227
6191
  if (found) return;
@@ -6244,7 +6208,6 @@ const containsJsx$1 = (root) => {
6244
6208
  }
6245
6209
  };
6246
6210
  visit(root);
6247
- containsJsxCache.set(root, found);
6248
6211
  return found;
6249
6212
  };
6250
6213
  const getStaticMemberName = (node) => {
@@ -6336,6 +6299,27 @@ const hasDisplayNameMember = (classNode) => {
6336
6299
  for (const member of members) if ((isNodeOfType(member, "PropertyDefinition") || isNodeOfType(member, "MethodDefinition")) && "static" in member && member.static && isNodeOfType(member.key, "Identifier") && member.key.name === "displayName") return true;
6337
6300
  return false;
6338
6301
  };
6302
+ const hasDisplayNameAssignment = (className, programRoot) => {
6303
+ let found = false;
6304
+ const visit = (node) => {
6305
+ if (found) return;
6306
+ if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.object, "Identifier") && node.left.object.name === className && getStaticMemberName(node.left) === "displayName") {
6307
+ found = true;
6308
+ return;
6309
+ }
6310
+ const record = node;
6311
+ for (const key of Object.keys(record)) {
6312
+ if (key === "parent") continue;
6313
+ const child = record[key];
6314
+ if (Array.isArray(child)) {
6315
+ for (const item of child) if (isAstNode(item)) visit(item);
6316
+ } else if (isAstNode(child)) visit(child);
6317
+ if (found) return;
6318
+ }
6319
+ };
6320
+ visit(programRoot);
6321
+ return found;
6322
+ };
6339
6323
  const memberExpressionPath = (node) => {
6340
6324
  if (isNodeOfType(node, "Identifier")) return [node.name];
6341
6325
  if (!isNodeOfType(node, "MemberExpression")) return [];
@@ -6343,16 +6327,15 @@ const memberExpressionPath = (node) => {
6343
6327
  const propertyName = getStaticMemberName(node);
6344
6328
  return propertyName ? [...objectPath, propertyName] : objectPath;
6345
6329
  };
6346
- const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
6347
- const getDisplayNameAssignmentIndex = (programRoot) => {
6348
- const cached = displayNameAssignmentIndexCache.get(programRoot);
6349
- if (cached) return cached;
6350
- const identifierTargets = /* @__PURE__ */ new Set();
6351
- const memberPathSegments = /* @__PURE__ */ new Set();
6330
+ const hasDisplayNameAssignmentForProperty = (propertyName, programRoot) => {
6331
+ let found = false;
6352
6332
  const visit = (node) => {
6333
+ if (found) return;
6353
6334
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName(node.left) === "displayName") {
6354
- if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
6355
- for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
6335
+ if (memberExpressionPath(node.left.object).includes(propertyName)) {
6336
+ found = true;
6337
+ return;
6338
+ }
6356
6339
  }
6357
6340
  const record = node;
6358
6341
  for (const key of Object.keys(record)) {
@@ -6361,18 +6344,12 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
6361
6344
  if (Array.isArray(child)) {
6362
6345
  for (const item of child) if (isAstNode(item)) visit(item);
6363
6346
  } else if (isAstNode(child)) visit(child);
6347
+ if (found) return;
6364
6348
  }
6365
6349
  };
6366
6350
  visit(programRoot);
6367
- const index = {
6368
- identifierTargets,
6369
- memberPathSegments
6370
- };
6371
- displayNameAssignmentIndexCache.set(programRoot, index);
6372
- return index;
6351
+ return found;
6373
6352
  };
6374
- const hasDisplayNameAssignment = (className, programRoot) => getDisplayNameAssignmentIndex(programRoot).identifierTargets.has(className);
6375
- const hasDisplayNameAssignmentForProperty = (propertyName, programRoot) => getDisplayNameAssignmentIndex(programRoot).memberPathSegments.has(propertyName);
6376
6353
  const displayName = defineRule({
6377
6354
  id: "display-name",
6378
6355
  title: "Component missing display name",
@@ -8637,7 +8614,7 @@ const DEFAULT_HEADING_TAGS = [
8637
8614
  const resolveSettings$40 = (settings) => {
8638
8615
  const reactDoctor = settings?.["react-doctor"];
8639
8616
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
8640
- return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
8617
+ return { headingTags: [...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []] };
8641
8618
  };
8642
8619
  const headingHasContent = defineRule({
8643
8620
  id: "heading-has-content",
@@ -8650,7 +8627,7 @@ const headingHasContent = defineRule({
8650
8627
  const settings = resolveSettings$40(context.settings);
8651
8628
  return { JSXOpeningElement(node) {
8652
8629
  const elementType = getElementType(node, context.settings);
8653
- if (!settings.headingTags.has(elementType)) return;
8630
+ if (!settings.headingTags.includes(elementType)) return;
8654
8631
  const parent = node.parent;
8655
8632
  if (parent && isNodeOfType(parent, "JSXElement")) {
8656
8633
  if (objectHasAccessibleChild(parent, context.settings)) return;
@@ -9289,10 +9266,10 @@ const imgRedundantAlt = defineRule({
9289
9266
  if (isGeneratedImageRenderContext(context)) return {};
9290
9267
  const settings = resolveSettings$37(context.settings);
9291
9268
  return { JSXOpeningElement(node) {
9269
+ if (isGeneratedImageRenderContext(context, node)) return;
9292
9270
  const tag = getElementType(node, context.settings);
9293
9271
  if (!settings.components.includes(tag)) return;
9294
9272
  if (isHiddenFromScreenReader(node, context.settings)) return;
9295
- if (isGeneratedImageRenderContext(context, node)) return;
9296
9273
  const altAttribute = hasJsxPropIgnoreCase(node.attributes, "alt");
9297
9274
  if (!altAttribute) return;
9298
9275
  if (altValueRedundant(altAttribute, settings.words)) context.report({
@@ -9347,7 +9324,6 @@ const SECURITY_RANDOM_CONTEXT_PATTERN = /token|secret|password|nonce|salt|csrf|c
9347
9324
  const UI_NONCE_CONTEXT_PATTERN = /(?:focus|render|refresh|remount|redraw|animation|layout|cache|update)[-_]?nonce/i;
9348
9325
  const MATH_RANDOM_CALL_PATTERN = /Math\.random\s*\(/g;
9349
9326
  const SECURITY_CONTEXT_WINDOW_CHARS = 250;
9350
- const CRYPTO_SURFACE_TRIGGER_PATTERN = /createHash|md5|cipher|encrypt|decrypt|crypto|signature|Math\.random/i;
9351
9327
  const findMatchIndexNearContext = (content, pattern, contextPattern, excludeContextPattern) => {
9352
9328
  for (const callMatch of content.matchAll(pattern)) {
9353
9329
  const surroundingText = content.slice(Math.max(0, callMatch.index - SECURITY_CONTEXT_WINDOW_CHARS), callMatch.index + SECURITY_CONTEXT_WINDOW_CHARS);
@@ -9377,7 +9353,6 @@ const insecureCryptoRisk = defineRule({
9377
9353
  if (!isProductionSourcePath(file.relativePath)) return [];
9378
9354
  if (DEMO_CONTEXT_PATTERN.test(file.relativePath)) return [];
9379
9355
  if (PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN.test(file.relativePath)) return [];
9380
- if (!CRYPTO_SURFACE_TRIGGER_PATTERN.test(file.content)) return [];
9381
9356
  const content = getScannableContent(file);
9382
9357
  let matchIndex = findMatchIndexNearContext(content, WEAK_HASH_PATTERN, SECURITY_CONTEXT_PATTERN, PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN);
9383
9358
  if (matchIndex < 0) matchIndex = content.search(WEAK_CIPHER_ALGORITHM_PATTERN);
@@ -9556,7 +9531,6 @@ const KEYBOARD_EVENT_HANDLERS = [
9556
9531
  "onKeyUp"
9557
9532
  ];
9558
9533
  const ALL_EVENT_HANDLERS = [...MOUSE_EVENT_HANDLERS, ...KEYBOARD_EVENT_HANDLERS];
9559
- const ALL_EVENT_HANDLERS_LOWER = new Set(ALL_EVENT_HANDLERS.map((handlerName) => handlerName.toLowerCase()));
9560
9534
  //#endregion
9561
9535
  //#region src/plugin/utils/is-disabled-element.ts
9562
9536
  const isDisabledElement = (openingElement) => {
@@ -9611,25 +9585,15 @@ const interactiveSupportsFocus = defineRule({
9611
9585
  const settings = resolveSettings$36(context.settings);
9612
9586
  const tabbableSet = new Set(settings.tabbable);
9613
9587
  return { JSXOpeningElement(node) {
9614
- if (node.attributes.length === 0) return;
9615
9588
  if (hasJsxSpreadAttribute$1(node.attributes)) return;
9616
9589
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
9617
9590
  const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
9618
- if (!role) return;
9619
- let hasInteractiveHandler = false;
9620
- for (const attribute of node.attributes) {
9621
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
9622
- const attributeName = getJsxAttributeName(attribute.name);
9623
- if (attributeName && ALL_EVENT_HANDLERS_LOWER.has(attributeName.toLowerCase())) {
9624
- hasInteractiveHandler = true;
9625
- break;
9626
- }
9627
- }
9628
- if (!hasInteractiveHandler) return;
9629
9591
  const elementType = getElementType(node, context.settings);
9630
9592
  if (!HTML_TAGS.has(elementType)) return;
9631
- if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
9593
+ const hasInteractiveHandler = ALL_EVENT_HANDLERS.some((handler) => Boolean(hasJsxPropIgnoreCase(node.attributes, handler)));
9632
9594
  const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
9595
+ if (!hasInteractiveHandler || isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
9596
+ if (!role) return;
9633
9597
  if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
9634
9598
  const message = tabbableSet.has(role) ? buildTabbableMessage(role) : buildFocusableMessage(role);
9635
9599
  context.report({
@@ -10268,6 +10232,8 @@ const jsCachePropertyAccess = defineRule({
10268
10232
  walkAst(loopBody, (child) => {
10269
10233
  if (isNodeOfType(child, "AssignmentExpression")) recordWriteTarget(child.left);
10270
10234
  if (isNodeOfType(child, "UpdateExpression")) recordWriteTarget(child.argument);
10235
+ });
10236
+ walkAst(loopBody, (child) => {
10271
10237
  if (!isNodeOfType(child, "MemberExpression")) return;
10272
10238
  if (child.computed) return;
10273
10239
  if (isNodeOfType(child.parent, "MemberExpression") && child.parent.object === child) return;
@@ -10727,6 +10693,7 @@ const jsHoistIntl = defineRule({
10727
10693
  recommendation: "Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",
10728
10694
  create: (context) => ({ NewExpression(node) {
10729
10695
  if (!isIntlNewExpression(node)) return;
10696
+ if (isInsideCacheMemo(node)) return;
10730
10697
  let cursor = node.parent ?? null;
10731
10698
  let inFunctionBody = false;
10732
10699
  while (cursor) {
@@ -10743,7 +10710,6 @@ const jsHoistIntl = defineRule({
10743
10710
  cursor = cursor.parent ?? null;
10744
10711
  }
10745
10712
  if (!inFunctionBody) return;
10746
- if (isInsideCacheMemo(node)) return;
10747
10713
  const className = isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : "Intl";
10748
10714
  context.report({
10749
10715
  node,
@@ -10818,11 +10784,7 @@ const isSingleFieldEqualityPredicate = (node) => {
10818
10784
  if (!isNodeOfType(predicate, "BinaryExpression") || predicate.operator !== "===" && predicate.operator !== "==") return false;
10819
10785
  return referencesParameter(predicate.left, firstParameter.name) || referencesParameter(predicate.right, firstParameter.name);
10820
10786
  };
10821
- const loopBoundNamesCache = /* @__PURE__ */ new WeakMap();
10822
- const getLoopBoundNames = (loop) => {
10823
- const cached = loopBoundNamesCache.get(loop);
10824
- if (cached) return cached;
10825
- const names = /* @__PURE__ */ new Set();
10787
+ const collectLoopBoundNames = (loop, names) => {
10826
10788
  if ((isNodeOfType(loop, "ForOfStatement") || isNodeOfType(loop, "ForInStatement")) && loop.left) walkAst(loop.left, (child) => {
10827
10789
  if (isNodeOfType(child, "Identifier")) names.add(child.name);
10828
10790
  });
@@ -10839,8 +10801,6 @@ const getLoopBoundNames = (loop) => {
10839
10801
  if (targetRoot) names.add(targetRoot);
10840
10802
  }
10841
10803
  });
10842
- loopBoundNamesCache.set(loop, names);
10843
- return names;
10844
10804
  };
10845
10805
  const hasLoopBoundComputedIndex = (receiver, loopBoundNames) => {
10846
10806
  let cursor = receiver;
@@ -10866,7 +10826,7 @@ const isLoopVariantReceiver = (node) => {
10866
10826
  const loopBoundNames = /* @__PURE__ */ new Set();
10867
10827
  let ancestor = node.parent;
10868
10828
  while (ancestor) {
10869
- if (LOOP_TYPES.includes(ancestor.type)) for (const name of getLoopBoundNames(ancestor)) loopBoundNames.add(name);
10829
+ if (LOOP_TYPES.includes(ancestor.type)) collectLoopBoundNames(ancestor, loopBoundNames);
10870
10830
  ancestor = ancestor.parent;
10871
10831
  }
10872
10832
  if (loopBoundNames.has(receiverRoot)) return true;
@@ -14474,14 +14434,14 @@ const keyLifecycleRisk = defineRule({
14474
14434
  //#region src/plugin/rules/a11y/label-has-associated-control.ts
14475
14435
  const MESSAGE_NO_LABEL = "Blind users can't identify this field because screen readers find no label text, so add visible text, `aria-label`, or `aria-labelledby`.";
14476
14436
  const MESSAGE_NO_CONTROL = "Screen reader users can't tell which input this label names because it's tied to none, so add `htmlFor` or wrap the input inside it.";
14477
- const DEFAULT_CONTROL_COMPONENTS = new Set([
14437
+ const DEFAULT_CONTROL_COMPONENTS = [
14478
14438
  "input",
14479
14439
  "meter",
14480
14440
  "output",
14481
14441
  "progress",
14482
14442
  "select",
14483
14443
  "textarea"
14484
- ]);
14444
+ ];
14485
14445
  const DEFAULT_LABEL_ATTRIBUTES = [
14486
14446
  "alt",
14487
14447
  "aria-label",
@@ -14493,8 +14453,8 @@ const resolveSettings$24 = (settings) => {
14493
14453
  const jsxA11y = settings?.["jsx-a11y"];
14494
14454
  const forAttributes = (typeof jsxA11y === "object" && jsxA11y !== null ? jsxA11y : {}).attributes?.for ?? ["htmlFor"];
14495
14455
  return {
14496
- labelComponents: new Set(["label", ...ruleSettings.labelComponents ?? []]),
14497
- labelAttributes: new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []]),
14456
+ labelComponents: ["label", ...ruleSettings.labelComponents ?? []].sort(),
14457
+ labelAttributes: [...new Set([...DEFAULT_LABEL_ATTRIBUTES, ...ruleSettings.labelAttributes ?? []])].sort(),
14498
14458
  controlComponents: ruleSettings.controlComponents ?? [],
14499
14459
  assert: ruleSettings.assert ?? "either",
14500
14460
  depth: Math.min(ruleSettings.depth ?? 5, 25),
@@ -14502,7 +14462,7 @@ const resolveSettings$24 = (settings) => {
14502
14462
  };
14503
14463
  };
14504
14464
  const isControlComponent = (tagName, controlComponents) => {
14505
- if (DEFAULT_CONTROL_COMPONENTS.has(tagName)) return true;
14465
+ if (DEFAULT_CONTROL_COMPONENTS.includes(tagName)) return true;
14506
14466
  return controlComponents.some((pattern) => compileGlob(pattern).test(tagName));
14507
14467
  };
14508
14468
  const searchForNestedControl = (child, currentDepth, searchContext) => {
@@ -14528,7 +14488,7 @@ const searchForAccessibleLabel = (child, currentDepth, searchContext) => {
14528
14488
  const attributeName = attribute.name;
14529
14489
  if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
14530
14490
  const propName = getJsxAttributeName(attributeName);
14531
- if (!propName || !searchContext.labelAttributes.has(propName)) continue;
14491
+ if (!propName || !searchContext.labelAttributes.includes(propName)) continue;
14532
14492
  const attributeValue = attribute.value;
14533
14493
  if (!attributeValue) continue;
14534
14494
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
@@ -14550,7 +14510,7 @@ const hasAccessibleLabel = (element, searchContext) => {
14550
14510
  const attributeName = attribute.name;
14551
14511
  if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
14552
14512
  const propName = getJsxAttributeName(attributeName);
14553
- if (propName && searchContext.labelAttributes.has(propName)) return true;
14513
+ if (propName && searchContext.labelAttributes.includes(propName)) return true;
14554
14514
  }
14555
14515
  for (const child of element.children) if (searchForAccessibleLabel(child, 1, searchContext)) return true;
14556
14516
  return false;
@@ -14573,7 +14533,7 @@ const labelHasAssociatedControl = defineRule({
14573
14533
  if (isTestlikeFile) return;
14574
14534
  const opening = node.openingElement;
14575
14535
  const tagName = getElementType(opening, context.settings);
14576
- if (!settings.labelComponents.has(tagName)) return;
14536
+ if (!settings.labelComponents.includes(tagName)) return;
14577
14537
  const hasHtmlFor = settings.forAttributes.some((attributeName) => Boolean(hasJsxPropIgnoreCase(opening.attributes, attributeName)));
14578
14538
  const searchContext = {
14579
14539
  depth: settings.depth,
@@ -15162,9 +15122,9 @@ const resolveSettings$23 = (settings) => {
15162
15122
  const reactDoctor = settings?.["react-doctor"];
15163
15123
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.mediaHasCaption ?? {} : {};
15164
15124
  return {
15165
- audio: new Set([...DEFAULT_AUDIO, ...ruleSettings.audio ?? []]),
15166
- video: new Set([...DEFAULT_VIDEO, ...ruleSettings.video ?? []]),
15167
- track: new Set([...DEFAULT_TRACK, ...ruleSettings.track ?? []])
15125
+ audio: [...DEFAULT_AUDIO, ...ruleSettings.audio ?? []],
15126
+ video: [...DEFAULT_VIDEO, ...ruleSettings.video ?? []],
15127
+ track: [...DEFAULT_TRACK, ...ruleSettings.track ?? []]
15168
15128
  };
15169
15129
  };
15170
15130
  const evaluateMuted = (attribute) => {
@@ -15193,7 +15153,7 @@ const childMayRenderTrack = (child, trackTags, settings) => {
15193
15153
  let rendersCaptionTrack = false;
15194
15154
  walkAst(expression, (inner) => {
15195
15155
  if (rendersCaptionTrack) return false;
15196
- if (isNodeOfType(inner, "JSXElement") && trackTags.has(getElementType(inner.openingElement, settings)) && trackKindMightBeCaptions(inner.openingElement)) {
15156
+ if (isNodeOfType(inner, "JSXElement") && trackTags.includes(getElementType(inner.openingElement, settings)) && trackKindMightBeCaptions(inner.openingElement)) {
15197
15157
  rendersCaptionTrack = true;
15198
15158
  return false;
15199
15159
  }
@@ -15211,7 +15171,7 @@ const mediaHasCaption = defineRule({
15211
15171
  const settings = resolveSettings$23(context.settings);
15212
15172
  return { JSXOpeningElement(node) {
15213
15173
  const tag = getElementType(node, context.settings);
15214
- if (!(settings.audio.has(tag) || settings.video.has(tag))) return;
15174
+ if (!(settings.audio.includes(tag) || settings.video.includes(tag))) return;
15215
15175
  if (evaluateMuted(hasJsxPropIgnoreCase(node.attributes, "muted")) === true) return;
15216
15176
  const parent = node.parent;
15217
15177
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
@@ -15226,7 +15186,7 @@ const mediaHasCaption = defineRule({
15226
15186
  if (!isNodeOfType(child, "JSXElement")) return false;
15227
15187
  const opening = child.openingElement;
15228
15188
  const childTag = getElementType(opening, context.settings);
15229
- if (!settings.track.has(childTag)) return false;
15189
+ if (!settings.track.includes(childTag)) return false;
15230
15190
  const kindAttribute = hasJsxPropIgnoreCase(opening.attributes, "kind");
15231
15191
  if (!kindAttribute) return false;
15232
15192
  let kindValue = kindAttribute.value;
@@ -15537,30 +15497,16 @@ const stripJsComments = (sourceText) => sourceText.replace(BLOCK_COMMENT_PATTERN
15537
15497
  const DEFAULT_EXPORT_DECLARATION_PATTERN = /^\s*export\s+default\b/m;
15538
15498
  const NAMED_EXPORT_DECLARATION_PATTERN = /^\s*export\s+(?:declare\s+)?(?:(?:async\s+)?function|(?:abstract\s+)?class|const|let|var|enum|interface|type)\s+([\w$]+)/gm;
15539
15499
  const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1 = /^\s*export\s+(?:type\s+)?\{([\s\S]*?)\}(?:\s+from\s+["'][^"']+["'])?\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
15540
- const collectSourceTextExportNames = (sourceText) => {
15500
+ const doesSourceTextExportName = (sourceText, exportedName) => {
15541
15501
  const strippedSource = stripJsComments(sourceText);
15542
- const exportedNames = /* @__PURE__ */ new Set();
15543
- if (DEFAULT_EXPORT_DECLARATION_PATTERN.test(strippedSource)) exportedNames.add("default");
15544
- for (const match of strippedSource.matchAll(NAMED_EXPORT_DECLARATION_PATTERN)) if (match[1]) exportedNames.add(match[1]);
15545
- for (const match of strippedSource.matchAll(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1)) {
15546
- const specifiersText = match[1] ?? "";
15547
- for (const specifier of parseExportSpecifiers(specifiersText, false)) exportedNames.add(specifier.exportedName);
15548
- }
15549
- return exportedNames;
15502
+ if (exportedName === "default" && DEFAULT_EXPORT_DECLARATION_PATTERN.test(strippedSource)) return true;
15503
+ for (const match of strippedSource.matchAll(NAMED_EXPORT_DECLARATION_PATTERN)) if (match[1] === exportedName) return true;
15504
+ for (const match of strippedSource.matchAll(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN$1)) if (parseExportSpecifiers(match[1] ?? "", false).map((specifier) => specifier.exportedName).includes(exportedName)) return true;
15505
+ return false;
15550
15506
  };
15551
- const exportNamesCache = /* @__PURE__ */ new Map();
15552
15507
  const doesModuleExportName = (filePath, exportedName) => {
15553
15508
  try {
15554
- const fileStat = fs.statSync(filePath);
15555
- const cached = exportNamesCache.get(filePath);
15556
- if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return cached.exportedNames.has(exportedName);
15557
- const exportedNames = collectSourceTextExportNames(fs.readFileSync(filePath, "utf8"));
15558
- exportNamesCache.set(filePath, {
15559
- mtimeMs: fileStat.mtimeMs,
15560
- size: fileStat.size,
15561
- exportedNames
15562
- });
15563
- return exportedNames.has(exportedName);
15509
+ return doesSourceTextExportName(fs.readFileSync(filePath, "utf8"), exportedName);
15564
15510
  } catch {
15565
15511
  return false;
15566
15512
  }
@@ -15705,12 +15651,8 @@ const containsFetchCall = (node, options) => {
15705
15651
  const effectInvokedFunctions = options?.stopAtFunctionBoundary ? collectEffectInvokedFunctions(node) : null;
15706
15652
  let didFindFetchCall = false;
15707
15653
  walkAst(node, (child) => {
15708
- if (didFindFetchCall) return false;
15709
15654
  if (effectInvokedFunctions && child !== node && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
15710
- if (isFetchCall$1(child)) {
15711
- didFindFetchCall = true;
15712
- return false;
15713
- }
15655
+ if (isFetchCall$1(child)) didFindFetchCall = true;
15714
15656
  });
15715
15657
  return didFindFetchCall;
15716
15658
  };
@@ -16400,18 +16342,16 @@ const nextjsNoSideEffectInGetHandler = defineRule({
16400
16342
  recommendation: "GET requests can be prefetched and are open to CSRF. Move the side effect to a POST handler.",
16401
16343
  create: (context) => {
16402
16344
  let resolveBinding = () => null;
16403
- let isRouteHandlerFile = false;
16404
- let mutatingSegment = null;
16405
16345
  return {
16406
16346
  Program(node) {
16407
16347
  resolveBinding = buildProgramBindingLookup(node);
16408
- const filename = normalizeFilename(context.filename ?? "");
16409
- isRouteHandlerFile = ROUTE_HANDLER_FILE_PATTERN.test(filename) && !CRON_ROUTE_PATTERN.test(filename);
16410
- mutatingSegment = isRouteHandlerFile ? extractMutatingRouteSegment(filename) : null;
16411
16348
  },
16412
16349
  ExportNamedDeclaration(node) {
16413
- if (!isRouteHandlerFile) return;
16350
+ const filename = normalizeFilename(context.filename ?? "");
16351
+ if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
16352
+ if (CRON_ROUTE_PATTERN.test(filename)) return;
16414
16353
  if (!isExportedGetHandler(node)) return;
16354
+ const mutatingSegment = extractMutatingRouteSegment(filename);
16415
16355
  const handlerBodies = resolveGetHandlerBodies(node, resolveBinding);
16416
16356
  for (const handlerBody of handlerBodies) {
16417
16357
  const bodiesToScan = [handlerBody, ...collectCalledSameFileHelperBodies(handlerBody, resolveBinding)];
@@ -17443,15 +17383,8 @@ const getProgramAnalysis = (anyNode) => {
17443
17383
  programToAnalysis.set(programNode, analysis);
17444
17384
  return analysis;
17445
17385
  };
17446
- const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
17447
17386
  const getScopeForNode = (node, manager) => {
17448
17387
  if (!node.range) return null;
17449
- let scopeByNode = scopeByNodeCache.get(manager);
17450
- if (!scopeByNode) {
17451
- scopeByNode = /* @__PURE__ */ new WeakMap();
17452
- scopeByNodeCache.set(manager, scopeByNode);
17453
- }
17454
- if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
17455
17388
  let bestScope = null;
17456
17389
  let bestSize = Infinity;
17457
17390
  for (const scope of manager.scopes) {
@@ -17464,7 +17397,6 @@ const getScopeForNode = (node, manager) => {
17464
17397
  bestScope = scope;
17465
17398
  }
17466
17399
  }
17467
- scopeByNode.set(node, bestScope);
17468
17400
  return bestScope;
17469
17401
  };
17470
17402
  //#endregion
@@ -17499,20 +17431,11 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
17499
17431
  } else if (isAstNode(child)) descend(child, visit, visited);
17500
17432
  }
17501
17433
  };
17502
- const upstreamRefsCache = /* @__PURE__ */ new WeakMap();
17503
17434
  const getUpstreamRefs = (analysis, ref) => {
17504
- let upstreamByRef = upstreamRefsCache.get(analysis);
17505
- if (!upstreamByRef) {
17506
- upstreamByRef = /* @__PURE__ */ new WeakMap();
17507
- upstreamRefsCache.set(analysis, upstreamByRef);
17508
- }
17509
- const cached = upstreamByRef.get(ref);
17510
- if (cached) return cached;
17511
17435
  const refs = [];
17512
17436
  ascend(analysis, ref, (upRef) => {
17513
17437
  refs.push(upRef);
17514
17438
  });
17515
- upstreamByRef.set(ref, refs);
17516
17439
  return refs;
17517
17440
  };
17518
17441
  const findDownstreamNodes = (topNode, type) => {
@@ -17522,24 +17445,11 @@ const findDownstreamNodes = (topNode, type) => {
17522
17445
  });
17523
17446
  return nodes;
17524
17447
  };
17525
- const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
17526
17448
  const getRef = (analysis, identifier) => {
17527
- let refByIdentifier = refByIdentifierCache.get(analysis);
17528
- if (!refByIdentifier) {
17529
- refByIdentifier = /* @__PURE__ */ new WeakMap();
17530
- refByIdentifierCache.set(analysis, refByIdentifier);
17531
- }
17532
- if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
17533
- let resolvedReference = null;
17534
17449
  const scope = getScopeForNode(identifier, analysis.scopeManager);
17535
- if (scope) {
17536
- for (const reference of scope.references) if (reference.identifier === identifier) {
17537
- resolvedReference = reference;
17538
- break;
17539
- }
17540
- }
17541
- refByIdentifier.set(identifier, resolvedReference);
17542
- return resolvedReference;
17450
+ if (!scope) return null;
17451
+ for (const reference of scope.references) if (reference.identifier === identifier) return reference;
17452
+ return null;
17543
17453
  };
17544
17454
  const downstreamRefsCache = /* @__PURE__ */ new WeakMap();
17545
17455
  const getDownstreamRefs = (analysis, node) => {
@@ -17614,6 +17524,22 @@ const isEventualCallTo = (analysis, ref, predicate) => {
17614
17524
  };
17615
17525
  //#endregion
17616
17526
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
17527
+ const getOuterScopeContaining = (analysis, node) => {
17528
+ if (!node.range) return null;
17529
+ let best = null;
17530
+ let bestSize = Infinity;
17531
+ for (const scope of analysis.scopeManager.scopes) {
17532
+ const block = scope.block;
17533
+ if (!block?.range) continue;
17534
+ if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
17535
+ const size = block.range[1] - block.range[0];
17536
+ if (size <= bestSize) {
17537
+ bestSize = size;
17538
+ best = scope;
17539
+ }
17540
+ }
17541
+ return best;
17542
+ };
17617
17543
  const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
17618
17544
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
17619
17545
  const isReactFunctionalComponent = (node) => {
@@ -17644,7 +17570,7 @@ const isReactFunctionalHOC = (analysis, node) => {
17644
17570
  const isWrappedSeparately = () => {
17645
17571
  if (!isNodeOfType(node.id, "Identifier")) return false;
17646
17572
  const bindingName = node.id.name;
17647
- const containingScope = getScopeForNode(node, analysis.scopeManager);
17573
+ const containingScope = getOuterScopeContaining(analysis, node);
17648
17574
  if (!containingScope) return false;
17649
17575
  const variable = containingScope.variables.find((v) => v.name === bindingName);
17650
17576
  if (!variable) return false;
@@ -19509,8 +19435,8 @@ const CONTEXT_MODULES = [
19509
19435
  ];
19510
19436
  const isCreateContextCallee = (callee) => {
19511
19437
  if (isNodeOfType(callee, "Identifier")) {
19512
- const binding = getImportBindingForName(callee, callee.name);
19513
- return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
19438
+ for (const moduleName of CONTEXT_MODULES) if (getImportedNameFromModule(callee, callee.name, moduleName) === "createContext") return true;
19439
+ return false;
19514
19440
  }
19515
19441
  if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
19516
19442
  const namespaceIdentifier = callee.object;
@@ -19520,8 +19446,8 @@ const isCreateContextCallee = (callee) => {
19520
19446
  if (propertyIdentifier.name !== "createContext") return false;
19521
19447
  const namespaceName = namespaceIdentifier.name;
19522
19448
  if (isCanonicalReactNamespaceName(namespaceName)) return true;
19523
- const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
19524
- return importSource !== null && CONTEXT_MODULES.includes(importSource);
19449
+ for (const moduleName of CONTEXT_MODULES) if (isImportedFromModule(namespaceIdentifier, namespaceName, moduleName)) return true;
19450
+ return false;
19525
19451
  }
19526
19452
  return false;
19527
19453
  };
@@ -19923,44 +19849,38 @@ const convertHslToRgb = (hueDegrees, saturationPercent, lightnessPercent) => {
19923
19849
  };
19924
19850
  const parseColorToRgb = (value) => {
19925
19851
  const trimmed = value.trim().toLowerCase().replace(/_/g, " ");
19926
- if (trimmed.startsWith("#")) {
19927
- const hex8Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})[0-9a-f]{2}$/);
19928
- if (hex8Match) return {
19929
- red: parseInt(hex8Match[1], 16),
19930
- green: parseInt(hex8Match[2], 16),
19931
- blue: parseInt(hex8Match[3], 16)
19932
- };
19933
- const hex6Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
19934
- if (hex6Match) return {
19935
- red: parseInt(hex6Match[1], 16),
19936
- green: parseInt(hex6Match[2], 16),
19937
- blue: parseInt(hex6Match[3], 16)
19938
- };
19939
- const hex4Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])[0-9a-f]$/);
19940
- if (hex4Match) return {
19941
- red: parseInt(hex4Match[1] + hex4Match[1], 16),
19942
- green: parseInt(hex4Match[2] + hex4Match[2], 16),
19943
- blue: parseInt(hex4Match[3] + hex4Match[3], 16)
19944
- };
19945
- const hex3Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
19946
- if (hex3Match) return {
19947
- red: parseInt(hex3Match[1] + hex3Match[1], 16),
19948
- green: parseInt(hex3Match[2] + hex3Match[2], 16),
19949
- blue: parseInt(hex3Match[3] + hex3Match[3], 16)
19950
- };
19951
- }
19952
- if (trimmed.includes("rgb")) {
19953
- const rgbMatch = trimmed.match(/rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/);
19954
- if (rgbMatch) return {
19955
- red: parseInt(rgbMatch[1], 10),
19956
- green: parseInt(rgbMatch[2], 10),
19957
- blue: parseInt(rgbMatch[3], 10)
19958
- };
19959
- }
19960
- if (trimmed.includes("hsl")) {
19961
- const hslMatch = trimmed.match(/hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%/);
19962
- if (hslMatch) return convertHslToRgb(parseFloat(hslMatch[1]), parseFloat(hslMatch[2]), parseFloat(hslMatch[3]));
19963
- }
19852
+ const hex8Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})[0-9a-f]{2}$/);
19853
+ if (hex8Match) return {
19854
+ red: parseInt(hex8Match[1], 16),
19855
+ green: parseInt(hex8Match[2], 16),
19856
+ blue: parseInt(hex8Match[3], 16)
19857
+ };
19858
+ const hex6Match = trimmed.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
19859
+ if (hex6Match) return {
19860
+ red: parseInt(hex6Match[1], 16),
19861
+ green: parseInt(hex6Match[2], 16),
19862
+ blue: parseInt(hex6Match[3], 16)
19863
+ };
19864
+ const hex4Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])[0-9a-f]$/);
19865
+ if (hex4Match) return {
19866
+ red: parseInt(hex4Match[1] + hex4Match[1], 16),
19867
+ green: parseInt(hex4Match[2] + hex4Match[2], 16),
19868
+ blue: parseInt(hex4Match[3] + hex4Match[3], 16)
19869
+ };
19870
+ const hex3Match = trimmed.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
19871
+ if (hex3Match) return {
19872
+ red: parseInt(hex3Match[1] + hex3Match[1], 16),
19873
+ green: parseInt(hex3Match[2] + hex3Match[2], 16),
19874
+ blue: parseInt(hex3Match[3] + hex3Match[3], 16)
19875
+ };
19876
+ const rgbMatch = trimmed.match(/rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/);
19877
+ if (rgbMatch) return {
19878
+ red: parseInt(rgbMatch[1], 10),
19879
+ green: parseInt(rgbMatch[2], 10),
19880
+ blue: parseInt(rgbMatch[3], 10)
19881
+ };
19882
+ const hslMatch = trimmed.match(/hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%/);
19883
+ if (hslMatch) return convertHslToRgb(parseFloat(hslMatch[1]), parseFloat(hslMatch[2]), parseFloat(hslMatch[3]));
19964
19884
  return null;
19965
19885
  };
19966
19886
  //#endregion
@@ -19988,18 +19908,9 @@ const extractColorFromShadowLayer = (layer) => {
19988
19908
  if (hexMatch) return parseColorToRgb(`#${hexMatch[1]}`);
19989
19909
  return null;
19990
19910
  };
19991
- const RGB_FUNCTION_PATTERN = /rgba?\([^)]*\)/g;
19992
- const HEX_COLOR_PATTERN = /#[0-9a-f]{3,8}\b/gi;
19993
- const NUMERIC_TOKEN_PATTERN = /(\d+(?:\.\d+)?)(px)?/g;
19994
- const SHADOW_BLUR_TOKEN_INDEX = 2;
19995
19911
  const parseShadowLayerBlur = (layer) => {
19996
- const withoutColors = layer.replace(RGB_FUNCTION_PATTERN, "").replace(HEX_COLOR_PATTERN, "");
19997
- let tokenIndex = 0;
19998
- for (const match of withoutColors.matchAll(NUMERIC_TOKEN_PATTERN)) {
19999
- if (tokenIndex === SHADOW_BLUR_TOKEN_INDEX) return parseFloat(match[1]);
20000
- tokenIndex += 1;
20001
- }
20002
- return 0;
19912
+ const numericTokens = [...layer.replace(/rgba?\([^)]*\)/g, "").replace(/#[0-9a-f]{3,8}\b/gi, "").matchAll(/(\d+(?:\.\d+)?)(px)?/g)].map((match) => parseFloat(match[1]));
19913
+ return numericTokens.length >= 3 ? numericTokens[2] : 0;
20003
19914
  };
20004
19915
  const hasColoredGlowShadow = (shadowValue) => {
20005
19916
  for (const layer of splitShadowLayers(shadowValue)) {
@@ -20119,10 +20030,7 @@ const isIntrinsicJsxAttribute = (node) => {
20119
20030
  };
20120
20031
  //#endregion
20121
20032
  //#region src/plugin/rules/state-and-effects/utils/is-controlled-prop-mirror.ts
20122
- const componentPropNamesCache = /* @__PURE__ */ new WeakMap();
20123
20033
  const collectComponentPropNames = (componentFunction) => {
20124
- const cached = componentPropNamesCache.get(componentFunction);
20125
- if (cached) return cached;
20126
20034
  const propNames = /* @__PURE__ */ new Set();
20127
20035
  if (!isFunctionLike$1(componentFunction)) return propNames;
20128
20036
  const propsObjectParamNames = /* @__PURE__ */ new Set();
@@ -20136,23 +20044,27 @@ const collectComponentPropNames = (componentFunction) => {
20136
20044
  if (child !== componentBody && isFunctionLike$1(child)) return false;
20137
20045
  if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "ObjectPattern") && isNodeOfType(child.init, "Identifier") && propsObjectParamNames.has(child.init.name)) collectPatternNames(child.id, propNames);
20138
20046
  });
20139
- componentPropNamesCache.set(componentFunction, propNames);
20140
20047
  return propNames;
20141
20048
  };
20142
- const ownScopeBoundNamesCache = /* @__PURE__ */ new WeakMap();
20143
- const getOwnScopeBoundNames = (functionNode) => {
20144
- const cached = ownScopeBoundNamesCache.get(functionNode);
20145
- if (cached) return cached;
20049
+ const declaresBindingNamed = (functionNode, bindingName) => {
20146
20050
  const boundNames = /* @__PURE__ */ new Set();
20147
20051
  if (isFunctionLike$1(functionNode)) for (const param of functionNode.params ?? []) collectPatternNames(param, boundNames);
20052
+ if (boundNames.has(bindingName)) return true;
20053
+ let declaresName = false;
20148
20054
  walkAst(functionNode, (child) => {
20055
+ if (declaresName) return false;
20149
20056
  if (child !== functionNode && isFunctionLike$1(child)) return false;
20150
- if (isNodeOfType(child, "VariableDeclarator")) collectPatternNames(child.id, boundNames);
20057
+ if (isNodeOfType(child, "VariableDeclarator")) {
20058
+ const declaratorNames = /* @__PURE__ */ new Set();
20059
+ collectPatternNames(child.id, declaratorNames);
20060
+ if (declaratorNames.has(bindingName)) {
20061
+ declaresName = true;
20062
+ return false;
20063
+ }
20064
+ }
20151
20065
  });
20152
- ownScopeBoundNamesCache.set(functionNode, boundNames);
20153
- return boundNames;
20066
+ return declaresName;
20154
20067
  };
20155
- const declaresBindingNamed = (functionNode, bindingName) => getOwnScopeBoundNames(functionNode).has(bindingName);
20156
20068
  const isPropertyNamePosition = (identifier) => {
20157
20069
  const parent = identifier.parent;
20158
20070
  if (!parent) return false;
@@ -20575,28 +20487,36 @@ const isHandlerShapedReseed = (setterCall, componentFunction) => {
20575
20487
  }
20576
20488
  return hasNestedFunction;
20577
20489
  };
20578
- const isInRenderScope = (node, componentFunction) => {
20579
- let cursor = node.parent ?? null;
20580
- while (cursor && cursor !== componentFunction) {
20581
- if (isFunctionLike$1(cursor)) return false;
20582
- cursor = cursor.parent ?? null;
20583
- }
20584
- return true;
20490
+ const isReseededDraftBuffer = (useStateCall, isPropName) => {
20491
+ const setterName = getStateSetterName(useStateCall);
20492
+ if (!setterName) return false;
20493
+ const componentFunction = findEnclosingFunction(useStateCall);
20494
+ if (!componentFunction) return false;
20495
+ let isReseeded = false;
20496
+ walkAst(componentFunction, (child) => {
20497
+ if (isReseeded) return false;
20498
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName && isPropDerivedArgument(child.arguments?.[0], isPropName) && isHandlerShapedReseed(child, componentFunction)) {
20499
+ isReseeded = true;
20500
+ return false;
20501
+ }
20502
+ });
20503
+ return isReseeded;
20585
20504
  };
20586
- const isDraftReseedOrRenderAdjusted = (useStateCall, isPropName) => {
20505
+ const isAdjustedDuringRender = (useStateCall, isPropName) => {
20587
20506
  const setterName = getStateSetterName(useStateCall);
20588
20507
  if (!setterName) return false;
20589
20508
  const componentFunction = findEnclosingFunction(useStateCall);
20590
20509
  if (!componentFunction) return false;
20591
- let isExempt = false;
20510
+ let isAdjusted = false;
20592
20511
  walkAst(componentFunction, (child) => {
20593
- if (isExempt) return false;
20594
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName && isPropDerivedArgument(child.arguments?.[0], isPropName) && (isHandlerShapedReseed(child, componentFunction) || isInRenderScope(child, componentFunction))) {
20595
- isExempt = true;
20512
+ if (isAdjusted) return false;
20513
+ if (child !== componentFunction && isFunctionLike$1(child)) return false;
20514
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName && isPropDerivedArgument(child.arguments?.[0], isPropName)) {
20515
+ isAdjusted = true;
20596
20516
  return false;
20597
20517
  }
20598
20518
  });
20599
- return isExempt;
20519
+ return isAdjusted;
20600
20520
  };
20601
20521
  const noDerivedUseState = defineRule({
20602
20522
  id: "no-derived-useState",
@@ -20613,7 +20533,8 @@ const noDerivedUseState = defineRule({
20613
20533
  const initializer = node.arguments[0];
20614
20534
  if (isNodeOfType(initializer, "Identifier") && propStackTracker.isPropName(initializer.name)) {
20615
20535
  if (isInitialOnlyPropName(initializer.name)) return;
20616
- if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
20536
+ if (isReseededDraftBuffer(node, propStackTracker.isPropName)) return;
20537
+ if (isAdjustedDuringRender(node, propStackTracker.isPropName)) return;
20617
20538
  context.report({
20618
20539
  node,
20619
20540
  message: `Your users see a stale value when prop "${initializer.name}" changes because useState copies it once.`
@@ -20624,7 +20545,8 @@ const noDerivedUseState = defineRule({
20624
20545
  const rootIdentifierName = getRootIdentifierName(initializer);
20625
20546
  if (rootIdentifierName && propStackTracker.isPropName(rootIdentifierName)) {
20626
20547
  if (isNodeOfType(initializer.property, "Identifier") && isInitialOnlyPropName(initializer.property.name)) return;
20627
- if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
20548
+ if (isReseededDraftBuffer(node, propStackTracker.isPropName)) return;
20549
+ if (isAdjustedDuringRender(node, propStackTracker.isPropName)) return;
20628
20550
  context.report({
20629
20551
  node,
20630
20552
  message: `Your users see a stale value when prop "${rootIdentifierName}" changes because useState copies it once.`
@@ -22323,20 +22245,20 @@ const getTriggerGuardRootName = (testNode) => {
22323
22245
  return null;
22324
22246
  };
22325
22247
  const collectHandlerOnlyWriteStateNames = (componentBody, useStateBindings, handlerBindingNames) => {
22326
- const setterNames = new Set(useStateBindings.map((binding) => binding.setterName));
22327
- const settersWithAnyCall = /* @__PURE__ */ new Set();
22328
- const settersWithNonHandlerCall = /* @__PURE__ */ new Set();
22329
- walkAst(componentBody, (child) => {
22330
- if (!isNodeOfType(child, "CallExpression")) return;
22331
- if (!isNodeOfType(child.callee, "Identifier")) return;
22332
- const setterName = child.callee.name;
22333
- if (!setterNames.has(setterName)) return;
22334
- settersWithAnyCall.add(setterName);
22335
- if (settersWithNonHandlerCall.has(setterName)) return;
22336
- if (!isInsideEventHandler(child, handlerBindingNames)) settersWithNonHandlerCall.add(setterName);
22337
- });
22338
22248
  const handlerOnlyWriteStateNames = /* @__PURE__ */ new Set();
22339
- for (const binding of useStateBindings) if (settersWithAnyCall.has(binding.setterName) && !settersWithNonHandlerCall.has(binding.setterName)) handlerOnlyWriteStateNames.add(binding.valueName);
22249
+ for (const binding of useStateBindings) {
22250
+ let didFindAnySetterCall = false;
22251
+ let areAllSetterCallsInHandlers = true;
22252
+ walkAst(componentBody, (child) => {
22253
+ if (!areAllSetterCallsInHandlers) return false;
22254
+ if (!isNodeOfType(child, "CallExpression")) return;
22255
+ if (!isNodeOfType(child.callee, "Identifier")) return;
22256
+ if (child.callee.name !== binding.setterName) return;
22257
+ didFindAnySetterCall = true;
22258
+ if (!isInsideEventHandler(child, handlerBindingNames)) areAllSetterCallsInHandlers = false;
22259
+ });
22260
+ if (didFindAnySetterCall && areAllSetterCallsInHandlers) handlerOnlyWriteStateNames.add(binding.valueName);
22261
+ }
22340
22262
  return handlerOnlyWriteStateNames;
22341
22263
  };
22342
22264
  const noEventTriggerState = defineRule({
@@ -22676,10 +22598,6 @@ const COLORED_BG_PATTERN = /^bg-(?:red|orange|amber|yellow|lime|green|emerald|te
22676
22598
  const TEXT_COLOR_PATTERN = /^text-(?:white|black|transparent|current|inherit|\[|(?:gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-)/;
22677
22599
  const BG_COLOR_PATTERN = /^bg-(?:white|black|transparent|current|inherit|\[|(?:gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-)/;
22678
22600
  const splitVariantScope = (token) => {
22679
- if (!token.includes(":")) return {
22680
- scope: "",
22681
- utility: token.startsWith("!") ? token.slice(1) : token
22682
- };
22683
22601
  const segments = token.split(":");
22684
22602
  const rawUtility = segments[segments.length - 1];
22685
22603
  return {
@@ -22703,7 +22621,6 @@ const noGrayOnColoredBackground = defineRule({
22703
22621
  const bgColorScopes = /* @__PURE__ */ new Set();
22704
22622
  for (const token of classStr.split(/\s+/)) {
22705
22623
  if (!token) continue;
22706
- if (!token.includes("text-") && !token.includes("bg-")) continue;
22707
22624
  const { scope, utility } = splitVariantScope(token);
22708
22625
  if (TEXT_COLOR_PATTERN.test(utility)) textColorScopes.add(scope);
22709
22626
  if (BG_COLOR_PATTERN.test(utility)) bgColorScopes.add(scope);
@@ -23433,8 +23350,6 @@ const hasInfiniteIterationCount = (properties) => properties.some((property) =>
23433
23350
  return isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier") && property.value.name === "Infinity";
23434
23351
  });
23435
23352
  const isInfiniteAnimationSegment = (segment) => segment.trim().split(/\s+/).includes("infinite");
23436
- const DURATION_SEGMENT_PATTERN = /^([\d.]+)(m?s)$/;
23437
- const FIRST_TIME_TOKEN_PATTERN = /(?<![a-zA-Z\d])([\d.]+)(m?s)(?![a-zA-Z\d-])/;
23438
23353
  const noLongTransitionDuration = defineRule({
23439
23354
  id: "no-long-transition-duration",
23440
23355
  title: "Transition duration too long",
@@ -23458,10 +23373,11 @@ const noLongTransitionDuration = defineRule({
23458
23373
  if (key === "transitionDuration" || key === "animationDuration") {
23459
23374
  let longestDurationPropertyMs = 0;
23460
23375
  for (const segment of value.split(",")) {
23461
- const durationMatch = segment.trim().match(DURATION_SEGMENT_PATTERN);
23462
- if (!durationMatch) continue;
23463
- const segmentDurationMs = durationMatch[2] === "ms" ? parseFloat(durationMatch[1]) : parseFloat(durationMatch[1]) * 1e3;
23464
- longestDurationPropertyMs = Math.max(longestDurationPropertyMs, segmentDurationMs);
23376
+ const trimmedSegment = segment.trim();
23377
+ const msMatch = trimmedSegment.match(/^([\d.]+)ms$/);
23378
+ const secondsMatch = trimmedSegment.match(/^([\d.]+)s$/);
23379
+ if (msMatch) longestDurationPropertyMs = Math.max(longestDurationPropertyMs, parseFloat(msMatch[1]));
23380
+ else if (secondsMatch) longestDurationPropertyMs = Math.max(longestDurationPropertyMs, parseFloat(secondsMatch[1]) * 1e3);
23465
23381
  }
23466
23382
  if (longestDurationPropertyMs > 0) durationMs = longestDurationPropertyMs;
23467
23383
  }
@@ -23469,7 +23385,7 @@ const noLongTransitionDuration = defineRule({
23469
23385
  let longestDurationMs = 0;
23470
23386
  for (const segment of value.split(",")) {
23471
23387
  if (key === "animation" && isInfiniteAnimationSegment(segment)) continue;
23472
- const firstTimeMatch = segment.match(FIRST_TIME_TOKEN_PATTERN);
23388
+ const firstTimeMatch = segment.match(/(?<![a-zA-Z\d])([\d.]+)(m?s)(?![a-zA-Z\d-])/);
23473
23389
  if (!firstTimeMatch) continue;
23474
23390
  const segmentDurationMs = firstTimeMatch[2] === "ms" ? parseFloat(firstTimeMatch[1]) : parseFloat(firstTimeMatch[1]) * 1e3;
23475
23391
  longestDurationMs = Math.max(longestDurationMs, segmentDurationMs);
@@ -24641,14 +24557,14 @@ const collectRoleBranches = (expression, out) => {
24641
24557
  out.hasOpaqueBranch = true;
24642
24558
  };
24643
24559
  const buildMessage$12 = (tag) => `Keyboard & screen reader users can't trigger this \`<${tag}>\` because it isn't interactive, so use a button or link or add an interactive role.`;
24644
- const INTERACTIVE_HANDLERS_LOWER = new Set([
24560
+ const INTERACTIVE_HANDLERS = [
24645
24561
  "onClick",
24646
24562
  "onMouseDown",
24647
24563
  "onMouseUp",
24648
24564
  "onKeyDown",
24649
24565
  "onKeyPress",
24650
24566
  "onKeyUp"
24651
- ].map((handlerName) => handlerName.toLowerCase()));
24567
+ ];
24652
24568
  const noNoninteractiveElementInteractions = defineRule({
24653
24569
  id: "no-noninteractive-element-interactions",
24654
24570
  title: "Handler on non-interactive element",
@@ -24663,16 +24579,7 @@ const noNoninteractiveElementInteractions = defineRule({
24663
24579
  const tag = getElementType(node, context.settings);
24664
24580
  if (!NON_INTERACTIVE_ELEMENTS.has(tag)) return;
24665
24581
  if (tag === "label") return;
24666
- let hasHandler = false;
24667
- for (const attribute of node.attributes) {
24668
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
24669
- const attributeName = getJsxAttributeName(attribute.name);
24670
- if (attributeName && INTERACTIVE_HANDLERS_LOWER.has(attributeName.toLowerCase())) {
24671
- hasHandler = true;
24672
- break;
24673
- }
24674
- }
24675
- if (!hasHandler) return;
24582
+ if (!INTERACTIVE_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
24676
24583
  if (isHiddenFromScreenReader(node, context.settings)) return;
24677
24584
  const roleAttr = hasJsxPropIgnoreCase(node.attributes, "role");
24678
24585
  if (roleAttr) {
@@ -25985,22 +25892,16 @@ const ELEMENT_ROLE_PAIRS = [
25985
25892
  ["tr", "row"],
25986
25893
  ["ul", "list"]
25987
25894
  ];
25988
- const EMPTY_ROLE_LIST = [];
25989
- const buildLookup = (pairs, keyIndex) => {
25990
- const lookup = /* @__PURE__ */ new Map();
25991
- for (const pair of pairs) {
25992
- const key = pair[keyIndex];
25993
- const value = pair[keyIndex === 0 ? 1 : 0];
25994
- const values = lookup.get(key);
25995
- if (!values) lookup.set(key, [value]);
25996
- else if (!values.includes(value)) values.push(value);
25997
- }
25998
- return lookup;
25895
+ const getElementImplicitRoles = (tag) => {
25896
+ const out = [];
25897
+ for (const [element, role] of ELEMENT_ROLE_PAIRS) if (element === tag && !out.includes(role)) out.push(role);
25898
+ return out;
25899
+ };
25900
+ const getTagsForRole = (role) => {
25901
+ const out = [];
25902
+ for (const [element, r] of ELEMENT_ROLE_PAIRS) if (r === role && !out.includes(element)) out.push(element);
25903
+ return out;
25999
25904
  };
26000
- const IMPLICIT_ROLES_BY_TAG = buildLookup(ELEMENT_ROLE_PAIRS, 0);
26001
- const TAGS_BY_ROLE = buildLookup(ELEMENT_ROLE_PAIRS, 1);
26002
- const getElementImplicitRoles = (tag) => IMPLICIT_ROLES_BY_TAG.get(tag) ?? EMPTY_ROLE_LIST;
26003
- const getTagsForRole = (role) => TAGS_BY_ROLE.get(role) ?? EMPTY_ROLE_LIST;
26004
25905
  //#endregion
26005
25906
  //#region src/plugin/utils/get-implicit-role.ts
26006
25907
  const getImplicitRole = (node, elementType) => {
@@ -26242,14 +26143,14 @@ const noRenderInRender = defineRule({
26242
26143
  const expression = node.expression;
26243
26144
  if (!isNodeOfType(expression, "CallExpression")) return;
26244
26145
  let calleeName = null;
26245
- if (isNodeOfType(expression.callee, "Identifier")) calleeName = expression.callee.name;
26246
- else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) calleeName = expression.callee.property.name;
26247
- if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
26248
26146
  if (isNodeOfType(expression.callee, "Identifier")) {
26249
26147
  if (tracesToPropOrParameter(context.scopes.symbolFor(expression.callee), context.scopes)) return;
26250
- } else if (isNodeOfType(expression.callee, "MemberExpression")) {
26148
+ calleeName = expression.callee.name;
26149
+ } else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) {
26251
26150
  if (rootsInProps(expression.callee.object, context.scopes)) return;
26151
+ calleeName = expression.callee.property.name;
26252
26152
  }
26153
+ if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
26253
26154
  context.report({
26254
26155
  node: expression,
26255
26156
  message: `Your users lose state because "${calleeName}()" builds UI from an inline call that React remounts, so pull it into its own component instead.`
@@ -26500,7 +26401,6 @@ const TANSTACK_ROUTE_PROPERTY_ORDER = [
26500
26401
  "headers",
26501
26402
  "remountDeps"
26502
26403
  ];
26503
- const TANSTACK_ROUTE_PROPERTY_INDEX = new Map(TANSTACK_ROUTE_PROPERTY_ORDER.map((propertyName, orderIndex) => [propertyName, orderIndex]));
26504
26404
  const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
26505
26405
  "createFileRoute",
26506
26406
  "createRoute",
@@ -26516,7 +26416,6 @@ const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
26516
26416
  "server",
26517
26417
  "handler"
26518
26418
  ];
26519
- const TANSTACK_MIDDLEWARE_METHOD_INDEX = new Map(TANSTACK_MIDDLEWARE_METHOD_ORDER.map((methodName, orderIndex) => [methodName, orderIndex]));
26520
26419
  const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
26521
26420
  const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
26522
26421
  const TANSTACK_QUERY_HOOKS = new Set([
@@ -27221,21 +27120,14 @@ const noStaticElementInteractions = defineRule({
27221
27120
  category: "Accessibility",
27222
27121
  create: (context) => {
27223
27122
  const settings = resolveSettings$12(context.settings);
27224
- const handlersLower = new Set(settings.handlers.map((handlerName) => handlerName.toLowerCase()));
27225
27123
  const isTestlikeFile = isTestlikeFilename(context.filename);
27226
27124
  return { JSXOpeningElement(node) {
27227
27125
  if (isTestlikeFile) return;
27228
27126
  let hasNonBlockerHandler = false;
27229
27127
  let hasAnyHandler = false;
27230
- let seenHandlerNames = null;
27231
- for (const attribute of node.attributes) {
27232
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
27233
- const attributeName = getJsxAttributeName(attribute.name);
27234
- if (!attributeName) continue;
27235
- const handlerNameLower = attributeName.toLowerCase();
27236
- if (!handlersLower.has(handlerNameLower)) continue;
27237
- if (seenHandlerNames?.has(handlerNameLower)) continue;
27238
- (seenHandlerNames ??= /* @__PURE__ */ new Set()).add(handlerNameLower);
27128
+ for (const handler of settings.handlers) {
27129
+ const attribute = hasJsxPropIgnoreCase(node.attributes, handler);
27130
+ if (!attribute) continue;
27239
27131
  if (isNullValue(attribute)) continue;
27240
27132
  hasAnyHandler = true;
27241
27133
  if (!isPureEventBlockerHandler(attribute)) {
@@ -28416,13 +28308,13 @@ const DOM_ATTRIBUTES_TO_CAMEL = new Map([
28416
28308
  ["xml:lang", "xmlLang"],
28417
28309
  ["xml:space", "xmlSpace"]
28418
28310
  ]);
28419
- const DOM_PROPERTIES_IGNORE_CASE_BY_LOWER = new Map([
28311
+ const DOM_PROPERTIES_IGNORE_CASE = [
28420
28312
  "charset",
28421
28313
  "allowFullScreen",
28422
28314
  "webkitAllowFullScreen",
28423
28315
  "mozAllowFullScreen",
28424
28316
  "webkitDirectory"
28425
- ].map((name) => [name.toLowerCase(), name]));
28317
+ ];
28426
28318
  //#endregion
28427
28319
  //#region src/plugin/constants/dom-property-tags.ts
28428
28320
  const DOM_PROPERTY_TO_ALLOWED_TAGS = new Map([
@@ -28626,7 +28518,10 @@ const matchesHtmlTagConventions = (tagName) => {
28626
28518
  if (!(firstCharacter >= 97 && firstCharacter <= 122)) return false;
28627
28519
  return !tagName.includes("-");
28628
28520
  };
28629
- const normalizeAttributeCase = (name) => DOM_PROPERTIES_IGNORE_CASE_BY_LOWER.get(name.toLowerCase()) ?? name;
28521
+ const normalizeAttributeCase = (name) => {
28522
+ for (const ignoreCaseName of DOM_PROPERTIES_IGNORE_CASE) if (ignoreCaseName.toLowerCase() === name.toLowerCase()) return ignoreCaseName;
28523
+ return name;
28524
+ };
28630
28525
  const hasUppercaseChar = (input) => /[A-Z]/.test(input);
28631
28526
  const INVALID_PROP_ON_TAG = (propName, allowedTags) => `React ignores \`${propName}\` here because it only works on these tags: ${allowedTags}.`;
28632
28527
  const DATA_LOWERCASE_REQUIRED = () => `React drops this \`data-*\` prop because of its capital letters.`;
@@ -28970,26 +28865,32 @@ const isFirstArgumentOfHocCall = (node) => {
28970
28865
  if (!isHocCallee$1(parent)) return false;
28971
28866
  return parent.arguments[0] === node;
28972
28867
  };
28973
- const MAP_LIKE_METHOD_NAMES = new Set([
28974
- "map",
28975
- "forEach",
28976
- "filter",
28977
- "flatMap",
28978
- "reduce",
28979
- "reduceRight"
28980
- ]);
28981
28868
  const isReturnOfMapCallback = (node) => {
28982
28869
  const parent = node.parent;
28983
28870
  if (!parent) return false;
28984
28871
  if (isNodeOfType(parent, "CallExpression")) {
28985
28872
  const callee = parent.callee;
28986
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return MAP_LIKE_METHOD_NAMES.has(callee.property.name);
28873
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return [
28874
+ "map",
28875
+ "forEach",
28876
+ "filter",
28877
+ "flatMap",
28878
+ "reduce",
28879
+ "reduceRight"
28880
+ ].includes(callee.property.name);
28987
28881
  }
28988
28882
  if (isNodeOfType(parent, "ArrowFunctionExpression") || isNodeOfType(parent, "FunctionExpression")) {
28989
28883
  const callbackParent = parent.parent;
28990
28884
  if (callbackParent && isNodeOfType(callbackParent, "CallExpression")) {
28991
28885
  const callee = callbackParent.callee;
28992
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return MAP_LIKE_METHOD_NAMES.has(callee.property.name);
28886
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return [
28887
+ "map",
28888
+ "forEach",
28889
+ "filter",
28890
+ "flatMap",
28891
+ "reduce",
28892
+ "reduceRight"
28893
+ ].includes(callee.property.name);
28993
28894
  }
28994
28895
  }
28995
28896
  return false;
@@ -29025,10 +28926,10 @@ const isRenderFlowingReadReference = (identifier) => {
29025
28926
  valueNode = parent;
29026
28927
  parent = parent.parent;
29027
28928
  continue;
29028
- case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isReactComponentName(parent.id.name);
28929
+ case "VariableDeclarator": return parent.init === valueNode && isNodeOfType(parent.id, "Identifier") && isUppercaseName(parent.id.name);
29029
28930
  case "AssignmentExpression": {
29030
28931
  const assignmentTarget = parent.left;
29031
- return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isReactComponentName(assignmentTarget.name);
28932
+ return parent.right === valueNode && isNodeOfType(assignmentTarget, "Identifier") && isUppercaseName(assignmentTarget.name);
29032
28933
  }
29033
28934
  case "Property":
29034
28935
  if (parent.value !== valueNode) return false;
@@ -29121,14 +29022,14 @@ const noUnstableNestedComponents = defineRule({
29121
29022
  };
29122
29023
  return {
29123
29024
  JSXOpeningElement(node) {
29124
- if (isNodeOfType(node.name, "JSXIdentifier") && isReactComponentName(node.name.name)) {
29025
+ if (isNodeOfType(node.name, "JSXIdentifier") && isUppercaseName(node.name.name)) {
29125
29026
  recordInstantiation(node.name, node.name.name);
29126
29027
  return;
29127
29028
  }
29128
29029
  if (isNodeOfType(node.name, "JSXMemberExpression")) recordMemberChainInstantiation(node.name);
29129
29030
  },
29130
29031
  Identifier(node) {
29131
- if (!isReactComponentName(node.name)) return;
29032
+ if (!isUppercaseName(node.name)) return;
29132
29033
  if (!isRenderFlowingReadReference(node)) return;
29133
29034
  recordInstantiation(node, node.name);
29134
29035
  },
@@ -30938,13 +30839,13 @@ const preferStableEmptyFallback = defineRule({
30938
30839
  memoRegistry = buildSameFileMemoRegistry(node);
30939
30840
  },
30940
30841
  JSXAttribute(node) {
30842
+ if (!isInsideFunctionScope(node)) return;
30843
+ if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
30941
30844
  if (!node.value) return;
30942
30845
  if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
30943
30846
  const innerExpression = node.value.expression;
30944
30847
  if (!innerExpression) return;
30945
30848
  if (innerExpression.type === "JSXEmptyExpression") return;
30946
- if (!isInsideFunctionScope(node)) return;
30947
- if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
30948
30849
  const parentJsxOpening = node.parent;
30949
30850
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
30950
30851
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
@@ -32316,11 +32217,41 @@ const callsIdentifier = (root, identifierName) => {
32316
32217
  });
32317
32218
  return found;
32318
32219
  };
32220
+ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
32221
+ if (!componentBody) return false;
32222
+ let found = false;
32223
+ walkAst(componentBody, (child) => {
32224
+ if (found) return;
32225
+ if (!isFunctionLike$1(child)) return;
32226
+ const functionBody = child.body;
32227
+ if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
32228
+ if (callsIdentifier(functionBody, setterName)) found = true;
32229
+ });
32230
+ return found;
32231
+ };
32319
32232
  const PROMISE_CHAIN_METHOD_NAMES = new Set([
32320
32233
  "then",
32321
32234
  "catch",
32322
32235
  "finally"
32323
32236
  ]);
32237
+ const setterIsCalledInPromiseChain = (componentBody, setterName) => {
32238
+ if (!componentBody) return false;
32239
+ let found = false;
32240
+ walkAst(componentBody, (child) => {
32241
+ if (found) return;
32242
+ if (!isNodeOfType(child, "CallExpression")) return;
32243
+ const callee = child.callee;
32244
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !PROMISE_CHAIN_METHOD_NAMES.has(callee.property.name)) return;
32245
+ for (const argument of child.arguments ?? []) {
32246
+ if (!isFunctionLike$1(argument)) continue;
32247
+ if (callsIdentifier(argument.body, setterName)) {
32248
+ found = true;
32249
+ return;
32250
+ }
32251
+ }
32252
+ });
32253
+ return found;
32254
+ };
32324
32255
  const ASYNC_DATA_CALLEE_NAMES = new Set([
32325
32256
  "useApolloClient",
32326
32257
  "useMutation",
@@ -32333,36 +32264,20 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
32333
32264
  "fetch",
32334
32265
  "axios"
32335
32266
  ]);
32336
- const hasAsyncLoadingWork = (fnBody, setterName) => {
32267
+ const referencesAsyncDataApi = (body) => {
32268
+ if (!body) return false;
32337
32269
  let found = false;
32338
- walkAst(fnBody, (child) => {
32339
- if (found) return false;
32270
+ walkAst(body, (child) => {
32271
+ if (found) return;
32340
32272
  if (isNodeOfType(child, "CallExpression")) {
32341
32273
  const callee = child.callee;
32342
32274
  if (isNodeOfType(callee, "Identifier") && ASYNC_DATA_CALLEE_NAMES.has(callee.name)) {
32343
32275
  found = true;
32344
- return false;
32345
- }
32346
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
32347
- if (ASYNC_DATA_CALLEE_NAMES.has(callee.property.name)) {
32348
- found = true;
32349
- return false;
32350
- }
32351
- if (setterName !== null && PROMISE_CHAIN_METHOD_NAMES.has(callee.property.name)) for (const argument of child.arguments ?? []) {
32352
- if (!isFunctionLike$1(argument)) continue;
32353
- if (callsIdentifier(argument.body, setterName)) {
32354
- found = true;
32355
- return false;
32356
- }
32357
- }
32276
+ return;
32358
32277
  }
32359
- return;
32360
- }
32361
- if (setterName !== null && isFunctionLike$1(child)) {
32362
- const functionBody = child.body;
32363
- if ((Boolean(child.async) || hasOwnAwait(functionBody)) && callsIdentifier(functionBody, setterName)) {
32278
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ASYNC_DATA_CALLEE_NAMES.has(callee.property.name)) {
32364
32279
  found = true;
32365
- return false;
32280
+ return;
32366
32281
  }
32367
32282
  }
32368
32283
  });
@@ -32387,7 +32302,7 @@ const renderingUsetransitionLoading = defineRule({
32387
32302
  const secondBinding = node.id.elements[1];
32388
32303
  const setterName = isNodeOfType(secondBinding, "Identifier") ? secondBinding.name : null;
32389
32304
  const fnBody = enclosingFunctionBody(node);
32390
- if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
32305
+ if (fnBody && (setterName && setterIsCalledInAsyncContext(fnBody, setterName) || setterName && setterIsCalledInPromiseChain(fnBody, setterName) || referencesAsyncDataApi(fnBody))) return;
32391
32306
  context.report({
32392
32307
  node: node.init,
32393
32308
  message: `This adds an extra render because useState for "${stateVariableName}" re-renders just for the loading flag, so if it's a state change & not a data fetch, use useTransition instead`
@@ -33181,17 +33096,17 @@ const rerenderStateOnlyInHandlers = defineRule({
33181
33096
  const effectTriggerNames = /* @__PURE__ */ new Set();
33182
33097
  for (const dependencyName of collectDependencyArrayNames(componentBody)) if (!(stateValueNames.has(dependencyName) && effectCallbackReadNames.has(dependencyName))) effectTriggerNames.add(dependencyName);
33183
33098
  for (const reachableName of expandTransitiveDependencies(effectTriggerNames, dependencyGraph)) renderReachableNames.add(reachableName);
33184
- const setterNames = new Set(bindings.map((binding) => binding.setterName));
33185
- const calledSetterNames = /* @__PURE__ */ new Set();
33186
- walkAst(componentBody, (child) => {
33187
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
33188
- });
33189
33099
  for (const binding of bindings) {
33190
33100
  if (renderReachableNames.has(binding.valueName)) continue;
33191
33101
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
33192
33102
  const setterSuffix = binding.setterName.slice(3);
33193
33103
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
33194
- if (!calledSetterNames.has(binding.setterName)) continue;
33104
+ let setterCalled = false;
33105
+ walkAst(componentBody, (child) => {
33106
+ if (setterCalled) return;
33107
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === binding.setterName) setterCalled = true;
33108
+ });
33109
+ if (!setterCalled) continue;
33195
33110
  if (isSetterCalledDuringRender(componentBody, binding.setterName)) continue;
33196
33111
  context.report({
33197
33112
  node: binding.declarator,
@@ -33523,7 +33438,6 @@ const RECYCLABLE_LIST_PACKAGES = {
33523
33438
  FlashList: ["@shopify/flash-list"],
33524
33439
  LegendList: ["@legendapp/list"]
33525
33440
  };
33526
- const RECYCLABLE_LIST_PACKAGE_SOURCES = Object.values(RECYCLABLE_LIST_PACKAGES).flat();
33527
33441
  const REACT_NATIVE_LIST_COMPONENTS = new Set([...REACT_NATIVE_BUILTIN_LIST_COMPONENTS, ...Object.keys(RECYCLABLE_LIST_PACKAGES)]);
33528
33442
  const RENDER_ITEM_PROP_NAMES = new Set([
33529
33443
  "renderItem",
@@ -33766,42 +33680,33 @@ const rnListMissingEstimatedItemSize = defineRule({
33766
33680
  requires: ["react-native"],
33767
33681
  severity: "warn",
33768
33682
  recommendation: "Without `estimatedItemSize` the list guesses row height and can flash blank cells on fast scroll. Add `estimatedItemSize={<avg-row-height-in-px>}` so it matches your rows.",
33769
- create: (context) => {
33770
- let fileImportsRecycler = false;
33771
- return {
33772
- Program(node) {
33773
- fileImportsRecycler = hasImportFromModules(node, RECYCLABLE_LIST_PACKAGE_SOURCES);
33774
- },
33775
- JSXOpeningElement(node) {
33776
- if (!fileImportsRecycler) return;
33777
- const localElementName = resolveJsxElementName(node);
33778
- if (!localElementName) return;
33779
- const canonicalRecyclerName = resolveImportedRecyclerName(node, localElementName);
33780
- if (canonicalRecyclerName === null) return;
33781
- if (canonicalRecyclerName === "FlashList" && isFlashListV2OrNewer(context)) return;
33782
- let hasSizingHint = false;
33783
- let dataIsEmptyLiteral = false;
33784
- let hasDataProp = false;
33785
- for (const attribute of node.attributes ?? []) {
33786
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
33787
- if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
33788
- const attributeName = attribute.name.name;
33789
- if (SIZING_HINT_ATTRIBUTE_NAMES.has(attributeName)) hasSizingHint = true;
33790
- if (attributeName === "data") {
33791
- hasDataProp = true;
33792
- if (isEmptyArrayLiteral(attribute)) dataIsEmptyLiteral = true;
33793
- }
33794
- }
33795
- if (hasSizingHint) return;
33796
- if (dataIsEmptyLiteral) return;
33797
- if (!hasDataProp) return;
33798
- context.report({
33799
- node,
33800
- message: `Your users see blank cells flash on fast scroll when <${localElementName}> has no \`estimatedItemSize\`.`
33801
- });
33683
+ create: (context) => ({ JSXOpeningElement(node) {
33684
+ const localElementName = resolveJsxElementName(node);
33685
+ if (!localElementName) return;
33686
+ const canonicalRecyclerName = resolveImportedRecyclerName(node, localElementName);
33687
+ if (canonicalRecyclerName === null) return;
33688
+ if (canonicalRecyclerName === "FlashList" && isFlashListV2OrNewer(context)) return;
33689
+ let hasSizingHint = false;
33690
+ let dataIsEmptyLiteral = false;
33691
+ let hasDataProp = false;
33692
+ for (const attribute of node.attributes ?? []) {
33693
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
33694
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
33695
+ const attributeName = attribute.name.name;
33696
+ if (SIZING_HINT_ATTRIBUTE_NAMES.has(attributeName)) hasSizingHint = true;
33697
+ if (attributeName === "data") {
33698
+ hasDataProp = true;
33699
+ if (isEmptyArrayLiteral(attribute)) dataIsEmptyLiteral = true;
33802
33700
  }
33803
- };
33804
- }
33701
+ }
33702
+ if (hasSizingHint) return;
33703
+ if (dataIsEmptyLiteral) return;
33704
+ if (!hasDataProp) return;
33705
+ context.report({
33706
+ node,
33707
+ message: `Your users see blank cells flash on fast scroll when <${localElementName}> has no \`estimatedItemSize\`.`
33708
+ });
33709
+ } })
33805
33710
  });
33806
33711
  //#endregion
33807
33712
  //#region src/plugin/rules/react-native/rn-list-recyclable-without-types.ts
@@ -33812,34 +33717,25 @@ const rnListRecyclableWithoutTypes = defineRule({
33812
33717
  requires: ["react-native"],
33813
33718
  severity: "warn",
33814
33719
  recommendation: "When rows have different shapes, reused cells can show the wrong layout. Add `getItemType={item => item.kind}` so FlashList keeps a separate pool per row type.",
33815
- create: (context) => {
33816
- let fileImportsRecycler = false;
33817
- return {
33818
- Program(node) {
33819
- fileImportsRecycler = hasImportFromModules(node, RECYCLABLE_LIST_PACKAGE_SOURCES);
33820
- },
33821
- JSXOpeningElement(node) {
33822
- if (!fileImportsRecycler) return;
33823
- const elementName = resolveJsxElementName(node);
33824
- if (!elementName) return;
33825
- if (resolveImportedRecyclerName(node, elementName, { allowNamespaceMemberAccess: true }) === null) return;
33826
- let hasRecycleItemsEnabled = false;
33827
- let hasGetItemType = false;
33828
- for (const attr of node.attributes ?? []) {
33829
- if (!isNodeOfType(attr, "JSXAttribute")) continue;
33830
- if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
33831
- if (attr.name.name === "recycleItems") if (!attr.value) hasRecycleItemsEnabled = true;
33832
- else if (isNodeOfType(attr.value, "JSXExpressionContainer") && isNodeOfType(attr.value.expression, "Literal")) hasRecycleItemsEnabled = attr.value.expression.value === true;
33833
- else hasRecycleItemsEnabled = true;
33834
- if (attr.name.name === "getItemType") hasGetItemType = true;
33835
- }
33836
- if (hasRecycleItemsEnabled && !hasGetItemType) context.report({
33837
- node,
33838
- message: `Your users see rows of different shapes reuse the wrong cells when <${elementName} recycleItems> has no \`getItemType\`.`
33839
- });
33840
- }
33841
- };
33842
- }
33720
+ create: (context) => ({ JSXOpeningElement(node) {
33721
+ const elementName = resolveJsxElementName(node);
33722
+ if (!elementName) return;
33723
+ if (resolveImportedRecyclerName(node, elementName, { allowNamespaceMemberAccess: true }) === null) return;
33724
+ let hasRecycleItemsEnabled = false;
33725
+ let hasGetItemType = false;
33726
+ for (const attr of node.attributes ?? []) {
33727
+ if (!isNodeOfType(attr, "JSXAttribute")) continue;
33728
+ if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
33729
+ if (attr.name.name === "recycleItems") if (!attr.value) hasRecycleItemsEnabled = true;
33730
+ else if (isNodeOfType(attr.value, "JSXExpressionContainer") && isNodeOfType(attr.value.expression, "Literal")) hasRecycleItemsEnabled = attr.value.expression.value === true;
33731
+ else hasRecycleItemsEnabled = true;
33732
+ if (attr.name.name === "getItemType") hasGetItemType = true;
33733
+ }
33734
+ if (hasRecycleItemsEnabled && !hasGetItemType) context.report({
33735
+ node,
33736
+ message: `Your users see rows of different shapes reuse the wrong cells when <${elementName} recycleItems> has no \`getItemType\`.`
33737
+ });
33738
+ } })
33843
33739
  });
33844
33740
  //#endregion
33845
33741
  //#region src/plugin/rules/react-native/rn-no-deep-imports.ts
@@ -34823,10 +34719,9 @@ const resolveTextBoundaryName = (openingElement) => {
34823
34719
  if (isNodeOfType(openingElement.name, "JSXNamespacedName")) return openingElement.name.namespace.name;
34824
34720
  return resolveJsxElementName(openingElement);
34825
34721
  };
34826
- const TEXT_COMPONENT_KEYWORDS = [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS];
34827
34722
  const isTextHandlingComponent = (elementName) => {
34828
34723
  if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
34829
- return TEXT_COMPONENT_KEYWORDS.some((keyword) => elementName.includes(keyword));
34724
+ return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
34830
34725
  };
34831
34726
  const isTransparentTextWrapper = (elementName) => elementName !== null && REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS.has(elementName);
34832
34727
  const isInsideTextHandlingComponent = (node) => {
@@ -34870,7 +34765,6 @@ const rnNoRawText = defineRule({
34870
34765
  return {
34871
34766
  Program(programNode) {
34872
34767
  isDomComponentFile = hasDirective(programNode, "use dom");
34873
- if (!containsJsxElement(programNode)) return;
34874
34768
  const childrenForwarding = collectTextWrapperComponents(programNode, isTextHandlingComponent, isNonTextHostName);
34875
34769
  autoDetectedTextWrappers = childrenForwarding.textWrappers;
34876
34770
  autoDetectedNonTextWrappers = childrenForwarding.nonTextWrappers;
@@ -38825,7 +38719,14 @@ const roleSupportsAriaProps = defineRule({
38825
38719
  recommendation: "Only use `aria-*` attributes that the element's role supports.",
38826
38720
  category: "Accessibility",
38827
38721
  create: (context) => ({ JSXOpeningElement(node) {
38828
- let ariaAttributes = null;
38722
+ const elementType = getElementType(node, context.settings);
38723
+ const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
38724
+ const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
38725
+ if (!role) return;
38726
+ if (!VALID_ARIA_ROLES.has(role)) return;
38727
+ const isImplicit = !roleAttribute;
38728
+ const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
38729
+ if (!supported) return;
38829
38730
  for (const attribute of node.attributes) {
38830
38731
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
38831
38732
  const attributeNode = attribute;
@@ -38835,21 +38736,6 @@ const roleSupportsAriaProps = defineRule({
38835
38736
  const propName = propRawName.toLowerCase();
38836
38737
  if (!propName.startsWith("aria-")) continue;
38837
38738
  if (!ARIA_PROPERTIES.has(propName)) continue;
38838
- (ariaAttributes ??= []).push({
38839
- attribute,
38840
- propName
38841
- });
38842
- }
38843
- if (!ariaAttributes) return;
38844
- const elementType = getElementType(node, context.settings);
38845
- const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
38846
- const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
38847
- if (!role) return;
38848
- if (!VALID_ARIA_ROLES.has(role)) return;
38849
- const isImplicit = !roleAttribute;
38850
- const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
38851
- if (!supported) return;
38852
- for (const { attribute, propName } of ariaAttributes) {
38853
38739
  if (supported.has(propName)) continue;
38854
38740
  context.report({
38855
38741
  node: attribute,
@@ -39687,7 +39573,6 @@ const serverCacheWithObjectLiteral = defineRule({
39687
39573
  cachedFunctionNames.add(node.id.name);
39688
39574
  },
39689
39575
  CallExpression(node) {
39690
- if (cachedFunctionNames.size === 0) return;
39691
39576
  if (!isNodeOfType(node.callee, "Identifier")) return;
39692
39577
  if (!cachedFunctionNames.has(node.callee.name)) return;
39693
39578
  const firstArg = node.arguments?.[0];
@@ -40614,16 +40499,7 @@ const supabaseRlsPolicyRisk = defineRule({
40614
40499
  //#endregion
40615
40500
  //#region src/plugin/rules/security-scan/supabase-table-missing-rls.ts
40616
40501
  const CREATE_PUBLIC_TABLE_PATTERN = /create\s+(?:unlogged\s+)?table\s+(?:if\s+not\s+exists\s+)?(?!(?:auth|storage|realtime|vault|extensions|graphql|graphql_public|pgbouncer|net|supabase_functions|supabase_migrations|cron|pgsodium|pgmq|information_schema|pg_catalog|pg_temp|private|internal)\s*\.)(?:public\s*\.\s*)?["`]?([A-Za-z_][\w$]*)["`]?(?:\s*\(|\s+as\b)/gi;
40617
- const ENABLE_RLS_PATTERN = /alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?(?:public\s*\.\s*)?["`]?([A-Za-z_][\w$]*)["`]?\s+(?:force\s+)?enable\s+row\s+level\s+security/gi;
40618
- const collectLastEnableRlsIndexByTable = (content) => {
40619
- const lastEnableIndexByTable = /* @__PURE__ */ new Map();
40620
- for (const match of content.matchAll(ENABLE_RLS_PATTERN)) {
40621
- const tableName = match[1];
40622
- if (tableName === void 0) continue;
40623
- lastEnableIndexByTable.set(tableName.toLowerCase(), match.index);
40624
- }
40625
- return lastEnableIndexByTable;
40626
- };
40502
+ const enableRlsForTablePattern = (tableName) => new RegExp(`alter\\s+table\\s+(?:if\\s+exists\\s+)?(?:only\\s+)?(?:public\\s*\\.\\s*)?["\`]?${escapeRegExp(tableName)}["\`]?\\s+(?:force\\s+)?enable\\s+row\\s+level\\s+security`, "i");
40627
40503
  const supabaseTableMissingRls = defineRule({
40628
40504
  id: "supabase-table-missing-rls",
40629
40505
  title: "Supabase table created without Row Level Security",
@@ -40634,13 +40510,11 @@ const supabaseTableMissingRls = defineRule({
40634
40510
  const content = sanitizeSqlForScan(file.content);
40635
40511
  if (!/create\s+(?:unlogged\s+)?table/i.test(content)) return [];
40636
40512
  const findings = [];
40637
- const lastEnableIndexByTable = collectLastEnableRlsIndexByTable(content);
40638
40513
  CREATE_PUBLIC_TABLE_PATTERN.lastIndex = 0;
40639
40514
  for (let match = CREATE_PUBLIC_TABLE_PATTERN.exec(content); match !== null; match = CREATE_PUBLIC_TABLE_PATTERN.exec(content)) {
40640
40515
  const tableName = match[1];
40641
40516
  if (tableName === void 0) continue;
40642
- const lastEnableIndex = lastEnableIndexByTable.get(tableName.toLowerCase());
40643
- if (lastEnableIndex !== void 0 && lastEnableIndex >= match.index) continue;
40517
+ if (enableRlsForTablePattern(tableName).test(content.slice(match.index))) continue;
40644
40518
  const location = getLocationAtIndex(content, match.index);
40645
40519
  findings.push({
40646
40520
  message: "Supabase migration creates a public table but never enables Row Level Security, leaving every row exposed to the anon key.",
@@ -41330,10 +41204,10 @@ const tanstackStartRoutePropertyOrder = defineRule({
41330
41204
  const propertyName = getPropertyKeyName(property);
41331
41205
  if (propertyName !== null) orderedPropertyNames.push(propertyName);
41332
41206
  }
41333
- const sensitiveProperties = orderedPropertyNames.filter((propertyName) => TANSTACK_ROUTE_PROPERTY_INDEX.has(propertyName));
41207
+ const sensitiveProperties = orderedPropertyNames.filter((propertyName) => TANSTACK_ROUTE_PROPERTY_ORDER.includes(propertyName));
41334
41208
  let lastIndex = -1;
41335
41209
  for (const propertyName of sensitiveProperties) {
41336
- const currentIndex = TANSTACK_ROUTE_PROPERTY_INDEX.get(propertyName) ?? -1;
41210
+ const currentIndex = TANSTACK_ROUTE_PROPERTY_ORDER.indexOf(propertyName);
41337
41211
  if (currentIndex < lastIndex) {
41338
41212
  const expectedBefore = TANSTACK_ROUTE_PROPERTY_ORDER[lastIndex];
41339
41213
  context.report({
@@ -41370,10 +41244,10 @@ const tanstackStartServerFnMethodOrder = defineRule({
41370
41244
  } else return;
41371
41245
  const ownMethodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
41372
41246
  if (methodNames[methodNames.length - 1] !== ownMethodName) return;
41373
- const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_INDEX.has(toMethodOrderToken(name)));
41247
+ const orderSensitiveMethods = methodNames.filter((name) => TANSTACK_MIDDLEWARE_METHOD_ORDER.includes(toMethodOrderToken(name)));
41374
41248
  let lastIndex = -1;
41375
41249
  for (const methodName of orderSensitiveMethods) {
41376
- const currentIndex = TANSTACK_MIDDLEWARE_METHOD_INDEX.get(toMethodOrderToken(methodName)) ?? -1;
41250
+ const currentIndex = TANSTACK_MIDDLEWARE_METHOD_ORDER.indexOf(toMethodOrderToken(methodName));
41377
41251
  if (currentIndex < lastIndex) {
41378
41252
  const expectedBefore = TANSTACK_MIDDLEWARE_METHOD_ORDER[lastIndex];
41379
41253
  context.report({
@@ -41657,21 +41531,13 @@ const webhookSignatureRisk = defineRule({
41657
41531
  //#endregion
41658
41532
  //#region src/plugin/rules/zod/utils/zod-ast.ts
41659
41533
  const ZOD_MODULE = "zod";
41660
- const ZOD_MODULE_SOURCES = [ZOD_MODULE];
41661
41534
  const getStaticPropertyName = (member) => {
41662
41535
  const property = member.property;
41663
41536
  if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
41664
41537
  if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
41665
41538
  return null;
41666
41539
  };
41667
- const importInfoCache = /* @__PURE__ */ new WeakMap();
41668
41540
  const getImportInfoForIdentifier = (identifier) => {
41669
- if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
41670
- const importInfo = computeImportInfoForIdentifier(identifier);
41671
- importInfoCache.set(identifier, importInfo);
41672
- return importInfo;
41673
- };
41674
- const computeImportInfoForIdentifier = (identifier) => {
41675
41541
  const specifier = findVariableInitializer(identifier, identifier.name)?.initializer;
41676
41542
  if (!specifier) return null;
41677
41543
  const declaration = specifier.parent;
@@ -41808,33 +41674,25 @@ const zodV4NoDeprecatedErrorApis = defineRule({
41808
41674
  tags: ["migration-hint"],
41809
41675
  severity: "warn",
41810
41676
  recommendation: "Use the Zod 4 helpers instead: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or read `error.issues` directly.",
41811
- create: (context) => {
41812
- let fileImportsZod = false;
41813
- return {
41814
- Program(node) {
41815
- fileImportsZod = hasImportFromModules(node, ZOD_MODULE_SOURCES);
41816
- },
41817
- CallExpression(node) {
41818
- if (!fileImportsZod) return;
41819
- if (isZodErrorCreateCall(node) && isReceiverOfDeprecatedZodErrorMember(node)) return;
41820
- if (!isZodErrorCreateCall(node) && !isDeprecatedZodErrorMemberAccess(node.callee)) return;
41821
- context.report({
41822
- node,
41823
- message: ZOD_ERROR_API_MESSAGE
41824
- });
41825
- },
41826
- MemberExpression(node) {
41827
- if (!fileImportsZod) return;
41828
- const parent = node.parent;
41829
- if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
41830
- if (!isDeprecatedZodErrorMemberAccess(node)) return;
41831
- context.report({
41832
- node,
41833
- message: ZOD_ERROR_API_MESSAGE
41834
- });
41835
- }
41836
- };
41837
- }
41677
+ create: (context) => ({
41678
+ CallExpression(node) {
41679
+ if (isZodErrorCreateCall(node) && isReceiverOfDeprecatedZodErrorMember(node)) return;
41680
+ if (!isZodErrorCreateCall(node) && !isDeprecatedZodErrorMemberAccess(node.callee)) return;
41681
+ context.report({
41682
+ node,
41683
+ message: ZOD_ERROR_API_MESSAGE
41684
+ });
41685
+ },
41686
+ MemberExpression(node) {
41687
+ const parent = node.parent;
41688
+ if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
41689
+ if (!isDeprecatedZodErrorMemberAccess(node)) return;
41690
+ context.report({
41691
+ node,
41692
+ message: ZOD_ERROR_API_MESSAGE
41693
+ });
41694
+ }
41695
+ })
41838
41696
  });
41839
41697
  //#endregion
41840
41698
  //#region src/plugin/rules/zod/zod-v4-no-deprecated-error-customization.ts
@@ -42026,24 +41884,16 @@ const zodV4NoDeprecatedSchemaApis = defineRule({
42026
41884
  tags: ["migration-hint"],
42027
41885
  severity: "warn",
42028
41886
  recommendation: "Switch to the Zod 4 versions: top-level factories like `z.enum()`, object helpers like `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",
42029
- create: (context) => {
42030
- let fileImportsZod = false;
42031
- return {
42032
- Program(node) {
42033
- fileImportsZod = hasImportFromModules(node, ZOD_MODULE_SOURCES);
42034
- },
42035
- CallExpression(node) {
42036
- if (!fileImportsZod) return;
42037
- if (isCallToDeprecatedTopLevelFactory(node) || isCallToDroppedCreateFactory(node) || isSingleArgumentRecordCall(node) || isLiteralSymbolCall(node) || isDeprecatedFunctionChainCall(node) || isDirectMethodCallOnZodFactory(node, OBJECT_FACTORY, OBJECT_METHODS) || isDirectMethodCallOnZodFactory(node, NUMBER_FACTORY, NUMBER_METHODS) || isRefineSecondArgumentFunction(node)) reportSchemaMigration(context, node);
42038
- },
42039
- MemberExpression(node) {
42040
- if (!fileImportsZod) return;
42041
- const parent = node.parent;
42042
- if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
42043
- if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
42044
- }
42045
- };
42046
- }
41887
+ create: (context) => ({
41888
+ CallExpression(node) {
41889
+ if (isCallToDeprecatedTopLevelFactory(node) || isCallToDroppedCreateFactory(node) || isSingleArgumentRecordCall(node) || isLiteralSymbolCall(node) || isDeprecatedFunctionChainCall(node) || isDirectMethodCallOnZodFactory(node, OBJECT_FACTORY, OBJECT_METHODS) || isDirectMethodCallOnZodFactory(node, NUMBER_FACTORY, NUMBER_METHODS) || isRefineSecondArgumentFunction(node)) reportSchemaMigration(context, node);
41890
+ },
41891
+ MemberExpression(node) {
41892
+ const parent = node.parent;
41893
+ if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
41894
+ if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
41895
+ }
41896
+ })
42047
41897
  });
42048
41898
  //#endregion
42049
41899
  //#region src/plugin/rules/zod/zod-v4-prefer-top-level-string-formats.ts
@@ -42078,22 +41928,13 @@ const zodV4PreferTopLevelStringFormats = defineRule({
42078
41928
  tags: ["migration-hint"],
42079
41929
  severity: "warn",
42080
41930
  recommendation: "Use the Zod 4 top-level format checks like `z.email()`, `z.uuid()`, or `z.ipv4()` instead of `z.string().<format>()`.",
42081
- create: (context) => {
42082
- let fileImportsZod = false;
42083
- return {
42084
- Program(node) {
42085
- fileImportsZod = hasImportFromModules(node, ZOD_MODULE_SOURCES);
42086
- },
42087
- CallExpression(node) {
42088
- if (!fileImportsZod) return;
42089
- if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
42090
- context.report({
42091
- node,
42092
- message: "This `z.string().<format>()` check is deprecated in Zod 4, so it can break during the upgrade."
42093
- });
42094
- }
42095
- };
42096
- }
41931
+ create: (context) => ({ CallExpression(node) {
41932
+ if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
41933
+ context.report({
41934
+ node,
41935
+ message: "This `z.string().<format>()` check is deprecated in Zod 4, so it can break during the upgrade."
41936
+ });
41937
+ } })
42097
41938
  });
42098
41939
  //#endregion
42099
41940
  //#region src/plugin/rule-registry.ts