@sarj/eslint-plugin 15.17.10 → 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);
@@ -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) {
@@ -4549,7 +4614,7 @@ var interface_contract_members_private_default = createRule({
4549
4614
  });
4550
4615
 
4551
4616
  // src/rules/no-log-only-catch.ts
4552
- 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";
4553
4618
 
4554
4619
  // src/rules/_logging.ts
4555
4620
  import "@typescript-eslint/utils";
@@ -4721,7 +4786,7 @@ function seededFallbackHandled(tryStatement, scope) {
4721
4786
  if (previous.declarations.length !== 1 || declarator === void 0) return false;
4722
4787
  if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return false;
4723
4788
  if (declarator.init == null || !isSeedValue(declarator.init)) return false;
4724
- const variable = ASTUtils6.findVariable(scope, declarator.id.name);
4789
+ const variable = ASTUtils8.findVariable(scope, declarator.id.name);
4725
4790
  if (variable === null) return false;
4726
4791
  const [tryStart, tryEnd] = tryStatement.block.range;
4727
4792
  let writtenInTry = false;
@@ -4775,6 +4840,39 @@ var no_log_only_catch_default = createRule({
4775
4840
  const matcher = createLogMatcher(loggingOptions);
4776
4841
  const filename = context.filename;
4777
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
+ }
4778
4876
  function isLoggingCallStatement(statement) {
4779
4877
  if (statement.type !== "ExpressionStatement") {
4780
4878
  return false;
@@ -4801,17 +4899,11 @@ var no_log_only_catch_default = createRule({
4801
4899
  CatchClause(node) {
4802
4900
  const statements = node.body.body;
4803
4901
  const isDocumented = sourceCode.getCommentsInside(node.body).length > 0 || hasAdjacentRationale(node);
4804
- if (statements.length === 0) {
4805
- if (isDocumented) {
4806
- return;
4807
- }
4808
- if (fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4809
- return;
4810
- }
4811
- context.report({ node, messageId: "emptyCatch" });
4902
+ if (isDocumented || hasCoercionValidation(node) || fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4812
4903
  return;
4813
4904
  }
4814
- if (isDocumented) {
4905
+ if (statements.length === 0) {
4906
+ context.report({ node, messageId: "emptyCatch" });
4815
4907
  return;
4816
4908
  }
4817
4909
  const everyStatementIsLogging = statements.every(
@@ -4826,10 +4918,10 @@ var no_log_only_catch_default = createRule({
4826
4918
  });
4827
4919
 
4828
4920
  // src/rules/no-bare-return-from-test-catch.ts
4829
- 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";
4830
4922
  var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
4831
4923
  summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
4832
- 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.",
4833
4925
  remediation: "Rethrow the error, assert on it, or use the runner's explicit skip mechanism when the capability is optional.",
4834
4926
  category: "testing",
4835
4927
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
@@ -4851,7 +4943,7 @@ function staticMemberName2(node) {
4851
4943
  return null;
4852
4944
  }
4853
4945
  function importedName3(identifier, context, modules) {
4854
- const variable = ASTUtils7.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4946
+ const variable = ASTUtils9.findVariable(context.sourceCode.getScope(identifier), identifier.name);
4855
4947
  if (variable === null || variable.defs.length === 0) return identifier.name;
4856
4948
  for (const definition of variable.defs) {
4857
4949
  if (definition.node.type !== AST_NODE_TYPES19.ImportSpecifier) continue;
@@ -4916,7 +5008,7 @@ var no_bare_return_from_test_catch_default = createRule({
4916
5008
  type: "problem",
4917
5009
  docs: { description: "Disallow a bare return from a test catch block when it skips a later assertion." },
4918
5010
  schema: [],
4919
- 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." }
4920
5012
  },
4921
5013
  defaultOptions: [],
4922
5014
  create(context) {
@@ -4935,6 +5027,23 @@ var no_bare_return_from_test_catch_default = createRule({
4935
5027
  if (current === null || current === void 0) break;
4936
5028
  }
4937
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
+ }
4938
5047
  if (walkOwnScope(catchClause.body, (current) => current.type === AST_NODE_TYPES19.ThrowStatement || isExplicitSkip(current, context))) return;
4939
5048
  if (!walkOwnScope(owner.body, (current) => current.range[0] > node.range[1] && isAssertion(current, context))) return;
4940
5049
  context.report({ node, messageId: "bareReturnFromTestCatch" });
@@ -4944,15 +5053,15 @@ var no_bare_return_from_test_catch_default = createRule({
4944
5053
  });
4945
5054
 
4946
5055
  // src/rules/no-bespoke-api-case-conversion.ts
4947
- 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";
4948
5057
  var NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION = {
4949
- summary: "Disallow hand-written snake_case/camelCase object-key translation at a proven API adapter boundary.",
4950
- 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.",
4951
5060
  remediation: "Move wire-name ownership and case conversion into the generated SDK/model layer; keep application adapters on the generated typed surface.",
4952
5061
  category: "architecture",
4953
5062
  autofix: "none",
4954
5063
  limitations: [
4955
- "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.",
4956
5065
  "Only object properties that directly translate the same identifier between snake_case and lowerCamelCase are reported.",
4957
5066
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and indirect conversions are intentionally excluded."
4958
5067
  ],
@@ -5024,7 +5133,7 @@ var no_bespoke_api_case_conversion_default = createRule({
5024
5133
  docs: { description: NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION.summary },
5025
5134
  schema: [],
5026
5135
  messages: {
5027
- 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."
5028
5137
  }
5029
5138
  },
5030
5139
  defaultOptions: [],
@@ -5034,16 +5143,33 @@ var no_bespoke_api_case_conversion_default = createRule({
5034
5143
  if (!ADAPTER_BASENAME_RE.test(basename) || isGeneratedFile(filename, context.sourceCode.text) || isTestFile(filename, ["fixtureTree"])) {
5035
5144
  return {};
5036
5145
  }
5037
- const provenApiBoundary = context.sourceCode.ast.body.some(
5038
- (statement) => statement.type === AST_NODE_TYPES20.ImportDeclaration && API_BOUNDARY_IMPORT_RE.test(statement.source.value)
5039
- );
5040
- 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
+ };
5041
5166
  return {
5042
5167
  Property(node) {
5043
5168
  if (node.computed || node.method || node.shorthand) return;
5044
5169
  const key = propertyName(node.key);
5045
5170
  const value = memberName3(node.value);
5046
5171
  if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
5172
+ if (!hasApiReceiver(node.value)) return;
5047
5173
  const wireName = SNAKE_CASE_RE.test(key) ? key : value;
5048
5174
  const applicationName = wireName === key ? value : key;
5049
5175
  context.report({
@@ -5166,7 +5292,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5166
5292
  limitations: [
5167
5293
  "Only ESLint disable comments and TypeScript expect-error directives are checked.",
5168
5294
  "The rule uses a small anchored vocabulary and does not score prose quality generally.",
5169
- "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."
5170
5296
  ],
5171
5297
  examples: [
5172
5298
  {
@@ -5176,7 +5302,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5176
5302
  files: [
5177
5303
  {
5178
5304
  path: "src/adapter.ts",
5179
- 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}"
5180
5306
  }
5181
5307
  ],
5182
5308
  focusPath: "src/adapter.ts",
@@ -5190,7 +5316,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5190
5316
  files: [
5191
5317
  {
5192
5318
  path: "src/adapter.ts",
5193
- 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}"
5194
5320
  }
5195
5321
  ],
5196
5322
  focusPath: "src/adapter.ts",
@@ -5235,7 +5361,7 @@ var no_vague_suppression_description_default = createRule({
5235
5361
  });
5236
5362
 
5237
5363
  // src/rules/no-generic-single-export-module.ts
5238
- 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";
5239
5365
  var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
5240
5366
  summary: "Disallow generic module stems when one runtime export already names the responsibility.",
5241
5367
  rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
@@ -5362,7 +5488,7 @@ function typeOnlyBindings(program) {
5362
5488
  return new Set([...names].filter((name) => !runtimeNames.has(name)));
5363
5489
  }
5364
5490
  function isGlobalIdentifier2(context, node) {
5365
- const variable = ASTUtils8.findVariable(context.sourceCode.getScope(node), node.name);
5491
+ const variable = ASTUtils11.findVariable(context.sourceCode.getScope(node), node.name);
5366
5492
  return variable === null || variable.defs.length === 0;
5367
5493
  }
5368
5494
  function isConventionalFrameworkUtility(filename, exported) {
@@ -5423,11 +5549,11 @@ var no_generic_single_export_module_default = createRule({
5423
5549
  // src/rules/no-offset-pagination.ts
5424
5550
  import "@typescript-eslint/utils";
5425
5551
  var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5426
- 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.",
5427
5553
  rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
5428
- 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.",
5429
5555
  category: "performance",
5430
- 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."],
5431
5557
  examples: [
5432
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 },
5433
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 }
@@ -5435,17 +5561,18 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5435
5561
  };
5436
5562
  var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
5437
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;
5438
5565
  var no_offset_pagination_default = createRule({
5439
5566
  name: "no-offset-pagination",
5440
5567
  documentation: NO_OFFSET_PAGINATION_DOCUMENTATION,
5441
5568
  meta: {
5442
5569
  type: "problem",
5443
5570
  docs: {
5444
- 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."
5445
5572
  },
5446
5573
  schema: [],
5447
5574
  messages: {
5448
- 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."
5449
5576
  }
5450
5577
  },
5451
5578
  defaultOptions: [],
@@ -5454,7 +5581,7 @@ var no_offset_pagination_default = createRule({
5454
5581
  return {};
5455
5582
  }
5456
5583
  return createSqlListener((sql, node) => {
5457
- if (!OFFSET_PAGINATION.test(sql)) {
5584
+ if (!PAGINATION_CONTEXT.test(sql) || !OFFSET_PAGINATION.test(sql)) {
5458
5585
  return;
5459
5586
  }
5460
5587
  context.report({ node, messageId: "noOffsetPagination" });
@@ -5463,7 +5590,7 @@ var no_offset_pagination_default = createRule({
5463
5590
  });
5464
5591
 
5465
5592
  // src/rules/no-positional-tuple-return.ts
5466
- import { AST_NODE_TYPES as AST_NODE_TYPES23, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
5593
+ import { AST_NODE_TYPES as AST_NODE_TYPES23, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
5467
5594
  var NO_POSITIONAL_TUPLE_RETURN_DOCUMENTATION = {
5468
5595
  summary: "Disallow returning a multi-field tuple from a named function; return a named object so call sites cannot mismatch slots.",
5469
5596
  rationale: "Tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
@@ -5597,7 +5724,7 @@ function typeAliases(sourceCode) {
5597
5724
  get(identifier) {
5598
5725
  const declaration = aliases.get(identifier.name);
5599
5726
  if (declaration === void 0) return void 0;
5600
- const binding = ASTUtils9.findVariable(sourceCode.getScope(identifier), identifier.name);
5727
+ const binding = ASTUtils12.findVariable(sourceCode.getScope(identifier), identifier.name);
5601
5728
  return binding?.defs.length === 1 && binding.defs[0]?.node === declaration ? declaration.typeAnnotation : void 0;
5602
5729
  }
5603
5730
  };
@@ -5898,7 +6025,7 @@ var no_production_browser_source_maps_default = createRule({
5898
6025
  });
5899
6026
 
5900
6027
  // src/rules/no-raw-env.ts
5901
- import { ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
6028
+ import { ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
5902
6029
  var NO_RAW_ENV_DOCUMENTATION = {
5903
6030
  summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
5904
6031
  rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
@@ -5980,7 +6107,7 @@ var no_raw_env_default = createRule({
5980
6107
  },
5981
6108
  MemberExpression(node) {
5982
6109
  if (isProcessEnv(node) && node.object.type === "Identifier") {
5983
- const binding = ASTUtils10.findVariable(context.sourceCode.getScope(node), node.object.name);
6110
+ const binding = ASTUtils13.findVariable(context.sourceCode.getScope(node), node.object.name);
5984
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;
5985
6112
  }
5986
6113
  if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isExemptVariableAccess(node) && !isWriteTarget(node) && !isWholeEnvSpread(node)) {
@@ -5996,7 +6123,7 @@ var no_raw_env_default = createRule({
5996
6123
  });
5997
6124
 
5998
6125
  // src/rules/no-raw-fetch-outside-clients.ts
5999
- import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
6126
+ import { AST_NODE_TYPES as AST_NODE_TYPES24, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
6000
6127
  var NO_RAW_FETCH_OUTSIDE_CLIENTS_DOCUMENTATION = {
6001
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.",
6002
6129
  rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
@@ -6184,7 +6311,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6184
6311
  internalApiPrefixes.push(`${options.basePath}/api`);
6185
6312
  }
6186
6313
  function resolvesToGlobal(identifier) {
6187
- const variable = ASTUtils11.findVariable(
6314
+ const variable = ASTUtils14.findVariable(
6188
6315
  context.sourceCode.getScope(identifier),
6189
6316
  identifier.name
6190
6317
  );
@@ -6193,7 +6320,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6193
6320
  function resolveNode2(node) {
6194
6321
  if (node === void 0) return null;
6195
6322
  if (node.type !== AST_NODE_TYPES24.Identifier) return node;
6196
- const variable = ASTUtils11.findVariable(
6323
+ const variable = ASTUtils14.findVariable(
6197
6324
  context.sourceCode.getScope(node),
6198
6325
  node.name
6199
6326
  );
@@ -6272,7 +6399,7 @@ var no_raw_fetch_outside_clients_default = createRule({
6272
6399
  });
6273
6400
 
6274
6401
  // src/rules/no-restricted-library-load.ts
6275
- import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils12 } from "@typescript-eslint/utils";
6402
+ import { AST_NODE_TYPES as AST_NODE_TYPES25, ASTUtils as ASTUtils15 } from "@typescript-eslint/utils";
6276
6403
  var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
6277
6404
  summary: "Apply configured library restrictions to literal runtime loads and CommonJS resolution references.",
6278
6405
  rationale: "Dynamic imports, CommonJS loads, and package resolution checks can bypass library restrictions enforced for static imports.",
@@ -6345,7 +6472,7 @@ var no_restricted_library_load_default = createRule({
6345
6472
  });
6346
6473
  }
6347
6474
  function isUnshadowedRequire(node) {
6348
- const variable = ASTUtils12.findVariable(
6475
+ const variable = ASTUtils15.findVariable(
6349
6476
  context.sourceCode.getScope(node),
6350
6477
  node.name
6351
6478
  );
@@ -6378,7 +6505,7 @@ var no_restricted_library_load_default = createRule({
6378
6505
  });
6379
6506
 
6380
6507
  // src/rules/no-router-refresh-polling.ts
6381
- import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils13 } from "@typescript-eslint/utils";
6508
+ import { AST_NODE_TYPES as AST_NODE_TYPES26, ASTUtils as ASTUtils16 } from "@typescript-eslint/utils";
6382
6509
  var NO_ROUTER_REFRESH_POLLING_DOCUMENTATION = {
6383
6510
  summary: "Do not poll by calling a Next.js router's refresh method from a timer.",
6384
6511
  rationale: "A route refresh refetches and rerenders the whole route on every tick instead of loading the named resource that changed.",
@@ -6408,7 +6535,7 @@ function isIntervalCallee(sourceCode, node) {
6408
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";
6409
6536
  }
6410
6537
  function isUnshadowedGlobal2(sourceCode, node) {
6411
- const variable = ASTUtils13.findVariable(sourceCode.getScope(node), node.name);
6538
+ const variable = ASTUtils16.findVariable(sourceCode.getScope(node), node.name);
6412
6539
  return variable === null || variable.defs.length === 0;
6413
6540
  }
6414
6541
  var no_router_refresh_polling_default = createRule({
@@ -6431,21 +6558,21 @@ var no_router_refresh_polling_default = createRule({
6431
6558
  if (node.source.value !== "next/navigation") return;
6432
6559
  for (const specifier of node.specifiers) {
6433
6560
  if (specifier.type === AST_NODE_TYPES26.ImportSpecifier && importedName4(specifier) === "useRouter") {
6434
- const variable = ASTUtils13.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6561
+ const variable = ASTUtils16.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
6435
6562
  if (variable !== null) routerHooks.add(variable);
6436
6563
  }
6437
6564
  }
6438
6565
  },
6439
6566
  VariableDeclarator(node) {
6440
6567
  if (node.id.type === AST_NODE_TYPES26.Identifier && node.init?.type === AST_NODE_TYPES26.CallExpression && node.init.callee.type === AST_NODE_TYPES26.Identifier) {
6441
- const hook = ASTUtils13.findVariable(context.sourceCode.getScope(node.init.callee), node.init.callee.name);
6442
- const router = ASTUtils13.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);
6443
6570
  if (hook !== null && router !== null && routerHooks.has(hook)) routers.add(router);
6444
6571
  }
6445
6572
  },
6446
6573
  CallExpression(node) {
6447
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;
6448
- const router = ASTUtils13.findVariable(
6575
+ const router = ASTUtils16.findVariable(
6449
6576
  context.sourceCode.getScope(node.callee.object),
6450
6577
  node.callee.object.name
6451
6578
  );
@@ -6485,7 +6612,7 @@ var NO_REPEATED_STRING_LITERAL_DOCUMENTATION = {
6485
6612
  ]
6486
6613
  };
6487
6614
  function isStructured(value) {
6488
- 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);
6489
6616
  }
6490
6617
  function preview(value) {
6491
6618
  const oneLine = value.replaceAll("\n", " ").trim();
@@ -6506,7 +6633,7 @@ function isScaffolding(node) {
6506
6633
  }
6507
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;
6508
6635
  const isRequireSource = parent.type === AST_NODE_TYPES27.CallExpression && parent.callee.type === AST_NODE_TYPES27.Identifier && parent.callee.name === "require";
6509
- 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;
6510
6637
  }
6511
6638
  var no_repeated_string_literal_default = createRule({
6512
6639
  name: "no-repeated-string-literal",
@@ -7426,11 +7553,11 @@ var no_server_env_in_client_component_default = createRule({
7426
7553
  // src/rules/no-select-star.ts
7427
7554
  import "@typescript-eslint/utils";
7428
7555
  var NO_SELECT_STAR_DOCUMENTATION = {
7429
- 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.",
7430
7557
  rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
7431
7558
  remediation: "List every required column explicitly in the projection.",
7432
7559
  category: "correctness",
7433
- 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."],
7434
7561
  examples: [
7435
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 },
7436
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 }
@@ -7481,7 +7608,7 @@ var no_select_star_default = createRule({
7481
7608
  meta: {
7482
7609
  type: "problem",
7483
7610
  docs: {
7484
- 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."
7485
7612
  },
7486
7613
  schema: [],
7487
7614
  messages: {
@@ -7503,7 +7630,7 @@ var no_select_star_default = createRule({
7503
7630
  });
7504
7631
 
7505
7632
  // src/rules/no-sentinel-return-on-catch.ts
7506
- import { AST_NODE_TYPES as AST_NODE_TYPES30, ASTUtils as ASTUtils14 } from "@typescript-eslint/utils";
7633
+ import { AST_NODE_TYPES as AST_NODE_TYPES30, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
7507
7634
  var NO_SENTINEL_RETURN_ON_CATCH_DOCUMENTATION = {
7508
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.",
7509
7636
  rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
@@ -7925,7 +8052,7 @@ var no_sentinel_return_on_catch_default = createRule({
7925
8052
  return;
7926
8053
  }
7927
8054
  const returned = unwrapSentinelExpression(last.argument);
7928
- if (returned?.type === AST_NODE_TYPES30.Identifier && returned.name === "undefined" && (ASTUtils14.findVariable(context.sourceCode.getScope(returned), returned.name)?.defs.length ?? 0) > 0) return;
8055
+ if (returned?.type === AST_NODE_TYPES30.Identifier && returned.name === "undefined" && (ASTUtils17.findVariable(context.sourceCode.getScope(returned), returned.name)?.defs.length ?? 0) > 0) return;
7929
8056
  if (containsThrow(node.body)) {
7930
8057
  return;
7931
8058
  }
@@ -7959,13 +8086,13 @@ var no_sentinel_return_on_catch_default = createRule({
7959
8086
  });
7960
8087
 
7961
8088
  // src/rules/no-silent-promise-catch.ts
7962
- 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";
7963
8090
  var NO_SILENT_PROMISE_CATCH_DOCUMENTATION = {
7964
8091
  summary: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error.",
7965
8092
  rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
7966
8093
  remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
7967
8094
  category: "correctness",
7968
- 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."],
7969
8096
  examples: [
7970
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 },
7971
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 }
@@ -7979,6 +8106,43 @@ var BODY_PARSE_METHODS = /* @__PURE__ */ new Set([
7979
8106
  "json",
7980
8107
  "text"
7981
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
+ ]);
7982
8146
  function isBodyParseCall(node) {
7983
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);
7984
8148
  }
@@ -8052,6 +8216,26 @@ var no_silent_promise_catch_default = createRule({
8052
8216
  if (isTestFile(context.filename) || isScriptFile(context.filename)) {
8053
8217
  return {};
8054
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
+ }
8055
8239
  const hasExplanatoryComment = (call, handler) => {
8056
8240
  const sourceCode = context.sourceCode;
8057
8241
  if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
@@ -8076,6 +8260,7 @@ var no_silent_promise_catch_default = createRule({
8076
8260
  const method = node.callee.property.name;
8077
8261
  const handlerIndex = method === "catch" ? 0 : method === "then" ? 1 : null;
8078
8262
  if (handlerIndex === null) return;
8263
+ if (method === "catch" && isZodSchema(node.callee.object)) return;
8079
8264
  if (isBodyParseCall(node.callee.object)) {
8080
8265
  return;
8081
8266
  }
@@ -8108,16 +8293,16 @@ var no_silent_promise_catch_default = createRule({
8108
8293
  });
8109
8294
 
8110
8295
  // src/rules/no-sleep-in-test-body.ts
8111
- 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";
8112
8297
  var NO_SLEEP_IN_TEST_BODY_DOCUMENTATION = {
8113
- 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.",
8114
8299
  rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
8115
- 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.",
8116
8301
  category: "testing",
8117
8302
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
8118
- 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."],
8119
8304
  examples: [
8120
- { 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 },
8121
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 }
8122
8307
  ]
8123
8308
  };
@@ -8136,9 +8321,6 @@ var FUNCTION_TYPES5 = /* @__PURE__ */ new Set([
8136
8321
  function isNonzeroNumericLiteral(node) {
8137
8322
  return node?.type === AST_NODE_TYPES32.Literal && typeof node.value === "number" && node.value !== 0;
8138
8323
  }
8139
- function isTimedSetTimeout(node) {
8140
- 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]);
8141
- }
8142
8324
  function isPromiseSleep(node) {
8143
8325
  if (node.callee.type !== AST_NODE_TYPES32.Identifier || node.callee.name !== "Promise") {
8144
8326
  return false;
@@ -8148,12 +8330,14 @@ function isPromiseSleep(node) {
8148
8330
  return false;
8149
8331
  }
8150
8332
  const body2 = executor.body;
8151
- if (body2.type !== AST_NODE_TYPES32.BlockStatement) {
8152
- return isTimedSetTimeout(body2);
8153
- }
8154
- return body2.body.some(
8155
- (stmt) => stmt.type === AST_NODE_TYPES32.ExpressionStatement && isTimedSetTimeout(stmt.expression)
8156
- );
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]);
8157
8341
  }
8158
8342
  function isHelperSleep(node) {
8159
8343
  return node.callee.type === AST_NODE_TYPES32.Identifier && SLEEP_HELPERS.has(node.callee.name) && node.arguments.length >= 1 && isNonzeroNumericLiteral(node.arguments[0]);
@@ -8207,11 +8391,11 @@ var no_sleep_in_test_body_default = createRule({
8207
8391
  meta: {
8208
8392
  type: "problem",
8209
8393
  docs: {
8210
- 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."
8211
8395
  },
8212
8396
  schema: [],
8213
8397
  messages: {
8214
- 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."
8215
8399
  }
8216
8400
  },
8217
8401
  defaultOptions: [],
@@ -8232,11 +8416,17 @@ var no_sleep_in_test_body_default = createRule({
8232
8416
  return {
8233
8417
  NewExpression(node) {
8234
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;
8235
8422
  report2(node);
8236
8423
  }
8237
8424
  },
8238
8425
  CallExpression(node) {
8239
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;
8240
8430
  report2(node);
8241
8431
  }
8242
8432
  }
@@ -8257,7 +8447,7 @@ var NO_STORAGE_IN_STATELESS_MODULES_DOCUMENTATION = {
8257
8447
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
8258
8448
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
8259
8449
  category: "architecture",
8260
- 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."],
8261
8451
  examples: [
8262
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 },
8263
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 }
@@ -8291,6 +8481,17 @@ function storageMethodName(node, methods) {
8291
8481
  if (name === "put" && !isStorageLikeReceiver(callee.object)) {
8292
8482
  return null;
8293
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
+ }
8294
8495
  return name;
8295
8496
  }
8296
8497
  function isStorageLikeReceiver(node) {
@@ -8628,14 +8829,14 @@ var no_string_concat_in_loop_default = createRule({
8628
8829
  });
8629
8830
 
8630
8831
  // src/rules/no-tautological-expect.ts
8631
- 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";
8632
8833
  var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
8633
- 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.",
8634
8835
  rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
8635
8836
  remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
8636
8837
  category: "testing",
8637
8838
  limitations: [
8638
- "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."
8639
8840
  ],
8640
8841
  examples: [
8641
8842
  {
@@ -8672,11 +8873,11 @@ var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
8672
8873
  function isLiteral(node) {
8673
8874
  switch (node.type) {
8674
8875
  case AST_NODE_TYPES34.Literal:
8675
- return true;
8876
+ return !("regex" in node);
8676
8877
  case AST_NODE_TYPES34.TemplateLiteral:
8677
8878
  return node.expressions.length === 0;
8678
8879
  case AST_NODE_TYPES34.UnaryExpression:
8679
- 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";
8680
8881
  case AST_NODE_TYPES34.ArrayExpression:
8681
8882
  return node.elements.every((element) => element !== null && isLiteral(element));
8682
8883
  case AST_NODE_TYPES34.ObjectExpression:
@@ -8690,6 +8891,43 @@ function isLiteral(node) {
8690
8891
  function isStructuralLiteral(node) {
8691
8892
  return node.type === AST_NODE_TYPES34.ArrayExpression || node.type === AST_NODE_TYPES34.ObjectExpression;
8692
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
+ }
8693
8931
  function expectOperand(callee) {
8694
8932
  const receiver = callee.object;
8695
8933
  if (receiver.type !== AST_NODE_TYPES34.CallExpression || receiver.callee.type !== AST_NODE_TYPES34.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
@@ -8703,12 +8941,12 @@ var no_tautological_expect_default = createRule({
8703
8941
  meta: {
8704
8942
  type: "problem",
8705
8943
  docs: {
8706
- 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."
8707
8945
  },
8708
8946
  schema: [],
8709
8947
  messages: {
8710
- 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.",
8711
- 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."
8712
8950
  }
8713
8951
  },
8714
8952
  defaultOptions: [],
@@ -8730,11 +8968,20 @@ var no_tautological_expect_default = createRule({
8730
8968
  return;
8731
8969
  }
8732
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;
8733
8980
  const operand = expectOperand(callee);
8734
8981
  if (operand === null || !isLiteral(operand)) {
8735
8982
  return;
8736
8983
  }
8737
- if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0) {
8984
+ if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0 && passesZeroArgumentMatcher(operand, matcher)) {
8738
8985
  context.report({
8739
8986
  node,
8740
8987
  messageId: "tautologicalMatcher",
@@ -9968,7 +10215,7 @@ var no_unnecessary_use_client_default = createRule({
9968
10215
  // src/rules/no-unsafe-mock-casting.ts
9969
10216
  import {
9970
10217
  AST_NODE_TYPES as AST_NODE_TYPES40,
9971
- ASTUtils as ASTUtils15
10218
+ ASTUtils as ASTUtils21
9972
10219
  } from "@typescript-eslint/utils";
9973
10220
  var MOCK_TYPE_NAMES = /* @__PURE__ */ new Set([
9974
10221
  "Mock",
@@ -9989,15 +10236,16 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
9989
10236
  var NO_UNSAFE_MOCK_CASTING_DOCUMENTATION = {
9990
10237
  summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
9991
10238
  rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
9992
- 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.",
9993
10240
  category: "testing",
9994
- 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"],
9995
10243
  examples: [
9996
10244
  {
9997
10245
  id: "typed-mock-helper",
9998
10246
  title: "Use the framework helper",
9999
10247
  outcome: "no-match",
10000
- 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);" }],
10001
10249
  focusPath: "src/client.test.ts",
10002
10250
  expectedCount: 0,
10003
10251
  public: true
@@ -10023,7 +10271,7 @@ var no_unsafe_mock_casting_default = createRule({
10023
10271
  },
10024
10272
  schema: [],
10025
10273
  messages: {
10026
- 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."
10027
10275
  }
10028
10276
  },
10029
10277
  defaultOptions: [],
@@ -10034,7 +10282,7 @@ var no_unsafe_mock_casting_default = createRule({
10034
10282
  const directBindings = /* @__PURE__ */ new Set();
10035
10283
  const namespaceBindings = /* @__PURE__ */ new Set();
10036
10284
  function resolve2(identifier) {
10037
- return ASTUtils15.findVariable(
10285
+ return ASTUtils21.findVariable(
10038
10286
  context.sourceCode.getScope(identifier),
10039
10287
  identifier.name
10040
10288
  );
@@ -10084,12 +10332,12 @@ var no_unsafe_mock_casting_default = createRule({
10084
10332
  import {
10085
10333
  ESLintUtils as ESLintUtils3,
10086
10334
  AST_NODE_TYPES as AST_NODE_TYPES41,
10087
- ASTUtils as ASTUtils16
10335
+ ASTUtils as ASTUtils22
10088
10336
  } from "@typescript-eslint/utils";
10089
10337
  import * as ts2 from "typescript";
10090
10338
  var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
10091
10339
  summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
10092
- 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.",
10093
10341
  remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
10094
10342
  category: "maintainability",
10095
10343
  autofix: "none",
@@ -10194,7 +10442,7 @@ var no_zod_native_enum_default = createRule({
10194
10442
  const zodImportedBindings = /* @__PURE__ */ new Map();
10195
10443
  const zodNamespaceBindings = /* @__PURE__ */ new Set();
10196
10444
  function resolvedBinding(identifier) {
10197
- return ASTUtils16.findVariable(
10445
+ return ASTUtils22.findVariable(
10198
10446
  sourceCode.getScope(identifier),
10199
10447
  identifier.name
10200
10448
  );
@@ -10260,14 +10508,14 @@ var no_zod_native_enum_default = createRule({
10260
10508
  });
10261
10509
 
10262
10510
  // src/rules/test-loops-over-literal-cases.ts
10263
- import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils17 } from "@typescript-eslint/utils";
10511
+ import { AST_NODE_TYPES as AST_NODE_TYPES42, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
10264
10512
  var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
10265
10513
  summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
10266
10514
  rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
10267
10515
  remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
10268
10516
  category: "testing",
10269
10517
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
10270
- 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."],
10271
10519
  examples: [
10272
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 },
10273
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 }
@@ -10414,7 +10662,7 @@ var test_loops_over_literal_cases_default = createRule({
10414
10662
  },
10415
10663
  schema: [],
10416
10664
  messages: {
10417
- 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."
10418
10666
  }
10419
10667
  },
10420
10668
  defaultOptions: [],
@@ -10423,7 +10671,7 @@ var test_loops_over_literal_cases_default = createRule({
10423
10671
  return {};
10424
10672
  }
10425
10673
  const isFrameworkIdentifier = (identifier, modules) => {
10426
- const variable = ASTUtils17.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10674
+ const variable = ASTUtils23.findVariable(context.sourceCode.getScope(identifier), identifier.name);
10427
10675
  if (variable === null || variable.defs.length === 0) return true;
10428
10676
  return variable.defs.some((definition) => {
10429
10677
  let current = definition.node;
@@ -10439,6 +10687,9 @@ var test_loops_over_literal_cases_default = createRule({
10439
10687
  if (enclosing === null || !isTestBody2(enclosing, isFrameworkTest)) {
10440
10688
  return;
10441
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
+ }
10442
10693
  const cases = unwrapExpression(node.right);
10443
10694
  const callbackParameters = new Set(
10444
10695
  enclosing.params.flatMap((parameter) => parameter.type === AST_NODE_TYPES42.Identifier ? [parameter.name] : [])
@@ -10448,6 +10699,16 @@ var test_loops_over_literal_cases_default = createRule({
10448
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))) {
10449
10700
  return;
10450
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;
10451
10712
  context.report({
10452
10713
  node,
10453
10714
  messageId: "literalCaseLoop",
@@ -10900,7 +11161,7 @@ var prefer_discriminated_union_default = createRule({
10900
11161
  });
10901
11162
 
10902
11163
  // src/rules/prefer-input-group-search.ts
10903
- import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils18 } from "@typescript-eslint/utils";
11164
+ import { AST_NODE_TYPES as AST_NODE_TYPES47, ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
10904
11165
  var PREFER_INPUT_GROUP_SEARCH_DOCUMENTATION = {
10905
11166
  summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
10906
11167
  rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
@@ -11024,7 +11285,7 @@ var prefer_input_group_search_default = createRule({
11024
11285
  JSXOpeningElement(node) {
11025
11286
  const name = elementName(node);
11026
11287
  if (name === null) return;
11027
- const binding = ASTUtils18.findVariable(context.sourceCode.getScope(node), name);
11288
+ const binding = ASTUtils24.findVariable(context.sourceCode.getScope(node), name);
11028
11289
  if (binding?.defs.length !== 1 || binding.defs[0]?.node.type !== AST_NODE_TYPES47.ImportSpecifier || binding.defs[0].node.importKind === "type") return;
11029
11290
  const occurrence = {
11030
11291
  ancestors: context.sourceCode.getAncestors(node),
@@ -11064,7 +11325,7 @@ var prefer_input_group_search_default = createRule({
11064
11325
 
11065
11326
  // src/rules/prefer-millisecond-control-duration-schema.ts
11066
11327
  import {
11067
- ASTUtils as ASTUtils19,
11328
+ ASTUtils as ASTUtils25,
11068
11329
  AST_NODE_TYPES as AST_NODE_TYPES48
11069
11330
  } from "@typescript-eslint/utils";
11070
11331
  var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
@@ -11074,7 +11335,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11074
11335
  category: "correctness",
11075
11336
  autofix: "none",
11076
11337
  limitations: [
11077
- "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.",
11078
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.",
11079
11340
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and non-Zod schemas are excluded."
11080
11341
  ],
@@ -11086,7 +11347,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11086
11347
  files: [
11087
11348
  {
11088
11349
  path: "src/request.ts",
11089
- 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) });"
11090
11351
  }
11091
11352
  ],
11092
11353
  focusPath: "src/request.ts",
@@ -11100,7 +11361,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11100
11361
  files: [
11101
11362
  {
11102
11363
  path: "src/request.ts",
11103
- 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) });"
11104
11365
  }
11105
11366
  ],
11106
11367
  focusPath: "src/request.ts",
@@ -11132,8 +11393,9 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11132
11393
  }
11133
11394
  const zodNamespaces = /* @__PURE__ */ new Set();
11134
11395
  const objectFactories = /* @__PURE__ */ new Set();
11396
+ const numberFactories = /* @__PURE__ */ new Set();
11135
11397
  function binding(identifier) {
11136
- return ASTUtils19.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11398
+ return ASTUtils25.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11137
11399
  }
11138
11400
  function record(target, identifier) {
11139
11401
  const variable = binding(identifier);
@@ -11151,10 +11413,27 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11151
11413
  const variable = binding(callee.object);
11152
11414
  return variable !== null && zodNamespaces.has(variable);
11153
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
+ }
11154
11430
  return {
11155
11431
  ImportDeclaration(node) {
11156
11432
  if (!isZodModule(node.source.value)) return;
11157
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
+ }
11158
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") {
11159
11438
  record(zodNamespaces, specifier.local);
11160
11439
  } else if (specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && (specifier.imported.name === "object" || specifier.imported.name === "strictObject")) {
@@ -11167,7 +11446,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11167
11446
  const shape = node.arguments[0];
11168
11447
  if (shape?.type !== AST_NODE_TYPES48.ObjectExpression) return;
11169
11448
  for (const member of shape.properties) {
11170
- if (member.type !== AST_NODE_TYPES48.Property) continue;
11449
+ if (member.type !== AST_NODE_TYPES48.Property || !isNumericSchema(member.value)) continue;
11171
11450
  const key = directIdentifierKey(member);
11172
11451
  if (key === null || !CONTROL_SECONDS_RE.test(key.name) && !CONTROL_SECONDS_CAMEL_RE.test(key.name)) {
11173
11452
  continue;
@@ -11180,14 +11459,14 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11180
11459
  });
11181
11460
 
11182
11461
  // src/rules/prefer-immutable-module-constant.ts
11183
- import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils20 } from "@typescript-eslint/utils";
11462
+ import { AST_NODE_TYPES as AST_NODE_TYPES49, ASTUtils as ASTUtils26 } from "@typescript-eslint/utils";
11184
11463
  var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
11185
11464
  summary: "Require module-level constant collections to expose readonly state.",
11186
11465
  rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
11187
11466
  remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
11188
11467
  category: "correctness",
11189
11468
  limitations: [
11190
- "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."
11191
11470
  ],
11192
11471
  examples: [
11193
11472
  {
@@ -11333,7 +11612,7 @@ var prefer_immutable_module_constant_default = createRule({
11333
11612
  create(context) {
11334
11613
  const sourceCode = context.sourceCode;
11335
11614
  const isUnshadowedGlobal3 = (identifier) => {
11336
- const variable = ASTUtils20.findVariable(sourceCode.getScope(identifier), identifier.name);
11615
+ const variable = ASTUtils26.findVariable(sourceCode.getScope(identifier), identifier.name);
11337
11616
  return variable === null || variable.defs.length === 0;
11338
11617
  };
11339
11618
  if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
@@ -11341,7 +11620,7 @@ var prefer_immutable_module_constant_default = createRule({
11341
11620
  }
11342
11621
  const exportedNames2 = /* @__PURE__ */ new Set();
11343
11622
  const typeAliases2 = /* @__PURE__ */ new Map();
11344
- const mutatesThroughConstAlias = (root) => {
11623
+ const mutatesThroughAlias = (root) => {
11345
11624
  const pending = [root];
11346
11625
  const seen = /* @__PURE__ */ new Set();
11347
11626
  while (pending.length > 0) {
@@ -11353,7 +11632,7 @@ var prefer_immutable_module_constant_default = createRule({
11353
11632
  if (identifier.type !== AST_NODE_TYPES49.Identifier) continue;
11354
11633
  if (referenceMutates(identifier, isUnshadowedGlobal3)) return true;
11355
11634
  const declarator = identifier.parent;
11356
- 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) {
11357
11636
  continue;
11358
11637
  }
11359
11638
  const alias = sourceCode.getDeclaredVariables(declarator)[0];
@@ -11402,7 +11681,7 @@ var prefer_immutable_module_constant_default = createRule({
11402
11681
  return;
11403
11682
  }
11404
11683
  const variable = sourceCode.getDeclaredVariables(node)[0];
11405
- 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)) {
11406
11685
  return;
11407
11686
  }
11408
11687
  context.report({
@@ -11829,7 +12108,7 @@ var PREFER_MODULE_LEVEL_CONSTANT_DOCUMENTATION = {
11829
12108
  rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
11830
12109
  remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
11831
12110
  category: "performance",
11832
- 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."],
11833
12112
  examples: [
11834
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 },
11835
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 }
@@ -11984,12 +12263,16 @@ var NON_RETAINING_BUILTINS = /* @__PURE__ */ new Map(
11984
12263
  ]
11985
12264
  );
11986
12265
  function isSafeRead(identifier) {
11987
- const parent = identifier.parent;
12266
+ let parent = identifier.parent;
11988
12267
  if (parent.type === AST_NODE_TYPES51.MemberExpression) {
11989
12268
  if (parent.object !== identifier) {
11990
12269
  return true;
11991
12270
  }
12271
+ while (parent.parent.type === AST_NODE_TYPES51.MemberExpression && parent.parent.object === parent) {
12272
+ parent = parent.parent;
12273
+ }
11992
12274
  const grandparent = parent.parent;
12275
+ if (grandparent.type === AST_NODE_TYPES51.VariableDeclarator || grandparent.type === AST_NODE_TYPES51.SpreadElement) return false;
11993
12276
  if (grandparent.type === AST_NODE_TYPES51.AssignmentExpression && grandparent.left === parent) {
11994
12277
  return false;
11995
12278
  }
@@ -11999,7 +12282,7 @@ function isSafeRead(identifier) {
11999
12282
  if (grandparent.type === AST_NODE_TYPES51.UnaryExpression && grandparent.operator === "delete") {
12000
12283
  return false;
12001
12284
  }
12002
- 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))) {
12003
12286
  return false;
12004
12287
  }
12005
12288
  return true;
@@ -12107,9 +12390,15 @@ var prefer_module_level_constant_default = createRule({
12107
12390
  if (node.id.type !== AST_NODE_TYPES51.Identifier || node.init === null) {
12108
12391
  return;
12109
12392
  }
12110
- if (enclosingFunction3(node) === null) {
12393
+ const owner = enclosingFunction3(node);
12394
+ if (owner === null) {
12111
12395
  return;
12112
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;
12113
12402
  const candidate2 = classify(node.init, checkRegex);
12114
12403
  if (candidate2 === null) {
12115
12404
  return;
@@ -12134,10 +12423,10 @@ var prefer_module_level_constant_default = createRule({
12134
12423
  import { AST_NODE_TYPES as AST_NODE_TYPES52 } from "@typescript-eslint/utils";
12135
12424
  var PREFER_MODULE_LEVEL_SCHEMA_DOCUMENTATION = {
12136
12425
  summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
12137
- 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.",
12138
12427
  remediation: "Move the closed schema declaration to module scope and reference it from the function.",
12139
12428
  category: "performance",
12140
- 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."],
12141
12430
  examples: [
12142
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 },
12143
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 }
@@ -12154,6 +12443,36 @@ var DEFAULT_FACTORIES = [
12154
12443
  "union"
12155
12444
  ];
12156
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
+ ]);
12157
12476
  var MEMO_CALLEES = /* @__PURE__ */ new Set([
12158
12477
  "lazy",
12159
12478
  "memo",
@@ -12230,7 +12549,7 @@ function outermostEnclosingFunction(node) {
12230
12549
  }
12231
12550
  return outermost;
12232
12551
  }
12233
- function subtreeSome(root, predicate) {
12552
+ function subtreeSome(root, predicate, skipDeferredFunctions = false) {
12234
12553
  let found = false;
12235
12554
  const visit = (value) => {
12236
12555
  if (found || value === null || typeof value !== "object") {
@@ -12246,6 +12565,7 @@ function subtreeSome(root, predicate) {
12246
12565
  if (typeof candidate2.type !== "string") {
12247
12566
  return;
12248
12567
  }
12568
+ if (skipDeferredFunctions && FUNCTION_TYPES8.has(candidate2.type)) return;
12249
12569
  if (predicate(candidate2)) {
12250
12570
  found = true;
12251
12571
  return;
@@ -12344,6 +12664,15 @@ var prefer_module_level_schema_default = createRule({
12344
12664
  function isZodCall(node) {
12345
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);
12346
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
+ }
12347
12676
  function isCovered(node) {
12348
12677
  let current = node.parent ?? void 0;
12349
12678
  while (current !== void 0) {
@@ -12473,6 +12802,7 @@ var prefer_module_level_schema_default = createRule({
12473
12802
  return;
12474
12803
  }
12475
12804
  const outermost = outermostSchemaExpression(expression);
12805
+ if (hasEagerComputation(outermost)) return;
12476
12806
  if (outermost !== expression && (readsReceiver(outermost) || buildsLocalizedText(outermost) || !closesOverNothing(outermost, enclosing))) {
12477
12807
  return;
12478
12808
  }
@@ -12492,7 +12822,7 @@ var prefer_module_level_schema_default = createRule({
12492
12822
  // src/rules/prefer-module-level-refined-schema.ts
12493
12823
  import {
12494
12824
  AST_NODE_TYPES as AST_NODE_TYPES53,
12495
- ASTUtils as ASTUtils21
12825
+ ASTUtils as ASTUtils27
12496
12826
  } from "@typescript-eslint/utils";
12497
12827
  var BENCHMARK_PATH_RE = /(^|[/\\])(?:benchmarks?|bench)[/\\]/;
12498
12828
  var FACTORIES = /* @__PURE__ */ new Set([
@@ -12613,7 +12943,8 @@ var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
12613
12943
  limitations: [
12614
12944
  "Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
12615
12945
  "Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
12616
- "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."
12617
12948
  ],
12618
12949
  examples: [
12619
12950
  {
@@ -12655,7 +12986,7 @@ function collectReferences2(scope, output) {
12655
12986
  output.push(...scope.references);
12656
12987
  for (const child of scope.childScopes) collectReferences2(child, output);
12657
12988
  }
12658
- function subtreeSome2(root, predicate) {
12989
+ function subtreeSome2(root, predicate, skipDeferredFunctions = false) {
12659
12990
  let found = false;
12660
12991
  const visit = (value) => {
12661
12992
  if (found || value === null || typeof value !== "object") return;
@@ -12665,6 +12996,7 @@ function subtreeSome2(root, predicate) {
12665
12996
  }
12666
12997
  const candidate2 = value;
12667
12998
  if (typeof candidate2.type !== "string") return;
12999
+ if (skipDeferredFunctions && FUNCTION_TYPES9.has(candidate2.type)) return;
12668
13000
  if (predicate(candidate2)) {
12669
13001
  found = true;
12670
13002
  return;
@@ -12763,7 +13095,7 @@ var prefer_module_level_refined_schema_default = createRule({
12763
13095
  return {};
12764
13096
  const zodBindings = /* @__PURE__ */ new Set();
12765
13097
  function resolvedBinding(identifier) {
12766
- return ASTUtils21.findVariable(
13098
+ return ASTUtils27.findVariable(
12767
13099
  context.sourceCode.getScope(identifier),
12768
13100
  identifier.name
12769
13101
  );
@@ -12785,6 +13117,15 @@ var prefer_module_level_refined_schema_default = createRule({
12785
13117
  return names[1] ?? null;
12786
13118
  return null;
12787
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
+ }
12788
13129
  function isSharedEnumDomain(node, factory) {
12789
13130
  if (factory !== "enum") return false;
12790
13131
  const [argument] = node.arguments;
@@ -12853,7 +13194,7 @@ var prefer_module_level_refined_schema_default = createRule({
12853
13194
  const enclosing = outermostEnclosingFunction2(node);
12854
13195
  if (enclosing === void 0) return;
12855
13196
  const expression = schemaExpression2(node);
12856
- if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
13197
+ if (hasEagerComputation(expression) || readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
12857
13198
  return;
12858
13199
  context.report({ node, messageId: "hoistRefinedSchema" });
12859
13200
  }
@@ -12864,7 +13205,7 @@ var prefer_module_level_refined_schema_default = createRule({
12864
13205
  // src/rules/prefer-multi-value-zod-literal.ts
12865
13206
  import {
12866
13207
  AST_NODE_TYPES as AST_NODE_TYPES54,
12867
- ASTUtils as ASTUtils22
13208
+ ASTUtils as ASTUtils28
12868
13209
  } from "@typescript-eslint/utils";
12869
13210
  var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12870
13211
  summary: "Use the Zod 4 multi-value literal API instead of a union of literal schemas.",
@@ -12884,7 +13225,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12884
13225
  outcome: "no-match",
12885
13226
  files: [{
12886
13227
  path: "src/schema.ts",
12887
- 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]);"
12888
13229
  }],
12889
13230
  focusPath: "src/schema.ts",
12890
13231
  expectedCount: 0,
@@ -12896,7 +13237,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12896
13237
  outcome: "match",
12897
13238
  files: [{
12898
13239
  path: "src/schema.ts",
12899
- 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)]);"
12900
13241
  }],
12901
13242
  focusPath: "src/schema.ts",
12902
13243
  expectedCount: 1,
@@ -12911,7 +13252,7 @@ function isStaticPrimitive(node, context) {
12911
13252
  if (node.type === AST_NODE_TYPES54.TemplateLiteral && node.expressions.length === 0)
12912
13253
  return true;
12913
13254
  if (node.type === AST_NODE_TYPES54.Identifier && node.name === "undefined") {
12914
- const binding = ASTUtils22.findVariable(
13255
+ const binding = ASTUtils28.findVariable(
12915
13256
  context.sourceCode.getScope(node),
12916
13257
  node.name
12917
13258
  );
@@ -12948,7 +13289,7 @@ var prefer_multi_value_zod_literal_default = createRule({
12948
13289
  const zodBindings = /* @__PURE__ */ new Set();
12949
13290
  const zod4Bindings = /* @__PURE__ */ new Set();
12950
13291
  function resolvedBinding(identifier) {
12951
- return ASTUtils22.findVariable(
13292
+ return ASTUtils28.findVariable(
12952
13293
  context.sourceCode.getScope(identifier),
12953
13294
  identifier.name
12954
13295
  );
@@ -13055,11 +13396,11 @@ import { AST_NODE_TYPES as AST_NODE_TYPES56 } from "@typescript-eslint/utils";
13055
13396
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
13056
13397
  summary: "Prefer a named contract for structurally complex function return types.",
13057
13398
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
13058
- 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.",
13059
13400
  category: "maintainability",
13060
13401
  limitations: [
13061
13402
  "Only explicit object types with at least three members and unions with at least three object variants are reported.",
13062
- "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."
13063
13404
  ],
13064
13405
  examples: [
13065
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 },
@@ -13110,7 +13451,7 @@ var prefer_named_complex_return_type_default = createRule({
13110
13451
  });
13111
13452
 
13112
13453
  // src/rules/prefer-native-random-uuid.ts
13113
- import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils23 } from "@typescript-eslint/utils";
13454
+ import { AST_NODE_TYPES as AST_NODE_TYPES57, ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
13114
13455
  var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
13115
13456
  summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
13116
13457
  rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
@@ -13146,14 +13487,14 @@ var prefer_native_random_uuid_default = createRule({
13146
13487
  const directBindings = /* @__PURE__ */ new Set();
13147
13488
  const namespaceBindings = /* @__PURE__ */ new Set();
13148
13489
  function resolve2(identifier) {
13149
- return ASTUtils23.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13490
+ return ASTUtils29.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13150
13491
  }
13151
13492
  function record(identifier, destination) {
13152
13493
  const variable = resolve2(identifier);
13153
13494
  if (variable !== null) destination.add(variable);
13154
13495
  }
13155
13496
  function report2(node) {
13156
- const globalBinding = ASTUtils23.findVariable(context.sourceCode.getScope(node), "globalThis");
13497
+ const globalBinding = ASTUtils29.findVariable(context.sourceCode.getScope(node), "globalThis");
13157
13498
  const canSuggest = (globalBinding?.defs.length ?? 0) === 0 && context.sourceCode.getCommentsInside(node).length === 0;
13158
13499
  context.report({
13159
13500
  node,
@@ -13211,16 +13552,18 @@ var prefer_native_random_uuid_default = createRule({
13211
13552
  });
13212
13553
 
13213
13554
  // src/rules/prefer-node-crypto-hash.ts
13214
- import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils24 } from "@typescript-eslint/utils";
13555
+ import { AST_NODE_TYPES as AST_NODE_TYPES58, ASTUtils as ASTUtils30 } from "@typescript-eslint/utils";
13215
13556
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
13216
13557
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
13217
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.",
13218
- 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.",
13219
13560
  category: "performance",
13220
13561
  limitations: [
13221
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.",
13222
- "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."
13223
13565
  ],
13566
+ references: ["https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-options"],
13224
13567
  examples: [
13225
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 },
13226
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 }
@@ -13267,7 +13610,7 @@ var prefer_node_crypto_hash_default = createRule({
13267
13610
  const directBindings = /* @__PURE__ */ new Set();
13268
13611
  const namespaceBindings = /* @__PURE__ */ new Set();
13269
13612
  function resolve2(identifier) {
13270
- return ASTUtils24.findVariable(
13613
+ return ASTUtils30.findVariable(
13271
13614
  context.sourceCode.getScope(identifier),
13272
13615
  identifier.name
13273
13616
  );
@@ -13341,7 +13684,7 @@ function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
13341
13684
  }
13342
13685
 
13343
13686
  // src/rules/prefer-node-fs-promises.ts
13344
- 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";
13345
13688
  var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
13346
13689
  summary: "Prefer promise-based Node.js filesystem APIs over synchronous calls in production modules.",
13347
13690
  rationale: "Synchronous filesystem work blocks the event loop and can stall unrelated daemon, server, and worker tasks.",
@@ -13349,6 +13692,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
13349
13692
  category: "performance",
13350
13693
  limitations: [
13351
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.",
13352
13696
  "ESLint rule implementations under src/rules are excluded because visitor creation and execution are synchronous by contract.",
13353
13697
  "Only statically identifiable node:fs loads are inspected; filesystem objects passed through arbitrary functions or assignments require type-aware analysis."
13354
13698
  ],
@@ -13367,14 +13711,14 @@ function memberName5(node) {
13367
13711
  function unwrapAwait2(node) {
13368
13712
  return node.type === AST_NODE_TYPES59.AwaitExpression ? node.argument : node;
13369
13713
  }
13370
- function isFsLoader(node) {
13714
+ function isFsLoader(node, isGlobal) {
13371
13715
  const expression = unwrapAwait2(node);
13372
13716
  if (expression.type === AST_NODE_TYPES59.ImportExpression) return isFsSpecifier(expression.source);
13373
13717
  if (expression.type !== AST_NODE_TYPES59.CallExpression || expression.arguments.length !== 1) return false;
13374
13718
  const [argument] = expression.arguments;
13375
13719
  if (argument === void 0 || argument.type === AST_NODE_TYPES59.SpreadElement || !isFsSpecifier(argument)) return false;
13376
- if (expression.callee.type === AST_NODE_TYPES59.Identifier) return expression.callee.name === "require";
13377
- 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";
13378
13722
  }
13379
13723
  function isFsSpecifier(node) {
13380
13724
  return node.type === AST_NODE_TYPES59.Literal && (node.value === "node:fs" || node.value === "fs");
@@ -13401,13 +13745,26 @@ var prefer_node_fs_promises_default = createRule({
13401
13745
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text) || normalizedFilename.includes("src/rules/"))
13402
13746
  return {};
13403
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
+ };
13404
13761
  return {
13405
13762
  ImportDeclaration(node) {
13406
13763
  if (node.source.value !== "node:fs" && node.source.value !== "fs") return;
13407
13764
  const synchronousImports = [];
13408
13765
  for (const specifier of node.specifiers) {
13409
13766
  if (specifier.type === AST_NODE_TYPES59.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES59.ImportDefaultSpecifier) {
13410
- namespaces.add(specifier.local.name);
13767
+ recordNamespace(specifier.local);
13411
13768
  continue;
13412
13769
  }
13413
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);
@@ -13421,10 +13778,10 @@ var prefer_node_fs_promises_default = createRule({
13421
13778
  }
13422
13779
  },
13423
13780
  VariableDeclarator(node) {
13424
- 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)))
13425
13782
  return;
13426
13783
  if (node.id.type === AST_NODE_TYPES59.Identifier) {
13427
- namespaces.add(node.id.name);
13784
+ recordNamespace(node.id);
13428
13785
  return;
13429
13786
  }
13430
13787
  if (node.id.type !== AST_NODE_TYPES59.ObjectPattern) return;
@@ -13445,7 +13802,7 @@ var prefer_node_fs_promises_default = createRule({
13445
13802
  const name = memberName5(node);
13446
13803
  if (name?.endsWith("Sync") !== true) return;
13447
13804
  const object = unwrapAwait2(node.object);
13448
- 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)) {
13449
13806
  context.report({ node, messageId: "preferAsyncFs", data: { name } });
13450
13807
  }
13451
13808
  }
@@ -13454,13 +13811,13 @@ var prefer_node_fs_promises_default = createRule({
13454
13811
  });
13455
13812
 
13456
13813
  // src/rules/prefer-non-nullable-collection.ts
13457
- import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils25 } from "@typescript-eslint/utils";
13814
+ import { AST_NODE_TYPES as AST_NODE_TYPES60, ASTUtils as ASTUtils32 } from "@typescript-eslint/utils";
13458
13815
  var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
13459
- 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.",
13460
13817
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
13461
13818
  remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
13462
13819
  category: "maintainability",
13463
- 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."],
13464
13821
  examples: [
13465
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 },
13466
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 }
@@ -13529,22 +13886,6 @@ function sameAccess(node, access) {
13529
13886
  }
13530
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;
13531
13888
  }
13532
- function isNullGuard(node, access) {
13533
- if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && (sameAccess(node.argument, access) || optionalMemberLengthOf(node.argument, access))) return true;
13534
- if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "==="].includes(node.operator)) {
13535
- return false;
13536
- }
13537
- const nullish = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === null || value.type === AST_NODE_TYPES60.Identifier && value.name === "undefined";
13538
- return sameAccess(node.left, access) && nullish(node.right) || sameAccess(node.right, access) && nullish(node.left);
13539
- }
13540
- function isEmptyGuard(node, access) {
13541
- if (node.type === AST_NODE_TYPES60.UnaryExpression && node.operator === "!" && memberLengthOf(node.argument, access)) return true;
13542
- if (node.type !== AST_NODE_TYPES60.BinaryExpression || !["==", "===", "<="].includes(node.operator)) {
13543
- return false;
13544
- }
13545
- const zero = (value) => value.type === AST_NODE_TYPES60.Literal && value.value === 0;
13546
- return memberLengthOf(node.left, access) && zero(node.right) || memberLengthOf(node.right, access) && zero(node.left);
13547
- }
13548
13889
  function memberLengthOf(node, access) {
13549
13890
  const target = node.type === AST_NODE_TYPES60.ChainExpression ? node.expression : node;
13550
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);
@@ -13559,7 +13900,24 @@ function hasEquivalentLeadingGuard(fn, access, visitorKeys) {
13559
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);
13560
13901
  if (!terminating) return false;
13561
13902
  if (contains(first.consequent, visitorKeys, (node) => sameAccess(node, access))) return false;
13562
- 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);
13563
13921
  }
13564
13922
  function contains(node, visitorKeys, predicate) {
13565
13923
  if (predicate(node)) return true;
@@ -13584,20 +13942,20 @@ function directlyCoalesced(node) {
13584
13942
  return parent?.type === AST_NODE_TYPES60.LogicalExpression && parent.left === node && (parent.operator === "??" || parent.operator === "||") && emptyArray(parent.right);
13585
13943
  }
13586
13944
  function identifierIsOnlyCoalesced(context, binding, fn) {
13587
- const variable = ASTUtils25.findVariable(context.sourceCode.getScope(binding), binding.name);
13945
+ const variable = ASTUtils32.findVariable(context.sourceCode.getScope(binding), binding.name);
13588
13946
  if (variable === null || variable.references.length === 0) return false;
13589
13947
  return variable.references.every(
13590
13948
  (reference) => belongsToFunction(reference.identifier, fn) && directlyCoalesced(reference.identifier)
13591
13949
  );
13592
13950
  }
13593
13951
  function memberIsOnlyCoalesced(context, object, property, fn) {
13594
- const variable = ASTUtils25.findVariable(context.sourceCode.getScope(object), object.name);
13952
+ const variable = ASTUtils32.findVariable(context.sourceCode.getScope(object), object.name);
13595
13953
  if (variable === null) return false;
13596
13954
  const accesses = variable.references.flatMap((reference) => {
13597
13955
  if (!belongsToFunction(reference.identifier, fn)) return [null];
13598
13956
  const parent = reference.identifier.parent;
13599
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];
13600
- return [];
13958
+ return parent?.type === AST_NODE_TYPES60.MemberExpression && parent.object === reference.identifier ? [] : [null];
13601
13959
  });
13602
13960
  return accesses.length > 0 && accesses.every((access) => access !== null && directlyCoalesced(access));
13603
13961
  }
@@ -13607,11 +13965,11 @@ var prefer_non_nullable_collection_default = createRule({
13607
13965
  meta: {
13608
13966
  type: "suggestion",
13609
13967
  docs: {
13610
- 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."
13611
13969
  },
13612
13970
  schema: [],
13613
13971
  messages: {
13614
- 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."
13615
13973
  }
13616
13974
  },
13617
13975
  defaultOptions: [],
@@ -13701,7 +14059,7 @@ var prefer_non_nullable_collection_default = createRule({
13701
14059
  // src/rules/prefer-nullish-filter-predicate.ts
13702
14060
  import {
13703
14061
  AST_NODE_TYPES as AST_NODE_TYPES61,
13704
- ASTUtils as ASTUtils26,
14062
+ ASTUtils as ASTUtils33,
13705
14063
  ESLintUtils as ESLintUtils5
13706
14064
  } from "@typescript-eslint/utils";
13707
14065
  import ts3 from "typescript";
@@ -13747,7 +14105,7 @@ var PREFER_NULLISH_FILTER_PREDICATE_DOCUMENTATION = {
13747
14105
  ]
13748
14106
  };
13749
14107
  function isUnshadowedBoolean(node, context) {
13750
- const variable = ASTUtils26.findVariable(context.sourceCode.getScope(node), node.name);
14108
+ const variable = ASTUtils33.findVariable(context.sourceCode.getScope(node), node.name);
13751
14109
  return variable === null || variable.defs.length === 0;
13752
14110
  }
13753
14111
  function isBuiltinArrayFilter(node, services) {
@@ -13806,7 +14164,7 @@ function isProvablyTruthy(type, checker) {
13806
14164
  }
13807
14165
  function availableParameterName(node, context) {
13808
14166
  for (const name of ["value", "item", "element", "candidate"]) {
13809
- if (ASTUtils26.findVariable(context.sourceCode.getScope(node), name) === null) return name;
14167
+ if (ASTUtils33.findVariable(context.sourceCode.getScope(node), name) === null) return name;
13810
14168
  }
13811
14169
  return null;
13812
14170
  }
@@ -13860,7 +14218,7 @@ var prefer_nullish_filter_predicate_default = createRule({
13860
14218
 
13861
14219
  // src/rules/prefer-await-in-async-return.ts
13862
14220
  import {
13863
- ASTUtils as ASTUtils27,
14221
+ ASTUtils as ASTUtils34,
13864
14222
  ESLintUtils as ESLintUtils6,
13865
14223
  AST_NODE_TYPES as AST_NODE_TYPES62
13866
14224
  } from "@typescript-eslint/utils";
@@ -13868,12 +14226,12 @@ import * as ts4 from "typescript";
13868
14226
  var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
13869
14227
  summary: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform.",
13870
14228
  rationale: "Mixing a directly returned Promise callback into otherwise async control flow makes sequencing and failures harder to read.",
13871
- 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.",
13872
14230
  category: "maintainability",
13873
14231
  since: "15.6.3",
13874
14232
  limitations: [
13875
14233
  "Only a single directly returned `.then` call with an inline callback is checked.",
13876
- "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.",
13877
14235
  "Direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports are excluded because returning the module Promise is their framework contract."
13878
14236
  ],
13879
14237
  examples: [
@@ -13974,13 +14332,13 @@ var prefer_await_in_async_return_default = createRule({
13974
14332
  if (services === null) return {};
13975
14333
  const frameworkLoaders = /* @__PURE__ */ new Set();
13976
14334
  const rememberFrameworkLoader = (identifier) => {
13977
- const variable = ASTUtils27.findVariable(context.sourceCode.getScope(identifier), identifier.name);
14335
+ const variable = ASTUtils34.findVariable(context.sourceCode.getScope(identifier), identifier.name);
13978
14336
  if (variable !== null) frameworkLoaders.add(variable);
13979
14337
  };
13980
14338
  const isFrameworkLoaderCallback = (owner) => {
13981
14339
  const parent = owner.parent;
13982
14340
  if (parent.type !== AST_NODE_TYPES62.CallExpression || parent.arguments[0] !== owner || parent.callee.type !== AST_NODE_TYPES62.Identifier) return false;
13983
- const variable = ASTUtils27.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
14341
+ const variable = ASTUtils34.findVariable(context.sourceCode.getScope(parent.callee), parent.callee.name);
13984
14342
  return variable !== null && frameworkLoaders.has(variable);
13985
14343
  };
13986
14344
  return {
@@ -14010,16 +14368,21 @@ var prefer_await_in_async_return_default = createRule({
14010
14368
  });
14011
14369
 
14012
14370
  // src/rules/prefer-schema-for-api-payload.ts
14013
- 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";
14014
14372
  var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
14015
14373
  summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
14016
14374
  rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
14017
- 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.",
14018
14376
  category: "correctness",
14019
- 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
+ ],
14020
14383
  examples: [
14021
- { 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 },
14022
- { 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 }
14023
14386
  ]
14024
14387
  };
14025
14388
  var unwrap6 = (node) => {
@@ -14044,7 +14407,7 @@ var isSchemaParseReference = (node) => {
14044
14407
  const inner = unwrap6(node);
14045
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");
14046
14409
  };
14047
- var isRawPayloadSource = (node, isKnownLocalText) => {
14410
+ var isRawPayloadSource = (node, context, isKnownLocalText) => {
14048
14411
  let current = unwrap6(node);
14049
14412
  if (current === null) return false;
14050
14413
  if (current.type === AST_NODE_TYPES63.AwaitExpression) {
@@ -14062,13 +14425,31 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
14062
14425
  return false;
14063
14426
  }
14064
14427
  if (property.name === "json") {
14065
- return true;
14428
+ return !callee.computed && current.arguments.length === 0 && isResponseSource(callee.object, context);
14066
14429
  }
14067
14430
  if (PROMISE_CHAIN_METHODS.has(property.name)) {
14068
- return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
14431
+ return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object, context, isKnownLocalText);
14069
14432
  }
14070
14433
  const object = unwrap6(callee.object);
14071
- 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);
14072
14453
  };
14073
14454
  var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
14074
14455
  var isDirectLocalFileRead = (node) => {
@@ -14334,7 +14715,7 @@ var prefer_schema_for_api_payload_default = createRule({
14334
14715
  },
14335
14716
  schema: [],
14336
14717
  messages: {
14337
- 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."
14338
14719
  }
14339
14720
  },
14340
14721
  defaultOptions: [],
@@ -14345,6 +14726,40 @@ var prefer_schema_for_api_payload_default = createRule({
14345
14726
  const unvalidatedVariables = /* @__PURE__ */ new Set();
14346
14727
  const aliasGroups = /* @__PURE__ */ new Map();
14347
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
+ };
14348
14763
  const localFileTextRef = (node, scope) => {
14349
14764
  const unwrapped = unwrap6(node);
14350
14765
  if (unwrapped?.type !== AST_NODE_TYPES63.Identifier) return null;
@@ -14361,6 +14776,7 @@ var prefer_schema_for_api_payload_default = createRule({
14361
14776
  };
14362
14777
  const clearBinding = (variable) => {
14363
14778
  unvalidatedVariables.delete(variable);
14779
+ namedGuards.delete(variable);
14364
14780
  const group = aliasGroups.get(variable);
14365
14781
  aliasGroups.delete(variable);
14366
14782
  group?.delete(variable);
@@ -14395,10 +14811,24 @@ var prefer_schema_for_api_payload_default = createRule({
14395
14811
  };
14396
14812
  const isFullyNarrowedPattern = (declarator) => {
14397
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
+ };
14398
14825
  return declared.length > 0 && declared.every(
14399
- (variable) => variable.references.some(
14400
- (reference) => isValidationRead(reference.identifier)
14401
- )
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
+ })
14402
14832
  );
14403
14833
  };
14404
14834
  const trackInitializer = (declarator, scope) => {
@@ -14406,7 +14836,7 @@ var prefer_schema_for_api_payload_default = createRule({
14406
14836
  const variable = declaredVars[0];
14407
14837
  if (variable === void 0) return;
14408
14838
  const localText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14409
- if (isRawPayloadSource(declarator.init, localText)) {
14839
+ if (isRawPayloadSource(declarator.init, context, localText)) {
14410
14840
  trackRawBinding(variable);
14411
14841
  return;
14412
14842
  }
@@ -14427,6 +14857,7 @@ var prefer_schema_for_api_payload_default = createRule({
14427
14857
  if (node.id.type === AST_NODE_TYPES63.ObjectPattern || node.id.type === AST_NODE_TYPES63.ArrayPattern) {
14428
14858
  if (isRawPayloadSource(
14429
14859
  node.init,
14860
+ context,
14430
14861
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14431
14862
  )) {
14432
14863
  if (!isFullyNarrowedPattern(node)) {
@@ -14446,7 +14877,7 @@ var prefer_schema_for_api_payload_default = createRule({
14446
14877
  if (variable === null) return;
14447
14878
  const isLocalText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14448
14879
  updateLocalFileText(variable, node.right, scope);
14449
- if (isRawPayloadSource(node.right, isLocalText)) {
14880
+ if (isRawPayloadSource(node.right, context, isLocalText)) {
14450
14881
  trackRawBinding(variable);
14451
14882
  } else {
14452
14883
  const source = unvalidatedVariableRef(node.right, scope, unvalidatedVariables);
@@ -14458,6 +14889,7 @@ var prefer_schema_for_api_payload_default = createRule({
14458
14889
  if (node.left.type === AST_NODE_TYPES63.ObjectPattern || node.left.type === AST_NODE_TYPES63.ArrayPattern) {
14459
14890
  if (isRawPayloadSource(
14460
14891
  node.right,
14892
+ context,
14461
14893
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14462
14894
  )) {
14463
14895
  context.report({
@@ -14487,7 +14919,13 @@ var prefer_schema_for_api_payload_default = createRule({
14487
14919
  continue;
14488
14920
  }
14489
14921
  const variable = findVariable2(scope, unwrapped.name);
14490
- 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
+ }
14491
14929
  }
14492
14930
  },
14493
14931
  MemberExpression(node) {
@@ -14497,6 +14935,7 @@ var prefer_schema_for_api_payload_default = createRule({
14497
14935
  const obj = unwrap6(node.object);
14498
14936
  if (isRawPayloadSource(
14499
14937
  obj,
14938
+ context,
14500
14939
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14501
14940
  )) {
14502
14941
  const parent = node.parent;
@@ -14508,6 +14947,7 @@ var prefer_schema_for_api_payload_default = createRule({
14508
14947
  }
14509
14948
  const variable = obj?.type === AST_NODE_TYPES63.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
14510
14949
  if (variable !== null && obj?.type === AST_NODE_TYPES63.Identifier) {
14950
+ if (namedGuards.get(variable)?.some((call) => guardDominates(node, call))) return;
14511
14951
  if (isUseWithinValidatedBranch(node, obj.name)) {
14512
14952
  return;
14513
14953
  }
@@ -14527,13 +14967,13 @@ var prefer_schema_for_api_payload_default = createRule({
14527
14967
  });
14528
14968
 
14529
14969
  // src/rules/prefer-shared-zod-enum.ts
14530
- 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";
14531
14971
  var PREFER_SHARED_ZOD_ENUM_DOCUMENTATION = {
14532
14972
  summary: "Give literal Zod enum domains one reusable module-level schema.",
14533
14973
  rationale: "Inline or repeated literal domains hide a reusable contract and allow equivalent fields to drift independently.",
14534
14974
  remediation: "Declare a module-level named Zod enum schema and reuse it at each field or contract site.",
14535
14975
  category: "maintainability",
14536
- 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."],
14537
14977
  examples: [
14538
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 },
14539
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 }
@@ -14575,16 +15015,22 @@ var prefer_shared_zod_enum_default = createRule({
14575
15015
  create(context) {
14576
15016
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
14577
15017
  const zodBindings = /* @__PURE__ */ new Set();
15018
+ const bindingOf = (node) => ASTUtils36.findVariable(context.sourceCode.getScope(node), node.name);
14578
15019
  const seen = /* @__PURE__ */ new Set();
14579
15020
  return {
14580
15021
  ImportDeclaration(node) {
14581
15022
  if (!isZodModule(node.source.value)) return;
14582
15023
  for (const specifier of node.specifiers) {
14583
- 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
+ }
14584
15028
  }
14585
15029
  },
14586
15030
  CallExpression(node) {
14587
- 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;
14588
15034
  const domain = literalDomain(node);
14589
15035
  if (domain === null) return;
14590
15036
  const key = JSON.stringify(domain);
@@ -15211,7 +15657,7 @@ var prefer_semantic_colors_default = createRule({
15211
15657
  });
15212
15658
 
15213
15659
  // src/rules/prefer-server-actions.ts
15214
- import { ASTUtils as ASTUtils28 } from "@typescript-eslint/utils";
15660
+ import { ASTUtils as ASTUtils37 } from "@typescript-eslint/utils";
15215
15661
  var PREFER_SERVER_ACTIONS_DOCUMENTATION = {
15216
15662
  summary: "Prefer Next.js Server Actions over same-origin API mutations.",
15217
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.",
@@ -15243,7 +15689,7 @@ function resolvesToGlobalFetch(context, identifier) {
15243
15689
  function resolveNode(node, context) {
15244
15690
  if (!node) return null;
15245
15691
  if (node.type !== "Identifier") return node;
15246
- const variable = ASTUtils28.findVariable(getScope(context, node), node.name);
15692
+ const variable = ASTUtils37.findVariable(getScope(context, node), node.name);
15247
15693
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15248
15694
  if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return node;
15249
15695
  if (definition.node.init.type === "ObjectExpression" && variable?.references.some((reference) => reference.identifier !== node && reference.init !== true)) return node;
@@ -15252,7 +15698,7 @@ function resolveNode(node, context) {
15252
15698
  function isAxiosClient(node, context, seen = /* @__PURE__ */ new Set()) {
15253
15699
  if (node.type !== "Identifier" || seen.has(node)) return false;
15254
15700
  seen.add(node);
15255
- const variable = ASTUtils28.findVariable(getScope(context, node), node.name);
15701
+ const variable = ASTUtils37.findVariable(getScope(context, node), node.name);
15256
15702
  const definition = variable?.defs.length === 1 ? variable.defs[0] : void 0;
15257
15703
  if (definition === void 0 || variable?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
15258
15704
  if (variable?.references.some((reference) => {
@@ -15455,7 +15901,7 @@ var prefer_server_actions_default = createRule({
15455
15901
  });
15456
15902
 
15457
15903
  // src/rules/prefer-whole-object-assertion.ts
15458
- 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";
15459
15905
  var MERGEABLE_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
15460
15906
  var SYNTHETIC_LITERAL_MATCHERS = /* @__PURE__ */ new Map([
15461
15907
  ["toBeNull", "null"],
@@ -15562,6 +16008,13 @@ var prefer_whole_object_assertion_default = createRule({
15562
16008
  return null;
15563
16009
  }
15564
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;
15565
16018
  if (actual === void 0 || actual.type !== AST_NODE_TYPES67.MemberExpression || actual.optional) {
15566
16019
  return null;
15567
16020
  }
@@ -15721,14 +16174,14 @@ var prefer_whole_object_assertion_default = createRule({
15721
16174
  });
15722
16175
 
15723
16176
  // src/rules/repeated-static-call-cases.ts
15724
- import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils29 } from "@typescript-eslint/utils";
16177
+ import { AST_NODE_TYPES as AST_NODE_TYPES68, ASTUtils as ASTUtils39 } from "@typescript-eslint/utils";
15725
16178
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
15726
- summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
15727
- rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
15728
- 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.",
15729
16182
  category: "testing",
15730
16183
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
15731
- 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."],
15732
16185
  examples: [
15733
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 },
15734
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 }
@@ -15747,7 +16200,7 @@ function staticMemberName5(node) {
15747
16200
  return null;
15748
16201
  }
15749
16202
  function importedName6(identifier, context, modules) {
15750
- const variable = ASTUtils29.findVariable(context.sourceCode.getScope(identifier), identifier.name);
16203
+ const variable = ASTUtils39.findVariable(context.sourceCode.getScope(identifier), identifier.name);
15751
16204
  if (variable === null || variable.defs.length === 0) return identifier.name;
15752
16205
  for (const definition of variable.defs) {
15753
16206
  if (definition.node.type !== AST_NODE_TYPES68.ImportSpecifier) continue;
@@ -15809,7 +16262,7 @@ function staticShape(node) {
15809
16262
  return "dynamic";
15810
16263
  }
15811
16264
  }
15812
- function assertionShape(statement, context) {
16265
+ function assertionShape(statement, context, callback) {
15813
16266
  if (statement.type !== AST_NODE_TYPES68.ExpressionStatement || statement.expression.type !== AST_NODE_TYPES68.CallExpression) return null;
15814
16267
  const matcherCall = statement.expression;
15815
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;
@@ -15821,6 +16274,10 @@ function assertionShape(statement, context) {
15821
16274
  const expected = matcherCall.arguments[0];
15822
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;
15823
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;
15824
16281
  const values = [...observed.arguments, expected].map((item) => context.sourceCode.getText(item)).join("\0");
15825
16282
  return { statement, skeleton, values };
15826
16283
  }
@@ -15840,9 +16297,9 @@ var repeated_static_call_cases_default = createRule({
15840
16297
  documentation: REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
15841
16298
  meta: {
15842
16299
  type: "suggestion",
15843
- 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 },
15844
16301
  schema: [],
15845
- 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." }
15846
16303
  },
15847
16304
  defaultOptions: [],
15848
16305
  create(context) {
@@ -15877,7 +16334,7 @@ var repeated_static_call_cases_default = createRule({
15877
16334
  run = [];
15878
16335
  };
15879
16336
  for (const statement of node.body.body) {
15880
- const shape = assertionShape(statement, context);
16337
+ const shape = assertionShape(statement, context, node);
15881
16338
  if (shape === null || run.length > 0 && run[0]?.skeleton !== shape.skeleton) flush();
15882
16339
  if (shape !== null) run.push(shape);
15883
16340
  }
@@ -16757,7 +17214,7 @@ var require_assert_never_default = createRule({
16757
17214
  });
16758
17215
 
16759
17216
  // src/rules/require-fetch-timeout.ts
16760
- import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils30 } from "@typescript-eslint/utils";
17217
+ import { AST_NODE_TYPES as AST_NODE_TYPES71, ASTUtils as ASTUtils40 } from "@typescript-eslint/utils";
16761
17218
  var REQUIRE_FETCH_TIMEOUT_DOCUMENTATION = {
16762
17219
  summary: "Require an explicit abort signal on locally analyzable global fetch calls.",
16763
17220
  rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
@@ -16839,7 +17296,7 @@ var require_fetch_timeout_default = createRule({
16839
17296
  }
16840
17297
  function resolvesToGlobal(identifier) {
16841
17298
  const scope = context.sourceCode.getScope(identifier);
16842
- const variable = ASTUtils30.findVariable(scope, identifier.name);
17299
+ const variable = ASTUtils40.findVariable(scope, identifier.name);
16843
17300
  return variable === null || variable.defs.length === 0;
16844
17301
  }
16845
17302
  function isGlobalFetchCall2(callee) {
@@ -16849,7 +17306,7 @@ var require_fetch_timeout_default = createRule({
16849
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);
16850
17307
  }
16851
17308
  function localConstInitProvablyLacksSignal(identifier) {
16852
- const variable = ASTUtils30.findVariable(
17309
+ const variable = ASTUtils40.findVariable(
16853
17310
  context.sourceCode.getScope(identifier),
16854
17311
  identifier.name
16855
17312
  );
@@ -16871,7 +17328,7 @@ var require_fetch_timeout_default = createRule({
16871
17328
  function isForwardedRequest(argument) {
16872
17329
  let value = argument;
16873
17330
  if (value.type === AST_NODE_TYPES71.Identifier) {
16874
- const binding = ASTUtils30.findVariable(context.sourceCode.getScope(value), value.name);
17331
+ const binding = ASTUtils40.findVariable(context.sourceCode.getScope(value), value.name);
16875
17332
  const definition = binding?.defs.length === 1 ? binding.defs[0] : void 0;
16876
17333
  if (definition?.type !== "Variable" || definition.parent.kind !== "const" || definition.node.init === null || binding?.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
16877
17334
  value = definition.node.init;
@@ -16899,10 +17356,12 @@ var require_fetch_timeout_default = createRule({
16899
17356
  import { AST_NODE_TYPES as AST_NODE_TYPES72 } from "@typescript-eslint/utils";
16900
17357
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
16901
17358
  summary: "Require exported concrete classes with public behavior to declare a contract.",
16902
- 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.",
16903
17360
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
16904
17361
  category: "architecture",
16905
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.",
16906
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.",
16907
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.",
16908
17367
  "Static factories and data-only classes without public instance methods are outside the contract requirement."
@@ -16974,7 +17433,7 @@ var require_interface_for_exported_class_default = createRule({
16974
17433
  },
16975
17434
  defaultOptions: [],
16976
17435
  create(context) {
16977
- 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 {};
16978
17437
  return {
16979
17438
  "Program:exit"(program) {
16980
17439
  const classes = /* @__PURE__ */ new Map();
@@ -17581,7 +18040,7 @@ var require_port_for_service_default = createRule({
17581
18040
  });
17582
18041
 
17583
18042
  // src/rules/require-sql-access-class.ts
17584
- 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";
17585
18044
  var DIRECT_EXECUTION_METHODS = /* @__PURE__ */ new Set([
17586
18045
  "all",
17587
18046
  "batch",
@@ -17635,12 +18094,13 @@ var MODEL_EXECUTION_METHODS = /* @__PURE__ */ new Set([
17635
18094
  var DATABASE_NAMES = /^(?:db|database|connection|pool|prisma|query|transaction|tx)$/iu;
17636
18095
  var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
17637
18096
  summary: "Keep SQL reads and writes inside a class that receives its database dependency.",
17638
- 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.",
17639
18098
  remediation: "Move the query into a repository or store class and inject the pool, connection, transaction, or typed database binding through its constructor.",
17640
18099
  category: "architecture",
17641
18100
  limitations: [
17642
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.",
17643
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.",
17644
18104
  "Constructor injection inherited from a base class or transformed through a wrapper is not inferred by this syntax-only rule."
17645
18105
  ],
17646
18106
  examples: [
@@ -17846,11 +18306,23 @@ var require_sql_access_class_default = createRule({
17846
18306
  create(context) {
17847
18307
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
17848
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
+ }
17849
18320
  return {
17850
18321
  CallExpression(node) {
17851
18322
  if (node.callee.type !== AST_NODE_TYPES74.MemberExpression)
17852
18323
  return;
17853
18324
  const method = memberName6(node.callee);
18325
+ if (knownNonDatabase(node.callee.object)) return;
17854
18326
  if (method === null || !isDatabaseOperation(method, node.callee.object))
17855
18327
  return;
17856
18328
  const owner = owningClass2(node);
@@ -17871,6 +18343,8 @@ var REQUIRE_STATIC_NEXT_MATCHER_DOCUMENTATION = {
17871
18343
  rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
17872
18344
  remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
17873
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"],
17874
18348
  examples: [
17875
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 },
17876
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 }
@@ -17918,7 +18392,7 @@ var require_static_next_matcher_default = createRule({
17918
18392
  },
17919
18393
  schema: [],
17920
18394
  messages: {
17921
- 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."
17922
18396
  }
17923
18397
  },
17924
18398
  defaultOptions: [],
@@ -17954,7 +18428,7 @@ var require_static_next_matcher_default = createRule({
17954
18428
  });
17955
18429
 
17956
18430
  // src/rules/require-use-form-default-values.ts
17957
- import { ASTUtils as ASTUtils31 } from "@typescript-eslint/utils";
18431
+ import { ASTUtils as ASTUtils42 } from "@typescript-eslint/utils";
17958
18432
  var REQUIRE_USE_FORM_DEFAULT_VALUES_DOCUMENTATION = {
17959
18433
  summary: "react-hook-form useForm call without explicit initial or reactive values",
17960
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.",
@@ -18008,13 +18482,13 @@ var require_use_form_default_values_default = createRule({
18008
18482
  if (node.source.value !== "react-hook-form") return;
18009
18483
  for (const specifier of node.specifiers) {
18010
18484
  if (specifier.type !== "ImportSpecifier" || (specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value) !== "useForm") continue;
18011
- const variable = ASTUtils31.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
18485
+ const variable = ASTUtils42.findVariable(context.sourceCode.getScope(specifier.local), specifier.local.name);
18012
18486
  if (variable) importedHooks.add(variable);
18013
18487
  }
18014
18488
  },
18015
18489
  CallExpression(node) {
18016
18490
  if (node.callee.type !== "Identifier") return;
18017
- const variable = ASTUtils31.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
18491
+ const variable = ASTUtils42.findVariable(context.sourceCode.getScope(node.callee), node.callee.name);
18018
18492
  const options = node.arguments[0];
18019
18493
  if (!variable || !importedHooks.has(variable) || options !== void 0 && options.type !== "ObjectExpression" || hasInitializationOrUnknownOptions(options)) return;
18020
18494
  context.report({ node, messageId: "requireUseFormDefaultValues" });
@@ -18093,7 +18567,7 @@ var require_use_server_in_actions_file_default = createRule({
18093
18567
  // src/rules/require-zod-form-validation.ts
18094
18568
  import {
18095
18569
  AST_NODE_TYPES as AST_NODE_TYPES76,
18096
- ASTUtils as ASTUtils32
18570
+ ASTUtils as ASTUtils43
18097
18571
  } from "@typescript-eslint/utils";
18098
18572
  var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
18099
18573
  summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
@@ -18102,7 +18576,8 @@ var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
18102
18576
  category: "security",
18103
18577
  limitations: [
18104
18578
  "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
18105
- "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."
18106
18581
  ],
18107
18582
  examples: [
18108
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 },
@@ -18161,7 +18636,7 @@ var require_zod_form_validation_default = createRule({
18161
18636
  return {};
18162
18637
  }
18163
18638
  const zodBindings = /* @__PURE__ */ new Set();
18164
- const resolvedBinding = (identifier) => ASTUtils32.findVariable(
18639
+ const resolvedBinding = (identifier) => ASTUtils43.findVariable(
18165
18640
  context.sourceCode.getScope(identifier),
18166
18641
  identifier.name
18167
18642
  );
@@ -18217,6 +18692,15 @@ var require_zod_form_validation_default = createRule({
18217
18692
  let parent = node.parent;
18218
18693
  while (parent !== null && parent !== void 0) {
18219
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;
18220
18704
  parent = parent.parent;
18221
18705
  }
18222
18706
  return null;
@@ -18396,13 +18880,14 @@ var require_zod_form_validation_default = createRule({
18396
18880
  // src/rules/store-insert-requires-on-conflict.ts
18397
18881
  import "@typescript-eslint/utils";
18398
18882
  var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
18399
- summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
18400
- rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
18401
- 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.",
18402
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."],
18403
18888
  examples: [
18404
- { 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 },
18405
- { 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 }
18406
18891
  ]
18407
18892
  };
18408
18893
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
@@ -18414,14 +18899,18 @@ function owningCallableName(node) {
18414
18899
  return current.id?.name ?? null;
18415
18900
  }
18416
18901
  if (current.type === "MethodDefinition") {
18417
- return current.key.type === "Identifier" ? current.key.name : null;
18902
+ return !current.computed && current.key.type === "Identifier" ? current.key.name : null;
18418
18903
  }
18419
18904
  if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
18420
18905
  return current.parent.id.name;
18421
18906
  }
18422
- 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") {
18423
18908
  return current.parent.key.name;
18424
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
+ }
18425
18914
  }
18426
18915
  return null;
18427
18916
  }
@@ -18432,11 +18921,11 @@ var store_insert_requires_on_conflict_default = createRule({
18432
18921
  meta: {
18433
18922
  type: "problem",
18434
18923
  docs: {
18435
- description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
18924
+ description: "Review conflict handling for embedded inserts in replay-named callables."
18436
18925
  },
18437
18926
  schema: [],
18438
18927
  messages: {
18439
- 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."
18440
18929
  }
18441
18930
  },
18442
18931
  defaultOptions: [],
@@ -18449,7 +18938,7 @@ var store_insert_requires_on_conflict_default = createRule({
18449
18938
  return;
18450
18939
  }
18451
18940
  const owner = owningCallableName(node);
18452
- if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
18941
+ if (owner === null || !REPLAY_CONTRACT_NAME.test(owner)) {
18453
18942
  return;
18454
18943
  }
18455
18944
  context.report({ node, messageId: "storeInsertRequiresOnConflict" });
@@ -18458,19 +18947,17 @@ var store_insert_requires_on_conflict_default = createRule({
18458
18947
  });
18459
18948
 
18460
18949
  // src/rules/stepdown.ts
18461
- import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils33 } from "@typescript-eslint/utils";
18950
+ import { AST_NODE_TYPES as AST_NODE_TYPES77, ASTUtils as ASTUtils44 } from "@typescript-eslint/utils";
18462
18951
  var STEPDOWN_DOCUMENTATION = {
18463
18952
  summary: "Place a private helper below its sole direct same-scope caller.",
18464
18953
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
18465
- 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.",
18466
18955
  category: "maintainability",
18467
- autofix: "safe",
18468
18956
  limitations: [
18469
18957
  "Generated and test files, cycles, dynamic references, overload targets, and helpers with multiple callers are excluded.",
18470
- "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.",
18471
18959
  "Runtime class-field, static-block, computed-member, and decorator barriers are never crossed.",
18472
- "Autofix is limited to comment-free class-member boundaries; module functions and ambiguous comment ownership remain report-only.",
18473
- "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."
18474
18961
  ],
18475
18962
  examples: [
18476
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 },
@@ -18480,7 +18967,7 @@ var STEPDOWN_DOCUMENTATION = {
18480
18967
  function isFunction(node) {
18481
18968
  return node.type === AST_NODE_TYPES77.ArrowFunctionExpression || node.type === AST_NODE_TYPES77.FunctionDeclaration || node.type === AST_NODE_TYPES77.FunctionExpression;
18482
18969
  }
18483
- function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true, makeFix) {
18970
+ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true) {
18484
18971
  const byName = new Map(scopeDefinitions.map((definition) => [definition.name, definition]));
18485
18972
  const cycles = cycleComponents(calls);
18486
18973
  const callers = /* @__PURE__ */ new Map();
@@ -18499,12 +18986,10 @@ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned,
18499
18986
  if (callerName2 === void 0 || cycles.has(helper.name) && cycles.get(helper.name) === cycles.get(callerName2)) continue;
18500
18987
  const caller = byName.get(callerName2);
18501
18988
  if (caller === void 0 || helper.node.range[0] >= caller.node.range[0] || !canMove(helper, caller)) continue;
18502
- const fix = makeFix?.(helper, caller);
18503
18989
  context.report({
18504
18990
  node: helper.node,
18505
18991
  messageId: "helperAboveOnlyCaller",
18506
- data: { helper: helper.name, caller: callerName2 },
18507
- ...fix === void 0 ? {} : { fix }
18992
+ data: { helper: helper.name, caller: callerName2 }
18508
18993
  });
18509
18994
  }
18510
18995
  }
@@ -18663,7 +19148,7 @@ function methodName(node) {
18663
19148
  return !node.computed && node.key.type === AST_NODE_TYPES77.Identifier ? node.key.name : null;
18664
19149
  }
18665
19150
  function referencedMethod(context, node, classVariables) {
18666
- const objectVariable = node.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils33.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;
18667
19152
  const isClassReference = objectVariable !== null && classVariables.has(objectVariable);
18668
19153
  if (node.object.type !== AST_NODE_TYPES77.ThisExpression && !isClassReference) return null;
18669
19154
  if (node.property.type === AST_NODE_TYPES77.PrivateIdentifier) return `#${node.property.name}`;
@@ -18716,11 +19201,11 @@ function classScope(context, node, computedReferenceNames) {
18716
19201
  const pinned = /* @__PURE__ */ new Set();
18717
19202
  const classVariables = /* @__PURE__ */ new Set();
18718
19203
  if (node.id !== null) {
18719
- const internal = ASTUtils33.findVariable(context.sourceCode.getScope(node), node.id.name);
19204
+ const internal = ASTUtils44.findVariable(context.sourceCode.getScope(node), node.id.name);
18720
19205
  if (internal !== null) classVariables.add(internal);
18721
19206
  }
18722
19207
  if (node.type === AST_NODE_TYPES77.ClassExpression && node.parent.type === AST_NODE_TYPES77.VariableDeclarator && node.parent.id.type === AST_NODE_TYPES77.Identifier) {
18723
- const outer = ASTUtils33.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
19208
+ const outer = ASTUtils44.findVariable(context.sourceCode.getScope(node.parent), node.parent.id.name);
18724
19209
  if (outer !== null) classVariables.add(outer);
18725
19210
  }
18726
19211
  for (const method of methods) {
@@ -18756,7 +19241,7 @@ function classScope(context, node, computedReferenceNames) {
18756
19241
  return;
18757
19242
  }
18758
19243
  if (binding.type !== AST_NODE_TYPES77.Identifier) return;
18759
- const variable = ASTUtils33.findVariable(context.sourceCode.getScope(binding), binding.name);
19244
+ const variable = ASTUtils44.findVariable(context.sourceCode.getScope(binding), binding.name);
18760
19245
  if (variable !== null) {
18761
19246
  methodClassVariables.add(variable);
18762
19247
  methodAliases.add(variable);
@@ -18786,7 +19271,7 @@ function classScope(context, node, computedReferenceNames) {
18786
19271
  return;
18787
19272
  }
18788
19273
  if (!privateNames.has(target)) return;
18789
- const objectVariable = current.object.type === AST_NODE_TYPES77.Identifier ? ASTUtils33.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;
18790
19275
  if (objectVariable !== null && methodAliases.has(objectVariable)) {
18791
19276
  pinned.add(target);
18792
19277
  return;
@@ -18831,37 +19316,7 @@ function classScope(context, node, computedReferenceNames) {
18831
19316
  if (helperIndex === void 0 || callerIndex === void 0) return false;
18832
19317
  return runtimeBarrierPrefix[callerIndex + 1] === runtimeBarrierPrefix[helperIndex + 1];
18833
19318
  };
18834
- const incoming = /* @__PURE__ */ new Map();
18835
- for (const [caller, callees] of calls) {
18836
- for (const callee of callees) {
18837
- if (callee === caller) continue;
18838
- const callers = incoming.get(callee) ?? /* @__PURE__ */ new Set();
18839
- callers.add(caller);
18840
- incoming.set(callee, callers);
18841
- }
18842
- }
18843
- reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove, (helper, caller) => {
18844
- if (!canMove(helper, caller)) return void 0;
18845
- const helperCallsAnother = [...calls.get(helper.name) ?? []].some((callee) => callee !== helper.name);
18846
- const callerIsAnotherHelper = [...incoming.get(caller.name) ?? []].some((name) => name !== helper.name);
18847
- if (helperCallsAnother || callerIsAnotherHelper) return void 0;
18848
- const helperMember = helper.node;
18849
- const callerMember = caller.node;
18850
- const helperIndex = memberIndexes.get(helperMember);
18851
- if (helperIndex === void 0) return void 0;
18852
- const next = node.body.body[helperIndex + 1];
18853
- const suffixEnd = next?.range[0] ?? node.body.range[1] - 1;
18854
- const suffix = context.sourceCode.text.slice(helperMember.range[1], suffixEnd);
18855
- if (!/^\s*$/u.test(suffix)) return void 0;
18856
- const previous = node.body.body[helperIndex - 1];
18857
- const prefixStart = previous?.range[1] ?? node.body.range[0] + 1;
18858
- if (!/^\s*$/u.test(context.sourceCode.text.slice(prefixStart, helperMember.range[0]))) return void 0;
18859
- const helperText = context.sourceCode.getText(helperMember);
18860
- return (fixer) => [
18861
- fixer.removeRange([helperMember.range[0], suffixEnd]),
18862
- fixer.insertTextAfter(callerMember, `${suffix}${helperText}`)
18863
- ];
18864
- });
19319
+ reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove);
18865
19320
  }
18866
19321
  function isClassRuntimeBarrier(member) {
18867
19322
  switch (member.type) {
@@ -18883,7 +19338,6 @@ var stepdown_default = createRule({
18883
19338
  type: "suggestion",
18884
19339
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
18885
19340
  schema: [],
18886
- fixable: "code",
18887
19341
  messages: {
18888
19342
  helperAboveOnlyCaller: "Private helper `{{helper}}` is defined above its only caller `{{caller}}`; move it below the caller."
18889
19343
  }
@@ -18912,7 +19366,7 @@ var stepdown_default = createRule({
18912
19366
  });
18913
19367
 
18914
19368
  // src/rules/source-coupled-test.ts
18915
- 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";
18916
19370
  var GENERAL_SOURCE_SUFFIX_RE = /\.(?:bash|sh|ya?ml|jsonc|py|[cm]?[jt]s)$/iu;
18917
19371
  var FS_MODULES = /* @__PURE__ */ new Set(["fs", "node:fs", "fs/promises", "node:fs/promises"]);
18918
19372
  var FS_READERS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
@@ -18956,7 +19410,7 @@ var SOURCE_COUPLED_TEST_DOCUMENTATION = {
18956
19410
  remediation: "Parse the artifact, execute its validator, or assert on another runtime contract.",
18957
19411
  category: "testing",
18958
19412
  limitations: [
18959
- "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.",
18960
19414
  "When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason."
18961
19415
  ],
18962
19416
  examples: [
@@ -18995,6 +19449,11 @@ function stringValue(node) {
18995
19449
  const current = unwrap7(node);
18996
19450
  if (current.type === AST_NODE_TYPES78.Literal && typeof current.value === "string") return current.value;
18997
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
+ }
18998
19457
  return null;
18999
19458
  }
19000
19459
  function importSource(node) {
@@ -19024,14 +19483,19 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19024
19483
  const scopes = [newScope()];
19025
19484
  const reportedOrigins = /* @__PURE__ */ new Set();
19026
19485
  const currentScope = () => scopes.at(-1) ?? scopes[0];
19027
- 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;
19028
19490
  for (let index = scopes.length - 1; index >= 0; index--) {
19029
19491
  const scope = scopes[index];
19030
19492
  if (scope.declared.has(name2)) return scope[kind].has(name2);
19031
19493
  }
19032
19494
  return false;
19033
19495
  };
19034
- 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();
19035
19499
  for (let index = scopes.length - 1; index >= 0; index--) {
19036
19500
  const scope = scopes[index];
19037
19501
  if (scope.declared.has(name2)) return scope.rawOrigins.get(name2) ?? /* @__PURE__ */ new Set();
@@ -19042,15 +19506,14 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19042
19506
  const current = unwrap7(node);
19043
19507
  const value = stringValue(current);
19044
19508
  if (value !== null) return sourceSuffixRe.test(value);
19045
- if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current.name);
19046
- if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") {
19047
- return sourcePath(current.left) || sourcePath(current.right);
19048
- }
19049
- if (current.type === AST_NODE_TYPES78.TemplateLiteral) return current.expressions.some(sourcePath);
19509
+ if (current.type === AST_NODE_TYPES78.Identifier) return visible("paths", current);
19050
19510
  if (current.type === AST_NODE_TYPES78.CallExpression || current.type === AST_NODE_TYPES78.NewExpression) {
19051
- 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);
19052
19516
  }
19053
- if (current.type === AST_NODE_TYPES78.MemberExpression) return sourcePath(current.object);
19054
19517
  return false;
19055
19518
  };
19056
19519
  const rawRead = (node) => {
@@ -19058,16 +19521,16 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19058
19521
  if (current.type !== AST_NODE_TYPES78.CallExpression || current.arguments.length === 0) return false;
19059
19522
  const callee = unwrap7(current.callee);
19060
19523
  if (callee.type === AST_NODE_TYPES78.Identifier) {
19061
- return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
19524
+ return visible("fsReaders", callee) && sourcePath(current.arguments[0]);
19062
19525
  }
19063
19526
  if (callee.type !== AST_NODE_TYPES78.MemberExpression) return false;
19064
19527
  const name2 = staticMemberName7(callee);
19065
19528
  const object = unwrap7(callee.object);
19066
- 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]);
19067
19530
  };
19068
19531
  const rawOrigins = (node) => {
19069
19532
  const current = unwrap7(node);
19070
- if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current.name);
19533
+ if (current.type === AST_NODE_TYPES78.Identifier) return visibleRawOrigins(current);
19071
19534
  if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
19072
19535
  if (current.type === AST_NODE_TYPES78.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
19073
19536
  if (current.type === AST_NODE_TYPES78.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
@@ -19108,14 +19571,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19108
19571
  if (receiver.type !== AST_NODE_TYPES78.Identifier || receiver.name !== "assert" || !ASSERT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
19109
19572
  return new Set(node.arguments.flatMap((argument) => argument.type === AST_NODE_TYPES78.SpreadElement ? [] : [...evidenceOrigins(argument)]));
19110
19573
  };
19111
- const rawRegexExtractionOrigins = (node) => {
19112
- const callee = unwrap7(node.callee);
19113
- if (callee.type !== AST_NODE_TYPES78.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
19114
- const argument = node.arguments[0];
19115
- if (argument?.type !== AST_NODE_TYPES78.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
19116
- return rawOrigins(callee.object);
19117
- };
19118
- const declare = (name2, state) => {
19574
+ const declare = (node, state) => {
19575
+ const name2 = bindingOf(node);
19576
+ if (name2 === null) return;
19119
19577
  const scope = currentScope();
19120
19578
  scope.declared.add(name2);
19121
19579
  scope.collections.delete(name2);
@@ -19135,18 +19593,8 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19135
19593
  const current = unwrap7(node);
19136
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));
19137
19595
  };
19138
- const declaredNames2 = (node) => {
19139
- const current = unwrap7(node);
19140
- if (current.type === AST_NODE_TYPES78.Identifier) return [current.name];
19141
- if (current.type === AST_NODE_TYPES78.AssignmentPattern) return declaredNames2(current.left);
19142
- if (current.type === AST_NODE_TYPES78.RestElement) return declaredNames2(current.argument);
19143
- if (current.type === AST_NODE_TYPES78.ArrayPattern) return current.elements.flatMap((element) => element === null ? [] : declaredNames2(element));
19144
- if (current.type === AST_NODE_TYPES78.ObjectPattern) return current.properties.flatMap((property) => property.type === AST_NODE_TYPES78.RestElement ? declaredNames2(property.argument) : declaredNames2(property.value));
19145
- return [];
19146
- };
19147
- const enterFunction = (node) => {
19596
+ const enterFunction = () => {
19148
19597
  scopes.push(newScope());
19149
- for (const parameter of node.params) for (const name2 of declaredNames2(parameter)) declare(name2, {});
19150
19598
  };
19151
19599
  const exitFunction = () => {
19152
19600
  scopes.pop();
@@ -19158,9 +19606,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19158
19606
  for (const specifier of node.specifiers) {
19159
19607
  if (specifier.type === AST_NODE_TYPES78.ImportSpecifier) {
19160
19608
  const imported = specifier.imported.type === AST_NODE_TYPES78.Identifier ? specifier.imported.name : String(specifier.imported.value);
19161
- if (FS_READERS.has(imported)) declare(specifier.local.name, { fsReader: true });
19609
+ if (FS_READERS.has(imported)) declare(specifier.local, { fsReader: true });
19162
19610
  } else {
19163
- declare(specifier.local.name, { fsObject: true });
19611
+ declare(specifier.local, { fsObject: true });
19164
19612
  }
19165
19613
  }
19166
19614
  },
@@ -19169,35 +19617,34 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19169
19617
  VariableDeclarator(node) {
19170
19618
  if (node.init === null) return;
19171
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;
19172
19622
  if (required !== null && FS_MODULES.has(required) && node.id.type === AST_NODE_TYPES78.Identifier) {
19173
- declare(node.id.name, { fsObject: true });
19623
+ declare(node.id, { fsObject: true });
19174
19624
  return;
19175
19625
  }
19176
19626
  if (node.id.type === AST_NODE_TYPES78.ObjectPattern && required !== null && FS_MODULES.has(required)) {
19177
19627
  for (const property of node.id.properties) {
19178
19628
  if (property.type !== AST_NODE_TYPES78.Property || property.value.type !== AST_NODE_TYPES78.Identifier) continue;
19179
19629
  const key = property.key.type === AST_NODE_TYPES78.Identifier ? property.key.name : property.key.type === AST_NODE_TYPES78.Literal ? String(property.key.value) : "";
19180
- if (FS_READERS.has(key)) declare(property.value.name, { fsReader: true });
19630
+ if (FS_READERS.has(key)) declare(property.value, { fsReader: true });
19181
19631
  }
19182
19632
  return;
19183
19633
  }
19184
19634
  if (node.id.type !== AST_NODE_TYPES78.Identifier) return;
19185
- 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) });
19186
19636
  },
19187
19637
  AssignmentExpression(node) {
19188
- 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, {});
19189
19639
  },
19190
19640
  ForOfStatement(node) {
19191
19641
  const right = unwrap7(node.right);
19192
- const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right.name);
19642
+ const collection = right.type === AST_NODE_TYPES78.Identifier && visible("collections", right);
19193
19643
  const left = node.left.type === AST_NODE_TYPES78.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
19194
- 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 });
19195
19645
  },
19196
19646
  CallExpression(node) {
19197
- const origins = /* @__PURE__ */ new Set([
19198
- ...rawAssertionOrigins(node),
19199
- ...rawRegexExtractionOrigins(node)
19200
- ]);
19647
+ const origins = rawAssertionOrigins(node);
19201
19648
  if (origins.size === 0 || [...origins].every((origin) => reportedOrigins.has(origin))) return;
19202
19649
  for (const origin of origins) reportedOrigins.add(origin);
19203
19650
  context.report({ node, messageId: "rawSourceOracle" });
@@ -19340,8 +19787,8 @@ var IAC_SOURCE_COUPLED_TEST_DOCUMENTATION = {
19340
19787
  remediation: "Parse rendered plan JSON, query the provider, or exercise the deployed runtime contract.",
19341
19788
  category: "testing",
19342
19789
  limitations: [
19343
- "The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
19344
- "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."
19345
19792
  ],
19346
19793
  examples: [
19347
19794
  {
@@ -19373,7 +19820,7 @@ var iac_source_coupled_test_default = createSourceCoupledRule(
19373
19820
  // src/rules/require-pascal-case-zod-schema-name.ts
19374
19821
  import {
19375
19822
  AST_NODE_TYPES as AST_NODE_TYPES80,
19376
- ASTUtils as ASTUtils34
19823
+ ASTUtils as ASTUtils46
19377
19824
  } from "@typescript-eslint/utils";
19378
19825
  var REQUIRE_PASCAL_CASE_ZOD_SCHEMA_NAME_DOCUMENTATION = {
19379
19826
  summary: "Require confirmed module-level Zod schema contracts to use PascalCase with a `Schema` suffix.",
@@ -19574,7 +20021,7 @@ var require_pascal_case_zod_schema_name_default = createRule({
19574
20021
  const zodBindings = /* @__PURE__ */ new Set();
19575
20022
  const schemaBindings = /* @__PURE__ */ new Set();
19576
20023
  function resolvedBinding(identifier) {
19577
- return ASTUtils34.findVariable(
20024
+ return ASTUtils46.findVariable(
19578
20025
  context.sourceCode.getScope(identifier),
19579
20026
  identifier.name
19580
20027
  );
@@ -19819,7 +20266,7 @@ var RULES = {
19819
20266
  };
19820
20267
  var meta = {
19821
20268
  name: "@sarj/eslint-plugin",
19822
- version: "15.17.10"
20269
+ version: "15.17.11"
19823
20270
  };
19824
20271
  var APPLICATION_ONLY_RULES = [];
19825
20272
  var LIBRARY_IMPORT_POLICY = ["error", {