@sarj/eslint-plugin 15.17.1 → 15.17.3

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.
package/dist/index.js CHANGED
@@ -5849,7 +5849,7 @@ var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
5849
5849
  rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
5850
5850
  remediation: "Move the request into a client module and call that abstraction from application code.",
5851
5851
  category: "architecture",
5852
- limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
5852
+ limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded. Configure the same literal Next.js basePath here and on prefer-server-actions so one rule owns each internal mutation."],
5853
5853
  examples: [
5854
5854
  { id: "client-call", title: "Use a client abstraction", outcome: "no-match", files: [{ path: "src/routes/handler.ts", source: "const response = await billingClient.getInvoice(id);" }], focusPath: "src/routes/handler.ts", expectedCount: 0, public: true },
5855
5855
  { id: "raw-fetch", title: "Do not call global fetch here", outcome: "match", files: [{ path: "src/routes/handler.ts", source: "const response = await fetch('/api/invoices');" }], focusPath: "src/routes/handler.ts", expectedCount: 1, public: true }
@@ -5894,7 +5894,10 @@ var ANALYTICS_SEGMENTS2 = /* @__PURE__ */ new Set([
5894
5894
  ]);
5895
5895
  var SERVER_ACTION_SKIP_FILE_RE = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
5896
5896
  var NON_REACT_FRAMEWORK_RE = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
5897
- var NEXT_MODULE_PATH_RE = /(?:^|[/\\])(?:app|pages)[/\\]/u;
5897
+ var BASE_PATH_RE = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
5898
+ function isValidBasePath(basePath) {
5899
+ return BASE_PATH_RE.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
5900
+ }
5898
5901
  function isGlobalFetchCall(node, resolvesToGlobal) {
5899
5902
  const callee = node.callee;
5900
5903
  if (callee.type === "Identifier") {
@@ -5990,6 +5993,10 @@ var no_raw_fetch_outside_clients_default = createRule({
5990
5993
  type: "array",
5991
5994
  items: { type: "string" },
5992
5995
  description: "Regular-expression sources matched against the filename. Replaces the defaults."
5996
+ },
5997
+ basePath: {
5998
+ type: "string",
5999
+ pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
5993
6000
  }
5994
6001
  },
5995
6002
  additionalProperties: false
@@ -6011,12 +6018,18 @@ var no_raw_fetch_outside_clients_default = createRule({
6011
6018
  (statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration && typeof statement.source.value === "string" && NON_REACT_FRAMEWORK_RE.test(statement.source.value)
6012
6019
  );
6013
6020
  const hasUseClientDirective = context.sourceCode.ast.body.some(
6014
- (statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.expression.type === AST_NODE_TYPES24.Literal && statement.expression.value === "use client"
6021
+ (statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.directive === "use client"
6015
6022
  );
6016
- const hasNextImport = context.sourceCode.ast.body.some(
6017
- (statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "next" || statement.source.value.startsWith("next/"))
6023
+ const hasUseServerDirective = context.sourceCode.ast.body.some(
6024
+ (statement) => statement.type === AST_NODE_TYPES24.ExpressionStatement && statement.directive === "use server"
6018
6025
  );
6019
- const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE.test(filename);
6026
+ const importsServerOnly = context.sourceCode.ast.body.some(
6027
+ (statement) => statement.type === AST_NODE_TYPES24.ImportDeclaration && typeof statement.source.value === "string" && (statement.source.value === "server-only" || statement.source.value === "next/server")
6028
+ );
6029
+ const internalApiPrefixes = ["/api"];
6030
+ if (options?.basePath !== void 0 && isValidBasePath(options.basePath)) {
6031
+ internalApiPrefixes.push(`${options.basePath}/api`);
6032
+ }
6020
6033
  function resolvesToGlobal(identifier) {
6021
6034
  const variable = ASTUtils7.findVariable(
6022
6035
  context.sourceCode.getScope(identifier),
@@ -6049,15 +6062,15 @@ var no_raw_fetch_outside_clients_default = createRule({
6049
6062
  function isInternalApiUrl(node) {
6050
6063
  const resolved = resolveNode2(node ?? void 0);
6051
6064
  if (resolved?.type === AST_NODE_TYPES24.Literal) {
6052
- return typeof resolved.value === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(resolved.value);
6065
+ return typeof resolved.value === "string" && internalApiPrefixes.some(
6066
+ (prefix) => resolved.value === prefix || resolved.value.startsWith(`${prefix}/`)
6067
+ );
6053
6068
  }
6054
6069
  if (resolved?.type === AST_NODE_TYPES24.TemplateLiteral) {
6055
6070
  const prefix = resolved.quasis[0]?.value.cooked;
6056
- return typeof prefix === "string" && /^\/(?!\/)(?:[^/]+\/)*api(?:\/|$)/.test(prefix);
6057
- }
6058
- if (resolved?.type === AST_NODE_TYPES24.CallExpression && resolved.callee.type === AST_NODE_TYPES24.Identifier && resolved.callee.name === "withBase") {
6059
- const first = resolved.arguments[0];
6060
- return first !== void 0 && first.type !== AST_NODE_TYPES24.SpreadElement ? isInternalApiUrl(first) : false;
6071
+ return typeof prefix === "string" && internalApiPrefixes.some(
6072
+ (apiPrefix) => prefix === apiPrefix || prefix.startsWith(`${apiPrefix}/`)
6073
+ );
6061
6074
  }
6062
6075
  return resolved?.type === AST_NODE_TYPES24.BinaryExpression && resolved.operator === "+" && isInternalApiUrl(resolved.left);
6063
6076
  }
@@ -6077,7 +6090,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6077
6090
  return resolved?.type === AST_NODE_TYPES24.LogicalExpression && resolved.operator === "||" && (isMutationMethod2(resolved.left) || isMutationMethod2(resolved.right));
6078
6091
  }
6079
6092
  function serverActionOwns(node) {
6080
- if (node.callee.type !== AST_NODE_TYPES24.Identifier || !hasNextEvidence || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
6093
+ if (node.callee.type !== AST_NODE_TYPES24.Identifier || !hasUseClientDirective || hasUseServerDirective || importsServerOnly || SERVER_ACTION_SKIP_FILE_RE.test(filename) || nonReactFramework) {
6081
6094
  return false;
6082
6095
  }
6083
6096
  const url = node.arguments[0];
@@ -9737,25 +9750,25 @@ var no_unsafe_mock_casting_default = createRule({
9737
9750
  }
9738
9751
  const directBindings = /* @__PURE__ */ new Set();
9739
9752
  const namespaceBindings = /* @__PURE__ */ new Set();
9740
- function resolve(identifier) {
9753
+ function resolve2(identifier) {
9741
9754
  return ASTUtils10.findVariable(
9742
9755
  context.sourceCode.getScope(identifier),
9743
9756
  identifier.name
9744
9757
  );
9745
9758
  }
9746
9759
  function record(identifier, destination) {
9747
- const binding = resolve(identifier);
9760
+ const binding = resolve2(identifier);
9748
9761
  if (binding !== null) destination.add(binding);
9749
9762
  }
9750
9763
  function isMockTypeReference(node) {
9751
9764
  if (node.type !== AST_NODE_TYPES40.TSTypeReference) return false;
9752
9765
  const typeName = node.typeName;
9753
9766
  if (typeName.type === AST_NODE_TYPES40.Identifier) {
9754
- const binding = resolve(typeName);
9767
+ const binding = resolve2(typeName);
9755
9768
  return binding !== null && directBindings.has(binding);
9756
9769
  }
9757
9770
  if (typeName.type === AST_NODE_TYPES40.TSQualifiedName && typeName.left.type === AST_NODE_TYPES40.Identifier && MOCK_TYPE_NAMES.has(typeName.right.name)) {
9758
- const binding = resolve(typeName.left);
9771
+ const binding = resolve2(typeName.left);
9759
9772
  return binding !== null && namespaceBindings.has(binding);
9760
9773
  }
9761
9774
  return false;
@@ -11169,6 +11182,10 @@ function unwrapTransparentExport(node) {
11169
11182
 
11170
11183
  // src/rules/prefer-shadcn-primitives.ts
11171
11184
  import { AST_NODE_TYPES as AST_NODE_TYPES50 } from "@typescript-eslint/utils";
11185
+ import { parse as parseTypeScript } from "@typescript-eslint/typescript-estree";
11186
+ import { existsSync, lstatSync, readFileSync, realpathSync } from "fs";
11187
+ import { dirname, isAbsolute, join, parse as parsePath, relative, resolve, sep } from "path";
11188
+ import { parse as parseJsonc } from "jsonc-parser";
11172
11189
  var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
11173
11190
  summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
11174
11191
  rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
@@ -11176,22 +11193,35 @@ var PREFER_SHADCN_PRIMITIVES_DOCUMENTATION = {
11176
11193
  category: "style",
11177
11194
  limitations: [
11178
11195
  "Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded.",
11179
- "Tests and the shared components/ui primitive implementation tree are excluded."
11196
+ "Tests and the shared components/ui primitive implementation tree are excluded.",
11197
+ "Package-local project detection is opt-in and fails closed unless components.json, one unambiguous tsconfig/jsconfig alias, the exact primitive module, and its expected export all exist."
11180
11198
  ],
11181
11199
  examples: [
11182
11200
  { id: "shared-button", title: "Use a shared button", outcome: "no-match", files: [{ path: "src/form.tsx", source: "import { Button } from '@/components/ui/button'; const action = <Button>Save</Button>;" }], focusPath: "src/form.tsx", expectedCount: 0, public: true },
11183
11201
  { id: "raw-button", title: "Do not use a raw button", outcome: "match", files: [{ path: "src/form.tsx", source: "import { Card } from '@/components/ui/card'; const action = <button>Save</button>;" }], focusPath: "src/form.tsx", expectedCount: 1, public: true }
11184
11202
  ]
11185
11203
  };
11186
- var SHADCN_PRIMITIVES = {
11187
- button: "Button",
11188
- dialog: "Dialog or AlertDialog family",
11189
- input: "Input",
11190
- label: "Label",
11191
- progress: "Progress",
11192
- select: "Select family",
11193
- table: "Table family",
11194
- textarea: "Textarea"
11204
+ var RAW_PRIMITIVES = {
11205
+ button: { capability: "Button", replacement: "Button" },
11206
+ dialog: { capability: "Dialog", replacement: "Dialog or AlertDialog family" },
11207
+ input: { capability: "Input", replacement: "Input" },
11208
+ label: { capability: "Label", replacement: "Label" },
11209
+ progress: { capability: "Progress", replacement: "Progress" },
11210
+ select: { capability: "Select", replacement: "Select family" },
11211
+ table: { capability: "Table", replacement: "Table family" },
11212
+ textarea: { capability: "Textarea", replacement: "Textarea" }
11213
+ };
11214
+ var CAPABILITIES = {
11215
+ Button: "button",
11216
+ Checkbox: "checkbox",
11217
+ Dialog: "dialog",
11218
+ Input: "input",
11219
+ Label: "label",
11220
+ Progress: "progress",
11221
+ RadioGroup: "radio-group",
11222
+ Select: "select",
11223
+ Table: "table",
11224
+ Textarea: "textarea"
11195
11225
  };
11196
11226
  var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
11197
11227
  "button",
@@ -11203,7 +11233,7 @@ var LABELABLE_ELEMENTS = /* @__PURE__ */ new Set([
11203
11233
  "textarea"
11204
11234
  ]);
11205
11235
  var SHARED_PRIMITIVE_IMPLEMENTATION_RE = /(?:^|\/)components\/ui(?:\/|$)/i;
11206
- var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/[^/]+$/i;
11236
+ var SHARED_PRIMITIVE_IMPORT_RE = /(?:^|\/)components\/ui\/([^/]+)$/i;
11207
11237
  var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
11208
11238
  "button",
11209
11239
  "color",
@@ -11212,10 +11242,175 @@ var AMBIGUOUS_INPUT_TYPES = /* @__PURE__ */ new Set([
11212
11242
  "reset",
11213
11243
  "submit"
11214
11244
  ]);
11245
+ var MAX_PROJECT_FILE_BYTES = 1048576;
11246
+ var PROJECT_PRIMITIVES_CACHE = /* @__PURE__ */ new Map();
11247
+ function detectProjectPrimitives(filename, requested) {
11248
+ const packageRoot = findPackageRoot(dirname(filename));
11249
+ if (packageRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
11250
+ const manifest = readJsonc(join(packageRoot, "components.json"));
11251
+ const alias = stringProperty(manifest, "aliases", "ui");
11252
+ const unresolvedUiRoot = alias === null ? null : resolveAlias(packageRoot, alias);
11253
+ if (unresolvedUiRoot === null) return { available: /* @__PURE__ */ new Set(), uiRoot: null };
11254
+ const uiRoot = safeContainedDirectory(packageRoot, unresolvedUiRoot);
11255
+ if (uiRoot === null) {
11256
+ return { available: /* @__PURE__ */ new Set(), uiRoot: null };
11257
+ }
11258
+ const moduleCandidates = /* @__PURE__ */ new Map();
11259
+ for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
11260
+ if (!requested.has(capability)) continue;
11261
+ moduleCandidates.set(capability, ["tsx", "ts", "jsx", "js"].flatMap((extension) => [
11262
+ join(uiRoot, `${moduleName}.${extension}`),
11263
+ join(uiRoot, moduleName, `index.${extension}`)
11264
+ ]));
11265
+ }
11266
+ const fingerprint = [
11267
+ join(packageRoot, "components.json"),
11268
+ join(packageRoot, "tsconfig.json"),
11269
+ join(packageRoot, "jsconfig.json"),
11270
+ ...[...moduleCandidates.values()].flat()
11271
+ ].map(fileFingerprint).join("|");
11272
+ const cacheKey = `${packageRoot}:${[...requested].sort().join(",")}`;
11273
+ const cached = PROJECT_PRIMITIVES_CACHE.get(cacheKey);
11274
+ if (cached?.fingerprint === fingerprint) return cached.value;
11275
+ const available = /* @__PURE__ */ new Set();
11276
+ for (const [capability, candidates] of moduleCandidates) {
11277
+ if (candidates.some(
11278
+ (candidate2) => exportsPrimitive(candidate2, capability)
11279
+ )) {
11280
+ available.add(capability);
11281
+ }
11282
+ }
11283
+ const value = { available, uiRoot };
11284
+ PROJECT_PRIMITIVES_CACHE.set(cacheKey, { fingerprint, value });
11285
+ return value;
11286
+ }
11287
+ function fileFingerprint(path) {
11288
+ try {
11289
+ const stat = lstatSync(path);
11290
+ return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
11291
+ } catch {
11292
+ return `${path}:missing`;
11293
+ }
11294
+ }
11295
+ function isWithin2(root, candidate2) {
11296
+ const path = relative(root, candidate2);
11297
+ return path === "" || !path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path);
11298
+ }
11299
+ function safeContainedDirectory(root, candidate2) {
11300
+ try {
11301
+ if (!lstatSync(candidate2).isDirectory()) return null;
11302
+ const realRoot = realpathSync(root);
11303
+ const realCandidate = realpathSync(candidate2);
11304
+ return isWithin2(realRoot, realCandidate) ? realCandidate : null;
11305
+ } catch {
11306
+ return null;
11307
+ }
11308
+ }
11309
+ function findPackageRoot(startDir) {
11310
+ let dir = startDir;
11311
+ const filesystemRoot = parsePath(dir).root;
11312
+ for (; ; ) {
11313
+ if (existsSync(join(dir, "package.json"))) return dir;
11314
+ const parent = dirname(dir);
11315
+ if (dir === filesystemRoot || parent === dir) return null;
11316
+ dir = parent;
11317
+ }
11318
+ }
11319
+ function readJsonc(path) {
11320
+ try {
11321
+ const source = readSmallRegularFile(path);
11322
+ if (source === null) return null;
11323
+ const errors = [];
11324
+ const value = parseJsonc(source, errors, {
11325
+ allowTrailingComma: true,
11326
+ disallowComments: false
11327
+ });
11328
+ return errors.length === 0 ? value : null;
11329
+ } catch {
11330
+ return null;
11331
+ }
11332
+ }
11333
+ function readSmallRegularFile(path) {
11334
+ try {
11335
+ const stat = lstatSync(path);
11336
+ if (!stat.isFile() || stat.size > MAX_PROJECT_FILE_BYTES) return null;
11337
+ return readFileSync(path, "utf8");
11338
+ } catch {
11339
+ return null;
11340
+ }
11341
+ }
11342
+ function stringProperty(value, ...keys) {
11343
+ let current = value;
11344
+ for (const key of keys) {
11345
+ if (typeof current !== "object" || current === null || !(key in current)) return null;
11346
+ current = current[key];
11347
+ }
11348
+ return typeof current === "string" && current.trim() !== "" ? current : null;
11349
+ }
11350
+ function resolveAlias(packageRoot, alias) {
11351
+ if (isAbsolute(alias)) return null;
11352
+ if (alias.startsWith(".")) return resolve(packageRoot, alias);
11353
+ const configPath = ["tsconfig.json", "jsconfig.json"].map((name) => join(packageRoot, name)).find(existsSync);
11354
+ if (configPath === void 0) return null;
11355
+ const config = readJsonc(configPath);
11356
+ if (typeof config !== "object" || config === null) return null;
11357
+ const compilerOptions = config["compilerOptions"];
11358
+ if (typeof compilerOptions !== "object" || compilerOptions === null) return null;
11359
+ const options = compilerOptions;
11360
+ const baseUrl = typeof options["baseUrl"] === "string" ? options["baseUrl"] : ".";
11361
+ const paths = options["paths"];
11362
+ if (typeof paths !== "object" || paths === null) return null;
11363
+ const matches = [];
11364
+ for (const [pattern, rawTargets] of Object.entries(paths)) {
11365
+ if (!Array.isArray(rawTargets) || rawTargets.length !== 1) {
11366
+ continue;
11367
+ }
11368
+ const [target] = rawTargets;
11369
+ if (typeof target !== "string") continue;
11370
+ const star = pattern.indexOf("*");
11371
+ if (star === -1) {
11372
+ if (pattern === alias) matches.push(target);
11373
+ continue;
11374
+ }
11375
+ const prefix = pattern.slice(0, star);
11376
+ const suffix = pattern.slice(star + 1);
11377
+ if (!alias.startsWith(prefix) || !alias.endsWith(suffix)) continue;
11378
+ const substitution = alias.slice(prefix.length, alias.length - suffix.length);
11379
+ matches.push(target.replace("*", substitution));
11380
+ }
11381
+ const [match] = matches;
11382
+ if (matches.length !== 1 || match === void 0) return null;
11383
+ return resolve(packageRoot, baseUrl, match);
11384
+ }
11385
+ function exportsPrimitive(path, exportName) {
11386
+ try {
11387
+ const source = readSmallRegularFile(path);
11388
+ if (source === null) return false;
11389
+ const program = parseTypeScript(source, { jsx: true, sourceType: "module" });
11390
+ return program.body.some((statement) => {
11391
+ if (statement.type !== AST_NODE_TYPES50.ExportNamedDeclaration) return false;
11392
+ if (statement.exportKind === "type") return false;
11393
+ if (statement.specifiers.some(
11394
+ (specifier) => specifier.type === AST_NODE_TYPES50.ExportSpecifier && specifier.exportKind !== "type" && specifier.exported.type === AST_NODE_TYPES50.Identifier && specifier.exported.name === exportName
11395
+ )) {
11396
+ return true;
11397
+ }
11398
+ const declaration = statement.declaration;
11399
+ if (declaration?.type === AST_NODE_TYPES50.VariableDeclaration) {
11400
+ return declaration.declarations.some(
11401
+ (item) => item.id.type === AST_NODE_TYPES50.Identifier && item.id.name === exportName
11402
+ );
11403
+ }
11404
+ return (declaration?.type === AST_NODE_TYPES50.FunctionDeclaration || declaration?.type === AST_NODE_TYPES50.ClassDeclaration) && declaration.id?.name === exportName;
11405
+ });
11406
+ } catch {
11407
+ return false;
11408
+ }
11409
+ }
11215
11410
  function rawElementName(node) {
11216
11411
  if (node.name.type !== AST_NODE_TYPES50.JSXIdentifier) return null;
11217
11412
  const name = node.name.name;
11218
- return Object.hasOwn(SHADCN_PRIMITIVES, name) ? name : null;
11413
+ return Object.hasOwn(RAW_PRIMITIVES, name) ? name : null;
11219
11414
  }
11220
11415
  function effectiveAttribute(node, attributeName) {
11221
11416
  for (const attribute of node.attributes.toReversed()) {
@@ -11284,15 +11479,19 @@ function isStaticallyAssociatedLabel(node) {
11284
11479
  return node.parent.type === AST_NODE_TYPES50.JSXElement && containsLabelableElement(node.parent);
11285
11480
  }
11286
11481
  function replacementFor(node, element) {
11287
- if (element !== "input") return SHADCN_PRIMITIVES[element];
11482
+ if (element !== "input") return RAW_PRIMITIVES[element];
11288
11483
  const typeAttribute = effectiveAttribute(node, "type");
11289
11484
  if (typeAttribute.kind === "unknown") return null;
11290
11485
  const inputType = typeAttribute.kind === "known" ? typeAttribute.value.toLowerCase() : "text";
11291
11486
  if (inputType === "hidden" || inputType === "file") return null;
11292
- if (inputType === "checkbox") return "Checkbox";
11293
- if (inputType === "radio") return "RadioGroup family";
11487
+ if (inputType === "checkbox") {
11488
+ return { capability: "Checkbox", replacement: "Checkbox" };
11489
+ }
11490
+ if (inputType === "radio") {
11491
+ return { capability: "RadioGroup", replacement: "RadioGroup family" };
11492
+ }
11294
11493
  if (AMBIGUOUS_INPUT_TYPES.has(inputType)) return null;
11295
- return "Input";
11494
+ return RAW_PRIMITIVES.input;
11296
11495
  }
11297
11496
  var prefer_shadcn_primitives_default = createRule({
11298
11497
  name: "prefer-shadcn-primitives",
@@ -11306,7 +11505,8 @@ var prefer_shadcn_primitives_default = createRule({
11306
11505
  {
11307
11506
  type: "object",
11308
11507
  properties: {
11309
- assumeAvailable: { type: "boolean" }
11508
+ assumeAvailable: { type: "boolean" },
11509
+ detectProjectPrimitives: { type: "boolean" }
11310
11510
  },
11311
11511
  additionalProperties: false
11312
11512
  }
@@ -11318,15 +11518,21 @@ var prefer_shadcn_primitives_default = createRule({
11318
11518
  defaultOptions: [{}],
11319
11519
  create(context, [options]) {
11320
11520
  const filename = context.filename.replaceAll("\\", "/");
11321
- if (isTestFile(filename) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
11521
+ if (isTestFile(filename) || isStoryFile(filename) || isGeneratedFile(filename, context.sourceCode.text) || SHARED_PRIMITIVE_IMPLEMENTATION_RE.test(filename)) {
11322
11522
  return {};
11323
11523
  }
11324
- let hasSharedPrimitiveImport = options?.assumeAvailable ?? false;
11524
+ const detectsProject = options?.detectProjectPrimitives === true;
11525
+ let hasSharedPrimitiveImport = false;
11526
+ const importedCapabilities = /* @__PURE__ */ new Set();
11325
11527
  const candidates = [];
11326
11528
  return {
11327
11529
  ImportDeclaration(node) {
11328
- if (typeof node.source.value === "string" && SHARED_PRIMITIVE_IMPORT_RE.test(node.source.value)) {
11329
- hasSharedPrimitiveImport = true;
11530
+ if (typeof node.source.value !== "string") return;
11531
+ const match = SHARED_PRIMITIVE_IMPORT_RE.exec(node.source.value);
11532
+ if (match?.[1] === void 0) return;
11533
+ hasSharedPrimitiveImport = true;
11534
+ for (const [capability, moduleName] of Object.entries(CAPABILITIES)) {
11535
+ if (match[1].toLowerCase() === moduleName) importedCapabilities.add(capability);
11330
11536
  }
11331
11537
  },
11332
11538
  JSXOpeningElement(node) {
@@ -11335,11 +11541,15 @@ var prefer_shadcn_primitives_default = createRule({
11335
11541
  if (element === "label" && !isStaticallyAssociatedLabel(node)) return;
11336
11542
  const replacement = replacementFor(node, element);
11337
11543
  if (replacement === null) return;
11338
- candidates.push({ element, node, replacement });
11544
+ candidates.push({ element, node, ...replacement });
11339
11545
  },
11340
11546
  "Program:exit"() {
11341
- if (!hasSharedPrimitiveImport) return;
11342
- for (const { element, node, replacement } of candidates) {
11547
+ const requested = new Set(candidates.map(({ capability }) => capability));
11548
+ const projectPrimitives = detectsProject && requested.size > 0 ? detectProjectPrimitives(context.filename, requested) : { available: /* @__PURE__ */ new Set(), uiRoot: null };
11549
+ if (projectPrimitives.uiRoot !== null && isWithin2(projectPrimitives.uiRoot, realpathOrOriginal(context.filename))) return;
11550
+ for (const { capability, element, node, replacement } of candidates) {
11551
+ const available = options?.assumeAvailable === true || (detectsProject ? projectPrimitives.available.has(capability) || importedCapabilities.has(capability) : hasSharedPrimitiveImport);
11552
+ if (!available) continue;
11343
11553
  context.report({
11344
11554
  node,
11345
11555
  messageId: "preferShadcnPrimitive",
@@ -11350,6 +11560,13 @@ var prefer_shadcn_primitives_default = createRule({
11350
11560
  };
11351
11561
  }
11352
11562
  });
11563
+ function realpathOrOriginal(path) {
11564
+ try {
11565
+ return realpathSync(path);
11566
+ } catch {
11567
+ return path;
11568
+ }
11569
+ }
11353
11570
 
11354
11571
  // src/rules/prefer-module-level-constant.ts
11355
11572
  import { AST_NODE_TYPES as AST_NODE_TYPES51 } from "@typescript-eslint/utils";
@@ -12019,100 +12236,371 @@ var prefer_module_level_schema_default = createRule({
12019
12236
  });
12020
12237
 
12021
12238
  // src/rules/prefer-module-level-refined-schema.ts
12022
- import { AST_NODE_TYPES as AST_NODE_TYPES53 } from "@typescript-eslint/utils";
12239
+ import {
12240
+ AST_NODE_TYPES as AST_NODE_TYPES53,
12241
+ ASTUtils as ASTUtils15
12242
+ } from "@typescript-eslint/utils";
12243
+ var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
12023
12244
  var FACTORIES = /* @__PURE__ */ new Set([
12024
12245
  "array",
12246
+ "base64",
12247
+ "base64url",
12025
12248
  "bigint",
12026
12249
  "boolean",
12250
+ "cidrv4",
12251
+ "cidrv6",
12252
+ "codec",
12253
+ "custom",
12027
12254
  "date",
12028
- "number",
12029
- "string"
12030
- ]);
12031
- var REFINEMENTS = /* @__PURE__ */ new Set([
12032
- "brand",
12033
- "check",
12255
+ "datetime",
12256
+ "duration",
12034
12257
  "email",
12035
- "finite",
12036
- "int",
12037
- "length",
12038
- "max",
12039
- "min",
12040
- "multipleOf",
12041
- "nonempty",
12042
- "positive",
12043
- "regex",
12044
- "refine",
12045
- "safe",
12046
- "superRefine",
12047
- "transform",
12048
- "trim",
12258
+ "emoji",
12259
+ "enum",
12260
+ "file",
12261
+ "function",
12262
+ "hash",
12263
+ "hex",
12264
+ "hostname",
12265
+ "instanceof",
12266
+ "ipv4",
12267
+ "ipv6",
12268
+ "json",
12269
+ "jwt",
12270
+ "literal",
12271
+ "map",
12272
+ "nan",
12273
+ "nativeEnum",
12274
+ "never",
12275
+ "null",
12276
+ "nullable",
12277
+ "nullish",
12278
+ "number",
12279
+ "optional",
12280
+ "partialRecord",
12281
+ "preprocess",
12282
+ "promise",
12283
+ "set",
12284
+ "string",
12285
+ "stringbool",
12286
+ "symbol",
12287
+ "templateLiteral",
12288
+ "time",
12289
+ "undefined",
12049
12290
  "url",
12050
- "uuid"
12291
+ "uuid",
12292
+ "void"
12293
+ ]);
12294
+ var COMPOSITE_FACTORIES = /* @__PURE__ */ new Set([
12295
+ "discriminatedUnion",
12296
+ "intersection",
12297
+ "looseObject",
12298
+ "object",
12299
+ "record",
12300
+ "strictObject",
12301
+ "tuple",
12302
+ "union"
12303
+ ]);
12304
+ var FACTORY_NAMESPACES = /* @__PURE__ */ new Set(["coerce", "iso"]);
12305
+ var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
12306
+ "decode",
12307
+ "decodeAsync",
12308
+ "encode",
12309
+ "encodeAsync",
12310
+ "flattenError",
12311
+ "formatError",
12312
+ "implement",
12313
+ "isNullable",
12314
+ "isOptional",
12315
+ "parse",
12316
+ "parseAsync",
12317
+ "prettifyError",
12318
+ "registry",
12319
+ "safeDecode",
12320
+ "safeDecodeAsync",
12321
+ "safeEncode",
12322
+ "safeEncodeAsync",
12323
+ "safeParse",
12324
+ "safeParseAsync",
12325
+ "spa",
12326
+ "toJSONSchema",
12327
+ "treeifyError"
12328
+ ]);
12329
+ var MEMO_CALLEES2 = /* @__PURE__ */ new Set([
12330
+ "lazy",
12331
+ "memo",
12332
+ "once",
12333
+ "useMemo"
12334
+ ]);
12335
+ var I18N_CALLEE_NAMES2 = /* @__PURE__ */ new Set([
12336
+ "$t",
12337
+ "defineMessage",
12338
+ "gettext",
12339
+ "msg",
12340
+ "ngettext",
12341
+ "t",
12342
+ "translate"
12343
+ ]);
12344
+ var I18N_RECEIVER_NAMES2 = /* @__PURE__ */ new Set([
12345
+ "$i18n",
12346
+ "i18n",
12347
+ "intl"
12348
+ ]);
12349
+ var FUNCTION_TYPES9 = /* @__PURE__ */ new Set([
12350
+ AST_NODE_TYPES53.ArrowFunctionExpression,
12351
+ AST_NODE_TYPES53.FunctionDeclaration,
12352
+ AST_NODE_TYPES53.FunctionExpression
12051
12353
  ]);
12052
12354
  var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
12053
- summary: "Declare closed, refined Zod scalar and array schemas at module scope.",
12355
+ summary: "Declare closed Zod scalar, format, and wrapper schemas at module scope.",
12054
12356
  rationale: "A closed validation pipeline created inside a function is rebuilt on every invocation and obscures a reusable constraint.",
12055
- remediation: "Move the validation schema to module scope and call parse on the shared schema.",
12357
+ remediation: "Move the validation schema to module scope, name it with a PascalCase Schema suffix, and call parse on the shared schema.",
12056
12358
  category: "performance",
12057
- limitations: ["Only direct Zod scalar/array chains with at least two refinement methods and no non-literal arguments are reported."],
12359
+ limitations: [
12360
+ "Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
12361
+ "Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
12362
+ "Literal string z.enum domains are owned by prefer-shared-zod-enum."
12363
+ ],
12058
12364
  examples: [
12059
- { id: "module-refinement", title: "Share the validation schema", outcome: "no-match", files: [{ path: "src/options.ts", source: "import { z } from 'zod'; const BatchSize = z.number().int().min(1).max(1000); export function parse(value: unknown) { return BatchSize.parse(value); }" }], focusPath: "src/options.ts", expectedCount: 0, public: true },
12060
- { id: "local-refinement", title: "Do not rebuild a closed validation chain", outcome: "match", files: [{ path: "src/options.ts", source: "import { z } from 'zod'; export function parse(value: unknown) { return z.number().int().min(1).max(1000).parse(value); }" }], focusPath: "src/options.ts", expectedCount: 1, public: true }
12365
+ {
12366
+ id: "module-refinement",
12367
+ title: "Share the validation schema",
12368
+ outcome: "no-match",
12369
+ files: [{
12370
+ path: "src/options.ts",
12371
+ source: "import { z } from 'zod'; const BatchSizeSchema = z.number().int().min(1).max(1000); export function parse(value: unknown) { return BatchSizeSchema.parse(value); }"
12372
+ }],
12373
+ focusPath: "src/options.ts",
12374
+ expectedCount: 0,
12375
+ public: true
12376
+ },
12377
+ {
12378
+ id: "local-refinement",
12379
+ title: "Do not rebuild a closed validation chain",
12380
+ outcome: "match",
12381
+ files: [{
12382
+ path: "src/options.ts",
12383
+ source: "import { z } from 'zod'; export function parse(value: unknown) { return z.string().trim().min(1).max(128).parse(value); }"
12384
+ }],
12385
+ focusPath: "src/options.ts",
12386
+ expectedCount: 1,
12387
+ public: true
12388
+ }
12061
12389
  ]
12062
12390
  };
12063
- function enclosingFunction4(node) {
12391
+ function outermostEnclosingFunction2(node) {
12392
+ let outermost;
12064
12393
  let current = node.parent ?? void 0;
12065
12394
  while (current !== void 0) {
12066
- if ([AST_NODE_TYPES53.ArrowFunctionExpression, AST_NODE_TYPES53.FunctionDeclaration, AST_NODE_TYPES53.FunctionExpression].includes(current.type)) return current;
12395
+ if (FUNCTION_TYPES9.has(current.type)) outermost = current;
12067
12396
  current = current.parent ?? void 0;
12068
12397
  }
12069
- return void 0;
12398
+ return outermost;
12070
12399
  }
12071
12400
  function collectReferences2(scope, output) {
12072
12401
  output.push(...scope.references);
12073
12402
  for (const child of scope.childScopes) collectReferences2(child, output);
12074
12403
  }
12404
+ function subtreeSome2(root, predicate) {
12405
+ let found = false;
12406
+ const visit = (value) => {
12407
+ if (found || value === null || typeof value !== "object") return;
12408
+ if (Array.isArray(value)) {
12409
+ for (const item of value) visit(item);
12410
+ return;
12411
+ }
12412
+ const candidate2 = value;
12413
+ if (typeof candidate2.type !== "string") return;
12414
+ if (predicate(candidate2)) {
12415
+ found = true;
12416
+ return;
12417
+ }
12418
+ for (const key of Object.keys(candidate2)) {
12419
+ if (key === "parent" || key === "loc" || key === "range") continue;
12420
+ visit(candidate2[key]);
12421
+ }
12422
+ };
12423
+ visit(root);
12424
+ return found;
12425
+ }
12426
+ function readsReceiver2(node) {
12427
+ return subtreeSome2(
12428
+ node,
12429
+ (inner) => inner.type === AST_NODE_TYPES53.ThisExpression || inner.type === AST_NODE_TYPES53.Super || inner.type === AST_NODE_TYPES53.Identifier && inner.name === "arguments"
12430
+ );
12431
+ }
12432
+ function buildsLocalizedText2(node) {
12433
+ return subtreeSome2(node, (inner) => {
12434
+ if (inner.type === AST_NODE_TYPES53.TaggedTemplateExpression) return true;
12435
+ if (inner.type !== AST_NODE_TYPES53.CallExpression) return false;
12436
+ const { callee } = inner;
12437
+ if (callee.type === AST_NODE_TYPES53.Identifier)
12438
+ return I18N_CALLEE_NAMES2.has(callee.name);
12439
+ return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES53.Identifier && I18N_RECEIVER_NAMES2.has(callee.object.name);
12440
+ });
12441
+ }
12442
+ function calleeChainRoot(node) {
12443
+ let current = node;
12444
+ for (; ; ) {
12445
+ if (current.type === AST_NODE_TYPES53.Identifier) return current;
12446
+ if (current.type === AST_NODE_TYPES53.MemberExpression) {
12447
+ current = current.object;
12448
+ continue;
12449
+ }
12450
+ if (current.type === AST_NODE_TYPES53.CallExpression) {
12451
+ current = current.callee;
12452
+ continue;
12453
+ }
12454
+ return null;
12455
+ }
12456
+ }
12457
+ function chainMemberNames(node) {
12458
+ const names = [];
12459
+ let current = node;
12460
+ for (; ; ) {
12461
+ if (current.type === AST_NODE_TYPES53.MemberExpression) {
12462
+ if (current.computed || current.property.type !== AST_NODE_TYPES53.Identifier)
12463
+ return [];
12464
+ names.push(current.property.name);
12465
+ current = current.object;
12466
+ continue;
12467
+ }
12468
+ if (current.type === AST_NODE_TYPES53.CallExpression) {
12469
+ current = current.callee;
12470
+ continue;
12471
+ }
12472
+ break;
12473
+ }
12474
+ names.reverse();
12475
+ return names;
12476
+ }
12477
+ function schemaExpression2(node) {
12478
+ let current = node;
12479
+ for (; ; ) {
12480
+ const parent = current.parent ?? void 0;
12481
+ if (parent?.type === AST_NODE_TYPES53.MemberExpression && parent.object === current && !parent.computed && parent.property.type === AST_NODE_TYPES53.Identifier && parent.parent?.type === AST_NODE_TYPES53.CallExpression && parent.parent.callee === parent) {
12482
+ if (NON_SCHEMA_TERMINALS.has(parent.property.name)) return current;
12483
+ current = parent.parent;
12484
+ continue;
12485
+ }
12486
+ if (parent?.type === AST_NODE_TYPES53.TSAsExpression || parent?.type === AST_NODE_TYPES53.TSNonNullExpression || parent?.type === AST_NODE_TYPES53.TSSatisfiesExpression || parent?.type === AST_NODE_TYPES53.TSTypeAssertion) {
12487
+ current = parent;
12488
+ continue;
12489
+ }
12490
+ return current;
12491
+ }
12492
+ }
12075
12493
  var prefer_module_level_refined_schema_default = createRule({
12076
12494
  name: "prefer-module-level-refined-schema",
12077
12495
  documentation: PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION,
12078
- meta: { type: "suggestion", docs: { description: "Declare closed, refined Zod scalar and array schemas at module scope." }, schema: [], messages: { hoistRefinedSchema: "Move this closed refined Zod schema to module scope and reuse it for parsing." } },
12496
+ meta: {
12497
+ type: "suggestion",
12498
+ docs: {
12499
+ description: "Declare closed Zod scalar, format, and wrapper schemas at module scope."
12500
+ },
12501
+ schema: [],
12502
+ messages: {
12503
+ hoistRefinedSchema: "Move this closed Zod schema to module scope, give it a PascalCase Schema name, and reuse it for parsing."
12504
+ }
12505
+ },
12079
12506
  defaultOptions: [],
12080
12507
  create(context) {
12081
- if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
12082
- const namespaces = /* @__PURE__ */ new Set();
12508
+ if (isTestFile(context.filename) || BENCHMARK_PATH_RE.test(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
12509
+ return {};
12510
+ const zodBindings = /* @__PURE__ */ new Set();
12511
+ function resolvedBinding(identifier) {
12512
+ return ASTUtils15.findVariable(
12513
+ context.sourceCode.getScope(identifier),
12514
+ identifier.name
12515
+ );
12516
+ }
12517
+ function recordZodBinding(identifier) {
12518
+ const binding = resolvedBinding(identifier);
12519
+ if (binding !== null) zodBindings.add(binding);
12520
+ }
12521
+ function factoryName(node, allowed) {
12522
+ if (node.callee.type !== AST_NODE_TYPES53.MemberExpression) return null;
12523
+ const root = calleeChainRoot(node.callee);
12524
+ if (root === null) return null;
12525
+ const binding = resolvedBinding(root);
12526
+ if (binding === null || !zodBindings.has(binding)) return null;
12527
+ const names = chainMemberNames(node.callee);
12528
+ if (names.length === 1 && allowed.has(names[0] ?? ""))
12529
+ return names[0] ?? null;
12530
+ if (names.length === 2 && FACTORY_NAMESPACES.has(names[0] ?? "") && allowed.has(names[1] ?? ""))
12531
+ return names[1] ?? null;
12532
+ return null;
12533
+ }
12534
+ function isSharedEnumDomain(node, factory) {
12535
+ if (factory !== "enum") return false;
12536
+ const [argument] = node.arguments;
12537
+ return argument?.type === AST_NODE_TYPES53.ArrayExpression && argument.elements.length >= 2 && argument.elements.every(
12538
+ (element) => element?.type === AST_NODE_TYPES53.Literal && typeof element.value === "string"
12539
+ );
12540
+ }
12541
+ function isNestedInOwnedFactory(node) {
12542
+ let current = node;
12543
+ while (current.parent != null) {
12544
+ current = current.parent;
12545
+ if (FUNCTION_TYPES9.has(current.type)) return false;
12546
+ if (current.type === AST_NODE_TYPES53.CallExpression && (factoryName(current, FACTORIES) !== null || factoryName(current, COMPOSITE_FACTORIES) !== null))
12547
+ return true;
12548
+ }
12549
+ return false;
12550
+ }
12551
+ function isMemoized(node) {
12552
+ let current = node.parent ?? void 0;
12553
+ while (current !== void 0) {
12554
+ if (current.type === AST_NODE_TYPES53.CallExpression && (current.callee.type === AST_NODE_TYPES53.Identifier && MEMO_CALLEES2.has(current.callee.name) || current.callee.type === AST_NODE_TYPES53.MemberExpression && !current.callee.computed && current.callee.property.type === AST_NODE_TYPES53.Identifier && MEMO_CALLEES2.has(current.callee.property.name)))
12555
+ return true;
12556
+ current = current.parent ?? void 0;
12557
+ }
12558
+ return false;
12559
+ }
12560
+ function closesOverNothing(node, enclosing) {
12561
+ const references = [];
12562
+ collectReferences2(context.sourceCode.getScope(node), references);
12563
+ const [start, end] = node.range;
12564
+ const [functionStart, functionEnd] = enclosing.range;
12565
+ for (const reference of references) {
12566
+ const [referenceStart] = reference.identifier.range;
12567
+ if (referenceStart < start || referenceStart >= end) continue;
12568
+ const resolved = reference.resolved;
12569
+ if (resolved === null) continue;
12570
+ for (const definition of resolved.defs) {
12571
+ if (definition.type === "ImportBinding") {
12572
+ const parent = reference.identifier.parent;
12573
+ if (parent?.type === AST_NODE_TYPES53.CallExpression && parent.callee === reference.identifier && !zodBindings.has(resolved))
12574
+ return false;
12575
+ continue;
12576
+ }
12577
+ if (definition.node.type === AST_NODE_TYPES53.VariableDeclarator && definition.node.parent.type === AST_NODE_TYPES53.VariableDeclaration && definition.node.parent.kind !== "const")
12578
+ return false;
12579
+ const [definitionStart, definitionEnd] = definition.node.range;
12580
+ if (definitionStart >= start && definitionEnd <= end) continue;
12581
+ if (definitionStart >= functionStart && definitionEnd <= functionEnd)
12582
+ return false;
12583
+ }
12584
+ }
12585
+ return true;
12586
+ }
12083
12587
  return {
12084
12588
  ImportDeclaration(node) {
12085
12589
  if (!isZodModule(node.source.value)) return;
12086
- for (const specifier of node.specifiers) if (specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES53.Identifier && specifier.imported.name === "z") namespaces.add(specifier.local.name);
12590
+ for (const specifier of node.specifiers) {
12591
+ if (specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES53.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z"))
12592
+ recordZodBinding(specifier.local);
12593
+ }
12087
12594
  },
12088
12595
  CallExpression(node) {
12089
- const enclosing = enclosingFunction4(node);
12090
- if (enclosing === void 0 || node.parent?.type !== AST_NODE_TYPES53.MemberExpression || node.parent.object !== node) return;
12091
- if (node.callee.type !== AST_NODE_TYPES53.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES53.Identifier || !namespaces.has(node.callee.object.name) || node.callee.property.type !== AST_NODE_TYPES53.Identifier || !FACTORIES.has(node.callee.property.name)) return;
12092
- let current = node;
12093
- let refinements = 0;
12094
- while (current.parent?.type === AST_NODE_TYPES53.MemberExpression && current.parent.object === current && !current.parent.computed && current.parent.property.type === AST_NODE_TYPES53.Identifier && current.parent.parent?.type === AST_NODE_TYPES53.CallExpression && current.parent.parent.callee === current.parent) {
12095
- const call = current.parent.parent;
12096
- if (["parse", "parseAsync", "safeParse", "safeParseAsync"].includes(current.parent.property.name)) break;
12097
- if (REFINEMENTS.has(current.parent.property.name)) refinements += 1;
12098
- current = call;
12099
- }
12100
- if (refinements < 2) return;
12101
- const references = [];
12102
- collectReferences2(context.sourceCode.getScope(current), references);
12103
- const [start, end] = current.range;
12104
- const [functionStart, functionEnd] = enclosing.range;
12105
- for (const reference of references) {
12106
- const [referenceStart] = reference.identifier.range;
12107
- if (referenceStart < start || referenceStart >= end || reference.resolved === null) continue;
12108
- for (const definition of reference.resolved.defs) {
12109
- if (definition.type === "ImportBinding") continue;
12110
- if (definition.node.type === AST_NODE_TYPES53.VariableDeclarator && definition.node.parent.type === AST_NODE_TYPES53.VariableDeclaration && definition.node.parent.kind !== "const") return;
12111
- const [definitionStart, definitionEnd] = definition.node.range;
12112
- if (definitionStart >= start && definitionEnd <= end) continue;
12113
- if (definitionStart >= functionStart && definitionEnd <= functionEnd) return;
12114
- }
12115
- }
12596
+ const factory = factoryName(node, FACTORIES);
12597
+ if (factory === null || isSharedEnumDomain(node, factory) || isNestedInOwnedFactory(node) || isMemoized(node))
12598
+ return;
12599
+ const enclosing = outermostEnclosingFunction2(node);
12600
+ if (enclosing === void 0) return;
12601
+ const expression = schemaExpression2(node);
12602
+ if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
12603
+ return;
12116
12604
  context.report({ node, messageId: "hoistRefinedSchema" });
12117
12605
  }
12118
12606
  };
@@ -12120,26 +12608,30 @@ var prefer_module_level_refined_schema_default = createRule({
12120
12608
  });
12121
12609
 
12122
12610
  // src/rules/prefer-multi-value-zod-literal.ts
12123
- import { AST_NODE_TYPES as AST_NODE_TYPES54 } from "@typescript-eslint/utils";
12611
+ import {
12612
+ AST_NODE_TYPES as AST_NODE_TYPES54,
12613
+ ASTUtils as ASTUtils16
12614
+ } from "@typescript-eslint/utils";
12124
12615
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12125
12616
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
12126
12617
  rationale: "One multi-value literal expresses the same closed value domain without repeated schema wrappers.",
12127
12618
  remediation: "Replace the union with z.literal([value1, value2, ...]).",
12128
12619
  category: "maintainability",
12620
+ autofix: "safe",
12129
12621
  limitations: [
12130
- "Bare `zod` imports are analyzed only when the rule option explicitly declares `zodMajorVersion: 4`; `zod/v4` imports are self-declaring."
12622
+ "Bare zod imports are analyzed only when the rule option explicitly declares zodMajorVersion: 4; explicit zod/v4 entrypoints are self-declaring.",
12623
+ "All-string domains are left to zod/prefer-enum-over-literal-union.",
12624
+ "A finding containing comments is not autofixed because moving its trivia is ambiguous."
12131
12625
  ],
12132
12626
  examples: [
12133
12627
  {
12134
12628
  id: "multi-value",
12135
12629
  title: "Use one multi-value literal",
12136
12630
  outcome: "no-match",
12137
- files: [
12138
- {
12139
- path: "src/schema.ts",
12140
- source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
12141
- }
12142
- ],
12631
+ files: [{
12632
+ path: "src/schema.ts",
12633
+ source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
12634
+ }],
12143
12635
  focusPath: "src/schema.ts",
12144
12636
  expectedCount: 0,
12145
12637
  public: true
@@ -12148,80 +12640,116 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12148
12640
  id: "literal-union",
12149
12641
  title: "Avoid repeated literal wrappers",
12150
12642
  outcome: "match",
12151
- files: [
12152
- {
12153
- path: "src/schema.ts",
12154
- source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
12155
- }
12156
- ],
12643
+ files: [{
12644
+ path: "src/schema.ts",
12645
+ source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
12646
+ }],
12647
+ fixedFiles: [{
12648
+ path: "src/schema.ts",
12649
+ source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
12650
+ }],
12157
12651
  focusPath: "src/schema.ts",
12158
12652
  expectedCount: 1,
12159
12653
  public: true
12160
12654
  }
12161
12655
  ]
12162
12656
  };
12163
- function memberCall(node, object, method) {
12164
- return node.callee.type === AST_NODE_TYPES54.MemberExpression && !node.callee.computed && node.callee.object.type === AST_NODE_TYPES54.Identifier && node.callee.object.name === object && node.callee.property.type === AST_NODE_TYPES54.Identifier && node.callee.property.name === method;
12657
+ function isStaticPrimitive(node, context) {
12658
+ if (node.type === AST_NODE_TYPES54.Literal) {
12659
+ return node.value === null || ["bigint", "boolean", "number", "string"].includes(typeof node.value);
12660
+ }
12661
+ if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
12662
+ return true;
12663
+ if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
12664
+ const binding = ASTUtils16.findVariable(
12665
+ context.sourceCode.getScope(node),
12666
+ node.name
12667
+ );
12668
+ return binding === null || binding.defs.length === 0;
12669
+ }
12670
+ return node.type === AST_NODE_TYPES54.UnaryExpression && node.operator === "-" && node.argument.type === AST_NODE_TYPES54.Literal && ["bigint", "number"].includes(typeof node.argument.value);
12671
+ }
12672
+ function isStaticString(node) {
12673
+ return node.type === AST_NODE_TYPES54.Literal && typeof node.value === "string" || node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0;
12165
12674
  }
12166
12675
  var prefer_multi_value_zod_literal_default = createRule({
12167
12676
  name: "prefer-multi-value-zod-literal",
12168
12677
  documentation: PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION,
12169
12678
  meta: {
12170
12679
  type: "suggestion",
12680
+ fixable: "code",
12171
12681
  docs: {
12172
12682
  description: "Use the Zod 4 multi-value literal API instead of a union of literal schemas."
12173
12683
  },
12174
- schema: [
12175
- {
12176
- type: "object",
12177
- additionalProperties: false,
12178
- properties: {
12179
- zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
12180
- }
12684
+ schema: [{
12685
+ type: "object",
12686
+ additionalProperties: false,
12687
+ properties: {
12688
+ zodMajorVersion: { type: "integer", minimum: 4, maximum: 4 }
12181
12689
  }
12182
- ],
12690
+ }],
12183
12691
  messages: {
12184
- useMultiValueLiteral: "Replace this literal-schema union with `{{zod}}.literal([\u2026])`."
12692
+ useMultiValueLiteral: "Replace this literal-schema union with {{zod}}.literal([\u2026])."
12185
12693
  }
12186
12694
  },
12187
12695
  defaultOptions: [{}],
12188
12696
  create(context, [options]) {
12189
12697
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
12190
12698
  return {};
12191
- const namespaces = /* @__PURE__ */ new Set();
12192
- const zod4Namespaces = /* @__PURE__ */ new Set();
12699
+ const zodBindings = /* @__PURE__ */ new Set();
12700
+ const zod4Bindings = /* @__PURE__ */ new Set();
12701
+ function resolvedBinding(identifier) {
12702
+ return ASTUtils16.findVariable(
12703
+ context.sourceCode.getScope(identifier),
12704
+ identifier.name
12705
+ );
12706
+ }
12707
+ function directMemberCall(node, binding, method) {
12708
+ if (node.callee.type !== AST_NODE_TYPES54.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES54.Identifier || node.callee.property.type !== AST_NODE_TYPES54.Identifier || node.callee.property.name !== method)
12709
+ return false;
12710
+ return resolvedBinding(node.callee.object) === binding;
12711
+ }
12193
12712
  return {
12194
12713
  ImportDeclaration(node) {
12195
12714
  if (!isZodModule(node.source.value)) return;
12715
+ const isExplicitV4 = /^zod\/v4(?:$|[-/])/.test(node.source.value);
12196
12716
  for (const specifier of node.specifiers) {
12197
- if (specifier.type === AST_NODE_TYPES54.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES54.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES54.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES54.Identifier && specifier.imported.name === "z") {
12198
- namespaces.add(specifier.local.name);
12199
- if (node.source.value === "zod/v4" || node.source.value.startsWith("zod/v4/"))
12200
- zod4Namespaces.add(specifier.local.name);
12717
+ if (specifier.type === AST_NODE_TYPES54.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES54.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES54.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES54.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
12718
+ const binding = resolvedBinding(specifier.local);
12719
+ if (binding === null) continue;
12720
+ zodBindings.add(binding);
12721
+ if (isExplicitV4) zod4Bindings.add(binding);
12201
12722
  }
12202
12723
  }
12203
12724
  },
12204
12725
  CallExpression(node) {
12205
- const namespace = [...namespaces].find(
12206
- (name) => memberCall(node, name, "union")
12207
- );
12208
- if (namespace === void 0 || options?.zodMajorVersion !== 4 && !zod4Namespaces.has(namespace) || node.arguments.length !== 1)
12726
+ if (node.callee.type !== AST_NODE_TYPES54.MemberExpression || node.callee.object.type !== AST_NODE_TYPES54.Identifier)
12727
+ return;
12728
+ const binding = resolvedBinding(node.callee.object);
12729
+ if (binding === null || !zodBindings.has(binding) || !directMemberCall(node, binding, "union") || options?.zodMajorVersion !== 4 && !zod4Bindings.has(binding) || node.arguments.length !== 1)
12209
12730
  return;
12210
12731
  const [argument] = node.arguments;
12211
- if (argument?.type !== AST_NODE_TYPES54.ArrayExpression || argument.elements.length < 3 || argument.elements.some((element) => {
12212
- if (element === null || element.type !== AST_NODE_TYPES54.CallExpression || !memberCall(element, namespace, "literal") || element.arguments.length !== 1)
12213
- return true;
12214
- const [value] = element.arguments;
12215
- return value === void 0 || value.type === AST_NODE_TYPES54.SpreadElement || ![
12216
- AST_NODE_TYPES54.Literal,
12217
- AST_NODE_TYPES54.TemplateLiteral
12218
- ].includes(value.type);
12219
- }))
12732
+ if (argument?.type !== AST_NODE_TYPES54.ArrayExpression || argument.elements.length < 2)
12220
12733
  return;
12734
+ const values = [];
12735
+ for (const element of argument.elements) {
12736
+ if (element === null || element.type !== AST_NODE_TYPES54.CallExpression || !directMemberCall(element, binding, "literal") || element.arguments.length !== 1)
12737
+ return;
12738
+ const [value] = element.arguments;
12739
+ if (value === void 0 || !isStaticPrimitive(value, context)) return;
12740
+ values.push(value);
12741
+ }
12742
+ if (values.every(isStaticString)) return;
12743
+ const namespace = node.callee.object.name;
12744
+ const hasComments = context.sourceCode.getCommentsInside(node).length > 0;
12221
12745
  context.report({
12222
12746
  node,
12223
12747
  messageId: "useMultiValueLiteral",
12224
- data: { zod: namespace }
12748
+ data: { zod: namespace },
12749
+ fix: hasComments ? null : (fixer) => fixer.replaceText(
12750
+ node,
12751
+ `${namespace}.literal([${values.map((value) => context.sourceCode.getText(value)).join(", ")}])`
12752
+ )
12225
12753
  });
12226
12754
  }
12227
12755
  };
@@ -12338,7 +12866,7 @@ var prefer_named_complex_return_type_default = createRule({
12338
12866
  });
12339
12867
 
12340
12868
  // src/rules/prefer-native-random-uuid.ts
12341
- import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
12869
+ import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
12342
12870
  var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
12343
12871
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
12344
12872
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -12373,11 +12901,11 @@ var prefer_native_random_uuid_default = createRule({
12373
12901
  create(context) {
12374
12902
  const directBindings = /* @__PURE__ */ new Set();
12375
12903
  const namespaceBindings = /* @__PURE__ */ new Set();
12376
- function resolve(identifier) {
12377
- return ASTUtils15.findVariable(context.sourceCode.getScope(identifier), identifier.name);
12904
+ function resolve2(identifier) {
12905
+ return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
12378
12906
  }
12379
12907
  function record(identifier, destination) {
12380
- const variable = resolve(identifier);
12908
+ const variable = resolve2(identifier);
12381
12909
  if (variable !== null) destination.add(variable);
12382
12910
  }
12383
12911
  function report2(node) {
@@ -12405,7 +12933,7 @@ var prefer_native_random_uuid_default = createRule({
12405
12933
  },
12406
12934
  VariableDeclarator(node) {
12407
12935
  if (node.parent.kind !== "const" || !requireUuid(node.init)) return;
12408
- if (node.init?.type !== AST_NODE_TYPES57.CallExpression || node.init.callee.type !== AST_NODE_TYPES57.Identifier || (resolve(node.init.callee)?.defs.length ?? 0) > 0) {
12936
+ if (node.init?.type !== AST_NODE_TYPES57.CallExpression || node.init.callee.type !== AST_NODE_TYPES57.Identifier || (resolve2(node.init.callee)?.defs.length ?? 0) > 0) {
12409
12937
  return;
12410
12938
  }
12411
12939
  if (node.id.type === AST_NODE_TYPES57.Identifier) {
@@ -12422,14 +12950,14 @@ var prefer_native_random_uuid_default = createRule({
12422
12950
  "CallExpression:exit"(node) {
12423
12951
  if (node.arguments.length !== 0) return;
12424
12952
  if (node.callee.type === AST_NODE_TYPES57.Identifier) {
12425
- const variable2 = resolve(node.callee);
12953
+ const variable2 = resolve2(node.callee);
12426
12954
  if (variable2 !== null && directBindings.has(variable2)) report2(node);
12427
12955
  return;
12428
12956
  }
12429
12957
  if (node.callee.type !== AST_NODE_TYPES57.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES57.Identifier || node.callee.property.type !== AST_NODE_TYPES57.Identifier || node.callee.property.name !== "v4") {
12430
12958
  return;
12431
12959
  }
12432
- const variable = resolve(node.callee.object);
12960
+ const variable = resolve2(node.callee.object);
12433
12961
  if (variable !== null && namespaceBindings.has(variable)) report2(node);
12434
12962
  }
12435
12963
  };
@@ -12437,7 +12965,7 @@ var prefer_native_random_uuid_default = createRule({
12437
12965
  });
12438
12966
 
12439
12967
  // src/rules/prefer-node-crypto-hash.ts
12440
- import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
12968
+ import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
12441
12969
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
12442
12970
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
12443
12971
  rationale: "A createHash-update-digest chain allocates mutable streaming state for a single in-memory value; Node's built-in hash function expresses the one-shot operation directly and can use its optimized fast path.",
@@ -12459,22 +12987,22 @@ function memberName4(node) {
12459
12987
  }
12460
12988
  return null;
12461
12989
  }
12462
- function isCryptoLoader(node, resolve) {
12990
+ function isCryptoLoader(node, resolve2) {
12463
12991
  if (node.type !== AST_NODE_TYPES58.CallExpression || node.arguments.length !== 1) return false;
12464
12992
  const [argument] = node.arguments;
12465
12993
  if (argument === void 0 || argument.type === AST_NODE_TYPES58.SpreadElement || !isCryptoSpecifier(argument)) {
12466
12994
  return false;
12467
12995
  }
12468
12996
  if (node.callee.type === AST_NODE_TYPES58.Identifier) {
12469
- return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve);
12997
+ return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
12470
12998
  }
12471
- return node.callee.type === AST_NODE_TYPES58.MemberExpression && node.callee.object.type === AST_NODE_TYPES58.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve) && memberName4(node.callee) === "getBuiltinModule";
12999
+ return node.callee.type === AST_NODE_TYPES58.MemberExpression && node.callee.object.type === AST_NODE_TYPES58.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName4(node.callee) === "getBuiltinModule";
12472
13000
  }
12473
13001
  function isCryptoSpecifier(node) {
12474
13002
  return node.type === AST_NODE_TYPES58.Literal && (node.value === "crypto" || node.value === "node:crypto");
12475
13003
  }
12476
- function isUnshadowedBuiltinIdentifier(identifier, resolve) {
12477
- const variable = resolve(identifier);
13004
+ function isUnshadowedBuiltinIdentifier(identifier, resolve2) {
13005
+ const variable = resolve2(identifier);
12478
13006
  return variable === null || variable.defs.length === 0;
12479
13007
  }
12480
13008
  function propertyName4(node) {
@@ -12492,14 +13020,14 @@ var prefer_node_crypto_hash_default = createRule({
12492
13020
  create(context) {
12493
13021
  const directBindings = /* @__PURE__ */ new Set();
12494
13022
  const namespaceBindings = /* @__PURE__ */ new Set();
12495
- function resolve(identifier) {
12496
- return ASTUtils16.findVariable(
13023
+ function resolve2(identifier) {
13024
+ return ASTUtils18.findVariable(
12497
13025
  context.sourceCode.getScope(identifier),
12498
13026
  identifier.name
12499
13027
  );
12500
13028
  }
12501
13029
  function record(identifier, destination) {
12502
- const variable = resolve(identifier);
13030
+ const variable = resolve2(identifier);
12503
13031
  if (variable !== null) destination.add(variable);
12504
13032
  }
12505
13033
  return {
@@ -12514,7 +13042,7 @@ var prefer_node_crypto_hash_default = createRule({
12514
13042
  }
12515
13043
  },
12516
13044
  VariableDeclarator(node) {
12517
- if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init, resolve)) {
13045
+ if (node.parent.kind !== "const" || node.init === null || !isCryptoLoader(node.init, resolve2)) {
12518
13046
  return;
12519
13047
  }
12520
13048
  if (node.id.type === AST_NODE_TYPES58.Identifier) {
@@ -12538,7 +13066,7 @@ var prefer_node_crypto_hash_default = createRule({
12538
13066
  create,
12539
13067
  directBindings,
12540
13068
  namespaceBindings,
12541
- resolve
13069
+ resolve2
12542
13070
  ))
12543
13071
  return;
12544
13072
  context.report({ node, messageId: "preferNodeCryptoHash" });
@@ -12552,17 +13080,17 @@ function importedName5(node) {
12552
13080
  function isMemberCall(node, name) {
12553
13081
  return node.callee.type === AST_NODE_TYPES58.MemberExpression && memberName4(node.callee) === name;
12554
13082
  }
12555
- function isCreateHashCall(node, directBindings, namespaceBindings, resolve) {
13083
+ function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
12556
13084
  if (node.callee.type === AST_NODE_TYPES58.Identifier) {
12557
- const variable2 = resolve(node.callee);
13085
+ const variable2 = resolve2(node.callee);
12558
13086
  return variable2 !== null && directBindings.has(variable2);
12559
13087
  }
12560
13088
  if (node.callee.type !== AST_NODE_TYPES58.MemberExpression || memberName4(node.callee) !== "createHash") {
12561
13089
  return false;
12562
13090
  }
12563
- if (isCryptoLoader(node.callee.object, resolve)) return true;
13091
+ if (isCryptoLoader(node.callee.object, resolve2)) return true;
12564
13092
  if (node.callee.object.type !== AST_NODE_TYPES58.Identifier) return false;
12565
- const variable = resolve(node.callee.object);
13093
+ const variable = resolve2(node.callee.object);
12566
13094
  return variable !== null && namespaceBindings.has(variable);
12567
13095
  }
12568
13096
 
@@ -12680,7 +13208,7 @@ var prefer_node_fs_promises_default = createRule({
12680
13208
  });
12681
13209
 
12682
13210
  // src/rules/prefer-non-nullable-collection.ts
12683
- import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
13211
+ import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils19 } from "@typescript-eslint/utils";
12684
13212
  var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
12685
13213
  summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
12686
13214
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
@@ -12810,14 +13338,14 @@ function directlyCoalesced(node) {
12810
13338
  return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
12811
13339
  }
12812
13340
  function identifierIsOnlyCoalesced(context, binding, fn) {
12813
- const variable = ASTUtils17.findVariable(context.sourceCode.getScope(binding), binding.name);
13341
+ const variable = ASTUtils19.findVariable(context.sourceCode.getScope(binding), binding.name);
12814
13342
  if (variable === null || variable.references.length === 0) return false;
12815
13343
  return variable.references.every(
12816
13344
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
12817
13345
  );
12818
13346
  }
12819
13347
  function memberIsOnlyCoalesced(context, object, property, fn) {
12820
- const variable = ASTUtils17.findVariable(context.sourceCode.getScope(object), object.name);
13348
+ const variable = ASTUtils19.findVariable(context.sourceCode.getScope(object), object.name);
12821
13349
  if (variable === null) return false;
12822
13350
  const accesses = variable.references.flatMap((reference) => {
12823
13351
  if (!belongsToFunction(reference.identifier, fn)) return [null];
@@ -12927,7 +13455,7 @@ var prefer_non_nullable_collection_default = createRule({
12927
13455
  // src/rules/prefer-nullish-filter-predicate.ts
12928
13456
  import {
12929
13457
  AST_NODE_TYPES as AST_NODE_TYPES61,
12930
- ASTUtils as ASTUtils18,
13458
+ ASTUtils as ASTUtils20,
12931
13459
  ESLintUtils as ESLintUtils5
12932
13460
  } from "@typescript-eslint/utils";
12933
13461
  import ts3 from "typescript";
@@ -12973,7 +13501,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
12973
13501
  ]
12974
13502
  };
12975
13503
  function isUnshadowedBoolean(node, context) {
12976
- const variable = ASTUtils18.findVariable(context.sourceCode.getScope(node), node.name);
13504
+ const variable = ASTUtils20.findVariable(context.sourceCode.getScope(node), node.name);
12977
13505
  return variable === null || variable.defs.length === 0;
12978
13506
  }
12979
13507
  function isBuiltinArrayFilter(node, services) {
@@ -13032,7 +13560,7 @@ function isProvablyTruthy(type, checker) {
13032
13560
  }
13033
13561
  function availableParameterName(node, context) {
13034
13562
  for (const name of ["value", "item", "element", "candidate"]) {
13035
- if (ASTUtils18.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13563
+ if (ASTUtils20.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13036
13564
  }
13037
13565
  return null;
13038
13566
  }
@@ -13086,7 +13614,7 @@ var prefer_nullish_filter_predicate_default = createRule({
13086
13614
 
13087
13615
  // src/rules/prefer-await-in-async-return.ts
13088
13616
  import {
13089
- ASTUtils as ASTUtils19,
13617
+ ASTUtils as ASTUtils21,
13090
13618
  ESLintUtils as ESLintUtils6,
13091
13619
  AST_NODE_TYPES as AST_NODE_TYPES62
13092
13620
  } from "@typescript-eslint/utils";
@@ -13200,13 +13728,13 @@ var prefer_await_in_async_return_default = createRule({
13200
13728
  if (services === null) return {};
13201
13729
  const frameworkLoaders = /* @__PURE__ */ new Set();
13202
13730
  const rememberFrameworkLoader = (identifier) => {
13203
- const variable = ASTUtils19.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13731
+ const variable = ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13204
13732
  if (variable !== null) frameworkLoaders.add(variable);
13205
13733
  };
13206
13734
  const isFrameworkLoaderCallback = (owner) => {
13207
13735
  const parent = owner.parent;
13208
13736
  if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
13209
- const variable = ASTUtils19.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
13737
+ const variable = ASTUtils21.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
13210
13738
  return variable !== null && frameworkLoaders.has(variable);
13211
13739
  };
13212
13740
  return {
@@ -13888,11 +14416,34 @@ var prefer_switch_for_repeated_equality_default = createRule({
13888
14416
 
13889
14417
  // src/rules/prefer-semantic-colors.ts
13890
14418
  import { AST_NODE_TYPES as AST_NODE_TYPES66 } from "@typescript-eslint/utils";
13891
- import { existsSync, readdirSync, readFileSync } from "fs";
13892
- import { dirname, join, parse } from "path";
14419
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
14420
+ import { dirname as dirname2, join as join2, parse } from "path";
13893
14421
 
13894
14422
  // src/rules/_tailwind.ts
13895
- var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^!/, "");
14423
+ var tailwindVariantPrefix = (token) => {
14424
+ let bracketDepth = 0;
14425
+ let parenthesisDepth = 0;
14426
+ let escaped = false;
14427
+ let end = 0;
14428
+ for (let index = 0; index < token.length; index += 1) {
14429
+ const character = token[index];
14430
+ if (escaped) {
14431
+ escaped = false;
14432
+ continue;
14433
+ }
14434
+ if (character === "\\") {
14435
+ escaped = true;
14436
+ continue;
14437
+ }
14438
+ if (character === "[") bracketDepth += 1;
14439
+ else if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
14440
+ else if (character === "(") parenthesisDepth += 1;
14441
+ else if (character === ")") parenthesisDepth = Math.max(0, parenthesisDepth - 1);
14442
+ else if (character === ":" && bracketDepth === 0 && parenthesisDepth === 0) end = index + 1;
14443
+ }
14444
+ return token.slice(0, end);
14445
+ };
14446
+ var tailwindBase = (token) => token.slice(tailwindVariantPrefix(token).length).replace(/^!/, "");
13896
14447
  var classTokens = (value) => value.split(/\s+/).filter(Boolean);
13897
14448
 
13898
14449
  // src/rules/prefer-semantic-colors.ts
@@ -13901,7 +14452,10 @@ var PREFER_SEMANTIC_COLORS_DOCUMENTATION = {
13901
14452
  rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
13902
14453
  remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
13903
14454
  category: "style",
13904
- limitations: ["Email, PDF, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions."],
14455
+ limitations: [
14456
+ "Email, PDF, video-rendering, print-only, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions.",
14457
+ "Opaque-foreground checks are opt-in and require both a same-variant semantic background class and its package-local declared foreground token."
14458
+ ],
13905
14459
  examples: [
13906
14460
  { id: "semantic-text-color", title: "Use a semantic color token", outcome: "no-match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-destructive" />;' }], focusPath: "src/notice.tsx", expectedCount: 0, public: true },
13907
14461
  { id: "raw-text-color", title: "Do not use a raw palette color", outcome: "match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-red-500" />;' }], focusPath: "src/notice.tsx", expectedCount: 1, public: true }
@@ -14029,7 +14583,13 @@ var SVG_SHAPE_PRIMITIVES = /* @__PURE__ */ new Set([
14029
14583
  "tspan",
14030
14584
  "use"
14031
14585
  ]);
14032
- var EMAIL_OR_PDF_MODULE_RE = /^@react-(?:email|pdf)\//;
14586
+ var EXTERNAL_RENDERER_MODULE_RE = /^(?:@react-(?:email|pdf)\/|remotion$|@remotion\/)/;
14587
+ var OPAQUE_FOREGROUND_RE = /^text-(?:white|black)(?:\/100)?$/;
14588
+ var SEMANTIC_BACKGROUND_RE = /^bg-([a-z][a-z0-9-]*)$/;
14589
+ var CSS_COMMENT_RE = /\/\*[\s\S]*?\*\//gu;
14590
+ var DECLARED_FOREGROUND_RE = /--(?:color-)?([a-z][a-z0-9-]*)-foreground\s*:/giu;
14591
+ var MAX_TOKEN_STYLESHEET_BYTES = 1048576;
14592
+ var SEMANTIC_DECLARATIONS_CACHE = /* @__PURE__ */ new Map();
14033
14593
  function isSvgLikeElementName(name) {
14034
14594
  return name === "svg" || SVG_DEFS_CONTAINERS.has(name) || /svg$/i.test(name);
14035
14595
  }
@@ -14044,12 +14604,12 @@ var isInsideIconFactoryPath = (node) => {
14044
14604
  return false;
14045
14605
  };
14046
14606
  var hasMarkerAt = (dir) => {
14047
- if (PRESENCE_MARKERS.some((rel) => existsSync(join(dir, rel)))) return true;
14607
+ if (PRESENCE_MARKERS.some((rel) => existsSync2(join2(dir, rel)))) return true;
14048
14608
  for (const rel of CSS_DETECTION_FILES) {
14049
- const candidate2 = join(dir, rel);
14050
- if (!existsSync(candidate2)) continue;
14609
+ const candidate2 = join2(dir, rel);
14610
+ if (!existsSync2(candidate2)) continue;
14051
14611
  try {
14052
- const css = readFileSync(candidate2, "utf8");
14612
+ const css = readFileSync2(candidate2, "utf8");
14053
14613
  if (SEMANTIC_TOKEN_RE.test(css) || THEME_BLOCK_RE.test(css)) return true;
14054
14614
  } catch {
14055
14615
  }
@@ -14058,10 +14618,10 @@ var hasMarkerAt = (dir) => {
14058
14618
  };
14059
14619
  var readWorkspaceGlobs = (dir) => {
14060
14620
  const globs = [];
14061
- const packageJson = join(dir, "package.json");
14062
- if (existsSync(packageJson)) {
14621
+ const packageJson = join2(dir, "package.json");
14622
+ if (existsSync2(packageJson)) {
14063
14623
  try {
14064
- const parsed = JSON.parse(readFileSync(packageJson, "utf8"));
14624
+ const parsed = JSON.parse(readFileSync2(packageJson, "utf8"));
14065
14625
  const declared = typeof parsed === "object" && parsed !== null && "workspaces" in parsed ? parsed.workspaces : void 0;
14066
14626
  const list = Array.isArray(declared) ? declared : typeof declared === "object" && declared !== null && Array.isArray(declared.packages) ? declared.packages : [];
14067
14627
  for (const entry of list) if (typeof entry === "string") globs.push(entry);
@@ -14069,10 +14629,10 @@ var readWorkspaceGlobs = (dir) => {
14069
14629
  }
14070
14630
  }
14071
14631
  for (const name of ["pnpm-workspace.yaml", "pnpm-workspace.yml"]) {
14072
- const yaml = join(dir, name);
14073
- if (!existsSync(yaml)) continue;
14632
+ const yaml = join2(dir, name);
14633
+ if (!existsSync2(yaml)) continue;
14074
14634
  try {
14075
- for (const line of readFileSync(yaml, "utf8").split("\n")) {
14635
+ for (const line of readFileSync2(yaml, "utf8").split("\n")) {
14076
14636
  const match = /^\s*-\s*["']?([^"'#\s]+)["']?\s*$/u.exec(line);
14077
14637
  if (match?.[1] !== void 0) globs.push(match[1]);
14078
14638
  }
@@ -14082,7 +14642,7 @@ var readWorkspaceGlobs = (dir) => {
14082
14642
  return globs;
14083
14643
  };
14084
14644
  var hasSemanticTokenSystem = (filename) => {
14085
- const dir = dirname(filename);
14645
+ const dir = dirname2(filename);
14086
14646
  if (hasMarkerAtOrAbove(dir)) return true;
14087
14647
  const root = findWorkspaceRoot(dir);
14088
14648
  return root !== null && workspaceHasMarker(root);
@@ -14103,7 +14663,7 @@ var hasMarkerAtOrAbove = (startDir) => {
14103
14663
  answer = true;
14104
14664
  break;
14105
14665
  }
14106
- const parent = dirname(dir);
14666
+ const parent = dirname2(dir);
14107
14667
  if (dir === root || parent === dir) {
14108
14668
  answer = false;
14109
14669
  break;
@@ -14125,11 +14685,11 @@ var findWorkspaceRoot = (startDir) => {
14125
14685
  break;
14126
14686
  }
14127
14687
  visited.push(dir);
14128
- if (WORKSPACE_ROOT_FILES.some((name) => existsSync(join(dir, name))) || readWorkspaceGlobs(dir).length > 0) {
14688
+ if (WORKSPACE_ROOT_FILES.some((name) => existsSync2(join2(dir, name))) || readWorkspaceGlobs(dir).length > 0) {
14129
14689
  answer = dir;
14130
14690
  break;
14131
14691
  }
14132
- const parent = dirname(dir);
14692
+ const parent = dirname2(dir);
14133
14693
  if (dir === root || parent === dir) {
14134
14694
  answer = null;
14135
14695
  break;
@@ -14163,23 +14723,71 @@ var workspaceHasMarker = (root) => {
14163
14723
  };
14164
14724
  var expandWorkspaceGlob = (root, glob) => {
14165
14725
  const star = glob.indexOf("*");
14166
- if (star === -1) return [join(root, glob)];
14726
+ if (star === -1) return [join2(root, glob)];
14167
14727
  const prefix = glob.slice(0, star).replace(/\/$/u, "");
14168
- const parent = prefix === "" ? root : join(root, prefix);
14169
- if (!existsSync(parent)) return [];
14170
- return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => join(parent, entry.name));
14728
+ const parent = prefix === "" ? root : join2(root, prefix);
14729
+ if (!existsSync2(parent) || !lstatSync2(parent).isDirectory()) return [];
14730
+ return readdirSync(parent, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join2(parent, entry.name));
14171
14731
  };
14172
14732
  var propName = (key) => {
14173
14733
  if (key.type === AST_NODE_TYPES66.Identifier) return key.name;
14174
14734
  if (key.type === AST_NODE_TYPES66.Literal && typeof key.value === "string") return key.value;
14175
14735
  return null;
14176
14736
  };
14177
- var staticallyImportsEmailOrPdfRenderer = (program) => program.body.some((statement) => {
14737
+ var staticallyImportsExternalRenderer = (program) => program.body.some((statement) => {
14178
14738
  if (statement.type !== AST_NODE_TYPES66.ImportDeclaration && statement.type !== AST_NODE_TYPES66.ExportNamedDeclaration && statement.type !== AST_NODE_TYPES66.ExportAllDeclaration) {
14179
14739
  return false;
14180
14740
  }
14181
- return statement.source !== null && typeof statement.source.value === "string" && EMAIL_OR_PDF_MODULE_RE.test(statement.source.value);
14741
+ return statement.source !== null && typeof statement.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(statement.source.value);
14182
14742
  });
14743
+ var semanticForegroundRoles = (filename) => {
14744
+ const packageRoot = nearestPackageRoot(dirname2(filename));
14745
+ if (packageRoot === null) return /* @__PURE__ */ new Set();
14746
+ const candidates = CSS_DETECTION_FILES.map((relative2) => join2(packageRoot, relative2));
14747
+ const fingerprint = candidates.map(fileFingerprint2).join("|");
14748
+ const cached = SEMANTIC_DECLARATIONS_CACHE.get(packageRoot);
14749
+ if (cached?.fingerprint === fingerprint) return cached.value;
14750
+ const foregroundRoles = /* @__PURE__ */ new Set();
14751
+ for (const candidate2 of candidates) {
14752
+ const css = readTokenStylesheet(candidate2);
14753
+ if (css === null) continue;
14754
+ const declarations = css.replace(CSS_COMMENT_RE, "");
14755
+ for (const match of declarations.matchAll(DECLARED_FOREGROUND_RE)) {
14756
+ if (match[1] !== void 0) foregroundRoles.add(match[1].toLowerCase());
14757
+ }
14758
+ }
14759
+ SEMANTIC_DECLARATIONS_CACHE.set(packageRoot, { fingerprint, value: foregroundRoles });
14760
+ return foregroundRoles;
14761
+ };
14762
+ var nearestPackageRoot = (startDir) => {
14763
+ let dir = startDir;
14764
+ const root = parse(dir).root;
14765
+ for (; ; ) {
14766
+ if (existsSync2(join2(dir, "package.json"))) {
14767
+ return dir;
14768
+ }
14769
+ const parent = dirname2(dir);
14770
+ if (dir === root || parent === dir) return null;
14771
+ dir = parent;
14772
+ }
14773
+ };
14774
+ var fileFingerprint2 = (path) => {
14775
+ try {
14776
+ const stat = lstatSync2(path);
14777
+ return stat.isFile() ? `${path}:${stat.size}:${stat.mtimeMs}` : `${path}:excluded`;
14778
+ } catch {
14779
+ return `${path}:missing`;
14780
+ }
14781
+ };
14782
+ var readTokenStylesheet = (path) => {
14783
+ try {
14784
+ const stat = lstatSync2(path);
14785
+ if (!stat.isFile() || stat.size > MAX_TOKEN_STYLESHEET_BYTES) return null;
14786
+ return readFileSync2(path, "utf8");
14787
+ } catch {
14788
+ return null;
14789
+ }
14790
+ };
14183
14791
  var prefer_semantic_colors_default = createRule({
14184
14792
  name: "prefer-semantic-colors",
14185
14793
  documentation: PREFER_SEMANTIC_COLORS_DOCUMENTATION,
@@ -14193,30 +14801,34 @@ var prefer_semantic_colors_default = createRule({
14193
14801
  type: "object",
14194
14802
  additionalProperties: false,
14195
14803
  properties: {
14196
- requireSemanticTokens: { type: "boolean" }
14804
+ requireSemanticTokens: { type: "boolean" },
14805
+ opaqueForegroundPairs: { type: "boolean" }
14197
14806
  }
14198
14807
  }
14199
14808
  ],
14200
14809
  messages: {
14201
14810
  rawPalette: "Raw palette class '{{class}}' \u2014 use a semantic token (e.g. text-foreground, bg-primary, text-destructive, bg-muted).",
14202
14811
  arbitraryColor: "Hardcoded color '{{class}}' \u2014 use a semantic token, or var(--\u2026). For charts/brand add an eslint-disable with a reason.",
14203
- inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason."
14812
+ inlineColor: "Hardcoded color '{{value}}' \u2014 use a semantic token / CSS variable. For charts/standalone pages add an eslint-disable with a reason.",
14813
+ opaqueForegroundPair: "'{{class}}' bypasses the declared '{{replacement}}' token paired with '{{background}}'."
14204
14814
  }
14205
14815
  },
14206
14816
  defaultOptions: [{}],
14207
14817
  create(context, [options]) {
14208
14818
  if (STORIES_FILE_RE.test(context.filename)) return {};
14209
- if (staticallyImportsEmailOrPdfRenderer(context.sourceCode.ast)) return {};
14210
- if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) {
14211
- return {};
14212
- }
14213
- let importsEmailOrPdfRenderer = false;
14214
- const pendingReports = [];
14819
+ if (staticallyImportsExternalRenderer(context.sourceCode.ast)) return {};
14820
+ if (options?.requireSemanticTokens === true && !hasSemanticTokenSystem(context.filename)) return {};
14821
+ const foregroundRoles = options?.opaqueForegroundPairs === true ? semanticForegroundRoles(context.filename) : /* @__PURE__ */ new Set();
14822
+ const checkOpaqueForegroundPairs = foregroundRoles.size > 0;
14823
+ let importsExternalRenderer = false;
14824
+ const pendingReports = /* @__PURE__ */ new Map();
14215
14825
  const report2 = (node, messageId, data) => {
14216
- pendingReports.push({ node, messageId, data });
14826
+ const key = `${node.range[0]}:${node.range[1]}:${messageId}:${JSON.stringify(data)}`;
14827
+ pendingReports.set(key, { node, messageId, data });
14217
14828
  };
14218
14829
  const reportClasses = (value, node) => {
14219
- for (const token of classTokens(value)) {
14830
+ const tokens = classTokens(value);
14831
+ for (const token of tokens) {
14220
14832
  const base = tailwindBase(token);
14221
14833
  if (RAW_PALETTE_RE.test(base)) {
14222
14834
  report2(node, "rawPalette", { class: token });
@@ -14224,6 +14836,26 @@ var prefer_semantic_colors_default = createRule({
14224
14836
  report2(node, "arbitraryColor", { class: token });
14225
14837
  }
14226
14838
  }
14839
+ if (!checkOpaqueForegroundPairs || isInsideSvg(node)) return;
14840
+ for (const token of tokens) {
14841
+ const prefix = tailwindVariantPrefix(token);
14842
+ if (prefix.split(":").includes("print")) continue;
14843
+ const base = tailwindBase(token);
14844
+ if (!OPAQUE_FOREGROUND_RE.test(base)) continue;
14845
+ const semanticBackground = tokens.find((candidate2) => {
14846
+ if (tailwindVariantPrefix(candidate2) !== prefix) return false;
14847
+ const match = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(candidate2));
14848
+ return match?.[1] !== void 0 && foregroundRoles.has(match[1]);
14849
+ });
14850
+ if (semanticBackground === void 0) continue;
14851
+ const role = SEMANTIC_BACKGROUND_RE.exec(tailwindBase(semanticBackground))?.[1];
14852
+ if (role === void 0) continue;
14853
+ report2(node, "opaqueForegroundPair", {
14854
+ background: semanticBackground,
14855
+ class: token,
14856
+ replacement: `${prefix}text-${role}-foreground`
14857
+ });
14858
+ }
14227
14859
  };
14228
14860
  const checkClassNode = (node) => {
14229
14861
  if (node === null) return;
@@ -14271,8 +14903,8 @@ var prefer_semantic_colors_default = createRule({
14271
14903
  }
14272
14904
  },
14273
14905
  CallExpression(node) {
14274
- if (node.callee.type === AST_NODE_TYPES66.Identifier && node.callee.name === "require" && node.arguments[0]?.type === AST_NODE_TYPES66.Literal && typeof node.arguments[0].value === "string" && EMAIL_OR_PDF_MODULE_RE.test(node.arguments[0].value)) {
14275
- importsEmailOrPdfRenderer = true;
14906
+ if (node.callee.type === AST_NODE_TYPES66.Identifier && node.callee.name === "require" && node.arguments[0]?.type === AST_NODE_TYPES66.Literal && typeof node.arguments[0].value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.arguments[0].value)) {
14907
+ importsExternalRenderer = true;
14276
14908
  }
14277
14909
  if (node.callee.type === AST_NODE_TYPES66.Identifier && CLASS_FNS.has(node.callee.name)) {
14278
14910
  for (const arg of node.arguments) {
@@ -14307,13 +14939,13 @@ var prefer_semantic_colors_default = createRule({
14307
14939
  if (name !== null && STYLE_COLOR_PROPS.has(name)) checkColorValueNode(node.value);
14308
14940
  },
14309
14941
  ImportExpression(node) {
14310
- if (node.source.type === AST_NODE_TYPES66.Literal && typeof node.source.value === "string" && EMAIL_OR_PDF_MODULE_RE.test(node.source.value)) {
14311
- importsEmailOrPdfRenderer = true;
14942
+ if (node.source.type === AST_NODE_TYPES66.Literal && typeof node.source.value === "string" && EXTERNAL_RENDERER_MODULE_RE.test(node.source.value)) {
14943
+ importsExternalRenderer = true;
14312
14944
  }
14313
14945
  },
14314
14946
  "Program:exit"() {
14315
- if (importsEmailOrPdfRenderer) return;
14316
- for (const descriptor of pendingReports) context.report(descriptor);
14947
+ if (importsExternalRenderer) return;
14948
+ for (const descriptor of pendingReports.values()) context.report(descriptor);
14317
14949
  }
14318
14950
  };
14319
14951
  }
@@ -14322,11 +14954,11 @@ var prefer_semantic_colors_default = createRule({
14322
14954
  // src/rules/prefer-server-actions.ts
14323
14955
  import "@typescript-eslint/utils";
14324
14956
  var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
14325
- summary: "Prefer Next.js Server Actions over /api/* mutations.",
14957
+ summary: "Prefer Next.js Server Actions over same-origin API mutations.",
14326
14958
  rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
14327
14959
  remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
14328
14960
  category: "architecture",
14329
- limitations: ["Only statically recognizable /api/ mutations in modules with positive Next.js evidence are reported: an explicit next import, or an app/pages path with a top-level use-client directive."],
14961
+ limitations: ["Only statically recognizable /api/ mutations, including one explicitly configured literal deployment base path, in use-client modules are reported; server boundaries and route handlers are excluded."],
14330
14962
  examples: [
14331
14963
  { id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
14332
14964
  { id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "'use client'; await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
@@ -14334,12 +14966,21 @@ var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
14334
14966
  };
14335
14967
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
14336
14968
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
14337
- var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
14969
+ var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|(?:^|\/)app(?:\/.*)?\/route\.[jt]sx?$|(?:^|\/)middleware\.[jt]sx?$|\/pages\/api\/)/;
14338
14970
  var NON_REACT_FRAMEWORK_RE2 = /^(?:@angular\/|@nestjs\/|vue$|vue\/|svelte$|svelte\/|solid-js$|solid-js\/|@ember\/|rxjs$|rxjs\/)/;
14339
- var NEXT_MODULE_PATH_RE2 = /(?:^|[/\\])(?:app|pages)[/\\]/u;
14971
+ var BASE_PATH_RE2 = /^\/(?!$)(?!.*[?#])(?:[^/]+\/)*[^/]+$/u;
14340
14972
  function getScope(context, node) {
14341
14973
  return context.sourceCode.getScope(node);
14342
14974
  }
14975
+ function resolvesToGlobalFetch(context, identifier) {
14976
+ let scope = getScope(context, identifier);
14977
+ while (scope) {
14978
+ const variable = scope.set.get(identifier.name);
14979
+ if (variable !== void 0) return variable.defs.length === 0;
14980
+ scope = scope.upper;
14981
+ }
14982
+ return true;
14983
+ }
14343
14984
  function resolveNode(node, context) {
14344
14985
  if (!node) return null;
14345
14986
  if (node.type !== "Identifier") return node;
@@ -14359,22 +15000,29 @@ function resolveNode(node, context) {
14359
15000
  }
14360
15001
  return node;
14361
15002
  }
14362
- function isApiUrl(node, context) {
15003
+ function isApiUrl(node, context, apiPrefixes) {
14363
15004
  const resolved = resolveNode(node, context);
14364
15005
  if (!resolved) return false;
14365
15006
  if (resolved.type === "Literal" && typeof resolved.value === "string") {
14366
- return resolved.value.startsWith("/api/");
15007
+ return apiPrefixes.some(
15008
+ (prefix) => resolved.value === prefix.slice(0, -1) || resolved.value.startsWith(prefix)
15009
+ );
14367
15010
  }
14368
15011
  if (resolved.type === "TemplateLiteral") {
14369
15012
  const firstQuasi = resolved.quasis[0];
14370
15013
  const cooked = firstQuasi?.value.cooked;
14371
- return typeof cooked === "string" && cooked.startsWith("/api/");
15014
+ return typeof cooked === "string" && apiPrefixes.some(
15015
+ (prefix) => cooked === prefix.slice(0, -1) || cooked.startsWith(prefix)
15016
+ );
14372
15017
  }
14373
15018
  if (resolved.type === "BinaryExpression" && resolved.operator === "+") {
14374
- return isApiUrl(resolved.left, context);
15019
+ return isApiUrl(resolved.left, context, apiPrefixes);
14375
15020
  }
14376
15021
  return false;
14377
15022
  }
15023
+ function isValidBasePath2(basePath) {
15024
+ return BASE_PATH_RE2.test(basePath) && !basePath.split("/").some((segment) => segment === "." || segment === "..");
15025
+ }
14378
15026
  function isMutationMethod(node, context) {
14379
15027
  const resolved = resolveNode(node, context);
14380
15028
  if (!resolved) return false;
@@ -14434,16 +15082,27 @@ var prefer_server_actions_default = createRule({
14434
15082
  meta: {
14435
15083
  type: "suggestion",
14436
15084
  docs: {
14437
- description: "Prefer Next.js Server Actions over /api/* mutations."
15085
+ description: "Prefer Next.js Server Actions over same-origin API mutations."
14438
15086
  },
14439
- schema: [],
15087
+ schema: [
15088
+ {
15089
+ type: "object",
15090
+ additionalProperties: false,
15091
+ properties: {
15092
+ basePath: {
15093
+ type: "string",
15094
+ pattern: "^/(?!$)(?!.*[?#])(?!(?:.*/)?\\.\\.?(?:/|$))(?:[^/]+/)*[^/]+$"
15095
+ }
15096
+ }
15097
+ }
15098
+ ],
14440
15099
  messages: {
14441
- preferServerAction: "Mutation against /api/* \u2014 prefer a Next.js Server Action for type-safety and to avoid the JSON round-trip."
15100
+ preferServerAction: "Mutation against a same-origin API route \u2014 prefer a Next.js Server Action for type-safety and to avoid the JSON round-trip."
14442
15101
  }
14443
15102
  },
14444
- defaultOptions: [],
14445
- create(context) {
14446
- const filename = context.filename;
15103
+ defaultOptions: [{}],
15104
+ create(context, [options]) {
15105
+ const filename = context.filename.replaceAll("\\", "/");
14447
15106
  if (SKIP_FILE_REGEX.test(filename)) {
14448
15107
  return {};
14449
15108
  }
@@ -14451,22 +15110,28 @@ var prefer_server_actions_default = createRule({
14451
15110
  (node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && NON_REACT_FRAMEWORK_RE2.test(node.source.value)
14452
15111
  );
14453
15112
  const hasUseClientDirective = context.sourceCode.ast.body.some(
14454
- (node) => node.type === "ExpressionStatement" && node.expression.type === "Literal" && node.expression.value === "use client"
15113
+ (node) => node.type === "ExpressionStatement" && node.directive === "use client"
15114
+ );
15115
+ const hasUseServerDirective = context.sourceCode.ast.body.some(
15116
+ (node) => node.type === "ExpressionStatement" && node.directive === "use server"
14455
15117
  );
14456
- const hasNextImport = context.sourceCode.ast.body.some(
14457
- (node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "next" || node.source.value.startsWith("next/"))
15118
+ const importsServerOnly = context.sourceCode.ast.body.some(
15119
+ (node) => node.type === "ImportDeclaration" && typeof node.source.value === "string" && (node.source.value === "server-only" || node.source.value === "next/server")
14458
15120
  );
14459
- const hasNextEvidence = hasNextImport || hasUseClientDirective && NEXT_MODULE_PATH_RE2.test(filename);
14460
- if (!hasNextEvidence) {
15121
+ if (!hasUseClientDirective || hasUseServerDirective || importsServerOnly) {
14461
15122
  return {};
14462
15123
  }
15124
+ const apiPrefixes = ["/api/"];
15125
+ if (options?.basePath !== void 0 && isValidBasePath2(options.basePath)) {
15126
+ apiPrefixes.push(`${options.basePath}/api/`);
15127
+ }
14463
15128
  return {
14464
15129
  CallExpression(node) {
14465
15130
  if (isNonReactFramework) return;
14466
15131
  let isMutation = false;
14467
- if (node.callee.type === "Identifier" && node.callee.name === "fetch") {
15132
+ if (node.callee.type === "Identifier" && node.callee.name === "fetch" && resolvesToGlobalFetch(context, node.callee)) {
14468
15133
  const urlArg = node.arguments[0];
14469
- if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context)) {
15134
+ if (urlArg && urlArg.type !== "SpreadElement" && isApiUrl(urlArg, context, apiPrefixes)) {
14470
15135
  const initArg = node.arguments[1];
14471
15136
  if (initArg && initArg.type !== "SpreadElement") {
14472
15137
  const resolvedInit = resolveNode(initArg, context);
@@ -14483,7 +15148,7 @@ var prefer_server_actions_default = createRule({
14483
15148
  const hasHandlerArg = node.arguments.some(
14484
15149
  (arg) => arg.type !== "SpreadElement" && isFunctionArgument(arg, context)
14485
15150
  );
14486
- if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context)) {
15151
+ if (urlArg && urlArg.type !== "SpreadElement" && !hasHandlerArg && isApiUrl(urlArg, context, apiPrefixes)) {
14487
15152
  isMutation = true;
14488
15153
  }
14489
15154
  }
@@ -14494,7 +15159,7 @@ var prefer_server_actions_default = createRule({
14494
15159
  if (configArg && configArg.type === "ObjectExpression") {
14495
15160
  const urlNode = getPropertyNode(configArg, "url");
14496
15161
  const methodNode = getPropertyNode(configArg, "method");
14497
- if (urlNode && isApiUrl(urlNode, context) && methodNode && isMutationMethod(methodNode, context)) {
15162
+ if (urlNode && isApiUrl(urlNode, context, apiPrefixes) && methodNode && isMutationMethod(methodNode, context)) {
14498
15163
  isMutation = true;
14499
15164
  }
14500
15165
  }
@@ -14786,7 +15451,7 @@ var prefer_whole_object_assertion_default = createRule({
14786
15451
  });
14787
15452
 
14788
15453
  // src/rules/repeated-static-call-cases.ts
14789
- import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils20 } from "@typescript-eslint/utils";
15454
+ import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
14790
15455
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
14791
15456
  summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
14792
15457
  rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
@@ -14812,7 +15477,7 @@ function staticMemberName5(node) {
14812
15477
  return null;
14813
15478
  }
14814
15479
  function importedName6(identifier, context, modules) {
14815
- const variable = ASTUtils20.findVariable(context.sourceCode.getScope(identifier), identifier.name);
15480
+ const variable = ASTUtils22.findVariable(context.sourceCode.getScope(identifier), identifier.name);
14816
15481
  if (variable === null || variable.defs.length === 0) return identifier.name;
14817
15482
  for (const definition of variable.defs) {
14818
15483
  if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
@@ -15813,7 +16478,7 @@ var require_assert_never_default = createRule({
15813
16478
  });
15814
16479
 
15815
16480
  // src/rules/require-fetch-timeout.ts
15816
- import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils21 } from "@typescript-eslint/utils";
16481
+ import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
15817
16482
  var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
15818
16483
  summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
15819
16484
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -15894,7 +16559,7 @@ var require_fetch_timeout_default = createRule({
15894
16559
  }
15895
16560
  function resolvesToGlobal(identifier) {
15896
16561
  const scope = context.sourceCode.getScope(identifier);
15897
- const variable = ASTUtils21.findVariable(scope, identifier.name);
16562
+ const variable = ASTUtils23.findVariable(scope, identifier.name);
15898
16563
  return variable === null || variable.defs.length === 0;
15899
16564
  }
15900
16565
  function isGlobalFetchCall2(callee) {
@@ -15904,7 +16569,7 @@ var require_fetch_timeout_default = createRule({
15904
16569
  return callee.type === AST_NODE_TYPES71.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES71.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES71.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
15905
16570
  }
15906
16571
  function localConstInitProvablyLacksSignal(identifier) {
15907
- const variable = ASTUtils21.findVariable(
16572
+ const variable = ASTUtils23.findVariable(
15908
16573
  context.sourceCode.getScope(identifier),
15909
16574
  identifier.name
15910
16575
  );
@@ -16993,7 +17658,7 @@ var require_static_next_matcher_default = createRule({
16993
17658
  });
16994
17659
 
16995
17660
  // src/rules/require-use-form-default-values.ts
16996
- import { ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
17661
+ import { ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
16997
17662
  var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
16998
17663
  summary: "react-hook-form useForm call without defaultValues",
16999
17664
  rationale: "Without an explicit initial value, fields can change from uncontrolled to controlled as data arrives, reset behavior becomes ambiguous, and the form's initial shape no longer documents the values users can edit.",
@@ -17047,13 +17712,13 @@ var require_use_form_default_values_default = createRule({
17047
17712
  if (node.source.value !== "react-hook-form") return;
17048
17713
  for (const specifier of node.specifiers) {
17049
17714
  if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
17050
- const variable = ASTUtils22.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
17715
+ const variable = ASTUtils24.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
17051
17716
  if (variable) importedHooks.add(variable);
17052
17717
  }
17053
17718
  },
17054
17719
  CallExpression(node) {
17055
17720
  if (node.callee.type !== "Identifier") return;
17056
- const variable = ASTUtils22.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
17721
+ const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
17057
17722
  const options = node.arguments[0];
17058
17723
  if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasDefaultValues(options)) return;
17059
17724
  context.report({ node, messageId: "requireUseFormDefaultValues" });
@@ -17131,7 +17796,7 @@ var require_use_server_in_actions_file_default = createRule({
17131
17796
  // src/rules/require-zod-form-validation.ts
17132
17797
  import {
17133
17798
  AST_NODE_TYPES as AST_NODE_TYPES76,
17134
- ASTUtils as ASTUtils23
17799
+ ASTUtils as ASTUtils25
17135
17800
  } from "@typescript-eslint/utils";
17136
17801
  var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
17137
17802
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
@@ -17199,7 +17864,7 @@ var require_zod_form_validation_default = createRule({
17199
17864
  return {};
17200
17865
  }
17201
17866
  const zodBindings = /* @__PURE__ */ new Set();
17202
- const resolvedBinding = (identifier) => ASTUtils23.findVariable(
17867
+ const resolvedBinding = (identifier) => ASTUtils25.findVariable(
17203
17868
  context.sourceCode.getScope(identifier),
17204
17869
  identifier.name
17205
17870
  );
@@ -17496,7 +18161,7 @@ var store_insert_requires_on_conflict_default = createRule({
17496
18161
  });
17497
18162
 
17498
18163
  // src/rules/stepdown.ts
17499
- import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
18164
+ import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
17500
18165
  var STEPDOWN_DOCUMENTATION = {
17501
18166
  summary: "Place a private helper below its sole direct same-scope caller.",
17502
18167
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
@@ -17701,7 +18366,7 @@ function methodName(node) {
17701
18366
  return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
17702
18367
  }
17703
18368
  function referencedMethod(context, node, classVariables) {
17704
- const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils24.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
18369
+ const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
17705
18370
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
17706
18371
  if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
17707
18372
  if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
@@ -17754,11 +18419,11 @@ function classScope(context, node, computedReferenceNames) {
17754
18419
  const pinned = /* @__PURE__ */ new Set();
17755
18420
  const classVariables = /* @__PURE__ */ new Set();
17756
18421
  if (node.id !== null) {
17757
- const internal = ASTUtils24.findVariable(context.sourceCode.getScope(node), node.id.name);
18422
+ const internal = ASTUtils26.findVariable(context.sourceCode.getScope(node), node.id.name);
17758
18423
  if (internal !== null) classVariables.add(internal);
17759
18424
  }
17760
18425
  if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
17761
- const outer = ASTUtils24.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
18426
+ const outer = ASTUtils26.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
17762
18427
  if (outer !== null) classVariables.add(outer);
17763
18428
  }
17764
18429
  for (const method of methods) {
@@ -17794,7 +18459,7 @@ function classScope(context, node, computedReferenceNames) {
17794
18459
  return;
17795
18460
  }
17796
18461
  if (binding.type !== AST_NODE_TYPES77.Identifier) return;
17797
- const variable = ASTUtils24.findVariable(context.sourceCode.getScope(binding), binding.name);
18462
+ const variable = ASTUtils26.findVariable(context.sourceCode.getScope(binding), binding.name);
17798
18463
  if (variable !== null) {
17799
18464
  methodClassVariables.add(variable);
17800
18465
  methodAliases.add(variable);
@@ -17824,7 +18489,7 @@ function classScope(context, node, computedReferenceNames) {
17824
18489
  return;
17825
18490
  }
17826
18491
  if (!privateNames.has(target)) return;
17827
- const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils24.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
18492
+ const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils26.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
17828
18493
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
17829
18494
  pinned.add(target);
17830
18495
  return;
@@ -18405,7 +19070,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
18405
19070
  // src/rules/require-pascal-case-zod-schema-name.ts
18406
19071
  import {
18407
19072
  AST_NODE_TYPES as AST_NODE_TYPES80,
18408
- ASTUtils as ASTUtils25
19073
+ ASTUtils as ASTUtils27
18409
19074
  } from "@typescript-eslint/utils";
18410
19075
  var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
18411
19076
  summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
@@ -18425,8 +19090,8 @@ var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
18425
19090
  ]
18426
19091
  };
18427
19092
  var PASCAL_SCHEMA_NAME_RE = /^[A-Z][A-Za-z0-9]*Schema$/;
18428
- var BENCHMARK_PATH_RE = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
18429
- var NON_SCHEMA_TERMINALS = /* @__PURE__ */ new Set([
19093
+ var BENCHMARK_PATH_RE2 = /(^|[\\/])(?:benchmarks?|bench)[\\/]/;
19094
+ var NON_SCHEMA_TERMINALS2 = /* @__PURE__ */ new Set([
18430
19095
  "parse",
18431
19096
  "parseAsync",
18432
19097
  "safeParse",
@@ -18539,7 +19204,7 @@ var SCHEMA_RETURNING_METHODS = /* @__PURE__ */ new Set([
18539
19204
  "transform"
18540
19205
  ]);
18541
19206
  var terminalMethodName = (callee) => !callee.computed && callee.property.type === AST_NODE_TYPES80.Identifier ? callee.property.name : null;
18542
- var calleeChainRoot = (node) => {
19207
+ var calleeChainRoot2 = (node) => {
18543
19208
  let current = node;
18544
19209
  for (; ; ) {
18545
19210
  if (current.type === AST_NODE_TYPES80.Identifier) {
@@ -18556,7 +19221,7 @@ var calleeChainRoot = (node) => {
18556
19221
  return null;
18557
19222
  }
18558
19223
  };
18559
- var chainMemberNames = (node) => {
19224
+ var chainMemberNames2 = (node) => {
18560
19225
  const names = [];
18561
19226
  let current = node;
18562
19227
  for (; ; ) {
@@ -18606,7 +19271,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
18606
19271
  const zodBindings = /* @__PURE__ */ new Set();
18607
19272
  const schemaBindings = /* @__PURE__ */ new Set();
18608
19273
  function resolvedBinding(identifier) {
18609
- return ASTUtils25.findVariable(
19274
+ return ASTUtils27.findVariable(
18610
19275
  context.sourceCode.getScope(identifier),
18611
19276
  identifier.name
18612
19277
  );
@@ -18616,7 +19281,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
18616
19281
  if (binding !== null) zodBindings.add(binding);
18617
19282
  }
18618
19283
  function isZodChain(node) {
18619
- const root = calleeChainRoot(node);
19284
+ const root = calleeChainRoot2(node);
18620
19285
  if (root === null) return false;
18621
19286
  const binding = resolvedBinding(root);
18622
19287
  return binding !== null && zodBindings.has(binding);
@@ -18632,16 +19297,16 @@ var require_pascal_case_zod_schema_name_default = createRule({
18632
19297
  return false;
18633
19298
  }
18634
19299
  const terminal = terminalMethodName(init.callee);
18635
- if (terminal === null || NON_SCHEMA_TERMINALS.has(terminal)) return false;
18636
- const names = chainMemberNames(init.callee);
19300
+ if (terminal === null || NON_SCHEMA_TERMINALS2.has(terminal)) return false;
19301
+ const names = chainMemberNames2(init.callee);
18637
19302
  if (names.length === 0) return false;
18638
19303
  if (isZodChain(init.callee)) {
18639
19304
  return ZOD_SCHEMA_FACTORIES.has(names[0] ?? "") || ZOD_FACTORY_NAMESPACES.has(names[0] ?? "") && ZOD_SCHEMA_FACTORIES.has(names[1] ?? "");
18640
19305
  }
18641
- const root = calleeChainRoot(init.callee);
19306
+ const root = calleeChainRoot2(init.callee);
18642
19307
  return root !== null && isSchemaBinding(root) && SCHEMA_RETURNING_METHODS.has(terminal);
18643
19308
  }
18644
- if (isTestFile(context.filename) || BENCHMARK_PATH_RE.test(context.filename.replaceAll("\\", "/")) || isGeneratedFile(context.filename, context.sourceCode.text)) {
19309
+ if (isTestFile(context.filename) || BENCHMARK_PATH_RE2.test(context.filename.replaceAll("\\", "/")) || isGeneratedFile(context.filename, context.sourceCode.text)) {
18645
19310
  return {};
18646
19311
  }
18647
19312
  return {
@@ -18844,14 +19509,14 @@ var RULES = {
18844
19509
  "require-static-next-matcher": require_static_next_matcher_default,
18845
19510
  "require-zod-form-validation": require_zod_form_validation_default,
18846
19511
  "store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
18847
- stepdown: stepdown_default,
19512
+ "stepdown": stepdown_default,
18848
19513
  "source-coupled-test": source_coupled_test_default,
18849
19514
  "sole-export-matches-filename": sole_export_matches_filename_default,
18850
19515
  "require-pascal-case-zod-schema-name": require_pascal_case_zod_schema_name_default
18851
19516
  };
18852
19517
  var meta = {
18853
19518
  name: "@sarj/eslint-plugin",
18854
- version: "15.17.1"
19519
+ version: "15.17.3"
18855
19520
  };
18856
19521
  var APPLICATION_ONLY_RULES = [
18857
19522
  "no-restricted-library-load",