@sarj/eslint-plugin 15.1.0 → 15.2.0

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
@@ -967,11 +967,11 @@ function isTrivialInitializer(node) {
967
967
  }
968
968
  function restatesStatementHead(body2, statement) {
969
969
  if (statement === null) return false;
970
- const words = body2.match(/[A-Za-z][\w$]*/g) ?? [];
971
- const opener = words[0];
972
- if (opener === void 0 || words.length > NARRATION_MAX_WORDS) return false;
970
+ const words2 = body2.match(/[A-Za-z][\w$]*/g) ?? [];
971
+ const opener = words2[0];
972
+ if (opener === void 0 || words2.length > NARRATION_MAX_WORDS) return false;
973
973
  if (!NARRATION_VERB_RE.test(opener)) return false;
974
- const content = words.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
974
+ const content = words2.slice(1).map(normalizeToken).filter((word) => !NARRATION_STOPWORDS.has(word));
975
975
  if (content.length < NARRATION_MIN_CONTENT) return false;
976
976
  const head = statement.split("(")[0] ?? statement;
977
977
  const code = headTokens(head);
@@ -1239,8 +1239,8 @@ function isRedundantNarration(body2, statementBelow, standalone, isolatedEnumera
1239
1239
  if (META_COMMENTARY_RE.test(t) && !justified) return true;
1240
1240
  if (isBareDeferral(t) && !justified) return true;
1241
1241
  if (HELPER_OPENER_RE.test(t) || LETS_RE.test(t)) return true;
1242
- const words = t.split(/\s+/);
1243
- if (words.length > 1 && words.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
1242
+ const words2 = t.split(/\s+/);
1243
+ if (words2.length > 1 && words2.length <= 4 && DUMMY_TRANSLATION_RE.test(t) && !/[():=]/.test(t)) {
1244
1244
  const lowerT = t.toLowerCase();
1245
1245
  if (!RATIONALE_WORDS.some((word) => lowerT.includes(word)) && restatesWholeStatement(t, statementBelow)) {
1246
1246
  return true;
@@ -1322,8 +1322,8 @@ function isWeakWalkthroughComment(body2, statement) {
1322
1322
  if (normalized.length === 0 || normalized.endsWith("?") || normalized.split(/\s+/).length > WALL_MAX_WORDS || isDirective(normalized) || isProtected(normalized) || !WALL_NARRATION_RE.test(normalized)) {
1323
1323
  return false;
1324
1324
  }
1325
- const words = contentTokens(normalized);
1326
- const described = words.slice(1);
1325
+ const words2 = contentTokens(normalized);
1326
+ const described = words2.slice(1);
1327
1327
  if (described.length === 0) return false;
1328
1328
  const code = codeTokens(statement);
1329
1329
  const matched = described.filter((word) => restates([word], code)).length;
@@ -2670,7 +2670,7 @@ var no_fat_try_blocks_default = createRule({
2670
2670
  const sourceCode = context.sourceCode;
2671
2671
  return {
2672
2672
  TryStatement(node) {
2673
- if (node.finalizer !== null) {
2673
+ if (node.finalizer !== null && node.handler === null) {
2674
2674
  return;
2675
2675
  }
2676
2676
  if (handlerRethrows(node.handler)) {
@@ -2910,15 +2910,32 @@ var noHandRolledSpinnerDocumentation = {
2910
2910
  rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
2911
2911
  remediation: "Render the design-system Spinner component instead.",
2912
2912
  category: "maintainability",
2913
- limitations: ["Only static className values on div and span elements are inspected."],
2913
+ limitations: ["Only static className values on div and span elements are inspected; tests, stories, generated files, and the design-system implementation are excluded."],
2914
2914
  examples: [
2915
2915
  { id: "design-system-spinner", title: "Use the shared spinner", outcome: "no-match", files: [{ path: "src/loading-state.tsx", source: '<Spinner className="size-4" />' }], focusPath: "src/loading-state.tsx", expectedCount: 0, public: true },
2916
2916
  { id: "border-ring-spinner", title: "Do not rebuild a spinner", outcome: "match", files: [{ path: "src/loading-state.tsx", source: '<div className="size-4 animate-spin rounded-full border-2 border-t-transparent" />' }], focusPath: "src/loading-state.tsx", expectedCount: 1, public: true }
2917
2917
  ]
2918
2918
  };
2919
2919
  var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
2920
- var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
2921
- var CONTRASTING_EDGE = /^border-[trbl]-(?!0$|[0-9]+$).+/u;
2920
+ var DIRECTIONAL_BORDER = /^border-([trblsexy])-(.+)$/u;
2921
+ var CSS_LENGTH = /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:cap|ch|cm|dvh|dvw|em|ex|ic|in|lh|lvh|lvw|mm|pc|pt|px|q|rcap|rch|rem|rex|ric|rlh|svh|svw|vb|vh|vi|vmax|vmin|vw|%)$/u;
2922
+ var ARBITRARY_LENGTH_FUNCTION = /^(?:calc|clamp|max|min)\(.+\)$/u;
2923
+ function isBorderWidthValue(value) {
2924
+ if (/^\d+$/u.test(value)) return true;
2925
+ if (value.startsWith("[") && value.endsWith("]")) {
2926
+ const arbitrary = value.slice(1, -1);
2927
+ const length = arbitrary.startsWith("length:") ? arbitrary.slice("length:".length) : arbitrary;
2928
+ return CSS_LENGTH.test(length) || ARBITRARY_LENGTH_FUNCTION.test(length) || arbitrary.startsWith("length:") && /^var\(.+\)$/u.test(length);
2929
+ }
2930
+ return value.startsWith("(length:") && value.endsWith(")") && value.length > "(length:)".length;
2931
+ }
2932
+ function isBorderWidth(token) {
2933
+ return token === "border" || token.startsWith("border-") && isBorderWidthValue(token.slice("border-".length));
2934
+ }
2935
+ function isContrastingEdge(token) {
2936
+ const match = DIRECTIONAL_BORDER.exec(token);
2937
+ return match?.[2] !== void 0 && !isBorderWidthValue(match[2]);
2938
+ }
2922
2939
  function staticClassName(attribute) {
2923
2940
  const value = attribute.value;
2924
2941
  if (value?.type === AST_NODE_TYPES11.Literal && typeof value.value === "string") {
@@ -2947,7 +2964,7 @@ var no_hand_rolled_spinner_default = createRule({
2947
2964
  },
2948
2965
  defaultOptions: [],
2949
2966
  create(context) {
2950
- if (DESIGN_SYSTEM_PATH.test(context.filename)) {
2967
+ if (DESIGN_SYSTEM_PATH.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
2951
2968
  return {};
2952
2969
  }
2953
2970
  return {
@@ -2962,7 +2979,7 @@ var no_hand_rolled_spinner_default = createRule({
2962
2979
  const className = staticClassName(classNameAttribute);
2963
2980
  if (className === null) return;
2964
2981
  const classes = className.split(/\s+/u);
2965
- if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some((token) => BORDER_WIDTH.test(token)) && classes.some((token) => CONTRASTING_EDGE.test(token))) {
2982
+ if (classes.includes("animate-spin") && classes.includes("rounded-full") && classes.some(isBorderWidth) && classes.some(isContrastingEdge)) {
2966
2983
  context.report({ node, messageId: "handRolledSpinner" });
2967
2984
  }
2968
2985
  }
@@ -3028,9 +3045,9 @@ function nameWords(name) {
3028
3045
  return name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").split(/[^A-Za-z0-9]+/u).filter(Boolean).map((word) => word.toLowerCase());
3029
3046
  }
3030
3047
  function isStrongSecurityName(name) {
3031
- const words = nameWords(name);
3032
- return words.some((word) => STRONG_SECURITY_WORDS.has(word)) || words.some(
3033
- (word, index) => word === "api" && words[index + 1] === "key" || word === "auth" && words[index + 1] === "id" || word === "verification" && words[index + 1] === "code"
3048
+ const words2 = nameWords(name);
3049
+ return words2.some((word) => STRONG_SECURITY_WORDS.has(word)) || words2.some(
3050
+ (word, index) => word === "api" && words2[index + 1] === "key" || word === "auth" && words2[index + 1] === "id" || word === "verification" && words2[index + 1] === "code"
3034
3051
  );
3035
3052
  }
3036
3053
  function isNonSecurityName(name) {
@@ -3994,7 +4011,7 @@ var VALUE_TAG_RE = /@(example|deprecated|see|remarks|throws|internal|public|alph
3994
4011
  var BOUNDARY_RE = /(?<=[.!?])["'`)\]]*\s+(?=[A-Z0-9`])/;
3995
4012
  var BULLET_RE = /^\s*(?:[-*+] |\d+[.)] )/;
3996
4013
  var HEADING_RE = /^[A-Za-z][A-Za-z ]+:$/;
3997
- var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\d|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
4014
+ var TECHNICAL_ANCHOR_RE = /https?:\/\/|`[^`\n]+`|:[a-z][a-z0-9_-]*:|(["'])[^"'\n]+\1|\bv?\d+\.\d+(?:\.\d+)?\b|\b\d+(?:\.\d+)?\s?(?:ns|us|ms|s|sec|secs|seconds?|mins?|minutes?|hours?|days?|bytes?|kib|mib|gib|kb|mb|gb|hz|khz|mhz|px|%)\b|\b[a-z][a-z0-9]*[A-Z][A-Za-z0-9]*\b|\b[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]+\b|(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|\b[\w.-]+\.(?:py|pyi|js|jsx|ts|tsx|json|ya?ml|toml|csv|parquet|md)\b|->|=>|==|!=|<=|>=|\|/mu;
3998
4015
  function body(comment) {
3999
4016
  return comment.value.replace(/^\*/, "").split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
4000
4017
  }
@@ -6810,22 +6827,25 @@ var noStorageInStatelessModulesDocumentation = {
6810
6827
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
6811
6828
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
6812
6829
  category: "architecture",
6813
- limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
6830
+ limitations: ["The rule is disabled until module path patterns are configured, recognizes only configured storage method names, and requires storage-like receiver evidence for the overloaded `put` method."],
6814
6831
  examples: [
6815
6832
  { id: "system-of-record", title: "Read from the system of record", outcome: "no-match", files: [{ path: "src/engineer-digest/post.ts", source: "const issues = await linear.listIssues();" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 0, public: true },
6816
6833
  { id: "private-storage", title: "Do not write private state in a stateless module", outcome: "match", files: [{ path: "src/engineer-digest/post.ts", source: "await kv.put('digest:last', timestamp);" }], focusPath: "src/engineer-digest/post.ts", expectedCount: 1, public: true }
6817
6834
  ]
6818
6835
  };
6819
6836
  function compile2(patterns) {
6820
- const compiled = [];
6821
- for (const pattern of patterns) {
6822
- try {
6823
- compiled.push(new RegExp(pattern));
6824
- } catch {
6825
- }
6826
- }
6827
- return compiled;
6837
+ return patterns.map((pattern) => new RegExp(pattern));
6828
6838
  }
6839
+ var STORAGE_RECEIVER_WORDS = /* @__PURE__ */ new Set([
6840
+ "bucket",
6841
+ "cache",
6842
+ "kv",
6843
+ "namespace",
6844
+ "r2",
6845
+ "redis",
6846
+ "storage",
6847
+ "store"
6848
+ ]);
6829
6849
  function storageMethodName(node, methods) {
6830
6850
  const callee = node.callee;
6831
6851
  if (callee.type !== AST_NODE_TYPES26.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES26.Identifier) {
@@ -6838,8 +6858,31 @@ function storageMethodName(node, methods) {
6838
6858
  if (node.arguments.length < (MIN_ARGUMENTS.get(name) ?? 1)) {
6839
6859
  return null;
6840
6860
  }
6861
+ if (name === "put" && !isStorageLikeReceiver(callee.object)) {
6862
+ return null;
6863
+ }
6841
6864
  return name;
6842
6865
  }
6866
+ function isStorageLikeReceiver(node) {
6867
+ if (node.type === AST_NODE_TYPES26.Identifier) {
6868
+ return isStorageIdentifier(node.name);
6869
+ }
6870
+ if (node.type !== AST_NODE_TYPES26.MemberExpression) {
6871
+ return false;
6872
+ }
6873
+ if (!node.computed && node.property.type === AST_NODE_TYPES26.Identifier && isStorageIdentifier(node.property.name)) {
6874
+ return true;
6875
+ }
6876
+ return isStorageLikeReceiver(node.object);
6877
+ }
6878
+ function isStorageIdentifier(name) {
6879
+ return identifierWords(name).some(
6880
+ (word) => STORAGE_RECEIVER_WORDS.has(word.toLowerCase())
6881
+ );
6882
+ }
6883
+ function identifierWords(name) {
6884
+ return name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)|\d+/gu) ?? [name];
6885
+ }
6843
6886
  var no_storage_in_stateless_modules_default = createRule({
6844
6887
  name: "no-storage-in-stateless-modules",
6845
6888
  documentation: noStorageInStatelessModulesDocumentation,
@@ -7241,12 +7284,86 @@ var no_tautological_expect_default = createRule({
7241
7284
  });
7242
7285
 
7243
7286
  // src/rules/no-typed-doc-sections.ts
7287
+ var TYPED_TAG_RE2 = /^\s*@(arg|argument|param|return|returns|yield|yields)\b(.*)$/iu;
7288
+ var PARAM_TAGS2 = /* @__PURE__ */ new Set(["arg", "argument", "param"]);
7289
+ var PARAMETER_FILLER = /* @__PURE__ */ new Set([
7290
+ "a",
7291
+ "an",
7292
+ "argument",
7293
+ "given",
7294
+ "input",
7295
+ "parameter",
7296
+ "passed",
7297
+ "provided",
7298
+ "the",
7299
+ "value"
7300
+ ]);
7301
+ var RESULT_FILLER = /* @__PURE__ */ new Set([
7302
+ "a",
7303
+ "an",
7304
+ "array",
7305
+ "boolean",
7306
+ "generator",
7307
+ "number",
7308
+ "object",
7309
+ "output",
7310
+ "promise",
7311
+ "result",
7312
+ "return",
7313
+ "returned",
7314
+ "returns",
7315
+ "string",
7316
+ "the",
7317
+ "value"
7318
+ ]);
7319
+ function hasVacuousTypedTag(text) {
7320
+ const tags = typedTags(text);
7321
+ return tags.length > 0 && tags.some(isVacuousTag);
7322
+ }
7323
+ function typedTags(text) {
7324
+ const tags = [];
7325
+ for (const raw of text.split("\n")) {
7326
+ const match = TYPED_TAG_RE2.exec(raw);
7327
+ if (match !== null) {
7328
+ tags.push({ kind: (match[1] ?? "").toLowerCase(), payload: (match[2] ?? "").trim() });
7329
+ } else if (tags.length > 0 && raw.trim().length > 0 && !raw.trim().startsWith("@")) {
7330
+ const last = tags.at(-1);
7331
+ last.payload = `${last.payload} ${raw.trim()}`.trim();
7332
+ }
7333
+ }
7334
+ return tags.map(({ kind, payload }) => {
7335
+ let rest = payload.replace(/^\{[^}\n]+\}\s*/u, "").trim();
7336
+ if (!PARAM_TAGS2.has(kind)) {
7337
+ return { kind, name: null, description: rest.replace(/^-\s*/u, "").trim() };
7338
+ }
7339
+ const match = /^(\[[^\]]+\]|[A-Za-z_$][\w$.[\]-]*)(?:\s+-\s*|\s+)?(.*)$/u.exec(rest);
7340
+ if (match === null) return { kind, name: null, description: "" };
7341
+ const rawName = (match[1] ?? "").replace(/^\[/u, "").replace(/\]$/u, "").split("=")[0] ?? "";
7342
+ rest = (match[2] ?? "").trim();
7343
+ return { kind, name: rawName, description: rest };
7344
+ });
7345
+ }
7346
+ function isVacuousTag(tag) {
7347
+ const description = words(tag.description).map(canonicalWord);
7348
+ if (description.length === 0) return true;
7349
+ if (tag.name === null) return description.every((word) => RESULT_FILLER.has(word));
7350
+ const nameWords2 = new Set(words(tag.name).map(canonicalWord));
7351
+ return description.every((word) => PARAMETER_FILLER.has(word) || nameWords2.has(word));
7352
+ }
7353
+ function words(text) {
7354
+ return text.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2").toLowerCase().match(/[a-z][a-z0-9]*/gu) ?? [];
7355
+ }
7356
+ function canonicalWord(word) {
7357
+ if (["identifier", "identifiers", "ids"].includes(word)) return "id";
7358
+ if (word.endsWith("s") && word.length > 3) return word.slice(0, -1);
7359
+ return word;
7360
+ }
7244
7361
  var noTypedDocSectionsDocumentation = {
7245
7362
  summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
7246
7363
  rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
7247
7364
  remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
7248
7365
  category: "maintainability",
7249
- limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7366
+ limitations: ["Description-free or name-restating parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7250
7367
  examples: [
7251
7368
  {
7252
7369
  id: "behavioral-documentation",
@@ -7284,7 +7401,7 @@ var no_typed_doc_sections_default = createRule({
7284
7401
  return {
7285
7402
  Program() {
7286
7403
  for (const group of proseGroups(context.filename, context.sourceCode, true)) {
7287
- if (group.hasTypedTags && documentsTypedFunction(context.sourceCode, group.comment)) {
7404
+ if (group.hasTypedTags && hasVacuousTypedTag(group.text) && documentsTypedFunction(context.sourceCode, group.comment)) {
7288
7405
  context.report({ node: group.comment, messageId: "typedSection" });
7289
7406
  }
7290
7407
  }
@@ -7385,28 +7502,32 @@ var STOPWORDS3 = /* @__PURE__ */ new Set([
7385
7502
  "with"
7386
7503
  ]);
7387
7504
  var DIRECTIVE_RE5 = /^\s*(?:eslint\b|eslint-|sarj-noqa\b|@ts-|prettier|biome-|c8\b|v8\b|istanbul\b|todo\b|fixme\b|hack\b|xxx\b)/i;
7505
+ var UNIT_NAME_SUFFIX_RE = /(?:_(?:NS|US|MS|S|SEC|SECS|SECOND|SECONDS|MIN|MINS|MINUTE|MINUTES|HOUR|HOURS|DAY|DAYS|BYTE|BYTES|KB|MB|GB|HZ|KHZ|MHZ|PX)|(?:Ns|Us|Ms|Sec|Secs|Second|Seconds|Min|Mins|Minute|Minutes|Hour|Hours|Day|Days|Byte|Bytes|Kb|Mb|Gb|Hz|Khz|Mhz|Px))$/u;
7388
7506
  function narratesValue(body2, code) {
7389
7507
  if (body2.length === 0 || DIRECTIVE_RE5.test(body2) || hasExternalReference(body2)) return false;
7390
7508
  const codeNumbers = numbersIn(code);
7391
7509
  if (codeNumbers.size === 0) return false;
7392
- const words = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7393
- if (words.length === 0) return false;
7510
+ const words2 = (body2.match(WORD_RE3) ?? []).map((word) => word.toLowerCase());
7511
+ if (words2.length === 0) return false;
7394
7512
  const commentNumbers = numbersIn(body2);
7395
7513
  if (commentNumbers.size === 0) return false;
7396
7514
  for (const number of commentNumbers) {
7397
7515
  if (!codeNumbers.has(number)) return false;
7398
7516
  }
7399
- if (!words.some((word) => UNIT_WORDS.has(word))) return false;
7517
+ if (!words2.some((word) => UNIT_WORDS.has(word))) return false;
7400
7518
  const identifiers = codeTokens(code);
7401
7519
  const stems = /* @__PURE__ */ new Set();
7402
7520
  for (const token of identifiers) stems.add(stem(token));
7403
- return words.every(
7521
+ return words2.every(
7404
7522
  (word) => STOPWORDS3.has(word) || UNIT_WORDS.has(word) || commentNumbers.has(word) || identifiers.has(word) || stems.has(stem(word))
7405
7523
  );
7406
7524
  }
7407
7525
  function numbersIn(text) {
7408
7526
  return new Set(text.match(NUMBER_RE) ?? []);
7409
7527
  }
7528
+ function nameAlreadyCarriesUnit(code) {
7529
+ return (code.match(/[A-Za-z_$][\w$]*/gu) ?? []).some((identifier) => UNIT_NAME_SUFFIX_RE.test(identifier));
7530
+ }
7410
7531
  var no_trailing_value_narration_default = createRule({
7411
7532
  name: "no-trailing-value-narration",
7412
7533
  documentation: noTrailingValueNarrationDocumentation,
@@ -7417,6 +7538,7 @@ var no_trailing_value_narration_default = createRule({
7417
7538
  },
7418
7539
  schema: [],
7419
7540
  messages: {
7541
+ deleteNarration: "Trailing comment restates the literal and the identifier already names its unit \u2014 delete the comment so it cannot drift.",
7420
7542
  narratesValue: "Trailing comment restates the literal on this line \u2014 put the unit in the name (STALE_TIME_MS) so it cannot drift."
7421
7543
  }
7422
7544
  },
@@ -7449,7 +7571,10 @@ var no_trailing_value_narration_default = createRule({
7449
7571
  const code = line.slice(0, comment.loc.start.column);
7450
7572
  const body2 = comment.value.replace(/^\*+/, "").replace(/\*+$/, "").trim();
7451
7573
  if (narratesValue(body2, code)) {
7452
- context.report({ node: comment, messageId: "narratesValue" });
7574
+ context.report({
7575
+ node: comment,
7576
+ messageId: nameAlreadyCarriesUnit(code) ? "deleteNarration" : "narratesValue"
7577
+ });
7453
7578
  }
7454
7579
  }
7455
7580
  }
@@ -8367,7 +8492,8 @@ var no_unsafe_mock_casting_default = createRule({
8367
8492
  // src/rules/no-zod-native-enum.ts
8368
8493
  import {
8369
8494
  ESLintUtils as ESLintUtils2,
8370
- AST_NODE_TYPES as AST_NODE_TYPES34
8495
+ AST_NODE_TYPES as AST_NODE_TYPES34,
8496
+ ASTUtils as ASTUtils8
8371
8497
  } from "@typescript-eslint/utils";
8372
8498
  import * as ts from "typescript";
8373
8499
  var noZodNativeEnumDocumentation = {
@@ -8496,15 +8622,23 @@ var no_zod_native_enum_default = createRule({
8496
8622
  } catch {
8497
8623
  services = null;
8498
8624
  }
8499
- const zodImportedNames = /* @__PURE__ */ new Map();
8500
- const zodNamespaces = /* @__PURE__ */ new Set();
8625
+ const zodImportedBindings = /* @__PURE__ */ new Map();
8626
+ const zodNamespaceBindings = /* @__PURE__ */ new Set();
8627
+ function resolvedBinding(identifier) {
8628
+ return ASTUtils8.findVariable(
8629
+ sourceCode.getScope(identifier),
8630
+ identifier.name
8631
+ );
8632
+ }
8501
8633
  function isZodMemberCall(node, api) {
8502
8634
  const callee = node.callee;
8503
- if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && zodNamespaces.has(callee.object.name) && callee.property.type === AST_NODE_TYPES34.Identifier) {
8504
- return callee.property.name === api;
8635
+ if (callee.type === AST_NODE_TYPES34.MemberExpression && !callee.computed && callee.object.type === AST_NODE_TYPES34.Identifier && callee.property.type === AST_NODE_TYPES34.Identifier) {
8636
+ const binding = resolvedBinding(callee.object);
8637
+ return binding !== null && zodNamespaceBindings.has(binding) && callee.property.name === api;
8505
8638
  }
8506
8639
  if (callee.type === AST_NODE_TYPES34.Identifier) {
8507
- return zodImportedNames.get(callee.name) === api;
8640
+ const binding = resolvedBinding(callee);
8641
+ return binding !== null && zodImportedBindings.get(binding) === api;
8508
8642
  }
8509
8643
  return false;
8510
8644
  }
@@ -8539,10 +8673,14 @@ var no_zod_native_enum_default = createRule({
8539
8673
  }
8540
8674
  for (const spec of node.specifiers) {
8541
8675
  if (spec.type === AST_NODE_TYPES34.ImportNamespaceSpecifier || spec.type === AST_NODE_TYPES34.ImportDefaultSpecifier || spec.type === AST_NODE_TYPES34.ImportSpecifier && (spec.imported.type === AST_NODE_TYPES34.Identifier ? spec.imported.name === "z" : spec.imported.value === "z")) {
8542
- zodNamespaces.add(spec.local.name);
8676
+ const binding = resolvedBinding(spec.local);
8677
+ if (binding !== null) zodNamespaceBindings.add(binding);
8543
8678
  }
8544
8679
  if (spec.type === AST_NODE_TYPES34.ImportSpecifier && spec.imported.type === AST_NODE_TYPES34.Identifier) {
8545
- zodImportedNames.set(spec.local.name, spec.imported.name);
8680
+ const binding = resolvedBinding(spec.local);
8681
+ if (binding !== null) {
8682
+ zodImportedBindings.set(binding, spec.imported.name);
8683
+ }
8546
8684
  }
8547
8685
  }
8548
8686
  },
@@ -8579,7 +8717,7 @@ var no_zod_native_enum_default = createRule({
8579
8717
  });
8580
8718
 
8581
8719
  // src/rules/test-loops-over-literal-cases.ts
8582
- import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
8720
+ import { AST_NODE_TYPES as AST_NODE_TYPES35, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
8583
8721
  var testLoopsOverLiteralCasesDocumentation = {
8584
8722
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
8585
8723
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
@@ -8742,7 +8880,7 @@ var test_loops_over_literal_cases_default = createRule({
8742
8880
  return {};
8743
8881
  }
8744
8882
  const isFrameworkIdentifier = (identifier, modules) => {
8745
- const variable = ASTUtils8.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8883
+ const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
8746
8884
  if (variable === null || variable.defs.length === 0) return true;
8747
8885
  return variable.defs.some((definition) => {
8748
8886
  let current = definition.node;
@@ -9179,7 +9317,7 @@ var prefer_input_group_search_default = createRule({
9179
9317
  });
9180
9318
 
9181
9319
  // src/rules/prefer-immutable-module-constant.ts
9182
- import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
9320
+ import { AST_NODE_TYPES as AST_NODE_TYPES39, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
9183
9321
  var preferImmutableModuleConstantDocumentation = {
9184
9322
  summary: "Require module-level constant collections to expose readonly state.",
9185
9323
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
@@ -9332,7 +9470,7 @@ var prefer_immutable_module_constant_default = createRule({
9332
9470
  create(context) {
9333
9471
  const sourceCode = context.sourceCode;
9334
9472
  const isUnshadowedGlobal = (identifier) => {
9335
- const variable = ASTUtils9.findVariable(sourceCode.getScope(identifier), identifier.name);
9473
+ const variable = ASTUtils10.findVariable(sourceCode.getScope(identifier), identifier.name);
9336
9474
  return variable === null || variable.defs.length === 0;
9337
9475
  };
9338
9476
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -10249,7 +10387,7 @@ var prefer_module_level_schema_default = createRule({
10249
10387
  });
10250
10388
 
10251
10389
  // src/rules/prefer-native-random-uuid.ts
10252
- import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
10390
+ import { AST_NODE_TYPES as AST_NODE_TYPES43, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10253
10391
  var preferNativeRandomUuidDocumentation = {
10254
10392
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
10255
10393
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -10285,7 +10423,7 @@ var prefer_native_random_uuid_default = createRule({
10285
10423
  const directBindings = /* @__PURE__ */ new Set();
10286
10424
  const namespaceBindings = /* @__PURE__ */ new Set();
10287
10425
  function resolve(identifier) {
10288
- return ASTUtils10.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10426
+ return ASTUtils11.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10289
10427
  }
10290
10428
  function record(identifier, destination) {
10291
10429
  const variable = resolve(identifier);
@@ -10348,7 +10486,7 @@ var prefer_native_random_uuid_default = createRule({
10348
10486
  });
10349
10487
 
10350
10488
  // src/rules/prefer-non-nullable-collection.ts
10351
- import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
10489
+ import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
10352
10490
  var preferNonNullableCollectionDocumentation = {
10353
10491
  summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
10354
10492
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
@@ -10478,14 +10616,14 @@ function directlyCoalesced(node) {
10478
10616
  return parent?.type === AST_NODE_TYPES44.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
10479
10617
  }
10480
10618
  function identifierIsOnlyCoalesced(context, binding, fn) {
10481
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(binding), binding.name);
10619
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(binding), binding.name);
10482
10620
  if (variable === null || variable.references.length === 0) return false;
10483
10621
  return variable.references.every(
10484
10622
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
10485
10623
  );
10486
10624
  }
10487
10625
  function memberIsOnlyCoalesced(context, object, property, fn) {
10488
- const variable = ASTUtils11.findVariable(context.sourceCode.getScope(object), object.name);
10626
+ const variable = ASTUtils12.findVariable(context.sourceCode.getScope(object), object.name);
10489
10627
  if (variable === null) return false;
10490
10628
  const accesses = variable.references.flatMap((reference) => {
10491
10629
  if (!belongsToFunction(reference.identifier, fn)) return [null];
@@ -12327,6 +12465,9 @@ var requireAssertNeverDocumentation = {
12327
12465
  };
12328
12466
  var isRuntimeHandlingStatement = (statement) => {
12329
12467
  if (statement.type === AST_NODE_TYPES49.EmptyStatement) return false;
12468
+ if (statement.type === AST_NODE_TYPES49.BreakStatement) {
12469
+ return statement.label !== null;
12470
+ }
12330
12471
  if (statement.type === AST_NODE_TYPES49.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES49.TSInterfaceDeclaration) {
12331
12472
  return false;
12332
12473
  }
@@ -12356,7 +12497,12 @@ function isExhaustiveFiniteSwitch(node, services) {
12356
12497
  const discriminant = services.esTreeNodeToTSNodeMap.get(node.discriminant);
12357
12498
  const discriminantType = checker.getTypeAtLocation(discriminant);
12358
12499
  const constituents = discriminantType.isUnion() ? discriminantType.types : [discriminantType];
12359
- if (constituents.length === 0) return false;
12500
+ if (!discriminantType.isUnion() || constituents.length < 2) return false;
12501
+ if (constituents.every(
12502
+ (constituent) => (constituent.flags & ts2.TypeFlags.BooleanLiteral) !== 0
12503
+ )) {
12504
+ return false;
12505
+ }
12360
12506
  const expected = /* @__PURE__ */ new Set();
12361
12507
  for (const constituent of constituents) {
12362
12508
  const key = finiteTypeKey(constituent, checker);
@@ -12424,7 +12570,7 @@ var require_assert_never_default = createRule({
12424
12570
  });
12425
12571
 
12426
12572
  // src/rules/require-fetch-timeout.ts
12427
- import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
12573
+ import { AST_NODE_TYPES as AST_NODE_TYPES50, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
12428
12574
  var requireFetchTimeoutDocumentation = {
12429
12575
  summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
12430
12576
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -12505,7 +12651,7 @@ var require_fetch_timeout_default = createRule({
12505
12651
  }
12506
12652
  function resolvesToGlobal(identifier) {
12507
12653
  const scope = context.sourceCode.getScope(identifier);
12508
- const variable = ASTUtils12.findVariable(scope, identifier.name);
12654
+ const variable = ASTUtils13.findVariable(scope, identifier.name);
12509
12655
  return variable === null || variable.defs.length === 0;
12510
12656
  }
12511
12657
  function isGlobalFetchCall2(callee) {
@@ -12515,7 +12661,7 @@ var require_fetch_timeout_default = createRule({
12515
12661
  return callee.type === AST_NODE_TYPES50.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES50.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES50.Identifier && GLOBAL_OBJECTS2.has(callee.object.name) && resolvesToGlobal(callee.object);
12516
12662
  }
12517
12663
  function localConstInitProvablyLacksSignal(identifier) {
12518
- const variable = ASTUtils12.findVariable(
12664
+ const variable = ASTUtils13.findVariable(
12519
12665
  context.sourceCode.getScope(identifier),
12520
12666
  identifier.name
12521
12667
  );
@@ -13157,34 +13303,35 @@ var require_static_next_matcher_default = createRule({
13157
13303
  });
13158
13304
 
13159
13305
  // src/rules/require-zod-form-validation.ts
13160
- import { AST_NODE_TYPES as AST_NODE_TYPES53 } from "@typescript-eslint/utils";
13306
+ import {
13307
+ AST_NODE_TYPES as AST_NODE_TYPES53,
13308
+ ASTUtils as ASTUtils14
13309
+ } from "@typescript-eslint/utils";
13161
13310
  var requireZodFormValidationDocumentation = {
13162
13311
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
13163
13312
  rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
13164
13313
  remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
13165
13314
  category: "security",
13315
+ limitations: [
13316
+ "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
13317
+ "Delayed raw-value use is accepted only after an unconditional successful parse in the same block; safeParse remains valid when the raw binding has no unvalidated consumer."
13318
+ ],
13166
13319
  examples: [
13167
13320
  { id: "validated-form-value", title: "Validate the form value", outcome: "no-match", files: [{ path: "src/action.ts", source: "const input = UserSchema.parse({ name: formData.get('name') });" }], focusPath: "src/action.ts", expectedCount: 0, public: true },
13168
13321
  { id: "raw-form-value", title: "Do not use a raw form value", outcome: "match", files: [{ path: "src/action.ts", source: "const name = formData.get('name');" }], focusPath: "src/action.ts", expectedCount: 1, public: true }
13169
13322
  ]
13170
13323
  };
13171
- var isZodParseCall = (node) => {
13172
- if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13173
- const callee = node.callee;
13174
- if (callee.type !== AST_NODE_TYPES53.MemberExpression) return false;
13175
- if (callee.computed) return false;
13176
- if (callee.property.type !== AST_NODE_TYPES53.Identifier) return false;
13177
- const method = callee.property.name;
13178
- if (method !== "parse" && method !== "safeParse" && method !== "parseAsync" && method !== "safeParseAsync") {
13179
- return false;
13180
- }
13181
- return looksLikeZodSchema(callee.object);
13182
- };
13183
- var looksLikeZodSchema = (node) => {
13324
+ var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set([
13325
+ "parse",
13326
+ "safeParse",
13327
+ "parseAsync",
13328
+ "safeParseAsync"
13329
+ ]);
13330
+ var zodReceiverRoot = (node) => {
13184
13331
  let current = node;
13185
13332
  while (true) {
13186
13333
  if (current.type === AST_NODE_TYPES53.Identifier) {
13187
- return current.name === "z" || ZOD_SCHEMA_NAME_RE.test(current.name);
13334
+ return current;
13188
13335
  }
13189
13336
  if (current.type === AST_NODE_TYPES53.CallExpression) {
13190
13337
  current = current.callee;
@@ -13194,7 +13341,7 @@ var looksLikeZodSchema = (node) => {
13194
13341
  current = current.object;
13195
13342
  continue;
13196
13343
  }
13197
- return false;
13344
+ return null;
13198
13345
  }
13199
13346
  };
13200
13347
  var isFormDataMethodCall = (node) => {
@@ -13224,6 +13371,34 @@ var require_zod_form_validation_default = createRule({
13224
13371
  if (isTestFile(context.filename)) {
13225
13372
  return {};
13226
13373
  }
13374
+ const zodBindings = /* @__PURE__ */ new Set();
13375
+ const resolvedBinding = (identifier) => ASTUtils14.findVariable(
13376
+ context.sourceCode.getScope(identifier),
13377
+ identifier.name
13378
+ );
13379
+ const isProvablyNonZodLocal = (identifier) => {
13380
+ const binding = resolvedBinding(identifier);
13381
+ if (binding === null || zodBindings.has(binding) || binding.defs.length !== 1) {
13382
+ return false;
13383
+ }
13384
+ const definition = binding.defs[0];
13385
+ if (definition?.type !== "Variable" || definition.node.type !== AST_NODE_TYPES53.VariableDeclarator) {
13386
+ return false;
13387
+ }
13388
+ const init = definition.node.init;
13389
+ return init?.type === AST_NODE_TYPES53.ObjectExpression || init?.type === AST_NODE_TYPES53.ArrayExpression || init?.type === AST_NODE_TYPES53.Literal || init?.type === AST_NODE_TYPES53.ArrowFunctionExpression || init?.type === AST_NODE_TYPES53.FunctionExpression;
13390
+ };
13391
+ const isZodParseCall = (node) => {
13392
+ if (node.type !== AST_NODE_TYPES53.CallExpression) return false;
13393
+ const callee = node.callee;
13394
+ if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || !ZOD_PARSE_METHODS.has(callee.property.name)) {
13395
+ return false;
13396
+ }
13397
+ const root = zodReceiverRoot(callee.object);
13398
+ if (root === null) return false;
13399
+ const binding = resolvedBinding(root);
13400
+ return binding !== null && zodBindings.has(binding) || (root.name === "z" || ZOD_SCHEMA_NAME_RE.test(root.name)) && !isProvablyNonZodLocal(root);
13401
+ };
13227
13402
  const isFormSourceIdentifier = (node) => {
13228
13403
  if (node.type !== AST_NODE_TYPES53.Identifier) return false;
13229
13404
  if (/formdata/i.test(node.name)) return true;
@@ -13249,14 +13424,15 @@ var require_zod_form_validation_default = createRule({
13249
13424
  }
13250
13425
  return isFormSourceIdentifier(callee.object);
13251
13426
  };
13252
- const hasZodParseAncestor = (node) => {
13427
+ const zodParseAncestor = (node) => {
13253
13428
  let parent = node.parent;
13254
13429
  while (parent !== null && parent !== void 0) {
13255
- if (isZodParseCall(parent)) return true;
13430
+ if (isZodParseCall(parent)) return parent;
13256
13431
  parent = parent.parent;
13257
13432
  }
13258
- return false;
13433
+ return null;
13259
13434
  };
13435
+ const hasZodParseAncestor = (node) => zodParseAncestor(node) !== null;
13260
13436
  const isInstanceofNarrowing = (node) => {
13261
13437
  const parent = node.parent;
13262
13438
  return parent !== null && parent !== void 0 && parent.type === AST_NODE_TYPES53.BinaryExpression && parent.operator === "instanceof" && parent.left === node && parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
@@ -13273,14 +13449,111 @@ var require_zod_form_validation_default = createRule({
13273
13449
  }
13274
13450
  return null;
13275
13451
  };
13452
+ const containingStatement = (node) => {
13453
+ let current = node;
13454
+ while (current.parent !== void 0) {
13455
+ const parent = current.parent;
13456
+ if (parent.type === AST_NODE_TYPES53.BlockStatement || parent.type === AST_NODE_TYPES53.Program) {
13457
+ return current;
13458
+ }
13459
+ current = parent;
13460
+ }
13461
+ return null;
13462
+ };
13463
+ const zodParseMethod = (call) => {
13464
+ const callee = call.callee;
13465
+ return callee.type === AST_NODE_TYPES53.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES53.Identifier ? callee.property.name : null;
13466
+ };
13467
+ const hasConditionalAncestorBeforeStatement = (node, statement) => {
13468
+ let current = node.parent;
13469
+ while (current !== void 0 && current !== statement) {
13470
+ if (current.type === AST_NODE_TYPES53.LogicalExpression || current.type === AST_NODE_TYPES53.ConditionalExpression) {
13471
+ return true;
13472
+ }
13473
+ current = current.parent;
13474
+ }
13475
+ return false;
13476
+ };
13477
+ const isAwaitedBeforeStatement = (node, statement) => {
13478
+ let current = node.parent;
13479
+ while (current !== void 0 && current !== statement) {
13480
+ if (current.type === AST_NODE_TYPES53.AwaitExpression) return true;
13481
+ current = current.parent;
13482
+ }
13483
+ return false;
13484
+ };
13485
+ const guaranteedValidationStatement = (declarator, reference) => {
13486
+ const parse2 = zodParseAncestor(reference);
13487
+ if (parse2 === null) return null;
13488
+ const declarationStatement = containingStatement(declarator);
13489
+ const validationStatement = containingStatement(parse2);
13490
+ if (declarationStatement === null || validationStatement === null || declarationStatement.parent !== validationStatement.parent || validationStatement.range[0] <= declarationStatement.range[1] || hasConditionalAncestorBeforeStatement(parse2, validationStatement)) {
13491
+ return null;
13492
+ }
13493
+ if (validationStatement.type !== AST_NODE_TYPES53.VariableDeclaration && validationStatement.type !== AST_NODE_TYPES53.ExpressionStatement) {
13494
+ return null;
13495
+ }
13496
+ const method = zodParseMethod(parse2);
13497
+ if (method === "parse") return validationStatement;
13498
+ if (method === "parseAsync" && isAwaitedBeforeStatement(parse2, validationStatement)) {
13499
+ return validationStatement;
13500
+ }
13501
+ return null;
13502
+ };
13503
+ const isSafePrevalidationInspection = (identifier) => {
13504
+ const parent = identifier.parent;
13505
+ if (parent.type === AST_NODE_TYPES53.UnaryExpression && parent.operator === "typeof") {
13506
+ return true;
13507
+ }
13508
+ if (parent.type !== AST_NODE_TYPES53.BinaryExpression || parent.left !== identifier) {
13509
+ return false;
13510
+ }
13511
+ if (parent.operator === "instanceof") {
13512
+ return parent.right.type === AST_NODE_TYPES53.Identifier && (parent.right.name === "File" || parent.right.name === "Blob");
13513
+ }
13514
+ return ["===", "!==", "==", "!="].includes(parent.operator) && (parent.right.type === AST_NODE_TYPES53.Literal && parent.right.value === null || parent.right.type === AST_NODE_TYPES53.Identifier && parent.right.name === "undefined");
13515
+ };
13516
+ const statementWithinBlock = (node, block) => {
13517
+ let current = node;
13518
+ while (current.parent !== void 0 && current.parent !== block) {
13519
+ current = current.parent;
13520
+ }
13521
+ return current.parent === block ? current : null;
13522
+ };
13276
13523
  const bindingIsValidated = (declarator) => {
13277
13524
  const variable = context.sourceCode.getDeclaredVariables(declarator)[0];
13278
13525
  if (variable === void 0) return false;
13279
- return variable.references.some(
13280
- (ref) => hasZodParseAncestor(ref.identifier) || isInstanceofNarrowing(ref.identifier)
13526
+ const references = variable.references.filter((reference) => !reference.isWriteOnly()).map((reference) => reference.identifier).filter(
13527
+ (identifier) => identifier.type === AST_NODE_TYPES53.Identifier
13281
13528
  );
13529
+ if (references.length === 0) return false;
13530
+ if (references.some(isInstanceofNarrowing)) return true;
13531
+ const validationStatements = references.map((reference) => guaranteedValidationStatement(declarator, reference)).filter(
13532
+ (statement) => statement !== null
13533
+ );
13534
+ const declarationStatement = containingStatement(declarator);
13535
+ const declarationBlock = declarationStatement?.parent;
13536
+ return references.every((reference) => {
13537
+ if (zodParseAncestor(reference) !== null || isSafePrevalidationInspection(reference)) {
13538
+ return true;
13539
+ }
13540
+ if (declarationBlock === void 0) return false;
13541
+ const useStatement = statementWithinBlock(reference, declarationBlock);
13542
+ return useStatement !== null && validationStatements.some(
13543
+ (statement) => statement.range[1] < useStatement.range[0]
13544
+ );
13545
+ });
13282
13546
  };
13283
13547
  return {
13548
+ ImportDeclaration(node) {
13549
+ if (!isZodModule(node.source.value)) return;
13550
+ for (const specifier of node.specifiers) {
13551
+ if (specifier.type === AST_NODE_TYPES53.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES53.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES53.ImportSpecifier && (specifier.imported.type === AST_NODE_TYPES53.Identifier ? specifier.imported.name === "z" : specifier.imported.value === "z")) {
13552
+ const binding = resolvedBinding(specifier.local);
13553
+ if (binding !== null) zodBindings.add(binding);
13554
+ }
13555
+ }
13556
+ },
13284
13557
  CallExpression(node) {
13285
13558
  if (!isFormDataGetCall(node)) return;
13286
13559
  if (hasZodParseAncestor(node) || isInstanceofNarrowing(node)) return;
@@ -13360,7 +13633,7 @@ var store_insert_requires_on_conflict_default = createRule({
13360
13633
  });
13361
13634
 
13362
13635
  // src/rules/stepdown.ts
13363
- import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
13636
+ import { AST_NODE_TYPES as AST_NODE_TYPES54, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
13364
13637
  var stepdownDocumentation = {
13365
13638
  summary: "Place a private helper below its sole direct same-scope caller.",
13366
13639
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
@@ -13553,7 +13826,7 @@ function methodName(node) {
13553
13826
  return !node.computed && node.key.type === AST_NODE_TYPES54.Identifier ? node.key.name : null;
13554
13827
  }
13555
13828
  function referencedMethod(context, node, classVariables) {
13556
- const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13829
+ const objectVariable = node.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
13557
13830
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
13558
13831
  if (node.object.type !== AST_NODE_TYPES54.ThisExpression && !isClassReference) return null;
13559
13832
  if (node.property.type === AST_NODE_TYPES54.PrivateIdentifier) return `#${node.property.name}`;
@@ -13606,11 +13879,11 @@ function classScope(context, node, computedReferenceNames) {
13606
13879
  const pinned = /* @__PURE__ */ new Set();
13607
13880
  const classVariables = /* @__PURE__ */ new Set();
13608
13881
  if (node.id !== null) {
13609
- const internal = ASTUtils13.findVariable(context.sourceCode.getScope(node), node.id.name);
13882
+ const internal = ASTUtils15.findVariable(context.sourceCode.getScope(node), node.id.name);
13610
13883
  if (internal !== null) classVariables.add(internal);
13611
13884
  }
13612
13885
  if (node.type === AST_NODE_TYPES54.ClassExpression && node.parent.type === AST_NODE_TYPES54.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES54.Identifier) {
13613
- const outer = ASTUtils13.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13886
+ const outer = ASTUtils15.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
13614
13887
  if (outer !== null) classVariables.add(outer);
13615
13888
  }
13616
13889
  for (const method of methods) {
@@ -13646,7 +13919,7 @@ function classScope(context, node, computedReferenceNames) {
13646
13919
  return;
13647
13920
  }
13648
13921
  if (binding.type !== AST_NODE_TYPES54.Identifier) return;
13649
- const variable = ASTUtils13.findVariable(context.sourceCode.getScope(binding), binding.name);
13922
+ const variable = ASTUtils15.findVariable(context.sourceCode.getScope(binding), binding.name);
13650
13923
  if (variable !== null) {
13651
13924
  methodClassVariables.add(variable);
13652
13925
  methodAliases.add(variable);
@@ -13676,7 +13949,7 @@ function classScope(context, node, computedReferenceNames) {
13676
13949
  return;
13677
13950
  }
13678
13951
  if (!privateNames.has(target)) return;
13679
- const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils13.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
13952
+ const objectVariable = current.object.type === AST_NODE_TYPES54.Identifier ? ASTUtils15.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
13680
13953
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
13681
13954
  pinned.add(target);
13682
13955
  return;
@@ -13760,7 +14033,7 @@ var stepdown_default = createRule({
13760
14033
  // src/rules/zod-naming-convention.ts
13761
14034
  import {
13762
14035
  AST_NODE_TYPES as AST_NODE_TYPES55,
13763
- ASTUtils as ASTUtils14
14036
+ ASTUtils as ASTUtils16
13764
14037
  } from "@typescript-eslint/utils";
13765
14038
  var zodNamingConventionDocumentation = {
13766
14039
  summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
@@ -13847,7 +14120,7 @@ var zod_naming_convention_default = createRule({
13847
14120
  const acceptsSchemaWord = convention !== "prefix";
13848
14121
  const zodBindings = /* @__PURE__ */ new Set();
13849
14122
  function resolvedBinding(identifier) {
13850
- return ASTUtils14.findVariable(
14123
+ return ASTUtils16.findVariable(
13851
14124
  context.sourceCode.getScope(identifier),
13852
14125
  identifier.name
13853
14126
  );
@@ -14043,7 +14316,7 @@ var rules = {
14043
14316
  };
14044
14317
  var meta = {
14045
14318
  name: "@sarj/eslint-plugin",
14046
- version: "15.1.0"
14319
+ version: "15.2.0"
14047
14320
  };
14048
14321
  var applicationOnlyRules = [
14049
14322
  "no-restricted-library-load",