@sarj/eslint-plugin 15.17.9 → 15.17.11

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
@@ -627,10 +627,11 @@ var TEST_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test",
627
627
  var DUPLICATE_TEST_BODY_DOCUMENTATION = {
628
628
  summary: "Disallow substantial sibling tests with the same body shape; express their differing inputs as a parameterized case table.",
629
629
  rationale: "Copy-pasted test bodies hide the cases that differ and allow equivalent assertions to drift independently.",
630
- remediation: "Move the varying inputs and expected values into a case table consumed by `test.each(...)` or `it.each(...)`.",
630
+ remediation: "Consider a case table with one named test or subtest per case; preserve setup lifetime, test modifiers, and each case's assertions rather than deleting coverage.",
631
631
  category: "testing",
632
632
  limitations: [
633
- "The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments."
633
+ "The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments.",
634
+ "Matching normalized body shapes do not prove runtime equivalence or independent setup; parameterization is a manual review, not an automatic deletion."
634
635
  ],
635
636
  examples: [
636
637
  {
@@ -815,6 +816,10 @@ function isDuplicateTestFrameworkIdentifier(identifier, sourceCode) {
815
816
  const variable = ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
816
817
  if (variable === null || variable.defs.length === 0) return true;
817
818
  return variable.defs.some((definition) => {
819
+ if (definition.node.type === AST_NODE_TYPES2.ImportDefaultSpecifier) return definition.node.parent.source.value === "node:test";
820
+ if (definition.node.type !== AST_NODE_TYPES2.ImportSpecifier) return false;
821
+ const imported = definition.node.imported;
822
+ if (!TEST_CALLERS.has(imported.type === AST_NODE_TYPES2.Identifier ? imported.name : String(imported.value))) return false;
818
823
  let current = definition.node;
819
824
  while (current != null && current.type !== AST_NODE_TYPES2.ImportDeclaration) current = current.parent;
820
825
  return current?.type === AST_NODE_TYPES2.ImportDeclaration && typeof current.source.value === "string" && TEST_MODULES.has(current.source.value);
@@ -2406,7 +2411,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
2406
2411
  VariableDeclarator(node) {
2407
2412
  if (node.id.type !== AST_NODE_TYPES9.Identifier) return;
2408
2413
  const variable = ASTUtils3.findVariable(context.sourceCode.getScope(node.id), node.id.name);
2409
- if (variable === null) return;
2414
+ if (variable === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) return;
2410
2415
  if (node.init?.type === AST_NODE_TYPES9.ArrowFunctionExpression || node.init?.type === AST_NODE_TYPES9.FunctionExpression) {
2411
2416
  functionCallbacks.set(node.init, variable);
2412
2417
  }
@@ -2417,7 +2422,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
2417
2422
  FunctionDeclaration(node) {
2418
2423
  if (node.id === null) return;
2419
2424
  const variable = ASTUtils3.findVariable(context.sourceCode.getScope(node.id), node.id.name);
2420
- if (variable !== null) functionCallbacks.set(node, variable);
2425
+ if (variable !== null && !variable.references.some((reference) => reference.isWrite() && !reference.init)) functionCallbacks.set(node, variable);
2421
2426
  },
2422
2427
  CallExpression(node) {
2423
2428
  const item = registration(context.sourceCode, node);
@@ -2561,12 +2566,33 @@ import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
2561
2566
  // src/rules/_sql.ts
2562
2567
  import { AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
2563
2568
  function stripSqlNoise(text) {
2564
- const out = [...text];
2569
+ return scanSqlNoise(text);
2570
+ }
2571
+ function sqlSingleQuotedRanges(text) {
2572
+ const ranges = [];
2573
+ scanSqlNoise(text, (start, end) => ranges.push([start, end]));
2574
+ return ranges;
2575
+ }
2576
+ function scanSqlNoise(text, onSingleQuoted) {
2577
+ const out = text.split("");
2565
2578
  const n = text.length;
2566
2579
  let i = 0;
2567
2580
  while (i < n) {
2568
2581
  const ch = text[i];
2582
+ if (ch === "$" && !/[\w$]/u.test(text[i - 1] ?? "")) {
2583
+ const delimiter = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/u.exec(text.slice(i))?.[0];
2584
+ if (delimiter !== void 0) {
2585
+ const closing = text.indexOf(delimiter, i + delimiter.length);
2586
+ const end = closing < 0 ? n : closing + delimiter.length;
2587
+ while (i < end) {
2588
+ if (text[i] !== "\n") out[i] = " ";
2589
+ i += 1;
2590
+ }
2591
+ continue;
2592
+ }
2593
+ }
2569
2594
  if (ch === "'" || ch === '"') {
2595
+ const start = i;
2570
2596
  out[i] = " ";
2571
2597
  i += 1;
2572
2598
  while (i < n) {
@@ -2580,6 +2606,7 @@ function stripSqlNoise(text) {
2580
2606
  }
2581
2607
  out[i] = " ";
2582
2608
  i += 1;
2609
+ if (ch === "'") onSingleQuoted?.(start, i);
2583
2610
  break;
2584
2611
  }
2585
2612
  if (c !== "\n") {
@@ -2600,17 +2627,20 @@ function stripSqlNoise(text) {
2600
2627
  out[i] = " ";
2601
2628
  out[i + 1] = " ";
2602
2629
  i += 2;
2603
- while (i < n && !(text[i] === "*" && text[i + 1] === "/")) {
2630
+ let depth = 1;
2631
+ while (i < n && depth > 0) {
2632
+ if (text[i] === "/" && text[i + 1] === "*" || text[i] === "*" && text[i + 1] === "/") {
2633
+ depth += text[i] === "/" ? 1 : -1;
2634
+ out[i] = " ";
2635
+ out[i + 1] = " ";
2636
+ i += 2;
2637
+ continue;
2638
+ }
2604
2639
  if (text[i] !== "\n") {
2605
2640
  out[i] = " ";
2606
2641
  }
2607
2642
  i += 1;
2608
2643
  }
2609
- if (i < n) {
2610
- out[i] = " ";
2611
- out[i + 1] = " ";
2612
- i += 2;
2613
- }
2614
2644
  continue;
2615
2645
  }
2616
2646
  i += 1;
@@ -2708,8 +2738,9 @@ var NO_DYNAMIC_SQL_DOCUMENTATION = {
2708
2738
  remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
2709
2739
  category: "security",
2710
2740
  limitations: [
2711
- "The rule reports only visibly quoted runtime values; dynamic identifiers and unquoted fragments require provenance that syntax-only linting cannot prove.",
2712
- "Static fragments and parameterizing tagged templates are exempt."
2741
+ "Only single-quoted SQL values are inspected. Double-quoted identifiers, comments, dollar strings, and unquoted fragments are excluded; this is not a general SQL injection detector.",
2742
+ "Literal fragments, legacy uppercase fragment names, and parameterizing tagged templates are exempt; uppercase spelling does not prove a value is static.",
2743
+ "The bounded lexer recognizes doubled quotes, comments, and PostgreSQL dollar strings; dialect-specific escape modes and SQL generated through other APIs require separate security review."
2713
2744
  ],
2714
2745
  examples: [
2715
2746
  {
@@ -2750,15 +2781,23 @@ function isStaticFragment(expression) {
2750
2781
  return false;
2751
2782
  }
2752
2783
  function runtimeInterpolations(template) {
2784
+ const parts = template.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw);
2785
+ const ranges = sqlSingleQuotedRanges(parts.join(RUNTIME_MARKER));
2786
+ let offset = 0;
2753
2787
  return template.expressions.filter(
2754
- (expression, index) => !isStaticFragment(expression) && endsWithSqlQuote(template.quasis[index]?.value.raw ?? "") && startsWithSqlQuote(template.quasis[index + 1]?.value.raw ?? "")
2788
+ (expression, index) => {
2789
+ offset += parts[index]?.length ?? 0;
2790
+ const inValue = ranges.some(([start, end]) => start < offset && offset < end);
2791
+ offset += RUNTIME_MARKER.length;
2792
+ return inValue && !isStaticFragment(expression) && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "");
2793
+ }
2755
2794
  );
2756
2795
  }
2757
2796
  function endsWithSqlQuote(text) {
2758
- return /['"]\s*$/u.test(text);
2797
+ return /'\s*$/u.test(text);
2759
2798
  }
2760
2799
  function startsWithSqlQuote(text) {
2761
- return /^\s*['"]/u.test(text);
2800
+ return /^\s*'/u.test(text);
2762
2801
  }
2763
2802
  function staticLiteralText(node) {
2764
2803
  if (node.type === AST_NODE_TYPES11.Literal && typeof node.value === "string") {
@@ -2780,7 +2819,13 @@ function runtimeConcatOperands(node) {
2780
2819
  if (!hasStringLiteral) {
2781
2820
  return [];
2782
2821
  }
2822
+ const parts = operands.map((operand) => staticLiteralText(operand) ?? RUNTIME_MARKER);
2823
+ const ranges = sqlSingleQuotedRanges(parts.join(""));
2824
+ let offset = 0;
2783
2825
  return operands.filter((operand, index) => {
2826
+ const inValue = ranges.some(([start, end]) => start < offset && offset < end);
2827
+ offset += parts[index]?.length ?? 0;
2828
+ if (!inValue) return false;
2784
2829
  if (isStaticFragment(operand)) return false;
2785
2830
  const before = operands[index - 1];
2786
2831
  const after = operands[index + 1];
@@ -2873,9 +2918,11 @@ var no_dynamic_sql_default = createRule({
2873
2918
  import "@typescript-eslint/utils";
2874
2919
  var NO_ENUM_DOCUMENTATION = {
2875
2920
  summary: "Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.",
2876
- rationale: "TypeScript enums emit runtime objects and numeric enums accept values outside their declared members, adding behavior where a type-only model is sufficient.",
2877
- remediation: "Replace the enum with a string-literal union or an `as const` object and derive its value type from that object.",
2921
+ rationale: "Literal unions keep type-only domains explicit, while constant objects make runtime values deliberate. This policy also avoids compiler-dependent const-enum inlining contracts.",
2922
+ remediation: "Use a literal union or an `as const` object after checking runtime member access, numeric reverse mappings, serialized values, and public consumers.",
2878
2923
  category: "maintainability",
2924
+ limitations: ["This is an explicit style policy for regular and const enums, not a claim that every enum emits an object. Generated files and configured exclusions are preserved; migration is manual."],
2925
+ references: ["https://www.typescriptlang.org/docs/handbook/enums.html"],
2879
2926
  examples: [
2880
2927
  {
2881
2928
  id: "string-literal-union",
@@ -2955,17 +3002,17 @@ var no_enum_default = createRule({
2955
3002
  // src/rules/no-fat-try-blocks.ts
2956
3003
  import { AST_NODE_TYPES as AST_NODE_TYPES12 } from "@typescript-eslint/utils";
2957
3004
  var NO_FAT_TRY_BLOCKS_DOCUMENTATION = {
2958
- summary: "Disallow `try` blocks containing more than three top-level operations that can throw.",
3005
+ summary: "Review try blocks exceeding the configured count of syntactically selected operations.",
2959
3006
  rationale: "A broad `try` block obscures which operation failed and encourages one catch clause to recover from unrelated errors.",
2960
3007
  remediation: "Keep only the operations that share one recovery policy inside the `try` block and move other work outside it.",
2961
3008
  category: "correctness",
2962
3009
  limitations: [
2963
- "The rule uses syntax to identify throwing operations and exempts generated files, finally blocks, rethrows, and terminal error boundaries."
3010
+ "The default threshold is three selected top-level operations, not a proof of every possible throw. A shared recovery policy may legitimately cover several operations; generated files, catchless finally blocks, rethrows, and terminal error boundaries are excluded."
2964
3011
  ],
2965
3012
  examples: [
2966
3013
  {
2967
3014
  id: "focused-try-block",
2968
- title: "A try block contains three throwing operations",
3015
+ title: "Three selected operations stay within the default threshold",
2969
3016
  outcome: "no-match",
2970
3017
  files: [{
2971
3018
  path: "src/load.ts",
@@ -2977,7 +3024,7 @@ var NO_FAT_TRY_BLOCKS_DOCUMENTATION = {
2977
3024
  },
2978
3025
  {
2979
3026
  id: "broad-try-block",
2980
- title: "A try block contains four throwing operations",
3027
+ title: "Review whether four selected operations share one recovery policy",
2981
3028
  outcome: "match",
2982
3029
  files: [{
2983
3030
  path: "src/load.ts",
@@ -3347,7 +3394,7 @@ var no_fat_try_blocks_default = createRule({
3347
3394
  meta: {
3348
3395
  type: "problem",
3349
3396
  docs: {
3350
- description: "Disallow `try` blocks containing more than three top-level operations that can throw."
3397
+ description: "Review try blocks exceeding the configured count of syntactically selected operations."
3351
3398
  },
3352
3399
  schema: [
3353
3400
  {
@@ -3359,7 +3406,7 @@ var no_fat_try_blocks_default = createRule({
3359
3406
  }
3360
3407
  ],
3361
3408
  messages: {
3362
- fatTryBlock: "This `try` block has {{count}} statements that can throw (max {{max}}). Isolate the throwing statement(s); move non-throwing work outside the `try`."
3409
+ fatTryBlock: "This `try` block has {{count}} syntactically selected operations (max {{max}}). Review whether they share one recovery policy; move unrelated work outside the boundary."
3363
3410
  }
3364
3411
  },
3365
3412
  defaultOptions: [{ max: MAX_TRY_BODY_STATEMENTS }],
@@ -3399,14 +3446,15 @@ var no_fat_try_blocks_default = createRule({
3399
3446
  });
3400
3447
 
3401
3448
  // src/rules/no-hand-rolled-sleep.ts
3402
- import { AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
3449
+ import { AST_NODE_TYPES as AST_NODE_TYPES13, ASTUtils as ASTUtils5 } from "@typescript-eslint/utils";
3403
3450
  var NO_HAND_ROLLED_SLEEP_DOCUMENTATION = {
3404
3451
  summary: "Disallow uncancellable promisified timers and timeout arms.",
3405
3452
  rationale: "A timer that outlives an aborted operation or a lost promise race retains work and can keep the process alive until it fires.",
3406
3453
  remediation: "Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.",
3407
3454
  category: "correctness",
3408
3455
  limitations: [
3409
- "The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions."
3456
+ "The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions.",
3457
+ "Locally shadowed constructors/timers and value-returning timers are excluded. Only recognized browser markers are excluded; choose a runtime-compatible cancellation API for other browser modules."
3410
3458
  ],
3411
3459
  examples: [
3412
3460
  {
@@ -3550,6 +3598,21 @@ var no_hand_rolled_sleep_default = createRule({
3550
3598
  return {};
3551
3599
  }
3552
3600
  const checkClientModules = optionsArg?.checkClientModules ?? false;
3601
+ const bindingOf = (identifier) => ASTUtils5.findVariable(sourceCode.getScope(identifier), identifier.name);
3602
+ const isGlobal = (identifier) => (bindingOf(identifier)?.defs.length ?? 0) === 0;
3603
+ const isBuiltinTimer = (callee) => {
3604
+ if (!isSetTimeoutCallee(callee)) return false;
3605
+ if (callee.type === AST_NODE_TYPES13.MemberExpression && callee.object.type === AST_NODE_TYPES13.Identifier) return isGlobal(callee.object);
3606
+ if (callee.type !== AST_NODE_TYPES13.Identifier) return false;
3607
+ const binding = bindingOf(callee);
3608
+ return binding === null || binding.defs.length === 0 || binding.defs.every((definition) => definition.node.type === AST_NODE_TYPES13.ImportSpecifier && definition.node.imported.type === AST_NODE_TYPES13.Identifier && definition.node.imported.name === "setTimeout" && definition.node.parent.type === AST_NODE_TYPES13.ImportDeclaration && ["node:timers", "timers"].includes(String(definition.node.parent.source.value)));
3609
+ };
3610
+ const settlesParameter = (callback, executor, index) => {
3611
+ const parameter = executor.params[index];
3612
+ if (parameter?.type !== AST_NODE_TYPES13.Identifier) return false;
3613
+ const callee = callback.type === AST_NODE_TYPES13.Identifier ? callback : callback.type === AST_NODE_TYPES13.ArrowFunctionExpression || callback.type === AST_NODE_TYPES13.FunctionExpression ? soleCall(callback)?.callee : null;
3614
+ return callee?.type === AST_NODE_TYPES13.Identifier && bindingOf(callee) === bindingOf(parameter);
3615
+ };
3553
3616
  function isClientModule2() {
3554
3617
  if (/\.[cm]?[jt]sx$/.test(filename)) {
3555
3618
  return true;
@@ -3575,7 +3638,7 @@ var no_hand_rolled_sleep_default = createRule({
3575
3638
  };
3576
3639
  return {
3577
3640
  NewExpression(node) {
3578
- if (node.callee.type !== AST_NODE_TYPES13.Identifier || node.callee.name !== "Promise") {
3641
+ if (node.callee.type !== AST_NODE_TYPES13.Identifier || node.callee.name !== "Promise" || !isGlobal(node.callee)) {
3579
3642
  return;
3580
3643
  }
3581
3644
  const executor = node.arguments[0];
@@ -3583,7 +3646,7 @@ var no_hand_rolled_sleep_default = createRule({
3583
3646
  return;
3584
3647
  }
3585
3648
  const call = soleCall(executor);
3586
- if (call === null || !isSetTimeoutCallee(call.callee)) {
3649
+ if (call === null || !isBuiltinTimer(call.callee)) {
3587
3650
  return;
3588
3651
  }
3589
3652
  const [callback, delay] = call.arguments;
@@ -3591,14 +3654,14 @@ var no_hand_rolled_sleep_default = createRule({
3591
3654
  return;
3592
3655
  }
3593
3656
  const resolveName = parameterName(executor, 0);
3594
- if (resolveName !== null && settlesWithoutValue(callback, resolveName)) {
3657
+ if (resolveName !== null && call.arguments.length === 2 && settlesWithoutValue(callback, resolveName) && settlesParameter(callback, executor, 0)) {
3595
3658
  if (reportsSleepHere()) {
3596
3659
  context.report({ node, messageId: "handRolledSleep" });
3597
3660
  }
3598
3661
  return;
3599
3662
  }
3600
3663
  const rejectName = parameterName(executor, 1);
3601
- if (rejectName !== null && isRaceArm(node) && rejectsInCallback(callback, rejectName)) {
3664
+ if (rejectName !== null && isRaceArm(node) && settlesParameter(callback, executor, 1) && rejectsInCallback(callback, rejectName)) {
3602
3665
  context.report({ node, messageId: "handRolledTimeoutRace" });
3603
3666
  }
3604
3667
  }
@@ -3691,13 +3754,13 @@ var no_hand_rolled_spinner_default = createRule({
3691
3754
  });
3692
3755
 
3693
3756
  // src/rules/no-insecure-random-id.ts
3694
- import "@typescript-eslint/utils";
3757
+ import { ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
3695
3758
  var NO_INSECURE_RANDOM_ID_DOCUMENTATION = {
3696
3759
  summary: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead.",
3697
3760
  rationale: "Math.random is predictable and lacks the entropy required for security-sensitive values.",
3698
3761
  remediation: "Generate the value with crypto.randomUUID or crypto.getRandomValues.",
3699
3762
  category: "security",
3700
- limitations: ["Ambiguous identifiers and test files are excluded to avoid flagging sampling and fixture data."],
3763
+ limitations: ["Names select security-sensitive bindings heuristically; they do not prove sensitivity. Sampling in unrelated bindings or branch tests, locally shadowed Math objects, ambiguous identifiers and test files are excluded. This is not interprocedural data-flow analysis."],
3701
3764
  examples: [
3702
3765
  { id: "cryptographic-id", title: "Use the Web Crypto API", outcome: "no-match", files: [{ path: "src/session.ts", source: "const sessionToken = crypto.randomUUID();" }], focusPath: "src/session.ts", expectedCount: 0, public: true },
3703
3766
  { id: "predictable-token", title: "Do not derive a token from Math.random", outcome: "match", files: [{ path: "src/session.ts", source: "const sessionToken = Math.random();" }], focusPath: "src/session.ts", expectedCount: 1, public: true }
@@ -3778,6 +3841,7 @@ function findEnclosingNames(node) {
3778
3841
  if (directBinding && parent.id.type === "Identifier") {
3779
3842
  names.push(parent.id.name);
3780
3843
  }
3844
+ return names;
3781
3845
  }
3782
3846
  if (parent.type === "Property" && parent.value === current) {
3783
3847
  const key = parent.key;
@@ -3812,7 +3876,7 @@ function findEnclosingNames(node) {
3812
3876
  if (directBinding && parent.id !== null) names.push(parent.id.name);
3813
3877
  return names;
3814
3878
  }
3815
- if (parent.type === "ExpressionStatement") {
3879
+ if (parent.type === "ExpressionStatement" || parent.type === "IfStatement" || parent.type === "ForStatement" || parent.type === "WhileStatement" || parent.type === "DoWhileStatement" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") {
3816
3880
  return names;
3817
3881
  }
3818
3882
  current = parent;
@@ -3904,6 +3968,7 @@ var no_insecure_random_id_default = createRule({
3904
3968
  if (!isMathRandomCall(node)) {
3905
3969
  return;
3906
3970
  }
3971
+ if ((ASTUtils6.findVariable(context.sourceCode.getScope(node), "Math")?.defs.length ?? 0) > 0) return;
3907
3972
  const names = findEnclosingNames(node);
3908
3973
  if (names.some(isStrongSecurityName)) {
3909
3974
  context.report({ node, messageId: "insecureRandomId" });
@@ -3921,7 +3986,7 @@ var no_insecure_random_id_default = createRule({
3921
3986
  });
3922
3987
 
3923
3988
  // src/rules/no-json-stringify-error.ts
3924
- import { ASTUtils as ASTUtils5 } from "@typescript-eslint/utils";
3989
+ import { ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
3925
3990
  var NO_JSON_STRINGIFY_ERROR_DOCUMENTATION = {
3926
3991
  summary: "Avoid generic JSON serialization that can omit native Error details.",
3927
3992
  rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
@@ -3961,7 +4026,7 @@ var BUILTIN_ERROR_CONSTRUCTORS = /* @__PURE__ */ new Set([
3961
4026
  "URIError"
3962
4027
  ]);
3963
4028
  function identifierIsProvenError(identifier, scope) {
3964
- const variable = ASTUtils5.findVariable(scope, identifier.name);
4029
+ const variable = ASTUtils7.findVariable(scope, identifier.name);
3965
4030
  if (variable === null || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
3966
4031
  const definition = variable.defs[0];
3967
4032
  if (definition?.type === "CatchClause") return true;
@@ -3970,7 +4035,7 @@ function identifierIsProvenError(identifier, scope) {
3970
4035
  return initializer?.type === "NewExpression" && initializer.callee.type === "Identifier" && BUILTIN_ERROR_CONSTRUCTORS.has(initializer.callee.name) && isGlobalIdentifier(initializer.callee.name, scope);
3971
4036
  }
3972
4037
  function isGlobalIdentifier(name, scope) {
3973
- const binding = ASTUtils5.findVariable(scope, name);
4038
+ const binding = ASTUtils7.findVariable(scope, name);
3974
4039
  return binding === null || binding.defs.length === 0;
3975
4040
  }
3976
4041
  function positiveErrorSubject(test) {
@@ -4434,105 +4499,9 @@ import * as ts from "typescript";
4434
4499
  import {
4435
4500
  AST_NODE_TYPES as AST_NODE_TYPES16
4436
4501
  } from "@typescript-eslint/utils";
4437
- function symbolAt(services, checker, node) {
4438
- return checker.getSymbolAtLocation(services.esTreeNodeToTSNodeMap.get(node));
4439
- }
4440
- function sameSymbol(left, right) {
4441
- return left !== void 0 && right !== void 0 && left === right;
4442
- }
4443
- function enclosingClass(node) {
4444
- let current = node.parent;
4445
- while (current !== void 0) {
4446
- if (current.type === AST_NODE_TYPES16.ClassDeclaration || current.type === AST_NODE_TYPES16.ClassExpression) {
4447
- return current;
4448
- }
4449
- current = current.parent;
4450
- }
4451
- return null;
4452
- }
4453
4502
  function memberName2(member) {
4454
4503
  return !member.computed && member.key.type === AST_NODE_TYPES16.Identifier ? member.key.name : null;
4455
4504
  }
4456
- function privateMemberFixes(context, services, owner, members, removePrivateKeyword) {
4457
- const first = members[0];
4458
- const name = first === void 0 ? null : memberName2(first);
4459
- if (first === void 0 || name === null || members.some((member) => member.static || member.decorators.length > 0)) {
4460
- return void 0;
4461
- }
4462
- if (owner.body.body.some((member) => {
4463
- if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return false;
4464
- return member.key.type === AST_NODE_TYPES16.PrivateIdentifier && member.key.name === name;
4465
- })) return void 0;
4466
- const selectedMembers = new Set(members);
4467
- if (owner.body.body.some((member) => {
4468
- if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return false;
4469
- return memberName2(member) === name && !selectedMembers.has(member);
4470
- })) return void 0;
4471
- const checker = services.program.getTypeChecker();
4472
- const symbols = members.map((member) => symbolAt(services, checker, member.key)).filter(
4473
- (symbol) => symbol !== void 0
4474
- );
4475
- if (symbols.length === 0) return void 0;
4476
- const references = [];
4477
- let unsafe = false;
4478
- walk(context.sourceCode.ast, context.sourceCode.visitorKeys, (node) => {
4479
- if (node.type === AST_NODE_TYPES16.Literal && node.value === name) {
4480
- unsafe = true;
4481
- return;
4482
- }
4483
- if (node.type !== AST_NODE_TYPES16.MemberExpression) return;
4484
- const propertyName6 = node.property.type === AST_NODE_TYPES16.Identifier || node.property.type === AST_NODE_TYPES16.PrivateIdentifier ? node.property.name : node.property.type === AST_NODE_TYPES16.Literal && typeof node.property.value === "string" ? node.property.value : null;
4485
- if (propertyName6 !== name) return;
4486
- const propertySymbol = symbolAt(services, checker, node.property);
4487
- if (node.computed || node.property.type !== AST_NODE_TYPES16.Identifier || node.object.type !== AST_NODE_TYPES16.ThisExpression || enclosingClass(node) !== owner || !symbols.some((symbol) => sameSymbol(symbol, propertySymbol))) {
4488
- unsafe = true;
4489
- return;
4490
- }
4491
- references.push(node);
4492
- });
4493
- if (unsafe) return void 0;
4494
- const privateKeywordRanges = /* @__PURE__ */ new Map();
4495
- if (removePrivateKeyword) {
4496
- const comments = context.sourceCode.getAllComments();
4497
- for (const member of members) {
4498
- const keyword = context.sourceCode.getTokens(member).find((token) => token.value === "private");
4499
- const next = keyword === void 0 ? void 0 : context.sourceCode.getTokenAfter(keyword);
4500
- if (keyword === void 0 || next === null || next === void 0) return void 0;
4501
- if (comments.some((comment) => comment.range[0] >= keyword.range[1] && comment.range[1] <= next.range[0])) {
4502
- return void 0;
4503
- }
4504
- privateKeywordRanges.set(member, [keyword.range[0], next.range[0]]);
4505
- }
4506
- }
4507
- return (fixer) => {
4508
- const fixes = [];
4509
- for (const member of members) {
4510
- fixes.push(fixer.replaceText(member.key, `#${name}`));
4511
- if (removePrivateKeyword) {
4512
- const range = privateKeywordRanges.get(member);
4513
- if (range === void 0) return [];
4514
- fixes.push(fixer.removeRange(range));
4515
- }
4516
- }
4517
- for (const reference of references) fixes.push(fixer.replaceText(reference.property, `#${name}`));
4518
- return fixes;
4519
- };
4520
- }
4521
- function walk(node, visitorKeys, visit) {
4522
- visit(node);
4523
- for (const key of visitorKeys[node.type] ?? []) {
4524
- const child = node[key];
4525
- if (Array.isArray(child)) {
4526
- for (const item of child) {
4527
- if (typeof item === "object" && item !== null && "type" in item) {
4528
- walk(item, visitorKeys, visit);
4529
- }
4530
- }
4531
- } else if (typeof child === "object" && child !== null && "type" in child) {
4532
- walk(child, visitorKeys, visit);
4533
- }
4534
- }
4535
- }
4536
4505
  function convertibleMemberName(member) {
4537
4506
  if (member.type !== AST_NODE_TYPES16.MethodDefinition && member.type !== AST_NODE_TYPES16.PropertyDefinition && member.type !== AST_NODE_TYPES16.AccessorProperty) return null;
4538
4507
  return memberName2(member);
@@ -4645,7 +4614,7 @@ var interface_contract_members_private_default = createRule({
4645
4614
  });
4646
4615
 
4647
4616
  // src/rules/no-log-only-catch.ts
4648
- import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
4617
+ import { AST_NODE_TYPES as AST_NODE_TYPES18, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
4649
4618
 
4650
4619
  // src/rules/_logging.ts
4651
4620
  import "@typescript-eslint/utils";
@@ -4817,7 +4786,7 @@ function seededFallbackHandled(tryStatement, scope) {
4817
4786
  if (previous.declarations.length !== 1 || declarator === void 0) return false;
4818
4787
  if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return false;
4819
4788
  if (declarator.init == null || !isSeedValue(declarator.init)) return false;
4820
- const variable = ASTUtils6.findVariable(scope, declarator.id.name);
4789
+ const variable = ASTUtils8.findVariable(scope, declarator.id.name);
4821
4790
  if (variable === null) return false;
4822
4791
  const [tryStart, tryEnd] = tryStatement.block.range;
4823
4792
  let writtenInTry = false;
@@ -4871,6 +4840,39 @@ var no_log_only_catch_default = createRule({
4871
4840
  const matcher = createLogMatcher(loggingOptions);
4872
4841
  const filename = context.filename;
4873
4842
  const sourceCode = context.sourceCode;
4843
+ function hasCoercionValidation(node) {
4844
+ const owner = node.parent;
4845
+ const statement = owner.block.body[0];
4846
+ if (node.body.body.length !== 0 || owner.finalizer !== null || owner.block.body.length !== 1 || statement?.type !== AST_NODE_TYPES18.ExpressionStatement || statement.expression.type !== AST_NODE_TYPES18.AssignmentExpression || statement.expression.operator !== "=") return false;
4847
+ const { left, right } = statement.expression;
4848
+ if (left.type !== AST_NODE_TYPES18.MemberExpression || left.computed || left.object.type !== AST_NODE_TYPES18.Identifier || right.type !== AST_NODE_TYPES18.CallExpression || right.optional || right.callee.type !== AST_NODE_TYPES18.Identifier || !["String", "Number", "Boolean", "BigInt"].includes(right.callee.name) || right.arguments.length !== 1) return false;
4849
+ const argument = right.arguments[0];
4850
+ if (argument === void 0 || sourceCode.getText(left) !== sourceCode.getText(argument)) return false;
4851
+ const global = ASTUtils8.findVariable(sourceCode.getScope(right.callee), right.callee.name);
4852
+ if (global !== null && global.defs.length > 0) return false;
4853
+ const root = ASTUtils8.findVariable(sourceCode.getScope(left.object), left.object.name);
4854
+ if (root === null || root.references.some((reference) => reference.isWrite() && !reference.init)) return false;
4855
+ let current = owner;
4856
+ let slot = statementSlot(current);
4857
+ while (slot === null && current.parent !== void 0 && !FUNCTION_TYPES2.has(current.parent.type)) {
4858
+ current = current.parent;
4859
+ slot = statementSlot(current);
4860
+ }
4861
+ let next = slot?.list[slot.index + 1];
4862
+ let target = sourceCode.getText(left);
4863
+ if (next?.type === AST_NODE_TYPES18.VariableDeclaration && next.kind === "const" && next.declarations.length === 1) {
4864
+ const alias = next.declarations[0];
4865
+ if (alias?.id.type !== AST_NODE_TYPES18.Identifier || alias.init === null || sourceCode.getText(alias.init) !== target) return false;
4866
+ target = alias.id.name;
4867
+ next = slot?.list[slot.index + 2];
4868
+ }
4869
+ if (next?.type !== AST_NODE_TYPES18.IfStatement) return false;
4870
+ let condition = next.test;
4871
+ while (condition.type === AST_NODE_TYPES18.LogicalExpression && condition.operator === "&&") condition = condition.left;
4872
+ if (condition.type !== AST_NODE_TYPES18.BinaryExpression || !["==", "==="].includes(condition.operator)) return false;
4873
+ const test = condition.left;
4874
+ return test.type === AST_NODE_TYPES18.UnaryExpression && test.operator === "typeof" && sourceCode.getText(test.argument) === target && condition.right.type === AST_NODE_TYPES18.Literal && condition.right.value === right.callee.name.toLowerCase();
4875
+ }
4874
4876
  function isLoggingCallStatement(statement) {
4875
4877
  if (statement.type !== "ExpressionStatement") {
4876
4878
  return false;
@@ -4897,17 +4899,11 @@ var no_log_only_catch_default = createRule({
4897
4899
  CatchClause(node) {
4898
4900
  const statements = node.body.body;
4899
4901
  const isDocumented = sourceCode.getCommentsInside(node.body).length > 0 || hasAdjacentRationale(node);
4900
- if (statements.length === 0) {
4901
- if (isDocumented) {
4902
- return;
4903
- }
4904
- if (fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4905
- return;
4906
- }
4907
- context.report({ node, messageId: "emptyCatch" });
4902
+ if (isDocumented || hasCoercionValidation(node) || fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4908
4903
  return;
4909
4904
  }
4910
- if (isDocumented) {
4905
+ if (statements.length === 0) {
4906
+ context.report({ node, messageId: "emptyCatch" });
4911
4907
  return;
4912
4908
  }
4913
4909
  const everyStatementIsLogging = statements.every(
@@ -4922,10 +4918,10 @@ var no_log_only_catch_default = createRule({
4922
4918
  });
4923
4919
 
4924
4920
  // src/rules/no-bare-return-from-test-catch.ts
4925
- import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
4921
+ import { AST_NODE_TYPES as AST_NODE_TYPES19, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
4926
4922
  var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
4927
4923
  summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
4928
- rationale: "The caught failure turns into a passing test without executing the assertion that follows it.",
4924
+ rationale: "An unasserted catch return can swallow a failure and skip later assertions; the complete test result also depends on other assertions and hooks.",
4929
4925
  remediation: "Rethrow the error, assert on it, or use the runner's explicit skip mechanism when the capability is optional.",
4930
4926
  category: "testing",
4931
4927
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
@@ -4947,7 +4943,7 @@ function staticMemberName2(node) {
4947
4943
  return null;
4948
4944
  }
4949
4945
  function importedName3(identifier, context, modules) {
4950
- const variable = ASTUtils7.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4946
+ const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4951
4947
  if (variable === null || variable.defs.length === 0) return identifier.name;
4952
4948
  for (const definition of variable.defs) {
4953
4949
  if (definition.node.type !== AST_NODE_TYPES19.ImportSpecifier) continue;
@@ -5012,7 +5008,7 @@ var no_bare_return_from_test_catch_default = createRule({
5012
5008
  type: "problem",
5013
5009
  docs: { description: "Disallow a bare return from a test catch block when it skips a later assertion." },
5014
5010
  schema: [],
5015
- messages: { bareReturnFromTestCatch: "This bare return turns the caught failure into a passing test and skips a later assertion. Rethrow, assert on the error, or explicitly skip the test." }
5011
+ messages: { bareReturnFromTestCatch: "This bare return can swallow the caught failure and skips a later assertion. Rethrow, assert on the error, or explicitly skip the test." }
5016
5012
  },
5017
5013
  defaultOptions: [],
5018
5014
  create(context) {
@@ -5031,6 +5027,23 @@ var no_bare_return_from_test_catch_default = createRule({
5031
5027
  if (current === null || current === void 0) break;
5032
5028
  }
5033
5029
  if (catchClause === null || catchClause.parent.finalizer !== null) return;
5030
+ const parameter = catchClause.param;
5031
+ if (parameter?.type === AST_NODE_TYPES19.Identifier && node.parent === catchClause.body) {
5032
+ const errorBinding = ASTUtils9.findVariable(context.sourceCode.getScope(parameter), parameter.name);
5033
+ const assertedError = catchClause.body.body.some((statement) => {
5034
+ if (statement.range[1] >= node.range[0] || statement.type !== AST_NODE_TYPES19.ExpressionStatement) return false;
5035
+ const expression = statement.expression;
5036
+ if (expression.type !== AST_NODE_TYPES19.CallExpression || !isAssertion(expression, context)) return false;
5037
+ const root = rootIdentifier2(expression.callee);
5038
+ if (root === null) return false;
5039
+ const assertionName = importedName3(root, context, ASSERTION_MODULES);
5040
+ let operand = expression.callee.type === AST_NODE_TYPES19.MemberExpression ? expression.callee.object : null;
5041
+ if (operand?.type === AST_NODE_TYPES19.MemberExpression && staticMemberName2(operand) === "not") operand = operand.object;
5042
+ if (assertionName !== "assert" && (assertionName !== "expect" || operand?.type !== AST_NODE_TYPES19.CallExpression || operand.callee !== root)) return false;
5043
+ return walkOwnScope(expression, (current) => current.type === AST_NODE_TYPES19.Identifier && errorBinding?.references.some((reference) => reference.identifier === current) === true);
5044
+ });
5045
+ if (assertedError) return;
5046
+ }
5034
5047
  if (walkOwnScope(catchClause.body, (current) => current.type === AST_NODE_TYPES19.ThrowStatement || isExplicitSkip(current, context))) return;
5035
5048
  if (!walkOwnScope(owner.body, (current) => current.range[0] > node.range[1] && isAssertion(current, context))) return;
5036
5049
  context.report({ node, messageId: "bareReturnFromTestCatch" });
@@ -5040,15 +5053,15 @@ var no_bare_return_from_test_catch_default = createRule({
5040
5053
  });
5041
5054
 
5042
5055
  // src/rules/no-bespoke-api-case-conversion.ts
5043
- import { AST_NODE_TYPES as AST_NODE_TYPES20 } from "@typescript-eslint/utils";
5056
+ import { AST_NODE_TYPES as AST_NODE_TYPES20, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
5044
5057
  var NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION = {
5045
- summary: "Disallow hand-written snake_case/camelCase object-key translation at a proven API adapter boundary.",
5046
- rationale: "A second, hand-maintained representation of an API wire contract drifts from the generated client and makes backend field renames compile successfully while failing at runtime.",
5058
+ summary: "Review direct snake_case/camelCase mirror mappings on explicitly API-typed adapter values.",
5059
+ rationale: "Duplicating wire-name translation can drift from an API client contract. When the SDK owns application-facing names, centralizing conversion avoids maintaining another mirror by hand.",
5047
5060
  remediation: "Move wire-name ownership and case conversion into the generated SDK/model layer; keep application adapters on the generated typed surface.",
5048
5061
  category: "architecture",
5049
5062
  autofix: "none",
5050
5063
  limitations: [
5051
- "Only files named adapter/adapters that import an API, client, SDK, contract, or generated module are checked.",
5064
+ "Only adapter/adapters files and receivers explicitly annotated with a scope-resolved type imported from an API/client/SDK/contract/generated module are checked. Unrelated imports, local type shadows, reassigned receivers and inferred receiver types are excluded.",
5052
5065
  "Only object properties that directly translate the same identifier between snake_case and lowerCamelCase are reported.",
5053
5066
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and indirect conversions are intentionally excluded."
5054
5067
  ],
@@ -5120,7 +5133,7 @@ var no_bespoke_api_case_conversion_default = createRule({
5120
5133
  docs: { description: NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION.summary },
5121
5134
  schema: [],
5122
5135
  messages: {
5123
- noBespokeApiCaseConversion: "This API adapter manually translates `{{wireName}}` and `{{applicationName}}`; make the generated SDK/model layer own wire-name conversion."
5136
+ noBespokeApiCaseConversion: "This API-typed adapter value mirrors `{{wireName}}` and `{{applicationName}}`. If the SDK owns application-facing names, move this conversion to its model boundary."
5124
5137
  }
5125
5138
  },
5126
5139
  defaultOptions: [],
@@ -5130,16 +5143,33 @@ var no_bespoke_api_case_conversion_default = createRule({
5130
5143
  if (!ADAPTER_BASENAME_RE.test(basename) || isGeneratedFile(filename, context.sourceCode.text) || isTestFile(filename, ["fixtureTree"])) {
5131
5144
  return {};
5132
5145
  }
5133
- const provenApiBoundary = context.sourceCode.ast.body.some(
5134
- (statement) => statement.type === AST_NODE_TYPES20.ImportDeclaration && API_BOUNDARY_IMPORT_RE.test(statement.source.value)
5135
- );
5136
- if (!provenApiBoundary) return {};
5146
+ const hasApiReceiver = (value) => {
5147
+ let current = value;
5148
+ while (true) {
5149
+ if (current.type === AST_NODE_TYPES20.MemberExpression) current = current.object;
5150
+ else if (current.type === AST_NODE_TYPES20.TSAsExpression || current.type === AST_NODE_TYPES20.TSNonNullExpression || current.type === AST_NODE_TYPES20.TSTypeAssertion) current = current.expression;
5151
+ else break;
5152
+ }
5153
+ if (current.type !== AST_NODE_TYPES20.Identifier) return false;
5154
+ const binding = ASTUtils10.findVariable(context.sourceCode.getScope(current), current.name);
5155
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
5156
+ const identifier = binding.defs[0]?.name;
5157
+ if (identifier?.type !== AST_NODE_TYPES20.Identifier) return false;
5158
+ const annotation = identifier.typeAnnotation?.typeAnnotation;
5159
+ if (annotation?.type !== AST_NODE_TYPES20.TSTypeReference) return false;
5160
+ let typeName = annotation.typeName;
5161
+ while (typeName.type === AST_NODE_TYPES20.TSQualifiedName) typeName = typeName.left;
5162
+ if (typeName.type !== AST_NODE_TYPES20.Identifier) return false;
5163
+ const typeBinding = ASTUtils10.findVariable(context.sourceCode.getScope(typeName), typeName.name);
5164
+ return typeBinding?.defs.length === 1 && typeBinding.defs[0]?.type === "ImportBinding" && typeBinding.defs[0].parent.type === AST_NODE_TYPES20.ImportDeclaration && API_BOUNDARY_IMPORT_RE.test(typeBinding.defs[0].parent.source.value);
5165
+ };
5137
5166
  return {
5138
5167
  Property(node) {
5139
5168
  if (node.computed || node.method || node.shorthand) return;
5140
5169
  const key = propertyName(node.key);
5141
5170
  const value = memberName3(node.value);
5142
5171
  if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
5172
+ if (!hasApiReceiver(node.value)) return;
5143
5173
  const wireName = SNAKE_CASE_RE.test(key) ? key : value;
5144
5174
  const applicationName = wireName === key ? value : key;
5145
5175
  context.report({
@@ -5262,7 +5292,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5262
5292
  limitations: [
5263
5293
  "Only ESLint disable comments and TypeScript expect-error directives are checked.",
5264
5294
  "The rule uses a small anchored vocabulary and does not score prose quality generally.",
5265
- "Generated files and descriptions containing any concrete context are excluded."
5295
+ "Generated files and descriptions outside that exact vocabulary are excluded; an unflagged reason is not proof of a justified suppression. Missing descriptions remain owned by the upstream description requirement."
5266
5296
  ],
5267
5297
  examples: [
5268
5298
  {
@@ -5272,7 +5302,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5272
5302
  files: [
5273
5303
  {
5274
5304
  path: "src/adapter.ts",
5275
- source: "// @ts-expect-error -- vendor types omit the runtime requestId field\nreturn response.requestId;"
5305
+ source: "function requestId(response: object) {\n // @ts-expect-error -- vendor types omit the runtime requestId field\n return response.requestId;\n}"
5276
5306
  }
5277
5307
  ],
5278
5308
  focusPath: "src/adapter.ts",
@@ -5286,7 +5316,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5286
5316
  files: [
5287
5317
  {
5288
5318
  path: "src/adapter.ts",
5289
- source: "// @ts-expect-error -- false positive\nreturn response.requestId;"
5319
+ source: "function requestId(response: object) {\n // @ts-expect-error -- false positive\n return response.requestId;\n}"
5290
5320
  }
5291
5321
  ],
5292
5322
  focusPath: "src/adapter.ts",
@@ -5331,15 +5361,15 @@ var no_vague_suppression_description_default = createRule({
5331
5361
  });
5332
5362
 
5333
5363
  // src/rules/no-generic-single-export-module.ts
5334
- import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
5364
+ import { AST_NODE_TYPES as AST_NODE_TYPES22, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
5335
5365
  var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
5336
5366
  summary: "Disallow generic module stems when one runtime export already names the responsibility.",
5337
5367
  rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
5338
5368
  remediation: "Choose a responsibility-bearing module name or colocate the export with its domain.",
5339
5369
  category: "maintainability",
5340
- limitations: ["Only configured generic stems with exactly one public runtime export are reported."],
5370
+ limitations: ["Only the fixed generic-stem vocabulary with exactly one public runtime export is checked; exported destructuring patterns are excluded rather than undercounted."],
5341
5371
  examples: [
5342
- { id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/order-parser.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/order-parser.ts", expectedCount: 0, public: true },
5372
+ { id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/parse-order.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/parse-order.ts", expectedCount: 0, public: true },
5343
5373
  { id: "generic-module-name", title: "Do not hide one export in a generic module", outcome: "match", files: [{ path: "src/utils.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/utils.ts", expectedCount: 1, public: true }
5344
5374
  ]
5345
5375
  };
@@ -5400,6 +5430,7 @@ function runtimeExports(program) {
5400
5430
  }
5401
5431
  if (statement.declaration !== null) {
5402
5432
  const declaration = statement.declaration;
5433
+ if (declaration.type === AST_NODE_TYPES22.VariableDeclaration && declaration.declarations.some((item) => item.id.type !== AST_NODE_TYPES22.Identifier)) ambiguous = true;
5403
5434
  exports.push(...declaredNames(declaration).map((name) => ({ key: name, name, node: declaration })));
5404
5435
  }
5405
5436
  for (const specifier of statement.specifiers) {
@@ -5457,7 +5488,7 @@ function typeOnlyBindings(program) {
5457
5488
  return new Set([...names].filter((name) => !runtimeNames.has(name)));
5458
5489
  }
5459
5490
  function isGlobalIdentifier2(context, node) {
5460
- const variable = ASTUtils8.findVariable(context.sourceCode.getScope(node), node.name);
5491
+ const variable = ASTUtils11.findVariable(context.sourceCode.getScope(node), node.name);
5461
5492
  return variable === null || variable.defs.length === 0;
5462
5493
  }
5463
5494
  function isConventionalFrameworkUtility(filename, exported) {
@@ -5518,11 +5549,11 @@ var no_generic_single_export_module_default = createRule({
5518
5549
  // src/rules/no-offset-pagination.ts
5519
5550
  import "@typescript-eslint/utils";
5520
5551
  var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5521
- summary: "Disallow OFFSET pagination in embedded SQL; it is O(N) per page and drops or repeats rows under concurrent writes. Use a keyset cursor.",
5552
+ summary: "Prefer keyset pagination for embedded SQL queries using OFFSET.",
5522
5553
  rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
5523
- remediation: "Page with a stable ordered key and a cursor predicate.",
5554
+ remediation: "Consider a keyset cursor that preserves the query's complete ordering, tie-breakers, and filters.",
5524
5555
  category: "performance",
5525
- limitations: ["Only embedded SQL is inspected; test files and non-pagination OFFSET syntax are excluded."],
5556
+ limitations: ["A SELECT/FROM query shape or adjacent LIMIT/OFFSET fragment is required. Isolated OFFSET fragments and test files are excluded; this lexical context does not prove a database execution sink. Performance and concurrent-write behavior depend on indexes, ordering, isolation, and dialect; bounded pages and random page access can justify OFFSET."],
5526
5557
  examples: [
5527
5558
  { id: "keyset-pagination", title: "Page from a stable cursor", outcome: "no-match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT id FROM runs WHERE id > ? ORDER BY id LIMIT ?`).all();" }], focusPath: "src/runs.ts", expectedCount: 0, public: true },
5528
5559
  { id: "offset-pagination", title: "Do not page by offset", outcome: "match", files: [{ path: "src/runs.ts", source: "db.query(`SELECT id FROM runs ORDER BY id LIMIT ? OFFSET ?`);" }], focusPath: "src/runs.ts", expectedCount: 1, public: true }
@@ -5530,17 +5561,18 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5530
5561
  };
5531
5562
  var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
5532
5563
  var OFFSET_GATE = /offset/i;
5564
+ var PAGINATION_CONTEXT = /\bSELECT\b[\s\S]*\bFROM\b[\s\S]*\bOFFSET\b|\bLIMIT\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)\s+OFFSET\b/i;
5533
5565
  var no_offset_pagination_default = createRule({
5534
5566
  name: "no-offset-pagination",
5535
5567
  documentation: NO_OFFSET_PAGINATION_DOCUMENTATION,
5536
5568
  meta: {
5537
5569
  type: "problem",
5538
5570
  docs: {
5539
- description: "Disallow OFFSET pagination in embedded SQL; it is O(N) per page and drops or repeats rows under concurrent writes. Use a keyset cursor."
5571
+ description: "Prefer keyset pagination for embedded SQL queries using OFFSET."
5540
5572
  },
5541
5573
  schema: [],
5542
5574
  messages: {
5543
- noOffsetPagination: "OFFSET pagination scans and discards every skipped row (O(N) per page) and shifts under concurrent inserts, so rows get repeated or missed. Use a keyset cursor: `WHERE id > ? ORDER BY id LIMIT ?`."
5575
+ noOffsetPagination: "Review OFFSET pagination for large or changing result sets. If a keyset cursor fits the access pattern, preserve the query's complete ordering, tie-breakers, and filters; bounded pages or random page access may justify OFFSET."
5544
5576
  }
5545
5577
  },
5546
5578
  defaultOptions: [],
@@ -5549,7 +5581,7 @@ var no_offset_pagination_default = createRule({
5549
5581
  return {};
5550
5582
  }
5551
5583
  return createSqlListener((sql, node) => {
5552
- if (!OFFSET_PAGINATION.test(sql)) {
5584
+ if (!PAGINATION_CONTEXT.test(sql) || !OFFSET_PAGINATION.test(sql)) {
5553
5585
  return;
5554
5586
  }
5555
5587
  context.report({ node, messageId: "noOffsetPagination" });
@@ -5558,7 +5590,7 @@ var no_offset_pagination_default = createRule({
5558
5590
  });
5559
5591
 
5560
5592
  // src/rules/no-positional-tuple-return.ts
5561
- import { AST_NODE_TYPES as AST_NODE_TYPES23 } from "@typescript-eslint/utils";
5593
+ import { AST_NODE_TYPES as AST_NODE_TYPES23, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
5562
5594
  var NO_POSITIONAL_TUPLE_RETURN_DOCUMENTATION = {
5563
5595
  summary: "Disallow returning a multi-field tuple from a named function; return a named object so call sites cannot mismatch slots.",
5564
5596
  rationale: "Tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
@@ -5588,7 +5620,7 @@ function tupleReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
5588
5620
  return argument === void 0 ? null : tupleReturnType(argument, aliases, resolving);
5589
5621
  }
5590
5622
  if (node.type === AST_NODE_TYPES23.TSTypeReference && node.typeName.type === AST_NODE_TYPES23.Identifier && !resolving.has(node.typeName.name)) {
5591
- const target = aliases.get(node.typeName.name);
5623
+ const target = aliases.get(node.typeName);
5592
5624
  if (target !== void 0) return tupleReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
5593
5625
  }
5594
5626
  if (node.type === AST_NODE_TYPES23.TSTypeOperator && node.operator === "readonly") {
@@ -5680,20 +5712,27 @@ function exportedTypeNames(program) {
5680
5712
  }
5681
5713
  return names;
5682
5714
  }
5683
- function typeAliases(program) {
5715
+ function typeAliases(sourceCode) {
5684
5716
  const aliases = /* @__PURE__ */ new Map();
5685
- for (const statement of program.body) {
5717
+ for (const statement of sourceCode.ast.body) {
5686
5718
  const declaration = statement.type === AST_NODE_TYPES23.ExportNamedDeclaration ? statement.declaration : statement;
5687
5719
  if (declaration?.type === AST_NODE_TYPES23.TSTypeAliasDeclaration) {
5688
- aliases.set(declaration.id.name, declaration.typeAnnotation);
5720
+ aliases.set(declaration.id.name, declaration);
5689
5721
  }
5690
5722
  }
5691
- return aliases;
5723
+ return {
5724
+ get(identifier) {
5725
+ const declaration = aliases.get(identifier.name);
5726
+ if (declaration === void 0) return void 0;
5727
+ const binding = ASTUtils12.findVariable(sourceCode.getScope(identifier), identifier.name);
5728
+ return binding?.defs.length === 1 && binding.defs[0]?.node === declaration ? declaration.typeAnnotation : void 0;
5729
+ }
5730
+ };
5692
5731
  }
5693
5732
  function callableReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
5694
5733
  if (node.type === AST_NODE_TYPES23.TSFunctionType) return node.returnType?.typeAnnotation ?? null;
5695
5734
  if (node.type === AST_NODE_TYPES23.TSTypeReference && node.typeName.type === AST_NODE_TYPES23.Identifier && !resolving.has(node.typeName.name)) {
5696
- const target = aliases.get(node.typeName.name);
5735
+ const target = aliases.get(node.typeName);
5697
5736
  if (target !== void 0) {
5698
5737
  return callableReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
5699
5738
  }
@@ -5822,7 +5861,7 @@ var no_positional_tuple_return_default = createRule({
5822
5861
  context.sourceCode.ast,
5823
5862
  exportedTypeNames(context.sourceCode.ast)
5824
5863
  );
5825
- const aliases = typeAliases(context.sourceCode.ast);
5864
+ const aliases = typeAliases(context.sourceCode);
5826
5865
  const reportedFunctions = /* @__PURE__ */ new WeakSet();
5827
5866
  const functionStack = [];
5828
5867
  const report2 = (annotation, name) => {
@@ -5986,7 +6025,7 @@ var no_production_browser_source_maps_default = createRule({
5986
6025
  });
5987
6026
 
5988
6027
  // src/rules/no-raw-env.ts
5989
- import { ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
6028
+ import { ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
5990
6029
  var NO_RAW_ENV_DOCUMENTATION = {
5991
6030
  summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
5992
6031
  rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
@@ -6068,7 +6107,7 @@ var no_raw_env_default = createRule({
6068
6107
  },
6069
6108
  MemberExpression(node) {
6070
6109
  if (isProcessEnv(node) && node.object.type === "Identifier") {
6071
- const binding = ASTUtils9.findVariable(context.sourceCode.getScope(node), node.object.name);
6110
+ const binding = ASTUtils13.findVariable(context.sourceCode.getScope(node), node.object.name);
6072
6111
  if (binding !== null && binding.defs.length > 0 && !binding.defs.every((definition) => definition.type === "ImportBinding" && definition.parent.type === "ImportDeclaration" && ["node:process", "process"].includes(definition.parent.source.value) && ["ImportDefaultSpecifier", "ImportNamespaceSpecifier"].includes(definition.node.type))) return;
6073
6112
  }
6074
6113
  if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
@@ -6084,7 +6123,7 @@ var no_raw_env_default = createRule({
6084
6123
  });
6085
6124
 
6086
6125
  // src/rules/no-raw-fetch-outside-clients.ts
6087
- import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
6126
+ import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
6088
6127
  var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
6089
6128
  summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
6090
6129
  rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
@@ -6272,7 +6311,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6272
6311
  internalApiPrefixes.push(`${options.basePath}/api`);
6273
6312
  }
6274
6313
  function resolvesToGlobal(identifier) {
6275
- const variable = ASTUtils10.findVariable(
6314
+ const variable = ASTUtils14.findVariable(
6276
6315
  context.sourceCode.getScope(identifier),
6277
6316
  identifier.name
6278
6317
  );
@@ -6281,7 +6320,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6281
6320
  function resolveNode2(node) {
6282
6321
  if (node === void 0) return null;
6283
6322
  if (node.type !== AST_NODE_TYPES24.Identifier) return node;
6284
- const variable = ASTUtils10.findVariable(
6323
+ const variable = ASTUtils14.findVariable(
6285
6324
  context.sourceCode.getScope(node),
6286
6325
  node.name
6287
6326
  );
@@ -6360,13 +6399,13 @@ var no_raw_fetch_outside_clients_default = createRule({
6360
6399
  });
6361
6400
 
6362
6401
  // src/rules/no-restricted-library-load.ts
6363
- import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
6402
+ import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
6364
6403
  var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
6365
- summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
6366
- rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
6367
- remediation: "Load the configured replacement library instead of the restricted module.",
6404
+ summary: "Apply configured library restrictions to literal runtime loads and CommonJS resolution references.",
6405
+ rationale: "Dynamic imports, CommonJS loads, and package resolution checks can bypass library restrictions enforced for static imports.",
6406
+ remediation: "Use the configured replacement for the runtime dependency reference; resolution checks do not themselves load a module.",
6368
6407
  category: "architecture",
6369
- limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
6408
+ limitations: ["Only literal dynamic imports, unshadowed CommonJS loads/resolution calls, and runtime TypeScript import-equals declarations are checked; erased type imports are excluded. A configured restriction list is required."],
6370
6409
  examples: [
6371
6410
  { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
6372
6411
  { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
@@ -6384,7 +6423,7 @@ var no_restricted_library_load_default = createRule({
6384
6423
  meta: {
6385
6424
  type: "problem",
6386
6425
  docs: {
6387
- description: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations."
6426
+ description: NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION.summary
6388
6427
  },
6389
6428
  schema: [
6390
6429
  {
@@ -6410,7 +6449,7 @@ var no_restricted_library_load_default = createRule({
6410
6449
  }
6411
6450
  ],
6412
6451
  messages: {
6413
- restrictedLibraryLoad: "{{id}}: Replace runtime loading of {{module}} with {{replacement}}.{{note}}"
6452
+ restrictedLibraryLoad: "{{id}}: Replace this runtime dependency reference to {{module}} with {{replacement}}.{{note}}"
6414
6453
  }
6415
6454
  },
6416
6455
  defaultOptions: [{ libraries: [] }],
@@ -6433,7 +6472,7 @@ var no_restricted_library_load_default = createRule({
6433
6472
  });
6434
6473
  }
6435
6474
  function isUnshadowedRequire(node) {
6436
- const variable = ASTUtils11.findVariable(
6475
+ const variable = ASTUtils15.findVariable(
6437
6476
  context.sourceCode.getScope(node),
6438
6477
  node.name
6439
6478
  );
@@ -6456,6 +6495,7 @@ var no_restricted_library_load_default = createRule({
6456
6495
  if (source !== null) report2(node.arguments[0], source);
6457
6496
  },
6458
6497
  TSImportEqualsDeclaration(node) {
6498
+ if (node.importKind === "type") return;
6459
6499
  if (node.moduleReference.type !== AST_NODE_TYPES25.TSExternalModuleReference) return;
6460
6500
  const source = literalModule(node.moduleReference.expression);
6461
6501
  if (source !== null) report2(node.moduleReference.expression, source);
@@ -6465,7 +6505,7 @@ var no_restricted_library_load_default = createRule({
6465
6505
  });
6466
6506
 
6467
6507
  // src/rules/no-router-refresh-polling.ts
6468
- import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
6508
+ import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
6469
6509
  var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
6470
6510
  summary: "Do not poll by calling a Next.js router's refresh method from a timer.",
6471
6511
  rationale: "A route refresh refetches and rerenders the whole route on every tick instead of loading the named resource that changed.",
@@ -6495,7 +6535,7 @@ function isIntervalCallee(sourceCode, node) {
6495
6535
  return node.type === AST_NODE_TYPES26.Identifier && node.name === "setInterval" && isUnshadowedGlobal2(sourceCode, node) || node.type === AST_NODE_TYPES26.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES26.Identifier && (node.object.name === "window" || node.object.name === "globalThis") && isUnshadowedGlobal2(sourceCode, node.object) && node.property.type === AST_NODE_TYPES26.Identifier && node.property.name === "setInterval";
6496
6536
  }
6497
6537
  function isUnshadowedGlobal2(sourceCode, node) {
6498
- const variable = ASTUtils12.findVariable(sourceCode.getScope(node), node.name);
6538
+ const variable = ASTUtils16.findVariable(sourceCode.getScope(node), node.name);
6499
6539
  return variable === null || variable.defs.length === 0;
6500
6540
  }
6501
6541
  var no_router_refresh_polling_default = createRule({
@@ -6518,21 +6558,21 @@ var no_router_refresh_polling_default = createRule({
6518
6558
  if (node.source.value !== "next/navigation") return;
6519
6559
  for (const specifier of node.specifiers) {
6520
6560
  if (specifier.type === AST_NODE_TYPES26.ImportSpecifier && importedName4(specifier) === "useRouter") {
6521
- const variable = ASTUtils12.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6561
+ const variable = ASTUtils16.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6522
6562
  if (variable !== null) routerHooks.add(variable);
6523
6563
  }
6524
6564
  }
6525
6565
  },
6526
6566
  VariableDeclarator(node) {
6527
6567
  if (node.id.type === AST_NODE_TYPES26.Identifier && node.init?.type === AST_NODE_TYPES26.CallExpression && node.init.callee.type === AST_NODE_TYPES26.Identifier) {
6528
- const hook = ASTUtils12.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
6529
- const router = ASTUtils12.findVariable(context.sourceCode.getScope(node.id), node.id.name);
6568
+ const hook = ASTUtils16.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
6569
+ const router = ASTUtils16.findVariable(context.sourceCode.getScope(node.id), node.id.name);
6530
6570
  if (hook !== null && router !== null && routerHooks.has(hook)) routers.add(router);
6531
6571
  }
6532
6572
  },
6533
6573
  CallExpression(node) {
6534
6574
  if (node.callee.type !== AST_NODE_TYPES26.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES26.Identifier || node.callee.property.type !== AST_NODE_TYPES26.Identifier || node.callee.property.name !== "refresh") return;
6535
- const router = ASTUtils12.findVariable(
6575
+ const router = ASTUtils16.findVariable(
6536
6576
  context.sourceCode.getScope(node.callee.object),
6537
6577
  node.callee.object.name
6538
6578
  );
@@ -6572,7 +6612,7 @@ var NO_REPEATED_STRING_LITERAL_DOCUMENTATION = {
6572
6612
  ]
6573
6613
  };
6574
6614
  function isStructured(value) {
6575
- return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
6615
+ return SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
6576
6616
  }
6577
6617
  function preview(value) {
6578
6618
  const oneLine = value.replaceAll("\n", " ").trim();
@@ -6593,7 +6633,7 @@ function isScaffolding(node) {
6593
6633
  }
6594
6634
  const isNonComputedPropertyKey = (parent.type === AST_NODE_TYPES27.Property || parent.type === AST_NODE_TYPES27.PropertyDefinition || parent.type === AST_NODE_TYPES27.MethodDefinition || parent.type === AST_NODE_TYPES27.AccessorProperty) && parent.key === node && !parent.computed;
6595
6635
  const isRequireSource = parent.type === AST_NODE_TYPES27.CallExpression && parent.callee.type === AST_NODE_TYPES27.Identifier && parent.callee.name === "require";
6596
- return parent.type === AST_NODE_TYPES27.ImportDeclaration || parent.type === AST_NODE_TYPES27.ImportExpression || parent.type === AST_NODE_TYPES27.ExportNamedDeclaration || parent.type === AST_NODE_TYPES27.ExportAllDeclaration || parent.type === AST_NODE_TYPES27.TSImportType || parent.type === AST_NODE_TYPES27.JSXAttribute || parent.type === AST_NODE_TYPES27.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
6636
+ return parent.type === AST_NODE_TYPES27.ImportDeclaration || parent.type === AST_NODE_TYPES27.ImportExpression || parent.type === AST_NODE_TYPES27.ExportNamedDeclaration || parent.type === AST_NODE_TYPES27.ExportAllDeclaration || parent.type === AST_NODE_TYPES27.TSImportType || parent.type === AST_NODE_TYPES27.JSXAttribute || parent.type === AST_NODE_TYPES27.JSXExpressionContainer && parent.parent.type === AST_NODE_TYPES27.JSXAttribute || parent.type === AST_NODE_TYPES27.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
6597
6637
  }
6598
6638
  var no_repeated_string_literal_default = createRule({
6599
6639
  name: "no-repeated-string-literal",
@@ -7513,11 +7553,11 @@ var no_server_env_in_client_component_default = createRule({
7513
7553
  // src/rules/no-select-star.ts
7514
7554
  import "@typescript-eslint/utils";
7515
7555
  var NO_SELECT_STAR_DOCUMENTATION = {
7516
- summary: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently.",
7556
+ summary: "Prefer explicit column projections over SELECT * in embedded SQL.",
7517
7557
  rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
7518
7558
  remediation: "List every required column explicitly in the projection.",
7519
7559
  category: "correctness",
7520
- limitations: ["Only statically visible embedded SQL is checked; function arguments such as COUNT(*) and stars inside EXISTS are excluded."],
7560
+ limitations: ["Only statically visible embedded SQL is checked; quoted strings (including PostgreSQL dollar strings), comments, function arguments such as COUNT(*), and stars inside EXISTS are excluded. This is a bounded lexical scan, not a complete SQL parser."],
7521
7561
  examples: [
7522
7562
  { id: "explicit-projection", title: "Select the required columns", outcome: "no-match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT id, status FROM runs`).all();" }], focusPath: "src/runs.ts", expectedCount: 0, public: true },
7523
7563
  { id: "wildcard-projection", title: "Do not select every column", outcome: "match", files: [{ path: "src/runs.ts", source: "db.prepare(`SELECT * FROM runs`).all();" }], focusPath: "src/runs.ts", expectedCount: 1, public: true }
@@ -7568,7 +7608,7 @@ var no_select_star_default = createRule({
7568
7608
  meta: {
7569
7609
  type: "problem",
7570
7610
  docs: {
7571
- description: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently."
7611
+ description: "Prefer explicit column projections over SELECT * in embedded SQL."
7572
7612
  },
7573
7613
  schema: [],
7574
7614
  messages: {
@@ -7590,13 +7630,13 @@ var no_select_star_default = createRule({
7590
7630
  });
7591
7631
 
7592
7632
  // src/rules/no-sentinel-return-on-catch.ts
7593
- import { AST_NODE_TYPES as AST_NODE_TYPES30 } from "@typescript-eslint/utils";
7633
+ import { AST_NODE_TYPES as AST_NODE_TYPES30, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
7594
7634
  var NO_SENTINEL_RETURN_ON_CATCH_DOCUMENTATION = {
7595
7635
  summary: "Disallow swallowing a caught error by returning an empty sentinel unless the error is handled or the sentinel is part of the function contract.",
7596
7636
  rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
7597
7637
  remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
7598
7638
  category: "correctness",
7599
- limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
7639
+ limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded. Locally shadowed undefined bindings are not treated as sentinels; recognized handling patterns are not a proof that every control-flow path handles the error."],
7600
7640
  examples: [
7601
7641
  { id: "reported-fallback", title: "Report an error before returning a fallback", outcome: "no-match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch (error) { logger.warn('load failed', error); return null; } }" }], focusPath: "src/load.ts", expectedCount: 0, public: true },
7602
7642
  { id: "silent-fallback", title: "Do not turn an unreported error into absence", outcome: "match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch { return null; } }" }], focusPath: "src/load.ts", expectedCount: 1, public: true }
@@ -8011,6 +8051,8 @@ var no_sentinel_return_on_catch_default = createRule({
8011
8051
  if (!isSentinelArgument(last.argument)) {
8012
8052
  return;
8013
8053
  }
8054
+ const returned = unwrapSentinelExpression(last.argument);
8055
+ if (returned?.type === AST_NODE_TYPES30.Identifier && returned.name === "undefined" && (ASTUtils17.findVariable(context.sourceCode.getScope(returned), returned.name)?.defs.length ?? 0) > 0) return;
8014
8056
  if (containsThrow(node.body)) {
8015
8057
  return;
8016
8058
  }
@@ -8044,13 +8086,13 @@ var no_sentinel_return_on_catch_default = createRule({
8044
8086
  });
8045
8087
 
8046
8088
  // src/rules/no-silent-promise-catch.ts
8047
- import { AST_NODE_TYPES as AST_NODE_TYPES31 } from "@typescript-eslint/utils";
8089
+ import { AST_NODE_TYPES as AST_NODE_TYPES31, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
8048
8090
  var NO_SILENT_PROMISE_CATCH_DOCUMENTATION = {
8049
8091
  summary: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error.",
8050
8092
  rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
8051
8093
  remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
8052
8094
  category: "correctness",
8053
- limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded."],
8095
+ limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded.", "Recognized imported Zod construction chains and their stable local aliases are excluded. Other untyped catch-like APIs are not proven to be Promises."],
8054
8096
  examples: [
8055
8097
  { id: "reported-rejection", title: "Report the rejection", outcome: "no-match", files: [{ path: "src/load.ts", source: "load().catch((error) => logger.error({ error }, 'load failed'));" }], focusPath: "src/load.ts", expectedCount: 0, public: true },
8056
8098
  { id: "silent-rejection", title: "Do not swallow the rejection", outcome: "match", files: [{ path: "src/load.ts", source: "load().catch(() => null);" }], focusPath: "src/load.ts", expectedCount: 1, public: true }
@@ -8064,6 +8106,43 @@ var BODY_PARSE_METHODS = /* @__PURE__ */ new Set([
8064
8106
  "json",
8065
8107
  "text"
8066
8108
  ]);
8109
+ var ZOD_CONSTRUCTORS = /* @__PURE__ */ new Set([
8110
+ "any",
8111
+ "array",
8112
+ "bigint",
8113
+ "boolean",
8114
+ "custom",
8115
+ "date",
8116
+ "enum",
8117
+ "literal",
8118
+ "map",
8119
+ "never",
8120
+ "null",
8121
+ "number",
8122
+ "object",
8123
+ "record",
8124
+ "set",
8125
+ "string",
8126
+ "tuple",
8127
+ "undefined",
8128
+ "union",
8129
+ "unknown"
8130
+ ]);
8131
+ var ZOD_CHAIN_METHODS = /* @__PURE__ */ new Set([
8132
+ "array",
8133
+ "catch",
8134
+ "default",
8135
+ "describe",
8136
+ "max",
8137
+ "min",
8138
+ "nullable",
8139
+ "nullish",
8140
+ "optional",
8141
+ "readonly",
8142
+ "refine",
8143
+ "superRefine",
8144
+ "transform"
8145
+ ]);
8067
8146
  function isBodyParseCall(node) {
8068
8147
  return node.type === AST_NODE_TYPES31.CallExpression && node.arguments.length === 0 && node.callee.type === AST_NODE_TYPES31.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES31.Identifier && BODY_PARSE_METHODS.has(node.callee.property.name);
8069
8148
  }
@@ -8137,6 +8216,26 @@ var no_silent_promise_catch_default = createRule({
8137
8216
  if (isTestFile(context.filename) || isScriptFile(context.filename)) {
8138
8217
  return {};
8139
8218
  }
8219
+ function isZodSchema(node, seen = /* @__PURE__ */ new Set()) {
8220
+ if (seen.has(node)) return false;
8221
+ seen.add(node);
8222
+ if (node.type === AST_NODE_TYPES31.Identifier) {
8223
+ const binding = ASTUtils18.findVariable(context.sourceCode.getScope(node), node.name);
8224
+ if (binding === null || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
8225
+ const [definition] = binding.defs;
8226
+ return binding.defs.length === 1 && definition?.node.type === AST_NODE_TYPES31.VariableDeclarator && definition.node.init !== null && isZodSchema(definition.node.init, seen);
8227
+ }
8228
+ if (node.type !== AST_NODE_TYPES31.CallExpression || node.callee.type !== AST_NODE_TYPES31.MemberExpression || node.callee.computed || node.callee.property.type !== AST_NODE_TYPES31.Identifier) return false;
8229
+ const { object, property } = node.callee;
8230
+ if (object.type === AST_NODE_TYPES31.Identifier && ZOD_CONSTRUCTORS.has(property.name)) {
8231
+ const binding = ASTUtils18.findVariable(context.sourceCode.getScope(object), object.name);
8232
+ if (binding?.defs.some((definition) => {
8233
+ const specifier = definition.node;
8234
+ return (specifier.type === AST_NODE_TYPES31.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES31.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES31.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES31.Identifier && specifier.imported.name === "z") && specifier.parent.type === AST_NODE_TYPES31.ImportDeclaration && isZodModule(String(specifier.parent.source.value));
8235
+ })) return true;
8236
+ }
8237
+ return ZOD_CHAIN_METHODS.has(property.name) && isZodSchema(object, seen);
8238
+ }
8140
8239
  const hasExplanatoryComment = (call, handler) => {
8141
8240
  const sourceCode = context.sourceCode;
8142
8241
  if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
@@ -8161,6 +8260,7 @@ var no_silent_promise_catch_default = createRule({
8161
8260
  const method = node.callee.property.name;
8162
8261
  const handlerIndex = method === "catch" ? 0 : method === "then" ? 1 : null;
8163
8262
  if (handlerIndex === null) return;
8263
+ if (method === "catch" && isZodSchema(node.callee.object)) return;
8164
8264
  if (isBodyParseCall(node.callee.object)) {
8165
8265
  return;
8166
8266
  }
@@ -8193,16 +8293,16 @@ var no_silent_promise_catch_default = createRule({
8193
8293
  });
8194
8294
 
8195
8295
  // src/rules/no-sleep-in-test-body.ts
8196
- import { AST_NODE_TYPES as AST_NODE_TYPES32 } from "@typescript-eslint/utils";
8296
+ import { AST_NODE_TYPES as AST_NODE_TYPES32, ASTUtils as ASTUtils19 } from "@typescript-eslint/utils";
8197
8297
  var NO_SLEEP_IN_TEST_BODY_DOCUMENTATION = {
8198
- summary: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers.",
8298
+ summary: "Avoid fixed timed sleeps directly in test bodies; synchronize on observable behavior or use controlled timers.",
8199
8299
  rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
8200
- remediation: "Await the observable signal or advance deterministic fake timers.",
8300
+ remediation: "Await the observable signal, or advance fake timers when supported and restore real timers in finally or a teardown hook.",
8201
8301
  category: "testing",
8202
8302
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
8203
- limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes and parameterized delays are excluded."],
8303
+ limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes, parameterized delays, local helper bindings, and Promise executors with additional work or rejection callbacks are excluded."],
8204
8304
  examples: [
8205
- { id: "fake-timer", title: "Advance time deterministically", outcome: "no-match", files: [{ path: "src/retry.test.ts", source: "it('retries', async () => { vi.useFakeTimers(); const result = retry(); await vi.advanceTimersByTimeAsync(50); await result; });" }], focusPath: "src/retry.test.ts", expectedCount: 0, public: true },
8305
+ { id: "fake-timer", title: "Advance controlled time and restore real timers", outcome: "no-match", files: [{ path: "src/retry.test.ts", source: "it('retries', async () => { vi.useFakeTimers(); try { const result = retry(); await vi.advanceTimersByTimeAsync(50); await result; } finally { vi.useRealTimers(); } });" }], focusPath: "src/retry.test.ts", expectedCount: 0, public: true },
8206
8306
  { id: "fixed-sleep", title: "Do not wait for wall-clock time", outcome: "match", files: [{ path: "src/retry.test.ts", source: "it('retries', async () => { await sleep(50); expect(done()).toBe(true); });" }], focusPath: "src/retry.test.ts", expectedCount: 1, public: true }
8207
8307
  ]
8208
8308
  };
@@ -8221,9 +8321,6 @@ var FUNCTION_TYPES5 = /* @__PURE__ */ new Set([
8221
8321
  function isNonzeroNumericLiteral(node) {
8222
8322
  return node?.type === AST_NODE_TYPES32.Literal && typeof node.value === "number" && node.value !== 0;
8223
8323
  }
8224
- function isTimedSetTimeout(node) {
8225
- return node.type === AST_NODE_TYPES32.CallExpression && node.callee.type === AST_NODE_TYPES32.Identifier && node.callee.name === "setTimeout" && node.arguments.length >= 2 && isNonzeroNumericLiteral(node.arguments[1]);
8226
- }
8227
8324
  function isPromiseSleep(node) {
8228
8325
  if (node.callee.type !== AST_NODE_TYPES32.Identifier || node.callee.name !== "Promise") {
8229
8326
  return false;
@@ -8233,12 +8330,14 @@ function isPromiseSleep(node) {
8233
8330
  return false;
8234
8331
  }
8235
8332
  const body2 = executor.body;
8236
- if (body2.type !== AST_NODE_TYPES32.BlockStatement) {
8237
- return isTimedSetTimeout(body2);
8238
- }
8239
- return body2.body.some(
8240
- (stmt) => stmt.type === AST_NODE_TYPES32.ExpressionStatement && isTimedSetTimeout(stmt.expression)
8241
- );
8333
+ const resolve2 = executor.params[0];
8334
+ if (executor.params.length !== 1 || resolve2?.type !== AST_NODE_TYPES32.Identifier || resolve2.name === "setTimeout") return false;
8335
+ const statement = body2.type === AST_NODE_TYPES32.BlockStatement && body2.body.length === 1 ? body2.body[0] : null;
8336
+ const timer = body2.type !== AST_NODE_TYPES32.BlockStatement ? body2 : statement?.type === AST_NODE_TYPES32.ExpressionStatement ? statement.expression : null;
8337
+ return timer?.type === AST_NODE_TYPES32.CallExpression && isTimedSetTimeout(timer) && timer.arguments[0]?.type === AST_NODE_TYPES32.Identifier && timer.arguments[0].name === resolve2.name;
8338
+ }
8339
+ function isTimedSetTimeout(node) {
8340
+ return node.type === AST_NODE_TYPES32.CallExpression && node.callee.type === AST_NODE_TYPES32.Identifier && node.callee.name === "setTimeout" && node.arguments.length >= 2 && isNonzeroNumericLiteral(node.arguments[1]);
8242
8341
  }
8243
8342
  function isHelperSleep(node) {
8244
8343
  return node.callee.type === AST_NODE_TYPES32.Identifier && SLEEP_HELPERS.has(node.callee.name) && node.arguments.length >= 1 && isNonzeroNumericLiteral(node.arguments[0]);
@@ -8292,11 +8391,11 @@ var no_sleep_in_test_body_default = createRule({
8292
8391
  meta: {
8293
8392
  type: "problem",
8294
8393
  docs: {
8295
- description: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers."
8394
+ description: "Avoid fixed timed sleeps directly in test bodies; synchronize on observable behavior or use controlled timers."
8296
8395
  },
8297
8396
  schema: [],
8298
8397
  messages: {
8299
- noSleepInTestBody: "A fixed sleep in a test body asserts on wall-clock time and flakes under CI load. Await the promise the code returns, or drive time with `vi.useFakeTimers()` + `await vi.advanceTimersByTimeAsync(ms)`."
8398
+ noSleepInTestBody: "A fixed sleep depends on wall-clock timing and can be flaky under load. Await observable completion or use controlled fake timers, restoring real timers afterward."
8300
8399
  }
8301
8400
  },
8302
8401
  defaultOptions: [],
@@ -8317,11 +8416,17 @@ var no_sleep_in_test_body_default = createRule({
8317
8416
  return {
8318
8417
  NewExpression(node) {
8319
8418
  if (isPromiseSleep(node)) {
8419
+ const constructor = ASTUtils19.findVariable(context.sourceCode.getScope(node), "Promise");
8420
+ const timer = ASTUtils19.findVariable(context.sourceCode.getScope(node), "setTimeout");
8421
+ if ((constructor?.defs.length ?? 0) > 0 || (timer?.defs.length ?? 0) > 0) return;
8320
8422
  report2(node);
8321
8423
  }
8322
8424
  },
8323
8425
  CallExpression(node) {
8324
8426
  if (isHelperSleep(node)) {
8427
+ if (node.callee.type !== AST_NODE_TYPES32.Identifier) return;
8428
+ const variable = ASTUtils19.findVariable(context.sourceCode.getScope(node), node.callee.name);
8429
+ if (variable?.defs.some((definition) => definition.type !== "ImportBinding")) return;
8325
8430
  report2(node);
8326
8431
  }
8327
8432
  }
@@ -8342,7 +8447,7 @@ var NO_STORAGE_IN_STATELESS_MODULES_DOCUMENTATION = {
8342
8447
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
8343
8448
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
8344
8449
  category: "architecture",
8345
- 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."],
8450
+ limitations: ["This opt-in architectural policy requires configured module paths and storage method names. Overloaded `put` requires storage-like receiver evidence; `prepare` requires SQL-shaped literal text or a conventional database receiver for dynamic text. These syntax heuristics do not prove database provenance or identify the system of record."],
8346
8451
  examples: [
8347
8452
  { 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 },
8348
8453
  { 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 }
@@ -8376,6 +8481,17 @@ function storageMethodName(node, methods) {
8376
8481
  if (name === "put" && !isStorageLikeReceiver(callee.object)) {
8377
8482
  return null;
8378
8483
  }
8484
+ if (name === "prepare") {
8485
+ const argument = node.arguments[0];
8486
+ const text = argument === void 0 ? null : sqlTextOf(argument);
8487
+ if (text !== null) {
8488
+ if (!/^\s*(?:SELECT|WITH|INSERT|UPDATE|DELETE|REPLACE|CREATE|ALTER|DROP|PRAGMA|EXPLAIN)\b/iu.test(stripSqlNoise(text))) return null;
8489
+ } else {
8490
+ const receiver = callee.object;
8491
+ const receiverName = receiver.type === AST_NODE_TYPES33.Identifier ? receiver.name : receiver.type === AST_NODE_TYPES33.MemberExpression && !receiver.computed && receiver.property.type === AST_NODE_TYPES33.Identifier ? receiver.property.name : "";
8492
+ if (!/^(?:db|database|connection)$/iu.test(receiverName)) return null;
8493
+ }
8494
+ }
8379
8495
  return name;
8380
8496
  }
8381
8497
  function isStorageLikeReceiver(node) {
@@ -8460,12 +8576,13 @@ var no_storage_in_stateless_modules_default = createRule({
8460
8576
  // src/rules/no-string-concat-in-loop.ts
8461
8577
  import "@typescript-eslint/utils";
8462
8578
  var NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION = {
8463
- summary: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead.",
8579
+ summary: "Prefer collecting string fragments over repeatedly accumulating a growing string inside a loop.",
8464
8580
  rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
8465
- remediation: "Collect each fragment in an array, then join the fragments after the loop.",
8581
+ remediation: "Consider collecting fragments and joining once; preserve intermediate observations and coercion timing, and measure hot paths.",
8466
8582
  category: "performance",
8467
8583
  limitations: [
8468
- "Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
8584
+ "Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected.",
8585
+ "Deferred function bodies are excluded except recognized direct forEach callbacks. Syntax does not establish engine-specific string allocation complexity."
8469
8586
  ],
8470
8587
  examples: [
8471
8588
  {
@@ -8583,6 +8700,7 @@ function enclosingLoop(node) {
8583
8700
  if ((parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") && parent.parent.type === "CallExpression" && parent.parent.arguments[0] === parent && parent.parent.callee.type === "MemberExpression" && !parent.parent.callee.computed && parent.parent.callee.property.type === "Identifier" && parent.parent.callee.property.name === "forEach") {
8584
8701
  return parent.parent;
8585
8702
  }
8703
+ if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") return null;
8586
8704
  if (LOOP_NODE_TYPES.has(parent.type)) {
8587
8705
  const loop = parent;
8588
8706
  if (loop.body === child) {
@@ -8594,6 +8712,19 @@ function enclosingLoop(node) {
8594
8712
  }
8595
8713
  return null;
8596
8714
  }
8715
+ function immediatelyExitsLoop(node, loop) {
8716
+ if (!LOOP_NODE_TYPES.has(loop.type) || node.parent.type !== "ExpressionStatement") return false;
8717
+ const statement = node.parent;
8718
+ const block = statement.parent;
8719
+ if (block.type !== "BlockStatement") return false;
8720
+ const next = block.body[block.body.indexOf(statement) + 1];
8721
+ if (next?.type !== "BreakStatement" && next?.type !== "ReturnStatement" && next?.type !== "ThrowStatement") return false;
8722
+ if (next.type === "BreakStatement" && next.label !== null) return false;
8723
+ for (let current = block; current !== void 0 && current !== loop; current = current.parent) {
8724
+ if (current.type === "TryStatement" || next.type === "BreakStatement" && current.type === "SwitchStatement") return false;
8725
+ }
8726
+ return true;
8727
+ }
8597
8728
  function isSmallStaticForLoop(node) {
8598
8729
  if (node.type !== "ForStatement" || node.init?.type !== "VariableDeclaration" || node.init.declarations.length !== 1 || node.test?.type !== "BinaryExpression" || node.test.operator !== "<" && node.test.operator !== "<=" || node.update?.type !== "UpdateExpression" || node.update.operator !== "++") {
8599
8730
  return false;
@@ -8632,12 +8763,12 @@ var no_string_concat_in_loop_default = createRule({
8632
8763
  meta: {
8633
8764
  type: "suggestion",
8634
8765
  docs: {
8635
- description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
8766
+ description: NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION.summary
8636
8767
  },
8637
8768
  schema: [],
8638
8769
  messages: {
8639
- noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.',
8640
- noStringReduce: "Avoid concatenating a growing string in `reduce` \u2014 this is O(n^2). Map the fragments and join them once instead."
8770
+ noStringConcatInLoop: "This loop repeatedly accumulates a growing string. Consider collecting fragments and joining once; preserve coercion timing and intermediate reads, and measure performance-sensitive paths.",
8771
+ noStringReduce: "This reduce repeatedly accumulates a growing string. Consider mapping fragments and joining once if coercion timing and intermediate observations are unchanged."
8641
8772
  }
8642
8773
  },
8643
8774
  defaultOptions: [],
@@ -8664,6 +8795,7 @@ var no_string_concat_in_loop_default = createRule({
8664
8795
  if (loop === null) {
8665
8796
  return;
8666
8797
  }
8798
+ if (immediatelyExitsLoop(node, loop)) return;
8667
8799
  if (isSmallStaticForLoop(loop)) {
8668
8800
  return;
8669
8801
  }
@@ -8697,14 +8829,14 @@ var no_string_concat_in_loop_default = createRule({
8697
8829
  });
8698
8830
 
8699
8831
  // src/rules/no-tautological-expect.ts
8700
- import { AST_NODE_TYPES as AST_NODE_TYPES34 } from "@typescript-eslint/utils";
8832
+ import { AST_NODE_TYPES as AST_NODE_TYPES34, ASTUtils as ASTUtils20 } from "@typescript-eslint/utils";
8701
8833
  var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
8702
- summary: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail.",
8834
+ summary: "Disallow supported literal-only assertions that are statically known to pass.",
8703
8835
  rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
8704
8836
  remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
8705
8837
  category: "testing",
8706
8838
  limitations: [
8707
- "Only direct supported `expect` matcher calls in recognized test files are inspected."
8839
+ "Only direct supported `expect` matcher calls in recognized test files are inspected. Local expect bindings, regular expressions, and unsupported coercions are excluded; failing constant assertions are not tautologies."
8708
8840
  ],
8709
8841
  examples: [
8710
8842
  {
@@ -8741,11 +8873,11 @@ var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
8741
8873
  function isLiteral(node) {
8742
8874
  switch (node.type) {
8743
8875
  case AST_NODE_TYPES34.Literal:
8744
- return true;
8876
+ return !("regex" in node);
8745
8877
  case AST_NODE_TYPES34.TemplateLiteral:
8746
8878
  return node.expressions.length === 0;
8747
8879
  case AST_NODE_TYPES34.UnaryExpression:
8748
- return NUMERIC_SIGNS.has(node.operator) && isLiteral(node.argument);
8880
+ return NUMERIC_SIGNS.has(node.operator) && node.argument.type === AST_NODE_TYPES34.Literal && typeof node.argument.value === "number";
8749
8881
  case AST_NODE_TYPES34.ArrayExpression:
8750
8882
  return node.elements.every((element) => element !== null && isLiteral(element));
8751
8883
  case AST_NODE_TYPES34.ObjectExpression:
@@ -8759,6 +8891,43 @@ function isLiteral(node) {
8759
8891
  function isStructuralLiteral(node) {
8760
8892
  return node.type === AST_NODE_TYPES34.ArrayExpression || node.type === AST_NODE_TYPES34.ObjectExpression;
8761
8893
  }
8894
+ function passesZeroArgumentMatcher(node, matcher) {
8895
+ let value;
8896
+ switch (node.type) {
8897
+ case AST_NODE_TYPES34.Literal:
8898
+ value = node.value;
8899
+ break;
8900
+ case AST_NODE_TYPES34.TemplateLiteral:
8901
+ value = node.quasis[0]?.value.cooked;
8902
+ break;
8903
+ case AST_NODE_TYPES34.UnaryExpression:
8904
+ if (node.argument.type !== AST_NODE_TYPES34.Literal || typeof node.argument.value !== "number") return false;
8905
+ value = node.operator === "-" ? -node.argument.value : node.argument.value;
8906
+ break;
8907
+ case AST_NODE_TYPES34.ArrayExpression:
8908
+ case AST_NODE_TYPES34.ObjectExpression:
8909
+ value = {};
8910
+ break;
8911
+ default:
8912
+ return false;
8913
+ }
8914
+ switch (matcher) {
8915
+ case "toBeDefined":
8916
+ return value !== void 0;
8917
+ case "toBeUndefined":
8918
+ return value === void 0;
8919
+ case "toBeNull":
8920
+ return value === null;
8921
+ case "toBeTruthy":
8922
+ return Boolean(value);
8923
+ case "toBeFalsy":
8924
+ return !value;
8925
+ case "toBeNaN":
8926
+ return typeof value === "number" && Number.isNaN(value);
8927
+ default:
8928
+ return false;
8929
+ }
8930
+ }
8762
8931
  function expectOperand(callee) {
8763
8932
  const receiver = callee.object;
8764
8933
  if (receiver.type !== AST_NODE_TYPES34.CallExpression || receiver.callee.type !== AST_NODE_TYPES34.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
@@ -8772,12 +8941,12 @@ var no_tautological_expect_default = createRule({
8772
8941
  meta: {
8773
8942
  type: "problem",
8774
8943
  docs: {
8775
- description: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail."
8944
+ description: "Disallow supported literal-only assertions that are statically known to pass."
8776
8945
  },
8777
8946
  schema: [],
8778
8947
  messages: {
8779
- tautologicalComparison: "`expect({{operand}}).{{matcher}}({{operand}})` compares a literal with an identical literal \u2014 it passes even if the code under test is deleted. Assert on a value the code produced, or delete the test.",
8780
- tautologicalMatcher: "`expect({{operand}}).{{matcher}}()` asserts on a literal, so its outcome is fixed before the code runs. Assert on a value the code produced, or delete the test."
8948
+ tautologicalComparison: "`expect({{operand}}).{{matcher}}({{operand}})` compares an identical literal and does not observe behavior. Assert on a produced value or remove only the redundant assertion, preserving other coverage.",
8949
+ tautologicalMatcher: "`expect({{operand}}).{{matcher}}()` is statically known to pass. Assert on a produced value or remove only the redundant assertion, preserving other coverage."
8781
8950
  }
8782
8951
  },
8783
8952
  defaultOptions: [],
@@ -8799,11 +8968,20 @@ var no_tautological_expect_default = createRule({
8799
8968
  return;
8800
8969
  }
8801
8970
  const matcher = callee.property.name;
8971
+ if (callee.object.type !== AST_NODE_TYPES34.CallExpression || callee.object.callee.type !== AST_NODE_TYPES34.Identifier) return;
8972
+ const expectIdentifier = callee.object.callee;
8973
+ const variable = ASTUtils20.findVariable(context.sourceCode.getScope(expectIdentifier), expectIdentifier.name);
8974
+ if (variable !== null && variable.defs.some((definition) => {
8975
+ if (definition.node.type !== AST_NODE_TYPES34.ImportSpecifier) return true;
8976
+ const declaration = definition.node.parent;
8977
+ const imported = definition.node.imported;
8978
+ return declaration.type !== AST_NODE_TYPES34.ImportDeclaration || !["vitest", "@jest/globals", "@playwright/test", "bun:test"].includes(String(declaration.source.value)) || (imported.type === AST_NODE_TYPES34.Identifier ? imported.name : imported.value) !== "expect";
8979
+ })) return;
8802
8980
  const operand = expectOperand(callee);
8803
8981
  if (operand === null || !isLiteral(operand)) {
8804
8982
  return;
8805
8983
  }
8806
- if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0) {
8984
+ if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0 && passesZeroArgumentMatcher(operand, matcher)) {
8807
8985
  context.report({
8808
8986
  node,
8809
8987
  messageId: "tautologicalMatcher",
@@ -10037,7 +10215,7 @@ var no_unnecessary_use_client_default = createRule({
10037
10215
  // src/rules/no-unsafe-mock-casting.ts
10038
10216
  import {
10039
10217
  AST_NODE_TYPES as AST_NODE_TYPES40,
10040
- ASTUtils as ASTUtils13
10218
+ ASTUtils as ASTUtils21
10041
10219
  } from "@typescript-eslint/utils";
10042
10220
  var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
10043
10221
  "Mock",
@@ -10058,15 +10236,16 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
10058
10236
  var NO_UNSAFE_MOCK_CASTING_DOCUMENTATION = {
10059
10237
  summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
10060
10238
  rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
10061
- remediation: "Use the test framework's `mocked` helper to obtain the typed mock reference.",
10239
+ remediation: "Create the mock or spy first, then use the framework's mocked helper to preserve the original value's type. The helper does not create or verify a runtime mock.",
10062
10240
  category: "testing",
10063
- limitations: ["Only mock types imported from Vitest or Jest modules are inspected."],
10241
+ limitations: ["Only mock types imported from Vitest or Jest modules are inspected. mocked is a type helper, not runtime validation or a replacement for mock setup."],
10242
+ references: ["https://vitest.dev/api/vi.html#vi-mocked"],
10064
10243
  examples: [
10065
10244
  {
10066
10245
  id: "typed-mock-helper",
10067
10246
  title: "Use the framework helper",
10068
10247
  outcome: "no-match",
10069
- files: [{ path: "src/client.test.ts", source: "const m = vi.mocked(myFn);" }],
10248
+ files: [{ path: "src/client.test.ts", source: "import { vi } from 'vitest'; const client = { read: () => 'value' }; vi.spyOn(client, 'read'); const m = vi.mocked(client.read);" }],
10070
10249
  focusPath: "src/client.test.ts",
10071
10250
  expectedCount: 0,
10072
10251
  public: true
@@ -10092,7 +10271,7 @@ var no_unsafe_mock_casting_default = createRule({
10092
10271
  },
10093
10272
  schema: [],
10094
10273
  messages: {
10095
- unsafeMockCast: "Do not cast to a Mock type. Use `vi.mocked(fn)` or `jest.mocked(fn)` instead to preserve type safety."
10274
+ unsafeMockCast: "Avoid a broad Mock cast. After creating the mock or spy, use `vi.mocked(fn)` or `jest.mocked(fn)` to retain its original type; the helper does not create a runtime mock."
10096
10275
  }
10097
10276
  },
10098
10277
  defaultOptions: [],
@@ -10103,7 +10282,7 @@ var no_unsafe_mock_casting_default = createRule({
10103
10282
  const directBindings = /* @__PURE__ */ new Set();
10104
10283
  const namespaceBindings = /* @__PURE__ */ new Set();
10105
10284
  function resolve2(identifier) {
10106
- return ASTUtils13.findVariable(
10285
+ return ASTUtils21.findVariable(
10107
10286
  context.sourceCode.getScope(identifier),
10108
10287
  identifier.name
10109
10288
  );
@@ -10153,12 +10332,12 @@ var no_unsafe_mock_casting_default = createRule({
10153
10332
  import {
10154
10333
  ESLintUtils as ESLintUtils3,
10155
10334
  AST_NODE_TYPES as AST_NODE_TYPES41,
10156
- ASTUtils as ASTUtils14
10335
+ ASTUtils as ASTUtils22
10157
10336
  } from "@typescript-eslint/utils";
10158
10337
  import * as ts2 from "typescript";
10159
10338
  var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
10160
10339
  summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
10161
- rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
10340
+ rationale: "The project prefers literal-first schema definitions. nativeEnum also accepts plain enum-like objects, so this is an explicit declaration policy, not proof that every call duplicates a TypeScript enum's runtime object.",
10162
10341
  remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
10163
10342
  category: "maintainability",
10164
10343
  autofix: "none",
@@ -10263,7 +10442,7 @@ var no_zod_native_enum_default = createRule({
10263
10442
  const zodImportedBindings = /* @__PURE__ */ new Map();
10264
10443
  const zodNamespaceBindings = /* @__PURE__ */ new Set();
10265
10444
  function resolvedBinding(identifier) {
10266
- return ASTUtils14.findVariable(
10445
+ return ASTUtils22.findVariable(
10267
10446
  sourceCode.getScope(identifier),
10268
10447
  identifier.name
10269
10448
  );
@@ -10329,14 +10508,14 @@ var no_zod_native_enum_default = createRule({
10329
10508
  });
10330
10509
 
10331
10510
  // src/rules/test-loops-over-literal-cases.ts
10332
- import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
10511
+ import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
10333
10512
  var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
10334
10513
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
10335
10514
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
10336
10515
  remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
10337
10516
  category: "testing",
10338
10517
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
10339
- limitations: ["Only inline literal for-of cases containing framework assertions are reported."],
10518
+ limitations: ["Only inline literal for-of cases containing framework assertions are reported. References to setup or parameters owned by the enclosing test, and loops followed by statements in the same or enclosing block, are excluded because they can belong to an ordered scenario. External helper purity is not inferred."],
10340
10519
  examples: [
10341
10520
  { id: "parameterized-cases", title: "Use a parameterized test", outcome: "no-match", files: [{ path: "src/parser.test.ts", source: "test.each(['a', 'b'])('parses %s', (value) => { expect(parse(value)).toBe(value); });" }], focusPath: "src/parser.test.ts", expectedCount: 0, public: true },
10342
10521
  { id: "looped-cases", title: "Do not hide cases in a loop", outcome: "match", files: [{ path: "src/parser.test.ts", source: "test('parses', () => { for (const value of ['a', 'b']) { expect(parse(value)).toBe(value); } });" }], focusPath: "src/parser.test.ts", expectedCount: 1, public: true }
@@ -10483,7 +10662,7 @@ var test_loops_over_literal_cases_default = createRule({
10483
10662
  },
10484
10663
  schema: [],
10485
10664
  messages: {
10486
- literalCaseLoop: "This loop asserts over {{count}} inline cases, but the runner sees one test and stops at the first failure. Create one named test or subtest per case; use `test.each(...)` or `it.each(...)` where supported."
10665
+ literalCaseLoop: "This loop asserts over {{count}} inline cases in one test; a thrown assertion may prevent later cases from running. Create one named test or subtest per independent case; use `test.each(...)` or `it.each(...)` where supported."
10487
10666
  }
10488
10667
  },
10489
10668
  defaultOptions: [],
@@ -10492,7 +10671,7 @@ var test_loops_over_literal_cases_default = createRule({
10492
10671
  return {};
10493
10672
  }
10494
10673
  const isFrameworkIdentifier = (identifier, modules) => {
10495
- const variable = ASTUtils15.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10674
+ const variable = ASTUtils23.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10496
10675
  if (variable === null || variable.defs.length === 0) return true;
10497
10676
  return variable.defs.some((definition) => {
10498
10677
  let current = definition.node;
@@ -10508,6 +10687,9 @@ var test_loops_over_literal_cases_default = createRule({
10508
10687
  if (enclosing === null || !isTestBody2(enclosing, isFrameworkTest)) {
10509
10688
  return;
10510
10689
  }
10690
+ for (let current = node; current !== void 0 && current !== enclosing; current = current.parent) {
10691
+ if (current.parent?.type === AST_NODE_TYPES42.BlockStatement && current.parent.body.at(-1) !== current) return;
10692
+ }
10511
10693
  const cases = unwrapExpression(node.right);
10512
10694
  const callbackParameters = new Set(
10513
10695
  enclosing.params.flatMap((parameter) => parameter.type === AST_NODE_TYPES42.Identifier ? [parameter.name] : [])
@@ -10517,6 +10699,16 @@ var test_loops_over_literal_cases_default = createRule({
10517
10699
  ) || !walkOwnScope2(node.body, (current) => isAssertion2(current, isFrameworkAssertion)) || walkOwnScope2(node.body, (current) => opensSubtest(current, callbackParameters)) || walkOwnScope2(node.body, (current) => LOOP_CARRIED_CONTROL.has(current.type))) {
10518
10700
  return;
10519
10701
  }
10702
+ const capturesSetup = walkOwnScope2(node.body, (current) => {
10703
+ if (current.type !== AST_NODE_TYPES42.Identifier) return false;
10704
+ const variable = ASTUtils23.findVariable(context.sourceCode.getScope(current), current.name);
10705
+ if (variable === null || !variable.references.some((reference) => reference.identifier === current)) return false;
10706
+ return variable.defs.some((definition) => {
10707
+ const declaration = definition.name;
10708
+ return declaration.range[0] >= enclosing.range[0] && declaration.range[1] <= enclosing.range[1] && (declaration.range[0] < node.range[0] || declaration.range[1] > node.range[1]);
10709
+ });
10710
+ });
10711
+ if (capturesSetup) return;
10520
10712
  context.report({
10521
10713
  node,
10522
10714
  messageId: "literalCaseLoop",
@@ -10740,13 +10932,12 @@ import {
10740
10932
  var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
10741
10933
  summary: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members.",
10742
10934
  rationale: "ECMAScript private names enforce encapsulation at runtime instead of erasing the boundary during compilation.",
10743
- remediation: "Replace the TypeScript `private` modifier and all proven same-class references with an ECMAScript private name.",
10935
+ remediation: "Review reflection, instance escape and framework contracts before replacing TypeScript privacy with ECMAScript private names and updating references.",
10744
10936
  category: "maintainability",
10745
- autofix: "safe",
10937
+ autofix: "none",
10746
10938
  limitations: [
10747
10939
  "Ambient, abstract, computed, decorated, override, parameter-property, and generated declarations are excluded.",
10748
- "A fix is offered only for an undecorated, unexported class declaration with no references outside its body and when type information proves every use is a direct `this.name` access inside that class.",
10749
- "Overloads, modifier-adjacent comments, reflection, and any potentially cross-file or escaping class remain report-only."
10940
+ "Migration is report-only: type information cannot prove that instances or constructors never escape through this, or that reflection and framework serialization do not observe ordinary private properties."
10750
10941
  ],
10751
10942
  references: ["https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements"],
10752
10943
  examples: [
@@ -10766,19 +10957,11 @@ var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
10766
10957
  files: [{ path: "src/vault.ts", source: "class Vault { private read() { return 1; } open() { return this.read(); } }" }],
10767
10958
  focusPath: "src/vault.ts",
10768
10959
  expectedCount: 1,
10769
- public: true,
10770
- fixedFiles: [{ path: "src/vault.ts", source: "class Vault { #read() { return 1; } open() { return this.#read(); } }" }]
10960
+ public: true
10771
10961
  }
10772
10962
  ]
10773
10963
  };
10774
- function reportClass2(context, services, owner) {
10775
- const parent = owner.parent;
10776
- const directlyExported = parent.type === AST_NODE_TYPES45.ExportNamedDeclaration || parent.type === AST_NODE_TYPES45.ExportDefaultDeclaration;
10777
- const locallyClosed = !directlyExported && owner.decorators.length === 0 && owner.type === AST_NODE_TYPES45.ClassDeclaration && context.sourceCode.getDeclaredVariables(owner).every(
10778
- (variable) => variable.references.every(
10779
- (reference) => reference.identifier.range[0] >= owner.range[0] && reference.identifier.range[1] <= owner.range[1]
10780
- )
10781
- );
10964
+ function reportClass2(context, owner) {
10782
10965
  const groups = /* @__PURE__ */ new Map();
10783
10966
  for (const member of owner.body.body) {
10784
10967
  if (!isConvertible(member)) continue;
@@ -10791,18 +10974,10 @@ function reportClass2(context, services, owner) {
10791
10974
  for (const [name, members] of groups) {
10792
10975
  const first = members[0];
10793
10976
  if (first === void 0) continue;
10794
- const fix = locallyClosed ? privateMemberFixes(
10795
- context,
10796
- services,
10797
- owner,
10798
- members,
10799
- true
10800
- ) : void 0;
10801
10977
  context.report({
10802
10978
  node: first.key,
10803
10979
  messageId: "preferEcmascriptPrivate",
10804
- data: { name },
10805
- ...fix === void 0 ? {} : { fix }
10980
+ data: { name }
10806
10981
  });
10807
10982
  }
10808
10983
  }
@@ -10816,7 +10991,6 @@ var prefer_ecmascript_private_members_default = createRule({
10816
10991
  meta: {
10817
10992
  type: "suggestion",
10818
10993
  docs: { description: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members." },
10819
- fixable: "code",
10820
10994
  schema: [],
10821
10995
  messages: {
10822
10996
  preferEcmascriptPrivate: "TypeScript `private {{name}}` is erased at runtime; use the ECMAScript private name `#{{name}}`."
@@ -10833,8 +11007,8 @@ var prefer_ecmascript_private_members_default = createRule({
10833
11007
  }
10834
11008
  if (services === null) return {};
10835
11009
  return {
10836
- ClassDeclaration: (node) => reportClass2(context, services, node),
10837
- ClassExpression: (node) => reportClass2(context, services, node)
11010
+ ClassDeclaration: (node) => reportClass2(context, node),
11011
+ ClassExpression: (node) => reportClass2(context, node)
10838
11012
  };
10839
11013
  }
10840
11014
  });
@@ -10844,10 +11018,10 @@ import "@typescript-eslint/utils";
10844
11018
  import { AST_NODE_TYPES as AST_NODE_TYPES46 } from "@typescript-eslint/utils";
10845
11019
  var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
10846
11020
  summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
10847
- rationale: "A boolean status plus optional branch data permits contradictory and incomplete states.",
10848
- remediation: "Represent each result branch as a discriminated union member with its required payload.",
11021
+ rationale: "When success and failure are mutually exclusive outcomes, a boolean plus optional branch data permits contradictory and incomplete states.",
11022
+ remediation: "If the outcomes are mutually exclusive, represent each branch as a discriminated union member with its required payload.",
10849
11023
  category: "correctness",
10850
- limitations: ["Only local object shapes with recognized positive status and payload names are inspected."],
11024
+ limitations: ["Only local object shapes with recognized non-computed status and payload names are inspected. Names do not prove that partial-success outcomes are forbidden; review the domain before changing its representation."],
10851
11025
  examples: [
10852
11026
  { id: "explicit-result-branches", title: "Use explicit result branches", outcome: "no-match", files: [{ path: "src/result.ts", source: "type Result = { ok: true; data: string } | { ok: false; error: string };" }], focusPath: "src/result.ts", expectedCount: 0, public: true },
10853
11027
  { id: "optional-result-payloads", title: "Do not make both result payloads optional", outcome: "match", files: [{ path: "src/result.ts", source: "type Result = { ok: boolean; data?: string; error?: string };" }], focusPath: "src/result.ts", expectedCount: 1, public: true }
@@ -10911,7 +11085,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
10911
11085
  return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
10912
11086
  }
10913
11087
  function getMemberName(member) {
10914
- if (member.type !== AST_NODE_TYPES46.TSPropertySignature) {
11088
+ if (member.type !== AST_NODE_TYPES46.TSPropertySignature || member.computed) {
10915
11089
  return null;
10916
11090
  }
10917
11091
  const { key } = member;
@@ -10947,7 +11121,7 @@ var prefer_discriminated_union_default = createRule({
10947
11121
  },
10948
11122
  schema: [],
10949
11123
  messages: {
10950
- preferDiscriminatedUnion: "This object type uses a boolean status flag alongside several optional fields, which lets illegal states be representable. Model it as a `z.discriminatedUnion` / discriminated union (e.g. `{ ok: true; data: T } | { ok: false; error: E }`) to make illegal states unrepresentable."
11124
+ preferDiscriminatedUnion: "This object type combines a boolean status with optional payloads. If success and failure are mutually exclusive, consider a discriminated union such as `{ ok: true; data: T } | { ok: false; error: E }`."
10951
11125
  }
10952
11126
  },
10953
11127
  defaultOptions: [],
@@ -10987,7 +11161,7 @@ var prefer_discriminated_union_default = createRule({
10987
11161
  });
10988
11162
 
10989
11163
  // src/rules/prefer-input-group-search.ts
10990
- import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
11164
+ import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
10991
11165
  var PREFER_INPUT_GROUP_SEARCH_DOCUMENTATION = {
10992
11166
  summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
10993
11167
  rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
@@ -11111,7 +11285,7 @@ var prefer_input_group_search_default = createRule({
11111
11285
  JSXOpeningElement(node) {
11112
11286
  const name = elementName(node);
11113
11287
  if (name === null) return;
11114
- const binding = ASTUtils16.findVariable(context.sourceCode.getScope(node), name);
11288
+ const binding = ASTUtils24.findVariable(context.sourceCode.getScope(node), name);
11115
11289
  if (binding?.defs.length !== 1 || binding.defs[0]?.node.type !== AST_NODE_TYPES47.ImportSpecifier || binding.defs[0].node.importKind === "type") return;
11116
11290
  const occurrence = {
11117
11291
  ancestors: context.sourceCode.getAncestors(node),
@@ -11151,7 +11325,7 @@ var prefer_input_group_search_default = createRule({
11151
11325
 
11152
11326
  // src/rules/prefer-millisecond-control-duration-schema.ts
11153
11327
  import {
11154
- ASTUtils as ASTUtils17,
11328
+ ASTUtils as ASTUtils25,
11155
11329
  AST_NODE_TYPES as AST_NODE_TYPES48
11156
11330
  } from "@typescript-eslint/utils";
11157
11331
  var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
@@ -11161,7 +11335,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11161
11335
  category: "correctness",
11162
11336
  autofix: "none",
11163
11337
  limitations: [
11164
- "Only direct identifier keys in application-owned z.object/z.strictObject schemas are checked.",
11338
+ "Only direct identifier keys with recognizable numeric Zod leaves in application-owned z.object/z.strictObject schemas are checked; aliases and transformations are not inferred.",
11165
11339
  "The rule covers control timings such as timeout, delay, interval, backoff, TTL, lease, heartbeat, debounce, and throttle; observed durations and business-domain periods are excluded.",
11166
11340
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and non-Zod schemas are excluded."
11167
11341
  ],
@@ -11173,7 +11347,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11173
11347
  files: [
11174
11348
  {
11175
11349
  path: "src/request.ts",
11176
- source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeoutMs: z.number().int().min(1) });"
11350
+ source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeoutMs: z.number().int().min(1000).max(300000).default(30000) });"
11177
11351
  }
11178
11352
  ],
11179
11353
  focusPath: "src/request.ts",
@@ -11187,7 +11361,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11187
11361
  files: [
11188
11362
  {
11189
11363
  path: "src/request.ts",
11190
- source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeout_seconds: z.number().int().min(1) });"
11364
+ source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeout_seconds: z.number().int().min(1).max(300).default(30) });"
11191
11365
  }
11192
11366
  ],
11193
11367
  focusPath: "src/request.ts",
@@ -11219,8 +11393,9 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11219
11393
  }
11220
11394
  const zodNamespaces = /* @__PURE__ */ new Set();
11221
11395
  const objectFactories = /* @__PURE__ */ new Set();
11396
+ const numberFactories = /* @__PURE__ */ new Set();
11222
11397
  function binding(identifier) {
11223
- return ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11398
+ return ASTUtils25.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11224
11399
  }
11225
11400
  function record(target, identifier) {
11226
11401
  const variable = binding(identifier);
@@ -11238,10 +11413,27 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11238
11413
  const variable = binding(callee.object);
11239
11414
  return variable !== null && zodNamespaces.has(variable);
11240
11415
  }
11416
+ function isNumericSchema(node) {
11417
+ if (node.type !== AST_NODE_TYPES48.CallExpression) return false;
11418
+ const callee = node.callee;
11419
+ if (callee.type === AST_NODE_TYPES48.Identifier) {
11420
+ const variable = binding(callee);
11421
+ return variable !== null && numberFactories.has(variable);
11422
+ }
11423
+ if (callee.type !== AST_NODE_TYPES48.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES48.Identifier) return false;
11424
+ if (callee.object.type === AST_NODE_TYPES48.Identifier) {
11425
+ const variable = binding(callee.object);
11426
+ return callee.property.name === "number" && variable !== null && zodNamespaces.has(variable);
11427
+ }
11428
+ return ["int", "min", "max", "positive", "nonnegative", "finite", "multipleOf", "optional", "nullable", "nullish", "default", "describe", "brand", "readonly"].includes(callee.property.name) && isNumericSchema(callee.object);
11429
+ }
11241
11430
  return {
11242
11431
  ImportDeclaration(node) {
11243
11432
  if (!isZodModule(node.source.value)) return;
11244
11433
  for (const specifier of node.specifiers) {
11434
+ if (specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && specifier.imported.name === "number") {
11435
+ record(numberFactories, specifier.local);
11436
+ }
11245
11437
  if (specifier.type === AST_NODE_TYPES48.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES48.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && specifier.imported.name === "z") {
11246
11438
  record(zodNamespaces, specifier.local);
11247
11439
  } else if (specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && (specifier.imported.name === "object" || specifier.imported.name === "strictObject")) {
@@ -11254,7 +11446,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11254
11446
  const shape = node.arguments[0];
11255
11447
  if (shape?.type !== AST_NODE_TYPES48.ObjectExpression) return;
11256
11448
  for (const member of shape.properties) {
11257
- if (member.type !== AST_NODE_TYPES48.Property) continue;
11449
+ if (member.type !== AST_NODE_TYPES48.Property || !isNumericSchema(member.value)) continue;
11258
11450
  const key = directIdentifierKey(member);
11259
11451
  if (key === null || !CONTROL_SECONDS_RE.test(key.name) && !CONTROL_SECONDS_CAMEL_RE.test(key.name)) {
11260
11452
  continue;
@@ -11267,14 +11459,14 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11267
11459
  });
11268
11460
 
11269
11461
  // src/rules/prefer-immutable-module-constant.ts
11270
- import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
11462
+ import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
11271
11463
  var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
11272
11464
  summary: "Require module-level constant collections to expose readonly state.",
11273
11465
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
11274
11466
  remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
11275
11467
  category: "correctness",
11276
11468
  limitations: [
11277
- "The rule skips generated files, test files, JavaScript files, and collections that are deliberately mutated in their declaring module."
11469
+ "Generated, test and JavaScript files are skipped. Private constants with observed direct or alias mutation are excluded; exported mutable collections remain advisory candidates. Reassigned aliases are conservatively followed, not flow-proven."
11278
11470
  ],
11279
11471
  examples: [
11280
11472
  {
@@ -11420,7 +11612,7 @@ var prefer_immutable_module_constant_default = createRule({
11420
11612
  create(context) {
11421
11613
  const sourceCode = context.sourceCode;
11422
11614
  const isUnshadowedGlobal3 = (identifier) => {
11423
- const variable = ASTUtils18.findVariable(sourceCode.getScope(identifier), identifier.name);
11615
+ const variable = ASTUtils26.findVariable(sourceCode.getScope(identifier), identifier.name);
11424
11616
  return variable === null || variable.defs.length === 0;
11425
11617
  };
11426
11618
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -11428,7 +11620,7 @@ var prefer_immutable_module_constant_default = createRule({
11428
11620
  }
11429
11621
  const exportedNames2 = /* @__PURE__ */ new Set();
11430
11622
  const typeAliases2 = /* @__PURE__ */ new Map();
11431
- const mutatesThroughConstAlias = (root) => {
11623
+ const mutatesThroughAlias = (root) => {
11432
11624
  const pending = [root];
11433
11625
  const seen = /* @__PURE__ */ new Set();
11434
11626
  while (pending.length > 0) {
@@ -11440,7 +11632,7 @@ var prefer_immutable_module_constant_default = createRule({
11440
11632
  if (identifier.type !== AST_NODE_TYPES49.Identifier) continue;
11441
11633
  if (referenceMutates(identifier, isUnshadowedGlobal3)) return true;
11442
11634
  const declarator = identifier.parent;
11443
- if (declarator.type !== AST_NODE_TYPES49.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== AST_NODE_TYPES49.Identifier || declarator.parent.type !== AST_NODE_TYPES49.VariableDeclaration || declarator.parent.kind !== "const") {
11635
+ if (declarator.type !== AST_NODE_TYPES49.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== AST_NODE_TYPES49.Identifier || declarator.parent.type !== AST_NODE_TYPES49.VariableDeclaration) {
11444
11636
  continue;
11445
11637
  }
11446
11638
  const alias = sourceCode.getDeclaredVariables(declarator)[0];
@@ -11489,7 +11681,7 @@ var prefer_immutable_module_constant_default = createRule({
11489
11681
  return;
11490
11682
  }
11491
11683
  const variable = sourceCode.getDeclaredVariables(node)[0];
11492
- if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughConstAlias(variable)) {
11684
+ if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughAlias(variable)) {
11493
11685
  return;
11494
11686
  }
11495
11687
  context.report({
@@ -11916,7 +12108,7 @@ var PREFER_MODULE_LEVEL_CONSTANT_DOCUMENTATION = {
11916
12108
  rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
11917
12109
  remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
11918
12110
  category: "performance",
11919
- limitations: ["Collections that are small, mutated, escape the function, or depend on local values are not reported."],
12111
+ limitations: ["Small collections, observed direct or nested mutations, nested aliases, direct escapes, and dependencies on local values are excluded. Directly invoked function expressions are excluded rather than assuming they run repeatedly. Callback effects and indirect escapes are not analyzed interprocedurally; review those before hoisting."],
11920
12112
  examples: [
11921
12113
  { id: "hoisted-collection", title: "Hoist a constant collection", outcome: "no-match", files: [{ path: "src/keys.ts", source: "const KEYS = ['a', 'b', 'c'] as const; function isAllowed(key: string) { return KEYS.includes(key); }" }], focusPath: "src/keys.ts", expectedCount: 0, public: true },
11922
12114
  { id: "local-collection", title: "Do not recreate a constant collection", outcome: "match", files: [{ path: "src/keys.ts", source: "function isAllowed(key: string) { const KEYS = ['a', 'b', 'c']; return KEYS.includes(key); }" }], focusPath: "src/keys.ts", expectedCount: 1, public: true }
@@ -12071,12 +12263,16 @@ var NON_RETAINING_BUILTINS = /* @__PURE__ */ new Map(
12071
12263
  ]
12072
12264
  );
12073
12265
  function isSafeRead(identifier) {
12074
- const parent = identifier.parent;
12266
+ let parent = identifier.parent;
12075
12267
  if (parent.type === AST_NODE_TYPES51.MemberExpression) {
12076
12268
  if (parent.object !== identifier) {
12077
12269
  return true;
12078
12270
  }
12271
+ while (parent.parent.type === AST_NODE_TYPES51.MemberExpression && parent.parent.object === parent) {
12272
+ parent = parent.parent;
12273
+ }
12079
12274
  const grandparent = parent.parent;
12275
+ if (grandparent.type === AST_NODE_TYPES51.VariableDeclarator || grandparent.type === AST_NODE_TYPES51.SpreadElement) return false;
12080
12276
  if (grandparent.type === AST_NODE_TYPES51.AssignmentExpression && grandparent.left === parent) {
12081
12277
  return false;
12082
12278
  }
@@ -12086,7 +12282,7 @@ function isSafeRead(identifier) {
12086
12282
  if (grandparent.type === AST_NODE_TYPES51.UnaryExpression && grandparent.operator === "delete") {
12087
12283
  return false;
12088
12284
  }
12089
- if (!parent.computed && parent.property.type === AST_NODE_TYPES51.Identifier && MUTATING_METHODS2.has(parent.property.name) && grandparent.type === AST_NODE_TYPES51.CallExpression && grandparent.callee === parent) {
12285
+ if (grandparent.type === AST_NODE_TYPES51.CallExpression && grandparent.callee === parent && (parent.computed ? parent.property.type !== AST_NODE_TYPES51.Literal || typeof parent.property.value !== "string" || MUTATING_METHODS2.has(parent.property.value) : parent.property.type === AST_NODE_TYPES51.Identifier && MUTATING_METHODS2.has(parent.property.name))) {
12090
12286
  return false;
12091
12287
  }
12092
12288
  return true;
@@ -12194,9 +12390,15 @@ var prefer_module_level_constant_default = createRule({
12194
12390
  if (node.id.type !== AST_NODE_TYPES51.Identifier || node.init === null) {
12195
12391
  return;
12196
12392
  }
12197
- if (enclosingFunction3(node) === null) {
12393
+ const owner = enclosingFunction3(node);
12394
+ if (owner === null) {
12198
12395
  return;
12199
12396
  }
12397
+ let expression = owner;
12398
+ while (expression.parent !== void 0 && unwrap4(expression.parent) === expression) {
12399
+ expression = expression.parent;
12400
+ }
12401
+ if (expression.parent?.type === AST_NODE_TYPES51.CallExpression && expression.parent.callee === expression) return;
12200
12402
  const candidate2 = classify(node.init, checkRegex);
12201
12403
  if (candidate2 === null) {
12202
12404
  return;
@@ -12221,10 +12423,10 @@ var prefer_module_level_constant_default = createRule({
12221
12423
  import { AST_NODE_TYPES as AST_NODE_TYPES52 } from "@typescript-eslint/utils";
12222
12424
  var PREFER_MODULE_LEVEL_SCHEMA_DOCUMENTATION = {
12223
12425
  summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
12224
- rationale: "A closed schema created inside a function is rebuilt on every call and cannot be reused or exported for inference.",
12426
+ rationale: "A schema created inside a function is rebuilt on each call. Module scope can enable reuse across callers; local schemas already support local type inference.",
12225
12427
  remediation: "Move the closed schema declaration to module scope and reference it from the function.",
12226
12428
  category: "performance",
12227
- limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded."],
12429
+ limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded.", "Eager calls outside the recognized Zod construction chain and new expressions are excluded. This is manual guidance, not a purity proof: review getters, callback effects, schema identity, error customization, and module initialization order before moving construction."],
12228
12430
  examples: [
12229
12431
  { id: "module-schema", title: "Declare the schema once", outcome: "no-match", files: [{ path: "src/handler.ts", source: "import { z } from 'zod'; const ZBody = z.object({ id: z.string(), name: z.string() }); export function handle(raw: unknown) { return ZBody.parse(raw); }" }], focusPath: "src/handler.ts", expectedCount: 0, public: true },
12230
12432
  { id: "local-schema", title: "Do not rebuild a closed schema", outcome: "match", files: [{ path: "src/handler.ts", source: "import { z } from 'zod'; export function handle(raw: unknown) { const ZBody = z.object({ id: z.string(), name: z.string() }); return ZBody.parse(raw); }" }], focusPath: "src/handler.ts", expectedCount: 1, public: true }
@@ -12241,6 +12443,36 @@ var DEFAULT_FACTORIES = [
12241
12443
  "union"
12242
12444
  ];
12243
12445
  var DEFAULT_MIN_PROPERTIES = 2;
12446
+ var CONSTRUCTION_FACTORIES = /* @__PURE__ */ new Set([
12447
+ ...DEFAULT_FACTORIES,
12448
+ "any",
12449
+ "array",
12450
+ "bigint",
12451
+ "boolean",
12452
+ "custom",
12453
+ "date",
12454
+ "enum",
12455
+ "instanceof",
12456
+ "lazy",
12457
+ "literal",
12458
+ "map",
12459
+ "nan",
12460
+ "nativeEnum",
12461
+ "never",
12462
+ "null",
12463
+ "nullable",
12464
+ "nullish",
12465
+ "number",
12466
+ "optional",
12467
+ "preprocess",
12468
+ "promise",
12469
+ "set",
12470
+ "string",
12471
+ "symbol",
12472
+ "undefined",
12473
+ "unknown",
12474
+ "void"
12475
+ ]);
12244
12476
  var MEMO_CALLEES = /* @__PURE__ */ new Set([
12245
12477
  "lazy",
12246
12478
  "memo",
@@ -12317,7 +12549,7 @@ function outermostEnclosingFunction(node) {
12317
12549
  }
12318
12550
  return outermost;
12319
12551
  }
12320
- function subtreeSome(root, predicate) {
12552
+ function subtreeSome(root, predicate, skipDeferredFunctions = false) {
12321
12553
  let found = false;
12322
12554
  const visit = (value) => {
12323
12555
  if (found || value === null || typeof value !== "object") {
@@ -12333,6 +12565,7 @@ function subtreeSome(root, predicate) {
12333
12565
  if (typeof candidate2.type !== "string") {
12334
12566
  return;
12335
12567
  }
12568
+ if (skipDeferredFunctions && FUNCTION_TYPES8.has(candidate2.type)) return;
12336
12569
  if (predicate(candidate2)) {
12337
12570
  found = true;
12338
12571
  return;
@@ -12431,6 +12664,15 @@ var prefer_module_level_schema_default = createRule({
12431
12664
  function isZodCall(node) {
12432
12665
  return node.type === AST_NODE_TYPES52.CallExpression && node.callee.type === AST_NODE_TYPES52.MemberExpression && !node.callee.computed && node.callee.object.type === AST_NODE_TYPES52.Identifier && zodNamespaces.has(node.callee.object.name);
12433
12666
  }
12667
+ function isSchemaConstruction(node) {
12668
+ const callee = node.callee;
12669
+ if (callee.type !== AST_NODE_TYPES52.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES52.Identifier || TERMINAL_METHODS.has(callee.property.name)) return false;
12670
+ if (callee.object.type === AST_NODE_TYPES52.CallExpression) return isSchemaConstruction(callee.object);
12671
+ return isZodCall(node) && CONSTRUCTION_FACTORIES.has(callee.property.name);
12672
+ }
12673
+ function hasEagerComputation(node) {
12674
+ return subtreeSome(node, (inner) => inner.type === AST_NODE_TYPES52.NewExpression || inner.type === AST_NODE_TYPES52.TaggedTemplateExpression || inner.type === AST_NODE_TYPES52.CallExpression && !isSchemaConstruction(inner), true);
12675
+ }
12434
12676
  function isCovered(node) {
12435
12677
  let current = node.parent ?? void 0;
12436
12678
  while (current !== void 0) {
@@ -12560,6 +12802,7 @@ var prefer_module_level_schema_default = createRule({
12560
12802
  return;
12561
12803
  }
12562
12804
  const outermost = outermostSchemaExpression(expression);
12805
+ if (hasEagerComputation(outermost)) return;
12563
12806
  if (outermost !== expression && (readsReceiver(outermost) || buildsLocalizedText(outermost) || !closesOverNothing(outermost, enclosing))) {
12564
12807
  return;
12565
12808
  }
@@ -12579,7 +12822,7 @@ var prefer_module_level_schema_default = createRule({
12579
12822
  // src/rules/prefer-module-level-refined-schema.ts
12580
12823
  import {
12581
12824
  AST_NODE_TYPES as AST_NODE_TYPES53,
12582
- ASTUtils as ASTUtils19
12825
+ ASTUtils as ASTUtils27
12583
12826
  } from "@typescript-eslint/utils";
12584
12827
  var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
12585
12828
  var FACTORIES = /* @__PURE__ */ new Set([
@@ -12700,7 +12943,8 @@ var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
12700
12943
  limitations: [
12701
12944
  "Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
12702
12945
  "Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
12703
- "Literal string z.enum domains are owned by prefer-shared-zod-enum."
12946
+ "Literal string z.enum domains are owned by prefer-shared-zod-enum.",
12947
+ "Eager calls outside recognized Zod construction chains and new expressions are excluded. This is manual guidance, not a purity proof: review getters, callback effects, error customization, schema identity, and module initialization order before moving construction."
12704
12948
  ],
12705
12949
  examples: [
12706
12950
  {
@@ -12742,7 +12986,7 @@ function collectReferences2(scope, output) {
12742
12986
  output.push(...scope.references);
12743
12987
  for (const child of scope.childScopes) collectReferences2(child, output);
12744
12988
  }
12745
- function subtreeSome2(root, predicate) {
12989
+ function subtreeSome2(root, predicate, skipDeferredFunctions = false) {
12746
12990
  let found = false;
12747
12991
  const visit = (value) => {
12748
12992
  if (found || value === null || typeof value !== "object") return;
@@ -12752,6 +12996,7 @@ function subtreeSome2(root, predicate) {
12752
12996
  }
12753
12997
  const candidate2 = value;
12754
12998
  if (typeof candidate2.type !== "string") return;
12999
+ if (skipDeferredFunctions && FUNCTION_TYPES9.has(candidate2.type)) return;
12755
13000
  if (predicate(candidate2)) {
12756
13001
  found = true;
12757
13002
  return;
@@ -12850,7 +13095,7 @@ var prefer_module_level_refined_schema_default = createRule({
12850
13095
  return {};
12851
13096
  const zodBindings = /* @__PURE__ */ new Set();
12852
13097
  function resolvedBinding(identifier) {
12853
- return ASTUtils19.findVariable(
13098
+ return ASTUtils27.findVariable(
12854
13099
  context.sourceCode.getScope(identifier),
12855
13100
  identifier.name
12856
13101
  );
@@ -12872,6 +13117,15 @@ var prefer_module_level_refined_schema_default = createRule({
12872
13117
  return names[1] ?? null;
12873
13118
  return null;
12874
13119
  }
13120
+ function isSchemaConstruction(node) {
13121
+ const callee = node.callee;
13122
+ if (callee.type !== AST_NODE_TYPES53.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES53.Identifier || NON_SCHEMA_TERMINALS.has(callee.property.name)) return false;
13123
+ if (callee.object.type === AST_NODE_TYPES53.CallExpression) return isSchemaConstruction(callee.object);
13124
+ return factoryName(node, FACTORIES) !== null || factoryName(node, COMPOSITE_FACTORIES) !== null;
13125
+ }
13126
+ function hasEagerComputation(node) {
13127
+ return subtreeSome2(node, (inner) => inner.type === AST_NODE_TYPES53.NewExpression || inner.type === AST_NODE_TYPES53.TaggedTemplateExpression || inner.type === AST_NODE_TYPES53.CallExpression && !isSchemaConstruction(inner), true);
13128
+ }
12875
13129
  function isSharedEnumDomain(node, factory) {
12876
13130
  if (factory !== "enum") return false;
12877
13131
  const [argument] = node.arguments;
@@ -12940,7 +13194,7 @@ var prefer_module_level_refined_schema_default = createRule({
12940
13194
  const enclosing = outermostEnclosingFunction2(node);
12941
13195
  if (enclosing === void 0) return;
12942
13196
  const expression = schemaExpression2(node);
12943
- if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
13197
+ if (hasEagerComputation(expression) || readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
12944
13198
  return;
12945
13199
  context.report({ node, messageId: "hoistRefinedSchema" });
12946
13200
  }
@@ -12951,7 +13205,7 @@ var prefer_module_level_refined_schema_default = createRule({
12951
13205
  // src/rules/prefer-multi-value-zod-literal.ts
12952
13206
  import {
12953
13207
  AST_NODE_TYPES as AST_NODE_TYPES54,
12954
- ASTUtils as ASTUtils20
13208
+ ASTUtils as ASTUtils28
12955
13209
  } from "@typescript-eslint/utils";
12956
13210
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12957
13211
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
@@ -12971,7 +13225,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12971
13225
  outcome: "no-match",
12972
13226
  files: [{
12973
13227
  path: "src/schema.ts",
12974
- source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
13228
+ source: "import { z } from 'zod/v4'; export const Version = z.literal([1, 2, 3]);"
12975
13229
  }],
12976
13230
  focusPath: "src/schema.ts",
12977
13231
  expectedCount: 0,
@@ -12983,7 +13237,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12983
13237
  outcome: "match",
12984
13238
  files: [{
12985
13239
  path: "src/schema.ts",
12986
- source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
13240
+ source: "import { z } from 'zod/v4'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
12987
13241
  }],
12988
13242
  focusPath: "src/schema.ts",
12989
13243
  expectedCount: 1,
@@ -12998,7 +13252,7 @@ function isStaticPrimitive(node, context) {
12998
13252
  if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
12999
13253
  return true;
13000
13254
  if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
13001
- const binding = ASTUtils20.findVariable(
13255
+ const binding = ASTUtils28.findVariable(
13002
13256
  context.sourceCode.getScope(node),
13003
13257
  node.name
13004
13258
  );
@@ -13035,7 +13289,7 @@ var prefer_multi_value_zod_literal_default = createRule({
13035
13289
  const zodBindings = /* @__PURE__ */ new Set();
13036
13290
  const zod4Bindings = /* @__PURE__ */ new Set();
13037
13291
  function resolvedBinding(identifier) {
13038
- return ASTUtils20.findVariable(
13292
+ return ASTUtils28.findVariable(
13039
13293
  context.sourceCode.getScope(identifier),
13040
13294
  identifier.name
13041
13295
  );
@@ -13106,7 +13360,7 @@ function isLiteralUnion(node) {
13106
13360
  function exportedContract(node) {
13107
13361
  let current = node;
13108
13362
  while (current !== void 0) {
13109
- if (current.type === AST_NODE_TYPES55.ExportNamedDeclaration) return true;
13363
+ if (current.type === AST_NODE_TYPES55.TSTypeAliasDeclaration || current.type === AST_NODE_TYPES55.TSInterfaceDeclaration) return current.parent.type === AST_NODE_TYPES55.ExportNamedDeclaration;
13110
13364
  if (current.type === AST_NODE_TYPES55.Program) return false;
13111
13365
  current = current.parent ?? void 0;
13112
13366
  }
@@ -13142,11 +13396,11 @@ import { AST_NODE_TYPES as AST_NODE_TYPES56 } from "@typescript-eslint/utils";
13142
13396
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
13143
13397
  summary: "Prefer a named contract for structurally complex function return types.",
13144
13398
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
13145
- remediation: "Extract the return annotation to a named type or interface and reference that contract from the signature.",
13399
+ remediation: "Name the complex nested shape while preserving its generic wrappers and type parameters; reference the named contract from the return annotation.",
13146
13400
  category: "maintainability",
13147
13401
  limitations: [
13148
13402
  "Only explicit object types with at least three members and unions with at least three object variants are reported.",
13149
- "Generic wrappers such as Promise and Readonly are unwrapped one level at a time; inferred return types are outside this rule."
13403
+ "Any single-argument generic wrapper is traversed to find nested shapes, not assumed semantically transparent. Inferred return types are outside this rule; extraction is manual and must preserve locally bound type parameters."
13150
13404
  ],
13151
13405
  examples: [
13152
13406
  { id: "named-result", title: "Name a multi-state result", outcome: "no-match", files: [{ path: "src/queue.ts", source: "type ClaimResult = { state: 'idle' } | { state: 'waiting'; retryAt: number } | { state: 'claimed'; id: string }; export function claim(): ClaimResult { return { state: 'idle' }; }" }], focusPath: "src/queue.ts", expectedCount: 0, public: true },
@@ -13197,14 +13451,14 @@ var prefer_named_complex_return_type_default = createRule({
13197
13451
  });
13198
13452
 
13199
13453
  // src/rules/prefer-native-random-uuid.ts
13200
- import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils21 } from "@typescript-eslint/utils";
13454
+ import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
13201
13455
  var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
13202
13456
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
13203
13457
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
13204
13458
  remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
13205
13459
  category: "maintainability",
13206
13460
  autofix: "suggestion",
13207
- limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
13461
+ limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded. Suggestions require unshadowed globalThis and comment-free calls; verify native randomUUID availability in the deployment runtime."],
13208
13462
  examples: [
13209
13463
  { id: "native-random-uuid", title: "Use the platform UUID generator", outcome: "no-match", files: [{ path: "src/id.ts", source: "const id = globalThis.crypto.randomUUID();" }], focusPath: "src/id.ts", expectedCount: 0, public: true },
13210
13464
  { id: "uuid-v4-package", title: "Do not call uuid v4 without options", outcome: "match", files: [{ path: "src/id.ts", source: "import { v4 } from 'uuid'; const id = v4();" }], focusPath: "src/id.ts", expectedCount: 1, public: true }
@@ -13224,7 +13478,7 @@ var prefer_native_random_uuid_default = createRule({
13224
13478
  hasSuggestions: true,
13225
13479
  schema: [],
13226
13480
  messages: {
13227
- preferNative: "Use the Node 22 native `globalThis.crypto.randomUUID()` instead of the `uuid` package for UUID v4.",
13481
+ preferNative: "Where supported by the deployment runtime, prefer native `globalThis.crypto.randomUUID()` over the `uuid` package for UUID v4.",
13228
13482
  replaceWithNative: "Replace this UUID v4 call with the native implementation."
13229
13483
  }
13230
13484
  },
@@ -13233,22 +13487,24 @@ var prefer_native_random_uuid_default = createRule({
13233
13487
  const directBindings = /* @__PURE__ */ new Set();
13234
13488
  const namespaceBindings = /* @__PURE__ */ new Set();
13235
13489
  function resolve2(identifier) {
13236
- return ASTUtils21.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13490
+ return ASTUtils29.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13237
13491
  }
13238
13492
  function record(identifier, destination) {
13239
13493
  const variable = resolve2(identifier);
13240
13494
  if (variable !== null) destination.add(variable);
13241
13495
  }
13242
13496
  function report2(node) {
13497
+ const globalBinding = ASTUtils29.findVariable(context.sourceCode.getScope(node), "globalThis");
13498
+ const canSuggest = (globalBinding?.defs.length ?? 0) === 0 && context.sourceCode.getCommentsInside(node).length === 0;
13243
13499
  context.report({
13244
13500
  node,
13245
13501
  messageId: "preferNative",
13246
- suggest: [
13502
+ suggest: canSuggest ? [
13247
13503
  {
13248
13504
  messageId: "replaceWithNative",
13249
13505
  fix: (fixer) => fixer.replaceText(node, "globalThis.crypto.randomUUID()")
13250
13506
  }
13251
- ]
13507
+ ] : []
13252
13508
  });
13253
13509
  }
13254
13510
  return {
@@ -13296,16 +13552,18 @@ var prefer_native_random_uuid_default = createRule({
13296
13552
  });
13297
13553
 
13298
13554
  // src/rules/prefer-node-crypto-hash.ts
13299
- import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils22 } from "@typescript-eslint/utils";
13555
+ import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils30 } from "@typescript-eslint/utils";
13300
13556
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
13301
13557
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
13302
13558
  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.",
13303
- remediation: "Import hash from node:crypto and replace a single-update chain with hash(algorithm, value, encoding). Keep createHash for streams or multiple incremental updates.",
13559
+ remediation: "On a supported Node runtime, consider hash(algorithm, value, encoding). Preserve the output encoding explicitly: digest() returns a Buffer, while hash defaults to hex. Keep createHash for streams or multiple updates.",
13304
13560
  category: "performance",
13305
13561
  limitations: [
13306
13562
  "Only bindings and inline calls with statically proven provenance from crypto or node:crypto are analyzed; arbitrary assignments and dynamic module specifiers are excluded.",
13307
- "Only a literal algorithm with exactly one update call is reported; streaming and incremental hashes remain valid."
13563
+ "Only a literal algorithm with exactly one update call is reported; streaming and incremental hashes remain valid.",
13564
+ "Runtime support and output encoding require manual review; no autofix or guaranteed speedup is promised."
13308
13565
  ],
13566
+ references: ["https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-options"],
13309
13567
  examples: [
13310
13568
  { id: "one-shot-hash", title: "Use Node's one-shot hash API", outcome: "no-match", files: [{ path: "case.ts", source: "import { hash } from 'node:crypto'; export const digest = hash('sha256', 'value', 'hex');" }], focusPath: "case.ts", expectedCount: 0, public: true },
13311
13569
  { id: "mutable-one-shot-chain", title: "Avoid mutable state for one value", outcome: "match", files: [{ path: "case.ts", source: "import { createHash } from 'node:crypto'; export const digest = createHash('sha256').update('value').digest('hex');" }], focusPath: "case.ts", expectedCount: 1, public: true }
@@ -13352,7 +13610,7 @@ var prefer_node_crypto_hash_default = createRule({
13352
13610
  const directBindings = /* @__PURE__ */ new Set();
13353
13611
  const namespaceBindings = /* @__PURE__ */ new Set();
13354
13612
  function resolve2(identifier) {
13355
- return ASTUtils22.findVariable(
13613
+ return ASTUtils30.findVariable(
13356
13614
  context.sourceCode.getScope(identifier),
13357
13615
  identifier.name
13358
13616
  );
@@ -13426,7 +13684,7 @@ function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
13426
13684
  }
13427
13685
 
13428
13686
  // src/rules/prefer-node-fs-promises.ts
13429
- import { AST_NODE_TYPES as AST_NODE_TYPES59 } from "@typescript-eslint/utils";
13687
+ import { AST_NODE_TYPES as AST_NODE_TYPES59, ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
13430
13688
  var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
13431
13689
  summary: "Prefer promise-based Node.js filesystem APIs over synchronous calls in production modules.",
13432
13690
  rationale: "Synchronous filesystem work blocks the event loop and can stall unrelated daemon, server, and worker tasks.",
@@ -13434,6 +13692,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
13434
13692
  category: "performance",
13435
13693
  limitations: [
13436
13694
  "Tests and generated files are excluded.",
13695
+ "This recommendation applies to Node-compatible runtimes. Changing a synchronous API to a promise changes its caller contract; review startup-only work and APIs that require synchronous execution rather than mechanically adding await.",
13437
13696
  "ESLint rule implementations under src/rules are excluded because visitor creation and execution are synchronous by contract.",
13438
13697
  "Only statically identifiable node:fs loads are inspected; filesystem objects passed through arbitrary functions or assignments require type-aware analysis."
13439
13698
  ],
@@ -13452,14 +13711,14 @@ function memberName5(node) {
13452
13711
  function unwrapAwait2(node) {
13453
13712
  return node.type === AST_NODE_TYPES59.AwaitExpression ? node.argument : node;
13454
13713
  }
13455
- function isFsLoader(node) {
13714
+ function isFsLoader(node, isGlobal) {
13456
13715
  const expression = unwrapAwait2(node);
13457
13716
  if (expression.type === AST_NODE_TYPES59.ImportExpression) return isFsSpecifier(expression.source);
13458
13717
  if (expression.type !== AST_NODE_TYPES59.CallExpression || expression.arguments.length !== 1) return false;
13459
13718
  const [argument] = expression.arguments;
13460
13719
  if (argument === void 0 || argument.type === AST_NODE_TYPES59.SpreadElement || !isFsSpecifier(argument)) return false;
13461
- if (expression.callee.type === AST_NODE_TYPES59.Identifier) return expression.callee.name === "require";
13462
- return expression.callee.type === AST_NODE_TYPES59.MemberExpression && expression.callee.object.type === AST_NODE_TYPES59.Identifier && expression.callee.object.name === "process" && memberName5(expression.callee) === "getBuiltinModule";
13720
+ if (expression.callee.type === AST_NODE_TYPES59.Identifier) return expression.callee.name === "require" && isGlobal(expression.callee);
13721
+ return expression.callee.type === AST_NODE_TYPES59.MemberExpression && expression.callee.object.type === AST_NODE_TYPES59.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) && memberName5(expression.callee) === "getBuiltinModule";
13463
13722
  }
13464
13723
  function isFsSpecifier(node) {
13465
13724
  return node.type === AST_NODE_TYPES59.Literal && (node.value === "node:fs" || node.value === "fs");
@@ -13486,13 +13745,26 @@ var prefer_node_fs_promises_default = createRule({
13486
13745
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text) || normalizedFilename.includes("src/rules/"))
13487
13746
  return {};
13488
13747
  const namespaces = /* @__PURE__ */ new Set();
13748
+ const bindingOf = (node) => ASTUtils31.findVariable(context.sourceCode.getScope(node), node.name);
13749
+ const isGlobal = (node) => {
13750
+ const binding = bindingOf(node);
13751
+ return binding === null || binding.defs.length === 0;
13752
+ };
13753
+ const isNamespace = (node) => {
13754
+ const binding = bindingOf(node);
13755
+ return binding !== null && namespaces.has(binding) && !binding.references.some((reference) => reference.isWrite() && reference.init !== true);
13756
+ };
13757
+ const recordNamespace = (node) => {
13758
+ const binding = bindingOf(node);
13759
+ if (binding !== null) namespaces.add(binding);
13760
+ };
13489
13761
  return {
13490
13762
  ImportDeclaration(node) {
13491
13763
  if (node.source.value !== "node:fs" && node.source.value !== "fs") return;
13492
13764
  const synchronousImports = [];
13493
13765
  for (const specifier of node.specifiers) {
13494
13766
  if (specifier.type === AST_NODE_TYPES59.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES59.ImportDefaultSpecifier) {
13495
- namespaces.add(specifier.local.name);
13767
+ recordNamespace(specifier.local);
13496
13768
  continue;
13497
13769
  }
13498
13770
  if (specifier.type === AST_NODE_TYPES59.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES59.Identifier && specifier.imported.name.endsWith("Sync")) synchronousImports.push(specifier.imported.name);
@@ -13506,10 +13778,10 @@ var prefer_node_fs_promises_default = createRule({
13506
13778
  }
13507
13779
  },
13508
13780
  VariableDeclarator(node) {
13509
- if (node.init === null || !isFsLoader(node.init) && (node.init.type !== AST_NODE_TYPES59.Identifier || !namespaces.has(node.init.name)))
13781
+ if (node.init === null || !isFsLoader(node.init, isGlobal) && (node.init.type !== AST_NODE_TYPES59.Identifier || !isNamespace(node.init)))
13510
13782
  return;
13511
13783
  if (node.id.type === AST_NODE_TYPES59.Identifier) {
13512
- namespaces.add(node.id.name);
13784
+ recordNamespace(node.id);
13513
13785
  return;
13514
13786
  }
13515
13787
  if (node.id.type !== AST_NODE_TYPES59.ObjectPattern) return;
@@ -13530,7 +13802,7 @@ var prefer_node_fs_promises_default = createRule({
13530
13802
  const name = memberName5(node);
13531
13803
  if (name?.endsWith("Sync") !== true) return;
13532
13804
  const object = unwrapAwait2(node.object);
13533
- if (object.type === AST_NODE_TYPES59.Identifier && namespaces.has(object.name) || isFsLoader(object)) {
13805
+ if (object.type === AST_NODE_TYPES59.Identifier && isNamespace(object) || isFsLoader(object, isGlobal)) {
13534
13806
  context.report({ node, messageId: "preferAsyncFs", data: { name } });
13535
13807
  }
13536
13808
  }
@@ -13539,13 +13811,13 @@ var prefer_node_fs_promises_default = createRule({
13539
13811
  });
13540
13812
 
13541
13813
  // src/rules/prefer-non-nullable-collection.ts
13542
- import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
13814
+ import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils32 } from "@typescript-eslint/utils";
13543
13815
  var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
13544
- summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
13816
+ summary: "Suggest reviewing nullish arrays that use local empty-array defaults or a shared null-or-empty guard.",
13545
13817
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
13546
13818
  remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
13547
13819
  category: "maintainability",
13548
- limitations: ["The rule requires local evidence that nullish and empty values are treated identically and skips exported wire shapes."],
13820
+ limitations: ["Recognized defaults and guards are manual modeling prompts, not proof of equivalence for every caller or later use. Exported wire shapes and unknown object escapes are excluded; preserve meaningful null states and boundary compatibility."],
13549
13821
  examples: [
13550
13822
  { id: "non-null-array", title: "Model an always-present collection", outcome: "no-match", files: [{ path: "src/search.ts", source: "interface Input { items: string[] } function search({ items }: Input) { return items.length; }" }], focusPath: "src/search.ts", expectedCount: 0, public: true },
13551
13823
  { id: "defaulted-nullish-array", title: "Do not retain a redundant nullish state", outcome: "match", files: [{ path: "src/search.ts", source: "interface Input { items: string[] | undefined } function search({ items = [] }: Input) { return items.length; }" }], focusPath: "src/search.ts", expectedCount: 1, public: true }
@@ -13614,22 +13886,6 @@ function sameAccess(node, access) {
13614
13886
  }
13615
13887
  return node.type === AST_NODE_TYPES60.MemberExpression && !node.computed && node.object.type === AST_NODE_TYPES60.Identifier && node.object.name === access.object && node.property.type === AST_NODE_TYPES60.Identifier && node.property.name === access.property;
13616
13888
  }
13617
- function isNullGuard(node, access) {
13618
- if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && (sameAccess(node.argument, access) || optionalMemberLengthOf(node.argument, access))) return true;
13619
- if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "==="].includes(node.operator)) {
13620
- return false;
13621
- }
13622
- const nullish = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === null || value.type === AST_NODE_TYPES60.Identifier && value.name === "undefined";
13623
- return sameAccess(node.left, access) && nullish(node.right) || sameAccess(node.right, access) && nullish(node.left);
13624
- }
13625
- function isEmptyGuard(node, access) {
13626
- if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && memberLengthOf(node.argument, access)) return true;
13627
- if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "===", "<="].includes(node.operator)) {
13628
- return false;
13629
- }
13630
- const zero = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === 0;
13631
- return memberLengthOf(node.left, access) && zero(node.right) || memberLengthOf(node.right, access) && zero(node.left);
13632
- }
13633
13889
  function memberLengthOf(node, access) {
13634
13890
  const target = node.type === AST_NODE_TYPES60.ChainExpression ? node.expression : node;
13635
13891
  return target.type === AST_NODE_TYPES60.MemberExpression && !target.computed && target.property.type === AST_NODE_TYPES60.Identifier && target.property.name === "length" && sameAccess(target.object, access);
@@ -13644,7 +13900,24 @@ function hasEquivalentLeadingGuard(fn, access, visitorKeys) {
13644
13900
  const terminating = first.consequent.type === AST_NODE_TYPES60.ReturnStatement || first.consequent.type === AST_NODE_TYPES60.ThrowStatement || first.consequent.type === AST_NODE_TYPES60.BlockStatement && first.consequent.body.length === 1 && (first.consequent.body[0]?.type === AST_NODE_TYPES60.ReturnStatement || first.consequent.body[0]?.type === AST_NODE_TYPES60.ThrowStatement);
13645
13901
  if (!terminating) return false;
13646
13902
  if (contains(first.consequent, visitorKeys, (node) => sameAccess(node, access))) return false;
13647
- return contains(first.test, visitorKeys, (node) => isNullGuard(node, access)) && contains(first.test, visitorKeys, (node) => isEmptyGuard(node, access));
13903
+ if (first.test.type === AST_NODE_TYPES60.UnaryExpression && first.test.operator === "!" && optionalMemberLengthOf(first.test.argument, access)) return true;
13904
+ return first.test.type === AST_NODE_TYPES60.LogicalExpression && first.test.operator === "||" && isNullGuard(first.test.left, access) && isEmptyGuard(first.test.right, access);
13905
+ }
13906
+ function isNullGuard(node, access) {
13907
+ if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && (sameAccess(node.argument, access) || optionalMemberLengthOf(node.argument, access))) return true;
13908
+ if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "==="].includes(node.operator)) {
13909
+ return false;
13910
+ }
13911
+ const nullish = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === null || value.type === AST_NODE_TYPES60.Identifier && value.name === "undefined";
13912
+ return sameAccess(node.left, access) && nullish(node.right) || sameAccess(node.right, access) && nullish(node.left);
13913
+ }
13914
+ function isEmptyGuard(node, access) {
13915
+ if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && memberLengthOf(node.argument, access)) return true;
13916
+ if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "===", "<="].includes(node.operator)) {
13917
+ return false;
13918
+ }
13919
+ const zero = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === 0;
13920
+ return memberLengthOf(node.left, access) && zero(node.right) || node.operator !== "<=" && memberLengthOf(node.right, access) && zero(node.left);
13648
13921
  }
13649
13922
  function contains(node, visitorKeys, predicate) {
13650
13923
  if (predicate(node)) return true;
@@ -13669,20 +13942,20 @@ function directlyCoalesced(node) {
13669
13942
  return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
13670
13943
  }
13671
13944
  function identifierIsOnlyCoalesced(context, binding, fn) {
13672
- const variable = ASTUtils23.findVariable(context.sourceCode.getScope(binding), binding.name);
13945
+ const variable = ASTUtils32.findVariable(context.sourceCode.getScope(binding), binding.name);
13673
13946
  if (variable === null || variable.references.length === 0) return false;
13674
13947
  return variable.references.every(
13675
13948
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
13676
13949
  );
13677
13950
  }
13678
13951
  function memberIsOnlyCoalesced(context, object, property, fn) {
13679
- const variable = ASTUtils23.findVariable(context.sourceCode.getScope(object), object.name);
13952
+ const variable = ASTUtils32.findVariable(context.sourceCode.getScope(object), object.name);
13680
13953
  if (variable === null) return false;
13681
13954
  const accesses = variable.references.flatMap((reference) => {
13682
13955
  if (!belongsToFunction(reference.identifier, fn)) return [null];
13683
13956
  const parent = reference.identifier.parent;
13684
13957
  if (parent?.type === AST_NODE_TYPES60.MemberExpression && !parent.computed && parent.object === reference.identifier && parent.property.type === AST_NODE_TYPES60.Identifier && parent.property.name === property) return [parent];
13685
- return [];
13958
+ return parent?.type === AST_NODE_TYPES60.MemberExpression && parent.object === reference.identifier ? [] : [null];
13686
13959
  });
13687
13960
  return accesses.length > 0 && accesses.every((access) => access !== null && directlyCoalesced(access));
13688
13961
  }
@@ -13692,11 +13965,11 @@ var prefer_non_nullable_collection_default = createRule({
13692
13965
  meta: {
13693
13966
  type: "suggestion",
13694
13967
  docs: {
13695
- description: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection."
13968
+ description: "Suggest reviewing nullish arrays that use local empty-array defaults or a shared null-or-empty guard."
13696
13969
  },
13697
13970
  schema: [],
13698
13971
  messages: {
13699
- preferNonNullableCollection: "`{{name}}` is locally treated exactly like `[]`; make it a non-null array and normalize omitted input at the boundary."
13972
+ preferNonNullableCollection: "`{{name}}` uses an empty-array default or shared null-or-empty guard; consider a non-null array after checking that the nullish state carries no separate meaning."
13700
13973
  }
13701
13974
  },
13702
13975
  defaultOptions: [],
@@ -13786,7 +14059,7 @@ var prefer_non_nullable_collection_default = createRule({
13786
14059
  // src/rules/prefer-nullish-filter-predicate.ts
13787
14060
  import {
13788
14061
  AST_NODE_TYPES as AST_NODE_TYPES61,
13789
- ASTUtils as ASTUtils24,
14062
+ ASTUtils as ASTUtils33,
13790
14063
  ESLintUtils as ESLintUtils5
13791
14064
  } from "@typescript-eslint/utils";
13792
14065
  import ts3 from "typescript";
@@ -13832,7 +14105,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
13832
14105
  ]
13833
14106
  };
13834
14107
  function isUnshadowedBoolean(node, context) {
13835
- const variable = ASTUtils24.findVariable(context.sourceCode.getScope(node), node.name);
14108
+ const variable = ASTUtils33.findVariable(context.sourceCode.getScope(node), node.name);
13836
14109
  return variable === null || variable.defs.length === 0;
13837
14110
  }
13838
14111
  function isBuiltinArrayFilter(node, services) {
@@ -13891,7 +14164,7 @@ function isProvablyTruthy(type, checker) {
13891
14164
  }
13892
14165
  function availableParameterName(node, context) {
13893
14166
  for (const name of ["value", "item", "element", "candidate"]) {
13894
- if (ASTUtils24.findVariable(context.sourceCode.getScope(node), name) === null) return name;
14167
+ if (ASTUtils33.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13895
14168
  }
13896
14169
  return null;
13897
14170
  }
@@ -13945,7 +14218,7 @@ var prefer_nullish_filter_predicate_default = createRule({
13945
14218
 
13946
14219
  // src/rules/prefer-await-in-async-return.ts
13947
14220
  import {
13948
- ASTUtils as ASTUtils25,
14221
+ ASTUtils as ASTUtils34,
13949
14222
  ESLintUtils as ESLintUtils6,
13950
14223
  AST_NODE_TYPES as AST_NODE_TYPES62
13951
14224
  } from "@typescript-eslint/utils";
@@ -13953,12 +14226,12 @@ import * as ts4 from "typescript";
13953
14226
  var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
13954
14227
  summary: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform.",
13955
14228
  rationale: "Mixing a directly returned Promise callback into otherwise async control flow makes sequencing and failures harder to read.",
13956
- remediation: "Await the Promise, then return the transformed value with ordinary async statements.",
14229
+ remediation: "Consider awaiting the Promise and returning the transformed value with ordinary async statements. Preserve catch boundaries, callback behavior, and observable scheduling when rewriting manually.",
13957
14230
  category: "maintainability",
13958
14231
  since: "15.6.3",
13959
14232
  limitations: [
13960
14233
  "Only a single directly returned `.then` call with an inline callback is checked.",
13961
- "The receiver must be proven Promise-like by TypeScript; untyped files and larger chains are intentionally ignored.",
14234
+ "The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. Untyped receivers, rejection handlers and named callbacks are excluded. This is not the upstream return-await policy and no scheduling equivalence is promised.",
13962
14235
  "Direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports are excluded because returning the module Promise is their framework contract."
13963
14236
  ],
13964
14237
  examples: [
@@ -14059,13 +14332,13 @@ var prefer_await_in_async_return_default = createRule({
14059
14332
  if (services === null) return {};
14060
14333
  const frameworkLoaders = /* @__PURE__ */ new Set();
14061
14334
  const rememberFrameworkLoader = (identifier) => {
14062
- const variable = ASTUtils25.findVariable(context.sourceCode.getScope(identifier), identifier.name);
14335
+ const variable = ASTUtils34.findVariable(context.sourceCode.getScope(identifier), identifier.name);
14063
14336
  if (variable !== null) frameworkLoaders.add(variable);
14064
14337
  };
14065
14338
  const isFrameworkLoaderCallback = (owner) => {
14066
14339
  const parent = owner.parent;
14067
14340
  if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
14068
- const variable = ASTUtils25.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
14341
+ const variable = ASTUtils34.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
14069
14342
  return variable !== null && frameworkLoaders.has(variable);
14070
14343
  };
14071
14344
  return {
@@ -14095,16 +14368,21 @@ var prefer_await_in_async_return_default = createRule({
14095
14368
  });
14096
14369
 
14097
14370
  // src/rules/prefer-schema-for-api-payload.ts
14098
- import { AST_NODE_TYPES as AST_NODE_TYPES63 } from "@typescript-eslint/utils";
14371
+ import { AST_NODE_TYPES as AST_NODE_TYPES63, ASTUtils as ASTUtils35 } from "@typescript-eslint/utils";
14099
14372
  var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
14100
14373
  summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
14101
14374
  rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
14102
- remediation: "Parse the payload through a schema or establish a recognized runtime validation guard before reading fields.",
14375
+ remediation: "For external payloads, parse through a schema or establish runtime validation before reading fields. Review validator implementations separately rather than recursively requiring another schema.",
14103
14376
  category: "correctness",
14104
- limitations: ["Test fixtures, generated clients, local JSON files, and recognized validation guards are excluded."],
14377
+ limitations: [
14378
+ "A JSON.parse call alone does not prove external input; exact native JSON stringify/parse round trips are excluded. Validator implementations and other non-network parsing can still require manual review rather than a schema rewrite.",
14379
+ "JSON.parse must resolve to the global JSON object. json() receivers require an unshadowed global Request/Response annotation or construction, or stable local aliases of global fetch results; imported, inferred and unknown response types are deliberately not inferred.",
14380
+ "Named validators remain conventions, not proof of their implementation. Their exemptions are confined to a valid branch or a preceding same-block validation statement; ignored predicate results and deferred callbacks do not validate later reads.",
14381
+ "Test fixtures, generated clients and recognized local-file reads are excluded. This is bounded local analysis, not a general control-flow or mutation proof."
14382
+ ],
14105
14383
  examples: [
14106
- { id: "validated-payload", title: "Validate before property access", outcome: "no-match", files: [{ path: "src/client.ts", source: "async function load(response) { const body = UserSchema.parse(await response.json()); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
14107
- { id: "unvalidated-payload", title: "Do not trust response JSON directly", outcome: "match", files: [{ path: "src/client.ts", source: "async function load(response) { const body = await response.json(); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
14384
+ { id: "validated-payload", title: "Validate before property access", outcome: "no-match", files: [{ path: "src/client.ts", source: "async function load(response: Response) { const body = UserSchema.parse(await response.json()); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
14385
+ { id: "unvalidated-payload", title: "Do not trust response JSON directly", outcome: "match", files: [{ path: "src/client.ts", source: "async function load(response: Response) { const body = await response.json(); return body.id; }" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
14108
14386
  ]
14109
14387
  };
14110
14388
  var unwrap6 = (node) => {
@@ -14129,7 +14407,7 @@ var isSchemaParseReference = (node) => {
14129
14407
  const inner = unwrap6(node);
14130
14408
  return inner !== null && inner.type === AST_NODE_TYPES63.MemberExpression && !inner.computed && inner.property.type === AST_NODE_TYPES63.Identifier && (inner.property.name === "parse" || inner.property.name === "safeParse");
14131
14409
  };
14132
- var isRawPayloadSource = (node, isKnownLocalText) => {
14410
+ var isRawPayloadSource = (node, context, isKnownLocalText) => {
14133
14411
  let current = unwrap6(node);
14134
14412
  if (current === null) return false;
14135
14413
  if (current.type === AST_NODE_TYPES63.AwaitExpression) {
@@ -14147,13 +14425,31 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
14147
14425
  return false;
14148
14426
  }
14149
14427
  if (property.name === "json") {
14150
- return true;
14428
+ return !callee.computed && current.arguments.length === 0 && isResponseSource(callee.object, context);
14151
14429
  }
14152
14430
  if (PROMISE_CHAIN_METHODS.has(property.name)) {
14153
- return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
14431
+ return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object, context, isKnownLocalText);
14154
14432
  }
14155
14433
  const object = unwrap6(callee.object);
14156
- return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES63.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
14434
+ const input = unwrap6(current.arguments[0]);
14435
+ if (input?.type === AST_NODE_TYPES63.CallExpression && input.arguments.length === 1 && input.callee.type === AST_NODE_TYPES63.MemberExpression && !input.callee.computed && input.callee.object.type === AST_NODE_TYPES63.Identifier && input.callee.object.name === "JSON" && input.callee.property.type === AST_NODE_TYPES63.Identifier && input.callee.property.name === "stringify" && (ASTUtils35.findVariable(context.sourceCode.getScope(input.callee.object), "JSON")?.defs.length ?? 0) === 0) return false;
14436
+ return property.name === "parse" && object !== null && object.type === AST_NODE_TYPES63.Identifier && object.name === "JSON" && (ASTUtils35.findVariable(context.sourceCode.getScope(object), "JSON")?.defs.length ?? 0) === 0 && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
14437
+ };
14438
+ var isResponseSource = (node, context, seen = /* @__PURE__ */ new Set()) => {
14439
+ let current = unwrap6(node);
14440
+ if (current?.type === AST_NODE_TYPES63.AwaitExpression) current = unwrap6(current.argument);
14441
+ if (current === null || seen.has(current)) return false;
14442
+ seen.add(current);
14443
+ const isGlobal = (identifier) => (ASTUtils35.findVariable(context.sourceCode.getScope(identifier), identifier.name)?.defs.length ?? 0) === 0;
14444
+ if (current.type === AST_NODE_TYPES63.CallExpression) return current.callee.type === AST_NODE_TYPES63.Identifier && current.callee.name === "fetch" && isGlobal(current.callee);
14445
+ if (current.type === AST_NODE_TYPES63.NewExpression) return current.callee.type === AST_NODE_TYPES63.Identifier && ["Request", "Response"].includes(current.callee.name) && isGlobal(current.callee);
14446
+ if (current.type !== AST_NODE_TYPES63.Identifier) return false;
14447
+ const binding = ASTUtils35.findVariable(context.sourceCode.getScope(current), current.name);
14448
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
14449
+ const definition = binding.defs[0];
14450
+ const annotation = definition?.name.type === AST_NODE_TYPES63.Identifier ? definition.name.typeAnnotation?.typeAnnotation : void 0;
14451
+ if (annotation?.type === AST_NODE_TYPES63.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES63.Identifier && ["Request", "Response"].includes(annotation.typeName.name) && isGlobal(annotation.typeName)) return true;
14452
+ return definition?.type === "Variable" && definition.parent.kind === "const" && definition.node.init !== null && isResponseSource(definition.node.init, context, seen);
14157
14453
  };
14158
14454
  var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
14159
14455
  var isDirectLocalFileRead = (node) => {
@@ -14419,7 +14715,7 @@ var prefer_schema_for_api_payload_default = createRule({
14419
14715
  },
14420
14716
  schema: [],
14421
14717
  messages: {
14422
- unparsedJsonAccess: "Property access on an unvalidated payload (`response.json()` / `JSON.parse()`) without a schema parse. Pipe through `XSchema.parse(...)` (Zod) before reading fields."
14718
+ unparsedJsonAccess: "Review property access on parsed JSON without a recognized validation boundary. For external payloads, validate before reading fields; validator implementations need manual review, not a recursive schema rewrite."
14423
14719
  }
14424
14720
  },
14425
14721
  defaultOptions: [],
@@ -14430,6 +14726,40 @@ var prefer_schema_for_api_payload_default = createRule({
14430
14726
  const unvalidatedVariables = /* @__PURE__ */ new Set();
14431
14727
  const aliasGroups = /* @__PURE__ */ new Map();
14432
14728
  const localFileTextVariables = /* @__PURE__ */ new Set();
14729
+ const namedGuards = /* @__PURE__ */ new Map();
14730
+ const guardDominates = (use, call) => {
14731
+ if (context.sourceCode.getScope(use).variableScope !== context.sourceCode.getScope(call).variableScope) return false;
14732
+ let guard = call;
14733
+ let positive = true;
14734
+ while (guard.parent.type === AST_NODE_TYPES63.UnaryExpression && guard.parent.operator === "!") {
14735
+ positive = !positive;
14736
+ guard = guard.parent;
14737
+ }
14738
+ while (positive && guard.parent.type === AST_NODE_TYPES63.LogicalExpression && guard.parent.operator === "&&") guard = guard.parent;
14739
+ const branch = guard.parent;
14740
+ if (branch.type === AST_NODE_TYPES63.IfStatement && branch.test === guard) {
14741
+ if (positive && nodeWithin2(use, branch.consequent)) return true;
14742
+ if (!positive && branch.alternate !== null && nodeWithin2(use, branch.alternate)) return true;
14743
+ const terminal = branch.consequent.type === AST_NODE_TYPES63.BlockStatement ? branch.consequent.body.at(-1) : branch.consequent;
14744
+ if (!positive && (terminal?.type === AST_NODE_TYPES63.ThrowStatement || terminal?.type === AST_NODE_TYPES63.ReturnStatement)) {
14745
+ let statement2 = use;
14746
+ while (statement2.parent !== void 0 && statement2.parent !== branch.parent && statement2.parent.type !== AST_NODE_TYPES63.Program) {
14747
+ if (statement2.type === AST_NODE_TYPES63.FunctionDeclaration || statement2.type === AST_NODE_TYPES63.FunctionExpression || statement2.type === AST_NODE_TYPES63.ArrowFunctionExpression) return false;
14748
+ statement2 = statement2.parent;
14749
+ }
14750
+ return statement2.parent === branch.parent && statement2.range[0] > branch.range[1];
14751
+ }
14752
+ }
14753
+ if (branch.type === AST_NODE_TYPES63.ConditionalExpression && branch.test === guard) return nodeWithin2(use, positive ? branch.consequent : branch.alternate);
14754
+ if (positive && branch.type === AST_NODE_TYPES63.WhileStatement && branch.test === guard) return nodeWithin2(use, branch.body);
14755
+ if (call.parent.type !== AST_NODE_TYPES63.ExpressionStatement || call.callee.type !== AST_NODE_TYPES63.Identifier || /^(?:is|has)[A-Z]/u.test(call.callee.name)) return false;
14756
+ let statement = use;
14757
+ while (statement.parent !== void 0 && statement.parent !== call.parent.parent && statement.parent.type !== AST_NODE_TYPES63.Program) {
14758
+ if (statement.type === AST_NODE_TYPES63.FunctionDeclaration || statement.type === AST_NODE_TYPES63.FunctionExpression || statement.type === AST_NODE_TYPES63.ArrowFunctionExpression) return false;
14759
+ statement = statement.parent;
14760
+ }
14761
+ return statement.parent === call.parent.parent && statement.range[0] > call.parent.range[1];
14762
+ };
14433
14763
  const localFileTextRef = (node, scope) => {
14434
14764
  const unwrapped = unwrap6(node);
14435
14765
  if (unwrapped?.type !== AST_NODE_TYPES63.Identifier) return null;
@@ -14446,6 +14776,7 @@ var prefer_schema_for_api_payload_default = createRule({
14446
14776
  };
14447
14777
  const clearBinding = (variable) => {
14448
14778
  unvalidatedVariables.delete(variable);
14779
+ namedGuards.delete(variable);
14449
14780
  const group = aliasGroups.get(variable);
14450
14781
  aliasGroups.delete(variable);
14451
14782
  group?.delete(variable);
@@ -14480,10 +14811,24 @@ var prefer_schema_for_api_payload_default = createRule({
14480
14811
  };
14481
14812
  const isFullyNarrowedPattern = (declarator) => {
14482
14813
  const declared = context.sourceCode.getDeclaredVariables(declarator);
14814
+ const statement = declarator.parent;
14815
+ const block = statement.parent;
14816
+ const followsRejectingGuard = (identifier) => {
14817
+ if (block.type !== AST_NODE_TYPES63.BlockStatement && block.type !== AST_NODE_TYPES63.Program) return false;
14818
+ if (context.sourceCode.getScope(identifier).variableScope !== context.sourceCode.getScope(declarator).variableScope) return false;
14819
+ return block.body.some((candidate2) => {
14820
+ if (candidate2.type !== AST_NODE_TYPES63.IfStatement || candidate2.range[1] >= identifier.range[0] || bindingValidationPolarity(candidate2.test, identifier.name) !== "valid-when-false") return false;
14821
+ const terminal = candidate2.consequent.type === AST_NODE_TYPES63.BlockStatement ? candidate2.consequent.body.at(-1) : candidate2.consequent;
14822
+ return terminal?.type === AST_NODE_TYPES63.ThrowStatement || terminal?.type === AST_NODE_TYPES63.ReturnStatement;
14823
+ });
14824
+ };
14483
14825
  return declared.length > 0 && declared.every(
14484
- (variable) => variable.references.some(
14485
- (reference) => isValidationRead(reference.identifier)
14486
- )
14826
+ (variable) => variable.references.some((reference) => isValidationRead(reference.identifier)) && variable.references.every((reference) => {
14827
+ const identifier = reference.identifier;
14828
+ if (reference.init === true) return true;
14829
+ if (reference.isWrite() || identifier.type !== AST_NODE_TYPES63.Identifier) return false;
14830
+ return isValidationRead(identifier) || isUseWithinValidatedBranch(identifier, identifier.name) || followsRejectingGuard(identifier);
14831
+ })
14487
14832
  );
14488
14833
  };
14489
14834
  const trackInitializer = (declarator, scope) => {
@@ -14491,7 +14836,7 @@ var prefer_schema_for_api_payload_default = createRule({
14491
14836
  const variable = declaredVars[0];
14492
14837
  if (variable === void 0) return;
14493
14838
  const localText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14494
- if (isRawPayloadSource(declarator.init, localText)) {
14839
+ if (isRawPayloadSource(declarator.init, context, localText)) {
14495
14840
  trackRawBinding(variable);
14496
14841
  return;
14497
14842
  }
@@ -14512,6 +14857,7 @@ var prefer_schema_for_api_payload_default = createRule({
14512
14857
  if (node.id.type === AST_NODE_TYPES63.ObjectPattern || node.id.type === AST_NODE_TYPES63.ArrayPattern) {
14513
14858
  if (isRawPayloadSource(
14514
14859
  node.init,
14860
+ context,
14515
14861
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14516
14862
  )) {
14517
14863
  if (!isFullyNarrowedPattern(node)) {
@@ -14531,7 +14877,7 @@ var prefer_schema_for_api_payload_default = createRule({
14531
14877
  if (variable === null) return;
14532
14878
  const isLocalText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14533
14879
  updateLocalFileText(variable, node.right, scope);
14534
- if (isRawPayloadSource(node.right, isLocalText)) {
14880
+ if (isRawPayloadSource(node.right, context, isLocalText)) {
14535
14881
  trackRawBinding(variable);
14536
14882
  } else {
14537
14883
  const source = unvalidatedVariableRef(node.right, scope, unvalidatedVariables);
@@ -14543,6 +14889,7 @@ var prefer_schema_for_api_payload_default = createRule({
14543
14889
  if (node.left.type === AST_NODE_TYPES63.ObjectPattern || node.left.type === AST_NODE_TYPES63.ArrayPattern) {
14544
14890
  if (isRawPayloadSource(
14545
14891
  node.right,
14892
+ context,
14546
14893
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14547
14894
  )) {
14548
14895
  context.report({
@@ -14572,7 +14919,13 @@ var prefer_schema_for_api_payload_default = createRule({
14572
14919
  continue;
14573
14920
  }
14574
14921
  const variable = findVariable2(scope, unwrapped.name);
14575
- if (variable !== null) clearAliasGroup(variable);
14922
+ if (variable !== null) {
14923
+ for (const alias of aliasGroups.get(variable) ?? [variable]) {
14924
+ const guards = namedGuards.get(alias) ?? [];
14925
+ guards.push(node);
14926
+ namedGuards.set(alias, guards);
14927
+ }
14928
+ }
14576
14929
  }
14577
14930
  },
14578
14931
  MemberExpression(node) {
@@ -14582,6 +14935,7 @@ var prefer_schema_for_api_payload_default = createRule({
14582
14935
  const obj = unwrap6(node.object);
14583
14936
  if (isRawPayloadSource(
14584
14937
  obj,
14938
+ context,
14585
14939
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14586
14940
  )) {
14587
14941
  const parent = node.parent;
@@ -14593,6 +14947,7 @@ var prefer_schema_for_api_payload_default = createRule({
14593
14947
  }
14594
14948
  const variable = obj?.type === AST_NODE_TYPES63.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
14595
14949
  if (variable !== null && obj?.type === AST_NODE_TYPES63.Identifier) {
14950
+ if (namedGuards.get(variable)?.some((call) => guardDominates(node, call))) return;
14596
14951
  if (isUseWithinValidatedBranch(node, obj.name)) {
14597
14952
  return;
14598
14953
  }
@@ -14612,13 +14967,13 @@ var prefer_schema_for_api_payload_default = createRule({
14612
14967
  });
14613
14968
 
14614
14969
  // src/rules/prefer-shared-zod-enum.ts
14615
- import { AST_NODE_TYPES as AST_NODE_TYPES64 } from "@typescript-eslint/utils";
14970
+ import { AST_NODE_TYPES as AST_NODE_TYPES64, ASTUtils as ASTUtils36 } from "@typescript-eslint/utils";
14616
14971
  var PREFER_SHARED_ZOD_ENUM_DOCUMENTATION = {
14617
14972
  summary: "Give literal Zod enum domains one reusable module-level schema.",
14618
14973
  rationale: "Inline or repeated literal domains hide a reusable contract and allow equivalent fields to drift independently.",
14619
14974
  remediation: "Declare a module-level named Zod enum schema and reuse it at each field or contract site.",
14620
14975
  category: "maintainability",
14621
- limitations: ["Only direct z.enum calls with string-literal arrays are inspected; computed domains require review."],
14976
+ limitations: ["Only direct z.enum calls with string-literal arrays are inspected; computed domains require review. Equal values do not prove a shared business domain: retain local schemas when ownership, error customization, or future evolution differs, and review initialization order before extraction."],
14622
14977
  examples: [
14623
14978
  { id: "shared-provider", title: "Reuse a named enum schema", outcome: "no-match", files: [{ path: "src/provider.ts", source: "import { z } from 'zod'; const ProviderSchema = z.enum(['agy', 'claude', 'sol']); const JobSchema = z.object({ provider: ProviderSchema }); const StatusSchema = z.object({ provider: ProviderSchema.optional() });" }], focusPath: "src/provider.ts", expectedCount: 0, public: true },
14624
14979
  { id: "inline-provider", title: "Do not inline enum domains in object fields", outcome: "match", files: [{ path: "src/provider.ts", source: "import { z } from 'zod'; const JobSchema = z.object({ provider: z.enum(['agy', 'claude', 'sol']) });" }], focusPath: "src/provider.ts", expectedCount: 1, public: true }
@@ -14660,16 +15015,22 @@ var prefer_shared_zod_enum_default = createRule({
14660
15015
  create(context) {
14661
15016
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
14662
15017
  const zodBindings = /* @__PURE__ */ new Set();
15018
+ const bindingOf = (node) => ASTUtils36.findVariable(context.sourceCode.getScope(node), node.name);
14663
15019
  const seen = /* @__PURE__ */ new Set();
14664
15020
  return {
14665
15021
  ImportDeclaration(node) {
14666
15022
  if (!isZodModule(node.source.value)) return;
14667
15023
  for (const specifier of node.specifiers) {
14668
- if (specifier.type === AST_NODE_TYPES64.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES64.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES64.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES64.Identifier && specifier.imported.name === "z") zodBindings.add(specifier.local.name);
15024
+ if (specifier.type === AST_NODE_TYPES64.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES64.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES64.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES64.Identifier && specifier.imported.name === "z") {
15025
+ const binding = bindingOf(specifier.local);
15026
+ if (binding !== null) zodBindings.add(binding);
15027
+ }
14669
15028
  }
14670
15029
  },
14671
15030
  CallExpression(node) {
14672
- if (node.callee.type !== AST_NODE_TYPES64.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES64.Identifier || !zodBindings.has(node.callee.object.name) || node.callee.property.type !== AST_NODE_TYPES64.Identifier || node.callee.property.name !== "enum") return;
15031
+ if (node.callee.type !== AST_NODE_TYPES64.MemberExpression || node.callee.computed || node.callee.object.type !== AST_NODE_TYPES64.Identifier || node.callee.property.type !== AST_NODE_TYPES64.Identifier || node.callee.property.name !== "enum") return;
15032
+ const binding = bindingOf(node.callee.object);
15033
+ if (binding === null || !zodBindings.has(binding)) return;
14673
15034
  const domain = literalDomain(node);
14674
15035
  if (domain === null) return;
14675
15036
  const key = JSON.stringify(domain);
@@ -14691,6 +15052,7 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
14691
15052
  category: "maintainability",
14692
15053
  limitations: [
14693
15054
  "Only direct if/else-if chains with at least three strict-equality tests are reported.",
15055
+ "The discriminant must be a bare identifier; calls, getters, indexed reads and other repeatedly evaluated expressions are excluded. Review case-value effects, selector mutation, branch scoping, and break/continue targets when converting manually; this is not an equivalence proof.",
14694
15056
  "Case values may be literals, enum-like member references, or upper-case named constants; dynamic expressions are excluded.",
14695
15057
  "The rule deliberately ignores compound predicates, loose equality, ranges, and chains that compare different discriminants."
14696
15058
  ],
@@ -14704,7 +15066,8 @@ function discriminantText(sourceCode, test) {
14704
15066
  const leftIsCase = isCaseValue(test.left);
14705
15067
  const rightIsCase = isCaseValue(test.right);
14706
15068
  if (leftIsCase === rightIsCase) return null;
14707
- return sourceCode.getText(leftIsCase ? test.right : test.left);
15069
+ const discriminant = leftIsCase ? test.right : test.left;
15070
+ return discriminant.type === AST_NODE_TYPES65.Identifier ? sourceCode.getText(discriminant) : null;
14708
15071
  }
14709
15072
  function isCaseValue(node) {
14710
15073
  if (node.type === AST_NODE_TYPES65.Literal) return true;
@@ -15294,7 +15657,7 @@ var prefer_semantic_colors_default = createRule({
15294
15657
  });
15295
15658
 
15296
15659
  // src/rules/prefer-server-actions.ts
15297
- import { ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
15660
+ import { ASTUtils as ASTUtils37 } from "@typescript-eslint/utils";
15298
15661
  var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
15299
15662
  summary: "Prefer Next.js Server Actions over same-origin API mutations.",
15300
15663
  rationale: "Server Actions can remove a hand-written internal API wrapper while retaining typed application calls. Client invocations still cross a network and serialization boundary.",
@@ -15326,7 +15689,7 @@ function resolvesToGlobalFetch(context, identifier) {
15326
15689
  function resolveNode(node, context) {
15327
15690
  if (!node) return null;
15328
15691
  if (node.type !== "Identifier") return node;
15329
- const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
15692
+ const variable = ASTUtils37.findVariable(getScope(context, node), node.name);
15330
15693
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15331
15694
  if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return node;
15332
15695
  if (definition.node.init.type === "ObjectExpression" && variable?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return node;
@@ -15335,7 +15698,7 @@ function resolveNode(node, context) {
15335
15698
  function isAxiosClient(node, context, seen = /* @__PURE__ */ new Set()) {
15336
15699
  if (node.type !== "Identifier" || seen.has(node)) return false;
15337
15700
  seen.add(node);
15338
- const variable = ASTUtils26.findVariable(getScope(context, node), node.name);
15701
+ const variable = ASTUtils37.findVariable(getScope(context, node), node.name);
15339
15702
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15340
15703
  if (definition === void 0 || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
15341
15704
  if (variable?.references.some((reference) => {
@@ -15538,7 +15901,7 @@ var prefer_server_actions_default = createRule({
15538
15901
  });
15539
15902
 
15540
15903
  // src/rules/prefer-whole-object-assertion.ts
15541
- import { AST_NODE_TYPES as AST_NODE_TYPES67 } from "@typescript-eslint/utils";
15904
+ import { AST_NODE_TYPES as AST_NODE_TYPES67, ASTUtils as ASTUtils38 } from "@typescript-eslint/utils";
15542
15905
  var MERGEABLE_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
15543
15906
  var SYNTHETIC_LITERAL_MATCHERS = /* @__PURE__ */ new Map([
15544
15907
  ["toBeNull", "null"],
@@ -15645,6 +16008,13 @@ var prefer_whole_object_assertion_default = createRule({
15645
16008
  return null;
15646
16009
  }
15647
16010
  const actual = expectCall.arguments[0];
16011
+ const variable = ASTUtils38.findVariable(sourceCode.getScope(expectCall.callee), expectCall.callee.name);
16012
+ if (variable !== null && variable.defs.some((definition) => {
16013
+ if (definition.node.type !== AST_NODE_TYPES67.ImportSpecifier) return true;
16014
+ const declaration = definition.node.parent;
16015
+ const imported = definition.node.imported;
16016
+ return declaration.type !== AST_NODE_TYPES67.ImportDeclaration || !["vitest", "@jest/globals", "@playwright/test", "bun:test"].includes(String(declaration.source.value)) || (imported.type === AST_NODE_TYPES67.Identifier ? imported.name : imported.value) !== "expect";
16017
+ })) return null;
15648
16018
  if (actual === void 0 || actual.type !== AST_NODE_TYPES67.MemberExpression || actual.optional) {
15649
16019
  return null;
15650
16020
  }
@@ -15804,14 +16174,14 @@ var prefer_whole_object_assertion_default = createRule({
15804
16174
  });
15805
16175
 
15806
16176
  // src/rules/repeated-static-call-cases.ts
15807
- import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils27 } from "@typescript-eslint/utils";
16177
+ import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils39 } from "@typescript-eslint/utils";
15808
16178
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
15809
- summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
15810
- rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
15811
- remediation: "Replace the repeated assertions with a named `test.each` or `it.each` table.",
16179
+ summary: "Review three or more consecutive static-input call assertions as potential named cases.",
16180
+ rationale: "Copy-pasted cases obscure the input table, and a thrown assertion can stop later cases from being reported.",
16181
+ remediation: "If the calls are independent, use the runner's named parameterized cases or subtests. Preserve ordered state-transition scenarios as one test.",
15812
16182
  category: "testing",
15813
16183
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
15814
- limitations: ["Only consecutive top-level assertions with direct calls and entirely static inputs and expected values are reported."],
16184
+ limitations: ["Only consecutive top-level assertions with direct calls and static inputs and expected values are checked. Test-local callees and fixtures are excluded; imported or outer functions can still be stateful, so manual independence review is required. Not every runner supports test.each."],
15815
16185
  examples: [
15816
16186
  { id: "parameterized", title: "Name each case", outcome: "no-match", files: [{ path: "src/parser.test.ts", source: "test.each([['a', true], ['b', false], ['c', true]])('parses %s', (input, expected) => { expect(parse(input)).toBe(expected); });" }], focusPath: "src/parser.test.ts", expectedCount: 0, public: true },
15817
16187
  { id: "repeated", title: "Do not repeat literal cases", outcome: "match", files: [{ path: "src/parser.test.ts", source: "test('parses', () => { expect(parse('a')).toBe(true); expect(parse('b')).toBe(false); expect(parse('c')).toBe(true); });" }], focusPath: "src/parser.test.ts", expectedCount: 1, public: true }
@@ -15830,7 +16200,7 @@ function staticMemberName5(node) {
15830
16200
  return null;
15831
16201
  }
15832
16202
  function importedName6(identifier, context, modules) {
15833
- const variable = ASTUtils27.findVariable(context.sourceCode.getScope(identifier), identifier.name);
16203
+ const variable = ASTUtils39.findVariable(context.sourceCode.getScope(identifier), identifier.name);
15834
16204
  if (variable === null || variable.defs.length === 0) return identifier.name;
15835
16205
  for (const definition of variable.defs) {
15836
16206
  if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
@@ -15892,7 +16262,7 @@ function staticShape(node) {
15892
16262
  return "dynamic";
15893
16263
  }
15894
16264
  }
15895
- function assertionShape(statement, context) {
16265
+ function assertionShape(statement, context, callback) {
15896
16266
  if (statement.type !== AST_NODE_TYPES68.ExpressionStatement || statement.expression.type !== AST_NODE_TYPES68.CallExpression) return null;
15897
16267
  const matcherCall = statement.expression;
15898
16268
  if (matcherCall.callee.type !== AST_NODE_TYPES68.MemberExpression || matcherCall.callee.computed || matcherCall.callee.property.type !== AST_NODE_TYPES68.Identifier || matcherCall.arguments.length !== 1) return null;
@@ -15904,6 +16274,10 @@ function assertionShape(statement, context) {
15904
16274
  const expected = matcherCall.arguments[0];
15905
16275
  if (observed?.type !== AST_NODE_TYPES68.CallExpression || observed.callee.type !== AST_NODE_TYPES68.Identifier || observed.arguments.length === 0 || observed.arguments.some((arg) => arg.type === AST_NODE_TYPES68.SpreadElement || !isStatic(arg)) || expected?.type === AST_NODE_TYPES68.SpreadElement || expected === void 0 || !isStatic(expected)) return null;
15906
16276
  const skeleton = `${observed.callee.name}/${observed.arguments.map((item) => staticShape(item)).join(",")}/${chain.modifiers.join(".")}/${matcher}/${staticShape(expected)}`;
16277
+ const binding = ASTUtils39.findVariable(context.sourceCode.getScope(observed.callee), observed.callee.name);
16278
+ if (binding?.defs.some(
16279
+ (definition) => definition.node.range[0] >= callback.range[0] && definition.node.range[1] <= callback.range[1]
16280
+ )) return null;
15907
16281
  const values = [...observed.arguments, expected].map((item) => context.sourceCode.getText(item)).join("\0");
15908
16282
  return { statement, skeleton, values };
15909
16283
  }
@@ -15923,9 +16297,9 @@ var repeated_static_call_cases_default = createRule({
15923
16297
  documentation: REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
15924
16298
  meta: {
15925
16299
  type: "suggestion",
15926
- docs: { description: "Report three or more consecutive literal call assertions that should be independently named test cases." },
16300
+ docs: { description: REPEATED_STATIC_CALL_CASES_DOCUMENTATION.summary },
15927
16301
  schema: [],
15928
- messages: { repeatedStaticCallCases: "These {{count}} consecutive assertions repeat the same call with static cases. Use a named `test.each` or `it.each` table so every case is independently reported." }
16302
+ messages: { repeatedStaticCallCases: "These {{count}} consecutive assertions repeat a call with static inputs. If independent, use named parameterized cases or subtests; preserve ordered scenarios as one test." }
15929
16303
  },
15930
16304
  defaultOptions: [],
15931
16305
  create(context) {
@@ -15960,7 +16334,7 @@ var repeated_static_call_cases_default = createRule({
15960
16334
  run = [];
15961
16335
  };
15962
16336
  for (const statement of node.body.body) {
15963
- const shape = assertionShape(statement, context);
16337
+ const shape = assertionShape(statement, context, node);
15964
16338
  if (shape === null || run.length > 0 && run[0]?.skeleton !== shape.skeleton) flush();
15965
16339
  if (shape !== null) run.push(shape);
15966
16340
  }
@@ -15993,6 +16367,11 @@ var PREFER_ZOD_INFER_DOCUMENTATION = {
15993
16367
  rationale: "A derived type stays synchronized when the runtime schema changes.",
15994
16368
  remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
15995
16369
  category: "correctness",
16370
+ limitations: [
16371
+ "Only module-level const schemas and module-level type declarations are paired; local declarations are excluded rather than matched by spelling across scopes.",
16372
+ "By default every field must positively agree; collection, nested-object and referenced-schema equivalence is not inferred from outer syntax alone.",
16373
+ "This is a bounded syntactic comparison, not general type equivalence. Review schema input versus output, interface augmentation, and separately evolving domain contracts before replacing a declaration."
16374
+ ],
15996
16375
  examples: [
15997
16376
  { id: "inferred-type", title: "Infer the schema type", outcome: "no-match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); type User = z.infer<typeof UserSchema>;' }], focusPath: "src/user.ts", expectedCount: 0, public: true },
15998
16377
  { id: "handwritten-twin", title: "Do not duplicate the schema shape", outcome: "match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); interface User { id: string }' }], focusPath: "src/user.ts", expectedCount: 1, public: true }
@@ -16172,6 +16551,10 @@ function sameDomain(left, right) {
16172
16551
  function isExportedDeclaration(node) {
16173
16552
  return node.parent?.type === AST_NODE_TYPES69.ExportNamedDeclaration;
16174
16553
  }
16554
+ function isModuleLevelDeclaration(node) {
16555
+ const parent = node.parent;
16556
+ return parent?.type === AST_NODE_TYPES69.Program || parent?.type === AST_NODE_TYPES69.ExportNamedDeclaration && parent.parent.type === AST_NODE_TYPES69.Program;
16557
+ }
16175
16558
  function isModuleLevelConst(node) {
16176
16559
  const declaration = node.parent;
16177
16560
  if (declaration.type !== AST_NODE_TYPES69.VariableDeclaration || declaration.kind !== "const") {
@@ -16222,6 +16605,9 @@ function leafAgrees(field, annotation) {
16222
16605
  return annotationDomain !== null && sameDomain(field.domain, annotationDomain);
16223
16606
  }
16224
16607
  const { leaf } = field;
16608
+ if (leaf !== null && ["array", "tuple", "object", "strictObject", "looseObject", "record", "map", "set", "promise", "intersection"].includes(leaf)) {
16609
+ return false;
16610
+ }
16225
16611
  if (leaf === null || annotation === null) {
16226
16612
  return null;
16227
16613
  }
@@ -16514,7 +16900,6 @@ var prefer_zod_infer_default = createRule({
16514
16900
  if (fields.size !== members.size) {
16515
16901
  return false;
16516
16902
  }
16517
- let agreements = 0;
16518
16903
  for (const [name, field] of fields) {
16519
16904
  const member = members.get(name);
16520
16905
  if (member === void 0) {
@@ -16533,14 +16918,11 @@ var prefer_zod_infer_default = createRule({
16533
16918
  return false;
16534
16919
  }
16535
16920
  const agrees = leafAgrees(field, member.annotation);
16536
- if (agrees === false) {
16921
+ if (agrees !== true) {
16537
16922
  return false;
16538
16923
  }
16539
- if (agrees === true) {
16540
- agreements += 1;
16541
- }
16542
16924
  }
16543
- return agreements > 0;
16925
+ return true;
16544
16926
  }
16545
16927
  return {
16546
16928
  Program(node) {
@@ -16554,7 +16936,7 @@ var prefer_zod_infer_default = createRule({
16554
16936
  recordZodImport(node);
16555
16937
  },
16556
16938
  VariableDeclarator(node) {
16557
- if (node.id.type !== AST_NODE_TYPES69.Identifier || node.init == null) {
16939
+ if (node.id.type !== AST_NODE_TYPES69.Identifier || node.init == null || !isModuleLevelConst(node)) {
16558
16940
  return;
16559
16941
  }
16560
16942
  const fields = schemaFields(node.init);
@@ -16587,6 +16969,7 @@ var prefer_zod_infer_default = createRule({
16587
16969
  }
16588
16970
  },
16589
16971
  TSInterfaceDeclaration(node) {
16972
+ if (!isModuleLevelDeclaration(node)) return;
16590
16973
  if (node.typeParameters !== void 0 || (node.extends?.length ?? 0) > 0) {
16591
16974
  return;
16592
16975
  }
@@ -16602,6 +16985,7 @@ var prefer_zod_infer_default = createRule({
16602
16985
  );
16603
16986
  },
16604
16987
  TSTypeAliasDeclaration(node) {
16988
+ if (!isModuleLevelDeclaration(node)) return;
16605
16989
  const schemaName = inferredSchemaName(node.typeAnnotation);
16606
16990
  if (schemaName !== null) {
16607
16991
  inferredAliases.push({
@@ -16719,6 +17103,7 @@ var REQUIRE_ASSERT_NEVER_DOCUMENTATION = {
16719
17103
  rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
16720
17104
  remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
16721
17105
  category: "correctness",
17106
+ limitations: ["Requires type information and singleton case values covering the current union. The helper must accept never to provide a compile-time check; its spelling alone is not a proof of that contract. Review the desired runtime behavior for unexpected external values."],
16722
17107
  examples: [
16723
17108
  { id: "assert-never-default", title: "Make the default exhaustive", outcome: "no-match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: assertNever(kind); }" }], focusPath: "src/render.ts", expectedCount: 0, public: true },
16724
17109
  { id: "empty-default", title: "Do not leave an exhaustive default empty", outcome: "match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: }" }], focusPath: "src/render.ts", expectedCount: 1, public: true }
@@ -16775,11 +17160,9 @@ function isExhaustiveFiniteSwitch(node, services) {
16775
17160
  if (caseNode.test === null) continue;
16776
17161
  const test = services.esTreeNodeToTSNodeMap.get(caseNode.test);
16777
17162
  const testType = checker.getTypeAtLocation(test);
16778
- const alternatives = testType.isUnion() ? testType.types : [testType];
16779
- for (const alternative of alternatives) {
16780
- const key = finiteTypeKey(alternative, checker);
16781
- if (key !== null) handled.add(key);
16782
- }
17163
+ if (testType.isUnion()) continue;
17164
+ const key = finiteTypeKey(testType, checker);
17165
+ if (key !== null) handled.add(key);
16783
17166
  }
16784
17167
  return [...expected].every((key) => handled.has(key));
16785
17168
  }
@@ -16831,7 +17214,7 @@ var require_assert_never_default = createRule({
16831
17214
  });
16832
17215
 
16833
17216
  // src/rules/require-fetch-timeout.ts
16834
- import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils28 } from "@typescript-eslint/utils";
17217
+ import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils40 } from "@typescript-eslint/utils";
16835
17218
  var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
16836
17219
  summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
16837
17220
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -16913,7 +17296,7 @@ var require_fetch_timeout_default = createRule({
16913
17296
  }
16914
17297
  function resolvesToGlobal(identifier) {
16915
17298
  const scope = context.sourceCode.getScope(identifier);
16916
- const variable = ASTUtils28.findVariable(scope, identifier.name);
17299
+ const variable = ASTUtils40.findVariable(scope, identifier.name);
16917
17300
  return variable === null || variable.defs.length === 0;
16918
17301
  }
16919
17302
  function isGlobalFetchCall2(callee) {
@@ -16923,7 +17306,7 @@ var require_fetch_timeout_default = createRule({
16923
17306
  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);
16924
17307
  }
16925
17308
  function localConstInitProvablyLacksSignal(identifier) {
16926
- const variable = ASTUtils28.findVariable(
17309
+ const variable = ASTUtils40.findVariable(
16927
17310
  context.sourceCode.getScope(identifier),
16928
17311
  identifier.name
16929
17312
  );
@@ -16945,7 +17328,7 @@ var require_fetch_timeout_default = createRule({
16945
17328
  function isForwardedRequest(argument) {
16946
17329
  let value = argument;
16947
17330
  if (value.type === AST_NODE_TYPES71.Identifier) {
16948
- const binding = ASTUtils28.findVariable(context.sourceCode.getScope(value), value.name);
17331
+ const binding = ASTUtils40.findVariable(context.sourceCode.getScope(value), value.name);
16949
17332
  const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
16950
17333
  if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
16951
17334
  value = definition.node.init;
@@ -16973,10 +17356,12 @@ var require_fetch_timeout_default = createRule({
16973
17356
  import { AST_NODE_TYPES as AST_NODE_TYPES72 } from "@typescript-eslint/utils";
16974
17357
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
16975
17358
  summary: "Require exported concrete classes with public behavior to declare a contract.",
16976
- rationale: "Consumers coupled only to a concrete class cannot substitute implementations or state the supported public capability independently of implementation details.",
17359
+ rationale: "An explicit contract names the intended public capability separately from implementation details. TypeScript already supports structural compatibility; this is an architecture policy, not a prerequisite for substitution.",
16977
17360
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
16978
17361
  category: "architecture",
16979
17362
  limitations: [
17363
+ "JavaScript files are excluded because implements is TypeScript syntax; imported framework base contracts still require manual policy review.",
17364
+ "An exported injected service may also receive require-port-for-service: its service-boundary error and this general exported-contract warning intentionally enforce distinct policy scopes.",
16980
17365
  "The warning-stage rule checks module-level class declarations and direct class-expression values exported directly, through local export specifiers, or through a default identifier; re-exports and expressions wrapped in other calls require review.",
16981
17366
  "An extends clause satisfies the contract only when its target is a locally declared abstract class; imported base-class contracts require an explicit implements clause.",
16982
17367
  "Static factories and data-only classes without public instance methods are outside the contract requirement."
@@ -17048,7 +17433,7 @@ var require_interface_for_exported_class_default = createRule({
17048
17433
  },
17049
17434
  defaultOptions: [],
17050
17435
  create(context) {
17051
- if (isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
17436
+ if (/\.(?:js|jsx|mjs|cjs)$/iu.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
17052
17437
  return {
17053
17438
  "Program:exit"(program) {
17054
17439
  const classes = /* @__PURE__ */ new Map();
@@ -17434,15 +17819,14 @@ var publicMethodNames = (body2, functionAliases) => {
17434
17819
  if (member.static || member.accessibility === "private" || member.accessibility === "protected") continue;
17435
17820
  if (member.key.type === AST_NODE_TYPES73.PrivateIdentifier) continue;
17436
17821
  if (member.value?.type !== AST_NODE_TYPES73.ArrowFunctionExpression && member.value?.type !== AST_NODE_TYPES73.FunctionExpression && member.typeAnnotation?.typeAnnotation.type !== AST_NODE_TYPES73.TSFunctionType && !(member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name))) continue;
17437
- names.push(member.key.type === AST_NODE_TYPES73.Identifier ? member.key.name : "\u2026");
17822
+ names.push(declaredMemberName(member) ?? "\u2026");
17438
17823
  continue;
17439
17824
  }
17440
17825
  if (member.type !== AST_NODE_TYPES73.MethodDefinition) continue;
17441
17826
  if (member.kind !== "method" || member.static) continue;
17442
17827
  if (member.accessibility === "private" || member.accessibility === "protected") continue;
17443
17828
  if (member.key.type === AST_NODE_TYPES73.PrivateIdentifier) continue;
17444
- if (member.key.type === AST_NODE_TYPES73.Identifier) names.push(member.key.name);
17445
- else names.push("\u2026");
17829
+ names.push(declaredMemberName(member) ?? "\u2026");
17446
17830
  }
17447
17831
  return names;
17448
17832
  };
@@ -17483,6 +17867,11 @@ function localClassAbstractness(program) {
17483
17867
  }
17484
17868
  return classes;
17485
17869
  }
17870
+ function declaredMemberName(member) {
17871
+ if (!member.computed && member.key.type === AST_NODE_TYPES73.Identifier) return member.key.name;
17872
+ if (member.key.type === AST_NODE_TYPES73.Literal && typeof member.key.value === "string") return member.key.value;
17873
+ return null;
17874
+ }
17486
17875
  function localInterfaceSurfaces(program) {
17487
17876
  const interfaces = /* @__PURE__ */ new Map();
17488
17877
  const parents = /* @__PURE__ */ new Map();
@@ -17505,14 +17894,15 @@ function localInterfaceSurfaces(program) {
17505
17894
  if (part.type !== AST_NODE_TYPES73.TSTypeLiteral) continue;
17506
17895
  for (const member of part.members) {
17507
17896
  if (member.type !== AST_NODE_TYPES73.TSMethodSignature && member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
17508
- if (member.computed || member.key.type !== AST_NODE_TYPES73.Identifier) continue;
17897
+ const name = declaredMemberName(member);
17898
+ if (name === null) continue;
17509
17899
  if (member.type === AST_NODE_TYPES73.TSMethodSignature) {
17510
- callables2.add(member.key.name);
17900
+ callables2.add(name);
17511
17901
  continue;
17512
17902
  }
17513
17903
  if (member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
17514
17904
  const annotation = member.typeAnnotation?.typeAnnotation;
17515
- if (annotation?.type === AST_NODE_TYPES73.TSFunctionType || annotation?.type === AST_NODE_TYPES73.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(member.key.name);
17905
+ if (annotation?.type === AST_NODE_TYPES73.TSFunctionType || annotation?.type === AST_NODE_TYPES73.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(name);
17516
17906
  }
17517
17907
  }
17518
17908
  interfaces.set(declaration.id.name, callables2);
@@ -17523,8 +17913,9 @@ function localInterfaceSurfaces(program) {
17523
17913
  const callables = interfaces.get(declaration.id.name) ?? /* @__PURE__ */ new Set();
17524
17914
  for (const member of declaration.body.body) {
17525
17915
  if (member.type !== AST_NODE_TYPES73.TSMethodSignature && member.type !== AST_NODE_TYPES73.TSPropertySignature) continue;
17526
- if (member.computed || member.key.type !== AST_NODE_TYPES73.Identifier) continue;
17527
- if (member.type === AST_NODE_TYPES73.TSMethodSignature || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSFunctionType || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name)) callables.add(member.key.name);
17916
+ const name = declaredMemberName(member);
17917
+ if (name === null) continue;
17918
+ if (member.type === AST_NODE_TYPES73.TSMethodSignature || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSFunctionType || member.typeAnnotation?.typeAnnotation.type === AST_NODE_TYPES73.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === AST_NODE_TYPES73.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name)) callables.add(name);
17528
17919
  }
17529
17920
  interfaces.set(declaration.id.name, callables);
17530
17921
  parents.set(
@@ -17649,7 +18040,7 @@ var require_port_for_service_default = createRule({
17649
18040
  });
17650
18041
 
17651
18042
  // src/rules/require-sql-access-class.ts
17652
- import { AST_NODE_TYPES as AST_NODE_TYPES74 } from "@typescript-eslint/utils";
18043
+ import { AST_NODE_TYPES as AST_NODE_TYPES74, ASTUtils as ASTUtils41 } from "@typescript-eslint/utils";
17653
18044
  var DIRECT_EXECUTION_METHODS = /* @__PURE__ */ new Set([
17654
18045
  "all",
17655
18046
  "batch",
@@ -17703,12 +18094,13 @@ var MODEL_EXECUTION_METHODS = /* @__PURE__ */ new Set([
17703
18094
  var DATABASE_NAMES = /^(?:db|database|connection|pool|prisma|query|transaction|tx)$/iu;
17704
18095
  var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
17705
18096
  summary: "Keep SQL reads and writes inside a class that receives its database dependency.",
17706
- rationale: "Free-function database access hides connection ownership and makes transaction, retry, observability, and test boundaries inconsistent.",
18097
+ rationale: "An injected repository class is the preferred ownership boundary for database access under this architectural policy; free functions can also express explicit dependencies.",
17707
18098
  remediation: "Move the query into a repository or store class and inject the pool, connection, transaction, or typed database binding through its constructor.",
17708
18099
  category: "architecture",
17709
18100
  limitations: [
17710
18101
  "The rule recognizes conventional database receiver names, Cloudflare DB bindings, direct pool.query calls, explicit query-builder terminals, and Prisma-style model delegates; unusually named or heavily aliased clients require architectural review.",
17711
18102
  "Query construction without a recognized execution terminal is intentionally not reported.",
18103
+ "Stable local Map, WeakMap, and URLSearchParams instances are excluded. Other conventional receiver names are heuristics, not proof of a database API.",
17712
18104
  "Constructor injection inherited from a base class or transformed through a wrapper is not inferred by this syntax-only rule."
17713
18105
  ],
17714
18106
  examples: [
@@ -17914,11 +18306,23 @@ var require_sql_access_class_default = createRule({
17914
18306
  create(context) {
17915
18307
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
17916
18308
  return {};
18309
+ function knownNonDatabase(node, seen = /* @__PURE__ */ new Set()) {
18310
+ if (seen.has(node)) return false;
18311
+ seen.add(node);
18312
+ if (node.type === AST_NODE_TYPES74.Identifier) {
18313
+ const binding = ASTUtils41.findVariable(context.sourceCode.getScope(node), node.name);
18314
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
18315
+ const definition = binding.defs[0];
18316
+ return definition?.type === "Variable" && definition.node.init !== null && knownNonDatabase(definition.node.init, seen);
18317
+ }
18318
+ return node.type === AST_NODE_TYPES74.NewExpression && node.callee.type === AST_NODE_TYPES74.Identifier && ["Map", "WeakMap", "URLSearchParams"].includes(node.callee.name) && (ASTUtils41.findVariable(context.sourceCode.getScope(node.callee), node.callee.name)?.defs.length ?? 0) === 0;
18319
+ }
17917
18320
  return {
17918
18321
  CallExpression(node) {
17919
18322
  if (node.callee.type !== AST_NODE_TYPES74.MemberExpression)
17920
18323
  return;
17921
18324
  const method = memberName6(node.callee);
18325
+ if (knownNonDatabase(node.callee.object)) return;
17922
18326
  if (method === null || !isDatabaseOperation(method, node.callee.object))
17923
18327
  return;
17924
18328
  const owner = owningClass2(node);
@@ -17939,6 +18343,8 @@ var REQUIRE_STATIC_NEXT_MATCHER_DOCUMENTATION = {
17939
18343
  rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
17940
18344
  remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
17941
18345
  category: "correctness",
18346
+ limitations: ["Only directly exported matcher configuration in middleware/proxy entry files is inspected. Literal matcher validity is left to Next.js; this rule checks static syntax, not the complete framework schema."],
18347
+ references: ["https://nextjs.org/docs/app/api-reference/file-conventions/proxy"],
17942
18348
  examples: [
17943
18349
  { id: "literal-matcher", title: "Use a literal matcher", outcome: "no-match", files: [{ path: "src/middleware.ts", source: 'export const config = { matcher: "/api/:path*" };' }], focusPath: "src/middleware.ts", expectedCount: 0, public: true },
17944
18350
  { id: "computed-matcher", title: "Do not compute the matcher", outcome: "match", files: [{ path: "src/middleware.ts", source: 'const matcher = "/api/:path*"; export const config = { matcher };' }], focusPath: "src/middleware.ts", expectedCount: 1, public: true }
@@ -17986,7 +18392,7 @@ var require_static_next_matcher_default = createRule({
17986
18392
  },
17987
18393
  schema: [],
17988
18394
  messages: {
17989
- dynamicMatcher: "Next.js matcher values must contain only literal arrays and objects. Calls, identifiers, concatenation, interpolated templates, and spreads are not statically analyzable and fail the production build."
18395
+ dynamicMatcher: "Keep Next.js matcher strings, arrays and object fields literal in the exported config. Dynamic values such as variables are not supported by its build-time static analysis and can be ignored."
17990
18396
  }
17991
18397
  },
17992
18398
  defaultOptions: [],
@@ -18022,7 +18428,7 @@ var require_static_next_matcher_default = createRule({
18022
18428
  });
18023
18429
 
18024
18430
  // src/rules/require-use-form-default-values.ts
18025
- import { ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
18431
+ import { ASTUtils as ASTUtils42 } from "@typescript-eslint/utils";
18026
18432
  var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
18027
18433
  summary: "react-hook-form useForm call without explicit initial or reactive values",
18028
18434
  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.",
@@ -18076,13 +18482,13 @@ var require_use_form_default_values_default = createRule({
18076
18482
  if (node.source.value !== "react-hook-form") return;
18077
18483
  for (const specifier of node.specifiers) {
18078
18484
  if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
18079
- const variable = ASTUtils29.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
18485
+ const variable = ASTUtils42.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
18080
18486
  if (variable) importedHooks.add(variable);
18081
18487
  }
18082
18488
  },
18083
18489
  CallExpression(node) {
18084
18490
  if (node.callee.type !== "Identifier") return;
18085
- const variable = ASTUtils29.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
18491
+ const variable = ASTUtils42.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
18086
18492
  const options = node.arguments[0];
18087
18493
  if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasInitializationOrUnknownOptions(options)) return;
18088
18494
  context.report({ node, messageId: "requireUseFormDefaultValues" });
@@ -18161,7 +18567,7 @@ var require_use_server_in_actions_file_default = createRule({
18161
18567
  // src/rules/require-zod-form-validation.ts
18162
18568
  import {
18163
18569
  AST_NODE_TYPES as AST_NODE_TYPES76,
18164
- ASTUtils as ASTUtils30
18570
+ ASTUtils as ASTUtils43
18165
18571
  } from "@typescript-eslint/utils";
18166
18572
  var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
18167
18573
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
@@ -18170,7 +18576,8 @@ var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
18170
18576
  category: "security",
18171
18577
  limitations: [
18172
18578
  "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
18173
- "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."
18579
+ "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.",
18580
+ "An enclosing parse does not validate an earlier call or deferred callback that consumes its input. Direct object/array construction, unshadowed Object.fromEntries and native Number/String/Boolean coercion remain supported; arbitrary preprocessing needs an explicitly reviewed boundary."
18174
18581
  ],
18175
18582
  examples: [
18176
18583
  { 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 },
@@ -18229,7 +18636,7 @@ var require_zod_form_validation_default = createRule({
18229
18636
  return {};
18230
18637
  }
18231
18638
  const zodBindings = /* @__PURE__ */ new Set();
18232
- const resolvedBinding = (identifier) => ASTUtils30.findVariable(
18639
+ const resolvedBinding = (identifier) => ASTUtils43.findVariable(
18233
18640
  context.sourceCode.getScope(identifier),
18234
18641
  identifier.name
18235
18642
  );
@@ -18285,6 +18692,15 @@ var require_zod_form_validation_default = createRule({
18285
18692
  let parent = node.parent;
18286
18693
  while (parent !== null && parent !== void 0) {
18287
18694
  if (isZodParseCall(parent)) return parent;
18695
+ if (parent.type === AST_NODE_TYPES76.CallExpression && parent.callee.type === AST_NODE_TYPES76.Identifier && ["Number", "String", "Boolean"].includes(parent.callee.name) && parent.arguments.length === 1 && (resolvedBinding(parent.callee)?.defs.length ?? 0) === 0) {
18696
+ parent = parent.parent;
18697
+ continue;
18698
+ }
18699
+ if (parent.type === AST_NODE_TYPES76.CallExpression && parent.callee.type === AST_NODE_TYPES76.MemberExpression && !parent.callee.computed && parent.callee.object.type === AST_NODE_TYPES76.Identifier && parent.callee.object.name === "Object" && parent.callee.property.type === AST_NODE_TYPES76.Identifier && parent.callee.property.name === "fromEntries" && (resolvedBinding(parent.callee.object)?.defs.length ?? 0) === 0) {
18700
+ parent = parent.parent;
18701
+ continue;
18702
+ }
18703
+ if (parent.type === AST_NODE_TYPES76.CallExpression || parent.type === AST_NODE_TYPES76.NewExpression || parent.type === AST_NODE_TYPES76.TaggedTemplateExpression || parent.type === AST_NODE_TYPES76.ArrowFunctionExpression || parent.type === AST_NODE_TYPES76.FunctionExpression || parent.type === AST_NODE_TYPES76.FunctionDeclaration) return null;
18288
18704
  parent = parent.parent;
18289
18705
  }
18290
18706
  return null;
@@ -18464,13 +18880,14 @@ var require_zod_form_validation_default = createRule({
18464
18880
  // src/rules/store-insert-requires-on-conflict.ts
18465
18881
  import "@typescript-eslint/utils";
18466
18882
  var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
18467
- summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
18468
- rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
18469
- remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
18883
+ summary: "Review conflict handling for embedded inserts in replay-named callables.",
18884
+ rationale: "Names such as seed, enqueue, or upsert suggest that repeated execution deserves a conflict-policy review, but do not prove a replay contract.",
18885
+ remediation: "Choose conflict handling appropriate to the schema and SQL dialect, or document why this insertion must fail on a duplicate.",
18470
18886
  category: "correctness",
18887
+ limitations: ["Only the nearest statically named callable is considered; top-level inserts and anonymous callbacks are excluded. Recognized conflict syntax does not prove idempotence or concurrency safety: WHERE NOT EXISTS can race, and INSERT OR REPLACE can delete an existing row. Review unique constraints and dialect semantics manually."],
18471
18888
  examples: [
18472
- { id: "conflict-safe-insert", title: "Handle a replayed insert", outcome: "no-match", files: [{ path: "src/store.ts", source: "db.prepare(`INSERT INTO runs (id) VALUES (?) ON CONFLICT(id) DO NOTHING`).run();" }], focusPath: "src/store.ts", expectedCount: 0, public: true },
18473
- { id: "bare-insert", title: "Do not issue a replay-unsafe insert", outcome: "match", files: [{ path: "src/store.ts", source: "db.prepare(`INSERT INTO runs (id) VALUES (?)`).run();" }], focusPath: "src/store.ts", expectedCount: 1, public: true }
18889
+ { id: "conflict-safe-insert", title: "Review the conflict policy for a replayed insert", outcome: "no-match", files: [{ path: "src/store.ts", source: "function seed() { db.prepare(`INSERT INTO runs (id) VALUES (?) ON CONFLICT(id) DO NOTHING`).run(); }" }], focusPath: "src/store.ts", expectedCount: 0, public: true },
18890
+ { id: "bare-insert", title: "Review a bare insert in a replay-named callable", outcome: "match", files: [{ path: "src/store.ts", source: "function seed() { db.prepare(`INSERT INTO runs (id) VALUES (?)`).run(); }" }], focusPath: "src/store.ts", expectedCount: 1, public: true }
18474
18891
  ]
18475
18892
  };
18476
18893
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
@@ -18482,14 +18899,18 @@ function owningCallableName(node) {
18482
18899
  return current.id?.name ?? null;
18483
18900
  }
18484
18901
  if (current.type === "MethodDefinition") {
18485
- return current.key.type === "Identifier" ? current.key.name : null;
18902
+ return !current.computed && current.key.type === "Identifier" ? current.key.name : null;
18486
18903
  }
18487
18904
  if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
18488
18905
  return current.parent.id.name;
18489
18906
  }
18490
- if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
18907
+ if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && !current.parent.computed && current.parent.key.type === "Identifier") {
18491
18908
  return current.parent.key.name;
18492
18909
  }
18910
+ if (current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") {
18911
+ const parent = current.parent;
18912
+ return parent.type === "MethodDefinition" && !parent.computed && parent.key.type === "Identifier" ? parent.key.name : null;
18913
+ }
18493
18914
  }
18494
18915
  return null;
18495
18916
  }
@@ -18500,11 +18921,11 @@ var store_insert_requires_on_conflict_default = createRule({
18500
18921
  meta: {
18501
18922
  type: "problem",
18502
18923
  docs: {
18503
- description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
18924
+ description: "Review conflict handling for embedded inserts in replay-named callables."
18504
18925
  },
18505
18926
  schema: [],
18506
18927
  messages: {
18507
- storeInsertRequiresOnConflict: "This INSERT is not replay-safe: a cron re-run or queue redelivery duplicates the row (or fails the handler on a unique-constraint violation). Add `ON CONFLICT (...) DO UPDATE` / `DO NOTHING` (or `INSERT OR IGNORE`)."
18928
+ storeInsertRequiresOnConflict: "This INSERT is inside a replay-named callable without recognized conflict handling. Review duplicate execution, unique constraints, and the appropriate conflict policy for your SQL dialect."
18508
18929
  }
18509
18930
  },
18510
18931
  defaultOptions: [],
@@ -18517,7 +18938,7 @@ var store_insert_requires_on_conflict_default = createRule({
18517
18938
  return;
18518
18939
  }
18519
18940
  const owner = owningCallableName(node);
18520
- if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
18941
+ if (owner === null || !REPLAY_CONTRACT_NAME.test(owner)) {
18521
18942
  return;
18522
18943
  }
18523
18944
  context.report({ node, messageId: "storeInsertRequiresOnConflict" });
@@ -18526,19 +18947,17 @@ var store_insert_requires_on_conflict_default = createRule({
18526
18947
  });
18527
18948
 
18528
18949
  // src/rules/stepdown.ts
18529
- import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
18950
+ import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils44 } from "@typescript-eslint/utils";
18530
18951
  var STEPDOWN_DOCUMENTATION = {
18531
18952
  summary: "Place a private helper below its sole direct same-scope caller.",
18532
18953
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
18533
- remediation: "Move the private helper below its sole caller.",
18954
+ remediation: "Consider moving the private helper below its sole caller after reviewing initialization and reflection dependencies.",
18534
18955
  category: "maintainability",
18535
- autofix: "safe",
18536
18956
  limitations: [
18537
18957
  "Generated and test files, cycles, dynamic references, overload targets, and helpers with multiple callers are excluded.",
18538
- "Class helpers must be private; their sole caller may be public, protected, or private so the fix also satisfies accessibility ordering.",
18958
+ "Class helpers must be private; their sole caller may be public, protected, or private.",
18539
18959
  "Runtime class-field, static-block, computed-member, and decorator barriers are never crossed.",
18540
- "Autofix is limited to comment-free class-member boundaries; module functions and ambiguous comment ownership remain report-only.",
18541
- "Overlapping helper chains remain report-only so ESLint never leaves a partially reordered class after exhausting its fix-pass limit."
18960
+ "This rule is report-only: reordering methods can change reflective property order, and moving module declarations can change initialization behavior or introduce temporal-dead-zone failures. Review module cycles and eager callers manually."
18542
18961
  ],
18543
18962
  examples: [
18544
18963
  { id: "caller-before-helper", title: "Place the caller first", outcome: "no-match", files: [{ path: "src/run.ts", source: "function run() { return load(); }\nfunction load() { return 1; }" }], focusPath: "src/run.ts", expectedCount: 0, public: true },
@@ -18548,7 +18967,7 @@ var STEPDOWN_DOCUMENTATION = {
18548
18967
  function isFunction(node) {
18549
18968
  return node.type === AST_NODE_TYPES77.ArrowFunctionExpression || node.type === AST_NODE_TYPES77.FunctionDeclaration || node.type === AST_NODE_TYPES77.FunctionExpression;
18550
18969
  }
18551
- function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true, makeFix) {
18970
+ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true) {
18552
18971
  const byName = new Map(scopeDefinitions.map((definition) => [definition.name, definition]));
18553
18972
  const cycles = cycleComponents(calls);
18554
18973
  const callers = /* @__PURE__ */ new Map();
@@ -18567,12 +18986,10 @@ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned,
18567
18986
  if (callerName2 === void 0 || cycles.has(helper.name) && cycles.get(helper.name) === cycles.get(callerName2)) continue;
18568
18987
  const caller = byName.get(callerName2);
18569
18988
  if (caller === void 0 || helper.node.range[0] >= caller.node.range[0] || !canMove(helper, caller)) continue;
18570
- const fix = makeFix?.(helper, caller);
18571
18989
  context.report({
18572
18990
  node: helper.node,
18573
18991
  messageId: "helperAboveOnlyCaller",
18574
- data: { helper: helper.name, caller: callerName2 },
18575
- ...fix === void 0 ? {} : { fix }
18992
+ data: { helper: helper.name, caller: callerName2 }
18576
18993
  });
18577
18994
  }
18578
18995
  }
@@ -18731,7 +19148,7 @@ function methodName(node) {
18731
19148
  return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
18732
19149
  }
18733
19150
  function referencedMethod(context, node, classVariables) {
18734
- const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
19151
+ const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils44.findVariable(context.sourceCode.getScope(node.object), node.object.name) : null;
18735
19152
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
18736
19153
  if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
18737
19154
  if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
@@ -18743,15 +19160,15 @@ function referencedPropertyName(node) {
18743
19160
  if (!node.computed && node.property.type === AST_NODE_TYPES77.Identifier) return node.property.name;
18744
19161
  return node.computed && node.property.type === AST_NODE_TYPES77.Literal && typeof node.property.value === "string" ? node.property.value : null;
18745
19162
  }
18746
- function walk2(node, visitorKeys, visit, nestedFunction = false) {
19163
+ function walk(node, visitorKeys, visit, nestedFunction = false) {
18747
19164
  visit(node, nestedFunction);
18748
19165
  const nested = nestedFunction || isFunction(node);
18749
19166
  for (const key of visitorKeys[node.type] ?? []) {
18750
19167
  const child = node[key];
18751
19168
  if (Array.isArray(child)) {
18752
- for (const item of child) if (typeof item === "object" && item !== null && "type" in item) walk2(item, visitorKeys, visit, nested);
19169
+ for (const item of child) if (typeof item === "object" && item !== null && "type" in item) walk(item, visitorKeys, visit, nested);
18753
19170
  } else if (typeof child === "object" && child !== null && "type" in child) {
18754
- walk2(child, visitorKeys, visit, nested);
19171
+ walk(child, visitorKeys, visit, nested);
18755
19172
  }
18756
19173
  }
18757
19174
  }
@@ -18784,11 +19201,11 @@ function classScope(context, node, computedReferenceNames) {
18784
19201
  const pinned = /* @__PURE__ */ new Set();
18785
19202
  const classVariables = /* @__PURE__ */ new Set();
18786
19203
  if (node.id !== null) {
18787
- const internal = ASTUtils31.findVariable(context.sourceCode.getScope(node), node.id.name);
19204
+ const internal = ASTUtils44.findVariable(context.sourceCode.getScope(node), node.id.name);
18788
19205
  if (internal !== null) classVariables.add(internal);
18789
19206
  }
18790
19207
  if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
18791
- const outer = ASTUtils31.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
19208
+ const outer = ASTUtils44.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
18792
19209
  if (outer !== null) classVariables.add(outer);
18793
19210
  }
18794
19211
  for (const method of methods) {
@@ -18799,7 +19216,7 @@ function classScope(context, node, computedReferenceNames) {
18799
19216
  const parameterDecoratorNodes = /* @__PURE__ */ new Set();
18800
19217
  for (const parameter of method.value.params) {
18801
19218
  for (const decorator of parameter.decorators) {
18802
- walk2(decorator, context.sourceCode.visitorKeys, (current) => parameterDecoratorNodes.add(current));
19219
+ walk(decorator, context.sourceCode.visitorKeys, (current) => parameterDecoratorNodes.add(current));
18803
19220
  }
18804
19221
  }
18805
19222
  const thisValue = (value) => {
@@ -18824,17 +19241,17 @@ function classScope(context, node, computedReferenceNames) {
18824
19241
  return;
18825
19242
  }
18826
19243
  if (binding.type !== AST_NODE_TYPES77.Identifier) return;
18827
- const variable = ASTUtils31.findVariable(context.sourceCode.getScope(binding), binding.name);
19244
+ const variable = ASTUtils44.findVariable(context.sourceCode.getScope(binding), binding.name);
18828
19245
  if (variable !== null) {
18829
19246
  methodClassVariables.add(variable);
18830
19247
  methodAliases.add(variable);
18831
19248
  }
18832
19249
  };
18833
19250
  for (const parameter of method.value.params) {
18834
- walk2(parameter, context.sourceCode.visitorKeys, collectAlias);
19251
+ walk(parameter, context.sourceCode.visitorKeys, collectAlias);
18835
19252
  }
18836
19253
  for (const statement of method.value.body.body) {
18837
- walk2(statement, context.sourceCode.visitorKeys, collectAlias);
19254
+ walk(statement, context.sourceCode.visitorKeys, collectAlias);
18838
19255
  }
18839
19256
  const visitCall = (current, nestedFunction) => {
18840
19257
  if (current.type === AST_NODE_TYPES77.VariableDeclarator && current.id.type === AST_NODE_TYPES77.ObjectPattern && thisValue(current.init)) {
@@ -18854,7 +19271,7 @@ function classScope(context, node, computedReferenceNames) {
18854
19271
  return;
18855
19272
  }
18856
19273
  if (!privateNames.has(target)) return;
18857
- const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils31.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
19274
+ const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils44.findVariable(context.sourceCode.getScope(current.object), current.object.name) : null;
18858
19275
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
18859
19276
  pinned.add(target);
18860
19277
  return;
@@ -18868,19 +19285,19 @@ function classScope(context, node, computedReferenceNames) {
18868
19285
  calls.set(caller, callees);
18869
19286
  };
18870
19287
  for (const decorator of method.decorators) {
18871
- walk2(decorator, context.sourceCode.visitorKeys, visitCall, true);
19288
+ walk(decorator, context.sourceCode.visitorKeys, visitCall, true);
18872
19289
  }
18873
- if (method.computed) walk2(method.key, context.sourceCode.visitorKeys, visitCall, true);
19290
+ if (method.computed) walk(method.key, context.sourceCode.visitorKeys, visitCall, true);
18874
19291
  for (const parameter of method.value.params) {
18875
- walk2(parameter, context.sourceCode.visitorKeys, visitCall);
19292
+ walk(parameter, context.sourceCode.visitorKeys, visitCall);
18876
19293
  }
18877
19294
  for (const statement of method.value.body.body) {
18878
- walk2(statement, context.sourceCode.visitorKeys, visitCall);
19295
+ walk(statement, context.sourceCode.visitorKeys, visitCall);
18879
19296
  }
18880
19297
  }
18881
19298
  for (const member of node.body.body) {
18882
19299
  if (member.type === AST_NODE_TYPES77.MethodDefinition || member.type === AST_NODE_TYPES77.TSAbstractMethodDefinition) continue;
18883
- walk2(member, context.sourceCode.visitorKeys, (current) => {
19300
+ walk(member, context.sourceCode.visitorKeys, (current) => {
18884
19301
  if (current.type !== AST_NODE_TYPES77.MemberExpression) return;
18885
19302
  const target = referencedMethod(context, current, classVariables);
18886
19303
  const possibleTarget = target ?? referencedPropertyName(current);
@@ -18899,37 +19316,7 @@ function classScope(context, node, computedReferenceNames) {
18899
19316
  if (helperIndex === void 0 || callerIndex === void 0) return false;
18900
19317
  return runtimeBarrierPrefix[callerIndex + 1] === runtimeBarrierPrefix[helperIndex + 1];
18901
19318
  };
18902
- const incoming = /* @__PURE__ */ new Map();
18903
- for (const [caller, callees] of calls) {
18904
- for (const callee of callees) {
18905
- if (callee === caller) continue;
18906
- const callers = incoming.get(callee) ?? /* @__PURE__ */ new Set();
18907
- callers.add(caller);
18908
- incoming.set(callee, callers);
18909
- }
18910
- }
18911
- reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove, (helper, caller) => {
18912
- if (!canMove(helper, caller)) return void 0;
18913
- const helperCallsAnother = [...calls.get(helper.name) ?? []].some((callee) => callee !== helper.name);
18914
- const callerIsAnotherHelper = [...incoming.get(caller.name) ?? []].some((name) => name !== helper.name);
18915
- if (helperCallsAnother || callerIsAnotherHelper) return void 0;
18916
- const helperMember = helper.node;
18917
- const callerMember = caller.node;
18918
- const helperIndex = memberIndexes.get(helperMember);
18919
- if (helperIndex === void 0) return void 0;
18920
- const next = node.body.body[helperIndex + 1];
18921
- const suffixEnd = next?.range[0] ?? node.body.range[1] - 1;
18922
- const suffix = context.sourceCode.text.slice(helperMember.range[1], suffixEnd);
18923
- if (!/^\s*$/u.test(suffix)) return void 0;
18924
- const previous = node.body.body[helperIndex - 1];
18925
- const prefixStart = previous?.range[1] ?? node.body.range[0] + 1;
18926
- if (!/^\s*$/u.test(context.sourceCode.text.slice(prefixStart, helperMember.range[0]))) return void 0;
18927
- const helperText = context.sourceCode.getText(helperMember);
18928
- return (fixer) => [
18929
- fixer.removeRange([helperMember.range[0], suffixEnd]),
18930
- fixer.insertTextAfter(callerMember, `${suffix}${helperText}`)
18931
- ];
18932
- });
19319
+ reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove);
18933
19320
  }
18934
19321
  function isClassRuntimeBarrier(member) {
18935
19322
  switch (member.type) {
@@ -18951,7 +19338,6 @@ var stepdown_default = createRule({
18951
19338
  type: "suggestion",
18952
19339
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
18953
19340
  schema: [],
18954
- fixable: "code",
18955
19341
  messages: {
18956
19342
  helperAboveOnlyCaller: "Private helper `{{helper}}` is defined above its only caller `{{caller}}`; move it below the caller."
18957
19343
  }
@@ -18970,7 +19356,7 @@ var stepdown_default = createRule({
18970
19356
  "Program:exit": (program) => {
18971
19357
  moduleScope(context, program);
18972
19358
  const computedReferenceNames = /* @__PURE__ */ new Set();
18973
- walk2(program, context.sourceCode.visitorKeys, (node) => {
19359
+ walk(program, context.sourceCode.visitorKeys, (node) => {
18974
19360
  if (node.type === AST_NODE_TYPES77.MemberExpression && node.computed && node.property.type === AST_NODE_TYPES77.Literal && typeof node.property.value === "string") computedReferenceNames.add(node.property.value);
18975
19361
  });
18976
19362
  for (const node of classes) classScope(context, node, computedReferenceNames);
@@ -18980,7 +19366,7 @@ var stepdown_default = createRule({
18980
19366
  });
18981
19367
 
18982
19368
  // src/rules/source-coupled-test.ts
18983
- import { AST_NODE_TYPES as AST_NODE_TYPES78 } from "@typescript-eslint/utils";
19369
+ import { AST_NODE_TYPES as AST_NODE_TYPES78, ASTUtils as ASTUtils45 } from "@typescript-eslint/utils";
18984
19370
  var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc|py|[cm]?[jt]s)$/iu;
18985
19371
  var FS_MODULES = /* @__PURE__ */ new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
18986
19372
  var FS_READERS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
@@ -19024,7 +19410,7 @@ var SOURCE_COUPLED_TEST_DOCUMENTATION = {
19024
19410
  remediation: "Parse the artifact, execute its validator, or assert on another runtime contract.",
19025
19411
  category: "testing",
19026
19412
  limitations: [
19027
- "The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
19413
+ "The rule follows stable lexical bindings, static source paths, awaited reads, and common text operations. Reassigned bindings, dynamic paths, unknown path wrappers, iterator pipelines, and interprocedural flows remain unreported.",
19028
19414
  "When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason."
19029
19415
  ],
19030
19416
  examples: [
@@ -19063,6 +19449,11 @@ function stringValue(node) {
19063
19449
  const current = unwrap7(node);
19064
19450
  if (current.type === AST_NODE_TYPES78.Literal && typeof current.value === "string") return current.value;
19065
19451
  if (current.type === AST_NODE_TYPES78.TemplateLiteral && current.expressions.length === 0) return current.quasis[0]?.value.cooked ?? null;
19452
+ if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") {
19453
+ const left = stringValue(current.left);
19454
+ const right = stringValue(current.right);
19455
+ return left === null || right === null ? null : left + right;
19456
+ }
19066
19457
  return null;
19067
19458
  }
19068
19459
  function importSource(node) {
@@ -19092,14 +19483,19 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19092
19483
  const scopes = [newScope()];
19093
19484
  const reportedOrigins = /* @__PURE__ */ new Set();
19094
19485
  const currentScope = () => scopes.at(-1) ?? scopes[0];
19095
- const visible = (kind, name2) => {
19486
+ const bindingOf = (node) => ASTUtils45.findVariable(context.sourceCode.getScope(node), node.name);
19487
+ const visible = (kind, node) => {
19488
+ const name2 = bindingOf(node);
19489
+ if (name2 === null || name2.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
19096
19490
  for (let index = scopes.length - 1; index >= 0; index--) {
19097
19491
  const scope = scopes[index];
19098
19492
  if (scope.declared.has(name2)) return scope[kind].has(name2);
19099
19493
  }
19100
19494
  return false;
19101
19495
  };
19102
- const visibleRawOrigins = (name2) => {
19496
+ const visibleRawOrigins = (node) => {
19497
+ const name2 = bindingOf(node);
19498
+ if (name2 === null || name2.references.some((reference) => reference.isWrite() && reference.init !== true)) return /* @__PURE__ */ new Set();
19103
19499
  for (let index = scopes.length - 1; index >= 0; index--) {
19104
19500
  const scope = scopes[index];
19105
19501
  if (scope.declared.has(name2)) return scope.rawOrigins.get(name2) ?? /* @__PURE__ */ new Set();
@@ -19110,15 +19506,14 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19110
19506
  const current = unwrap7(node);
19111
19507
  const value = stringValue(current);
19112
19508
  if (value !== null) return sourceSuffixRe.test(value);
19113
- if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current.name);
19114
- if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") {
19115
- return sourcePath(current.left) || sourcePath(current.right);
19116
- }
19117
- if (current.type === AST_NODE_TYPES78.TemplateLiteral) return current.expressions.some(sourcePath);
19509
+ if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current);
19118
19510
  if (current.type === AST_NODE_TYPES78.CallExpression || current.type === AST_NODE_TYPES78.NewExpression) {
19119
- return current.arguments.some((argument) => argument.type !== AST_NODE_TYPES78.SpreadElement && sourcePath(argument));
19511
+ const callee = current.callee;
19512
+ const first = current.arguments[0];
19513
+ if (first === void 0 || first.type === AST_NODE_TYPES78.SpreadElement) return false;
19514
+ if (current.type === AST_NODE_TYPES78.NewExpression && callee.type === AST_NODE_TYPES78.Identifier && callee.name === "URL" && (bindingOf(callee)?.defs.length ?? 0) === 0) return sourcePath(first);
19515
+ if (callee.type === AST_NODE_TYPES78.Identifier && bindingOf(callee)?.defs.some((definition) => definition.node.type === AST_NODE_TYPES78.ImportSpecifier && definition.node.imported.type === AST_NODE_TYPES78.Identifier && definition.node.imported.name === "fileURLToPath" && definition.node.parent.type === AST_NODE_TYPES78.ImportDeclaration && ["node:url", "url"].includes(String(definition.node.parent.source.value)))) return sourcePath(first);
19120
19516
  }
19121
- if (current.type === AST_NODE_TYPES78.MemberExpression) return sourcePath(current.object);
19122
19517
  return false;
19123
19518
  };
19124
19519
  const rawRead = (node) => {
@@ -19126,16 +19521,16 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19126
19521
  if (current.type !== AST_NODE_TYPES78.CallExpression || current.arguments.length === 0) return false;
19127
19522
  const callee = unwrap7(current.callee);
19128
19523
  if (callee.type === AST_NODE_TYPES78.Identifier) {
19129
- return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
19524
+ return visible("fsReaders", callee) && sourcePath(current.arguments[0]);
19130
19525
  }
19131
19526
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return false;
19132
19527
  const name2 = staticMemberName7(callee);
19133
19528
  const object = unwrap7(callee.object);
19134
- return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES78.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
19529
+ return name2 !== null && FS_READERS.has(name2) && object.type === AST_NODE_TYPES78.Identifier && visible("fsObjects", object) && sourcePath(current.arguments[0]);
19135
19530
  };
19136
19531
  const rawOrigins = (node) => {
19137
19532
  const current = unwrap7(node);
19138
- if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current.name);
19533
+ if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current);
19139
19534
  if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
19140
19535
  if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
19141
19536
  if (current.type === AST_NODE_TYPES78.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
@@ -19176,14 +19571,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19176
19571
  if (receiver.type !== AST_NODE_TYPES78.Identifier || receiver.name !== "assert" || !ASSERT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
19177
19572
  return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
19178
19573
  };
19179
- const rawRegexExtractionOrigins = (node) => {
19180
- const callee = unwrap7(node.callee);
19181
- if (callee.type !== AST_NODE_TYPES78.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
19182
- const argument = node.arguments[0];
19183
- if (argument?.type !== AST_NODE_TYPES78.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
19184
- return rawOrigins(callee.object);
19185
- };
19186
- const declare = (name2, state) => {
19574
+ const declare = (node, state) => {
19575
+ const name2 = bindingOf(node);
19576
+ if (name2 === null) return;
19187
19577
  const scope = currentScope();
19188
19578
  scope.declared.add(name2);
19189
19579
  scope.collections.delete(name2);
@@ -19203,18 +19593,8 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19203
19593
  const current = unwrap7(node);
19204
19594
  return current.type === AST_NODE_TYPES78.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== AST_NODE_TYPES78.SpreadElement && sourcePath(element));
19205
19595
  };
19206
- const declaredNames2 = (node) => {
19207
- const current = unwrap7(node);
19208
- if (current.type === AST_NODE_TYPES78.Identifier) return [current.name];
19209
- if (current.type === AST_NODE_TYPES78.AssignmentPattern) return declaredNames2(current.left);
19210
- if (current.type === AST_NODE_TYPES78.RestElement) return declaredNames2(current.argument);
19211
- if (current.type === AST_NODE_TYPES78.ArrayPattern) return current.elements.flatMap((element) => element === null ? [] : declaredNames2(element));
19212
- if (current.type === AST_NODE_TYPES78.ObjectPattern) return current.properties.flatMap((property) => property.type === AST_NODE_TYPES78.RestElement ? declaredNames2(property.argument) : declaredNames2(property.value));
19213
- return [];
19214
- };
19215
- const enterFunction = (node) => {
19596
+ const enterFunction = () => {
19216
19597
  scopes.push(newScope());
19217
- for (const parameter of node.params) for (const name2 of declaredNames2(parameter)) declare(name2, {});
19218
19598
  };
19219
19599
  const exitFunction = () => {
19220
19600
  scopes.pop();
@@ -19226,9 +19606,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19226
19606
  for (const specifier of node.specifiers) {
19227
19607
  if (specifier.type === AST_NODE_TYPES78.ImportSpecifier) {
19228
19608
  const imported = specifier.imported.type === AST_NODE_TYPES78.Identifier ? specifier.imported.name : String(specifier.imported.value);
19229
- if (FS_READERS.has(imported)) declare(specifier.local.name, { fsReader: true });
19609
+ if (FS_READERS.has(imported)) declare(specifier.local, { fsReader: true });
19230
19610
  } else {
19231
- declare(specifier.local.name, { fsObject: true });
19611
+ declare(specifier.local, { fsObject: true });
19232
19612
  }
19233
19613
  }
19234
19614
  },
@@ -19237,35 +19617,34 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19237
19617
  VariableDeclarator(node) {
19238
19618
  if (node.init === null) return;
19239
19619
  const required = requireSource(node.init);
19620
+ const initializer = unwrap7(node.init);
19621
+ if (required !== null && initializer.type === AST_NODE_TYPES78.CallExpression && initializer.callee.type === AST_NODE_TYPES78.Identifier && (bindingOf(initializer.callee)?.defs.length ?? 0) > 0) return;
19240
19622
  if (required !== null && FS_MODULES.has(required) && node.id.type === AST_NODE_TYPES78.Identifier) {
19241
- declare(node.id.name, { fsObject: true });
19623
+ declare(node.id, { fsObject: true });
19242
19624
  return;
19243
19625
  }
19244
19626
  if (node.id.type === AST_NODE_TYPES78.ObjectPattern && required !== null && FS_MODULES.has(required)) {
19245
19627
  for (const property of node.id.properties) {
19246
19628
  if (property.type !== AST_NODE_TYPES78.Property || property.value.type !== AST_NODE_TYPES78.Identifier) continue;
19247
19629
  const key = property.key.type === AST_NODE_TYPES78.Identifier ? property.key.name : property.key.type === AST_NODE_TYPES78.Literal ? String(property.key.value) : "";
19248
- if (FS_READERS.has(key)) declare(property.value.name, { fsReader: true });
19630
+ if (FS_READERS.has(key)) declare(property.value, { fsReader: true });
19249
19631
  }
19250
19632
  return;
19251
19633
  }
19252
19634
  if (node.id.type !== AST_NODE_TYPES78.Identifier) return;
19253
- declare(node.id.name, { collection: sourceCollection(node.init), path: sourcePath(node.init), rawOrigins: rawOrigins(node.init) });
19635
+ declare(node.id, { collection: sourceCollection(node.init), path: sourcePath(node.init), rawOrigins: rawOrigins(node.init) });
19254
19636
  },
19255
19637
  AssignmentExpression(node) {
19256
- if (node.left.type === AST_NODE_TYPES78.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
19638
+ if (node.left.type === AST_NODE_TYPES78.Identifier) declare(node.left, {});
19257
19639
  },
19258
19640
  ForOfStatement(node) {
19259
19641
  const right = unwrap7(node.right);
19260
- const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right.name);
19642
+ const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right);
19261
19643
  const left = node.left.type === AST_NODE_TYPES78.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
19262
- if (collection && left?.type === AST_NODE_TYPES78.Identifier) declare(left.name, { path: true });
19644
+ if (collection && left?.type === AST_NODE_TYPES78.Identifier) declare(left, { path: true });
19263
19645
  },
19264
19646
  CallExpression(node) {
19265
- const origins = /* @__PURE__ */ new Set([
19266
- ...rawAssertionOrigins(node),
19267
- ...rawRegexExtractionOrigins(node)
19268
- ]);
19647
+ const origins = rawAssertionOrigins(node);
19269
19648
  if (origins.size === 0 || [...origins].every((origin) => reportedOrigins.has(origin))) return;
19270
19649
  for (const origin of origins) reportedOrigins.add(origin);
19271
19650
  context.report({ node, messageId: "rawSourceOracle" });
@@ -19408,8 +19787,8 @@ var IAC_SOURCE_COUPLED_TEST_DOCUMENTATION = {
19408
19787
  remediation: "Parse rendered plan JSON, query the provider, or exercise the deployed runtime contract.",
19409
19788
  category: "testing",
19410
19789
  limitations: [
19411
- "The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
19412
- "The warning-stage rule remains suppressible for calibration; promotion may make the locked policy non-suppressible."
19790
+ "The rule follows stable lexical bindings and static source paths. Reassigned bindings, dynamic paths, unknown path wrappers, iterator pipelines, and interprocedural flows remain unreported.",
19791
+ "When raw representation is genuinely the contract, use an exact line suppression explaining that contract."
19413
19792
  ],
19414
19793
  examples: [
19415
19794
  {
@@ -19441,7 +19820,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
19441
19820
  // src/rules/require-pascal-case-zod-schema-name.ts
19442
19821
  import {
19443
19822
  AST_NODE_TYPES as AST_NODE_TYPES80,
19444
- ASTUtils as ASTUtils32
19823
+ ASTUtils as ASTUtils46
19445
19824
  } from "@typescript-eslint/utils";
19446
19825
  var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
19447
19826
  summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
@@ -19642,7 +20021,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
19642
20021
  const zodBindings = /* @__PURE__ */ new Set();
19643
20022
  const schemaBindings = /* @__PURE__ */ new Set();
19644
20023
  function resolvedBinding(identifier) {
19645
- return ASTUtils32.findVariable(
20024
+ return ASTUtils46.findVariable(
19646
20025
  context.sourceCode.getScope(identifier),
19647
20026
  identifier.name
19648
20027
  );
@@ -19887,7 +20266,7 @@ var RULES = {
19887
20266
  };
19888
20267
  var meta = {
19889
20268
  name: "@sarj/eslint-plugin",
19890
- version: "15.17.9"
20269
+ version: "15.17.11"
19891
20270
  };
19892
20271
  var APPLICATION_ONLY_RULES = [];
19893
20272
  var LIBRARY_IMPORT_POLICY = ["error", {