@sarj/eslint-plugin 15.17.9 → 15.17.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -678,10 +678,11 @@ var TEST_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test",
678
678
  var DUPLICATE_TEST_BODY_DOCUMENTATION = {
679
679
  summary: "Disallow substantial sibling tests with the same body shape; express their differing inputs as a parameterized case table.",
680
680
  rationale: "Copy-pasted test bodies hide the cases that differ and allow equivalent assertions to drift independently.",
681
- remediation: "Move the varying inputs and expected values into a case table consumed by `test.each(...)` or `it.each(...)`.",
681
+ 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.",
682
682
  category: "testing",
683
683
  limitations: [
684
- "The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments."
684
+ "The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments.",
685
+ "Matching normalized body shapes do not prove runtime equivalence or independent setup; parameterization is a manual review, not an automatic deletion."
685
686
  ],
686
687
  examples: [
687
688
  {
@@ -866,6 +867,10 @@ function isDuplicateTestFrameworkIdentifier(identifier, sourceCode) {
866
867
  const variable = import_utils3.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
867
868
  if (variable === null || variable.defs.length === 0) return true;
868
869
  return variable.defs.some((definition) => {
870
+ if (definition.node.type === import_utils3.AST_NODE_TYPES.ImportDefaultSpecifier) return definition.node.parent.source.value === "node:test";
871
+ if (definition.node.type !== import_utils3.AST_NODE_TYPES.ImportSpecifier) return false;
872
+ const imported = definition.node.imported;
873
+ if (!TEST_CALLERS.has(imported.type === import_utils3.AST_NODE_TYPES.Identifier ? imported.name : String(imported.value))) return false;
869
874
  let current = definition.node;
870
875
  while (current != null && current.type !== import_utils3.AST_NODE_TYPES.ImportDeclaration) current = current.parent;
871
876
  return current?.type === import_utils3.AST_NODE_TYPES.ImportDeclaration && typeof current.source.value === "string" && TEST_MODULES.has(current.source.value);
@@ -2454,7 +2459,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
2454
2459
  VariableDeclarator(node) {
2455
2460
  if (node.id.type !== import_utils10.AST_NODE_TYPES.Identifier) return;
2456
2461
  const variable = import_utils10.ASTUtils.findVariable(context.sourceCode.getScope(node.id), node.id.name);
2457
- if (variable === null) return;
2462
+ if (variable === null || variable.references.some((reference) => reference.isWrite() && !reference.init)) return;
2458
2463
  if (node.init?.type === import_utils10.AST_NODE_TYPES.ArrowFunctionExpression || node.init?.type === import_utils10.AST_NODE_TYPES.FunctionExpression) {
2459
2464
  functionCallbacks.set(node.init, variable);
2460
2465
  }
@@ -2465,7 +2470,7 @@ var no_duplicate_lifecycle_refresh_listeners_default = createRule({
2465
2470
  FunctionDeclaration(node) {
2466
2471
  if (node.id === null) return;
2467
2472
  const variable = import_utils10.ASTUtils.findVariable(context.sourceCode.getScope(node.id), node.id.name);
2468
- if (variable !== null) functionCallbacks.set(node, variable);
2473
+ if (variable !== null && !variable.references.some((reference) => reference.isWrite() && !reference.init)) functionCallbacks.set(node, variable);
2469
2474
  },
2470
2475
  CallExpression(node) {
2471
2476
  const item = registration(context.sourceCode, node);
@@ -2609,12 +2614,33 @@ var import_utils13 = require("@typescript-eslint/utils");
2609
2614
  // src/rules/_sql.ts
2610
2615
  var import_utils12 = require("@typescript-eslint/utils");
2611
2616
  function stripSqlNoise(text) {
2612
- const out = [...text];
2617
+ return scanSqlNoise(text);
2618
+ }
2619
+ function sqlSingleQuotedRanges(text) {
2620
+ const ranges = [];
2621
+ scanSqlNoise(text, (start, end) => ranges.push([start, end]));
2622
+ return ranges;
2623
+ }
2624
+ function scanSqlNoise(text, onSingleQuoted) {
2625
+ const out = text.split("");
2613
2626
  const n = text.length;
2614
2627
  let i = 0;
2615
2628
  while (i < n) {
2616
2629
  const ch = text[i];
2630
+ if (ch === "$" && !/[\w$]/u.test(text[i - 1] ?? "")) {
2631
+ const delimiter = /^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/u.exec(text.slice(i))?.[0];
2632
+ if (delimiter !== void 0) {
2633
+ const closing = text.indexOf(delimiter, i + delimiter.length);
2634
+ const end = closing < 0 ? n : closing + delimiter.length;
2635
+ while (i < end) {
2636
+ if (text[i] !== "\n") out[i] = " ";
2637
+ i += 1;
2638
+ }
2639
+ continue;
2640
+ }
2641
+ }
2617
2642
  if (ch === "'" || ch === '"') {
2643
+ const start = i;
2618
2644
  out[i] = " ";
2619
2645
  i += 1;
2620
2646
  while (i < n) {
@@ -2628,6 +2654,7 @@ function stripSqlNoise(text) {
2628
2654
  }
2629
2655
  out[i] = " ";
2630
2656
  i += 1;
2657
+ if (ch === "'") onSingleQuoted?.(start, i);
2631
2658
  break;
2632
2659
  }
2633
2660
  if (c !== "\n") {
@@ -2648,17 +2675,20 @@ function stripSqlNoise(text) {
2648
2675
  out[i] = " ";
2649
2676
  out[i + 1] = " ";
2650
2677
  i += 2;
2651
- while (i < n && !(text[i] === "*" && text[i + 1] === "/")) {
2678
+ let depth = 1;
2679
+ while (i < n && depth > 0) {
2680
+ if (text[i] === "/" && text[i + 1] === "*" || text[i] === "*" && text[i + 1] === "/") {
2681
+ depth += text[i] === "/" ? 1 : -1;
2682
+ out[i] = " ";
2683
+ out[i + 1] = " ";
2684
+ i += 2;
2685
+ continue;
2686
+ }
2652
2687
  if (text[i] !== "\n") {
2653
2688
  out[i] = " ";
2654
2689
  }
2655
2690
  i += 1;
2656
2691
  }
2657
- if (i < n) {
2658
- out[i] = " ";
2659
- out[i + 1] = " ";
2660
- i += 2;
2661
- }
2662
2692
  continue;
2663
2693
  }
2664
2694
  i += 1;
@@ -2756,8 +2786,9 @@ var NO_DYNAMIC_SQL_DOCUMENTATION = {
2756
2786
  remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
2757
2787
  category: "security",
2758
2788
  limitations: [
2759
- "The rule reports only visibly quoted runtime values; dynamic identifiers and unquoted fragments require provenance that syntax-only linting cannot prove.",
2760
- "Static fragments and parameterizing tagged templates are exempt."
2789
+ "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.",
2790
+ "Literal fragments, legacy uppercase fragment names, and parameterizing tagged templates are exempt; uppercase spelling does not prove a value is static.",
2791
+ "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."
2761
2792
  ],
2762
2793
  examples: [
2763
2794
  {
@@ -2798,15 +2829,23 @@ function isStaticFragment(expression) {
2798
2829
  return false;
2799
2830
  }
2800
2831
  function runtimeInterpolations(template) {
2832
+ const parts = template.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw);
2833
+ const ranges = sqlSingleQuotedRanges(parts.join(RUNTIME_MARKER));
2834
+ let offset = 0;
2801
2835
  return template.expressions.filter(
2802
- (expression, index) => !isStaticFragment(expression) && endsWithSqlQuote(template.quasis[index]?.value.raw ?? "") && startsWithSqlQuote(template.quasis[index + 1]?.value.raw ?? "")
2836
+ (expression, index) => {
2837
+ offset += parts[index]?.length ?? 0;
2838
+ const inValue = ranges.some(([start, end]) => start < offset && offset < end);
2839
+ offset += RUNTIME_MARKER.length;
2840
+ return inValue && !isStaticFragment(expression) && endsWithSqlQuote(parts[index] ?? "") && startsWithSqlQuote(parts[index + 1] ?? "");
2841
+ }
2803
2842
  );
2804
2843
  }
2805
2844
  function endsWithSqlQuote(text) {
2806
- return /['"]\s*$/u.test(text);
2845
+ return /'\s*$/u.test(text);
2807
2846
  }
2808
2847
  function startsWithSqlQuote(text) {
2809
- return /^\s*['"]/u.test(text);
2848
+ return /^\s*'/u.test(text);
2810
2849
  }
2811
2850
  function staticLiteralText(node) {
2812
2851
  if (node.type === import_utils13.AST_NODE_TYPES.Literal && typeof node.value === "string") {
@@ -2828,7 +2867,13 @@ function runtimeConcatOperands(node) {
2828
2867
  if (!hasStringLiteral) {
2829
2868
  return [];
2830
2869
  }
2870
+ const parts = operands.map((operand) => staticLiteralText(operand) ?? RUNTIME_MARKER);
2871
+ const ranges = sqlSingleQuotedRanges(parts.join(""));
2872
+ let offset = 0;
2831
2873
  return operands.filter((operand, index) => {
2874
+ const inValue = ranges.some(([start, end]) => start < offset && offset < end);
2875
+ offset += parts[index]?.length ?? 0;
2876
+ if (!inValue) return false;
2832
2877
  if (isStaticFragment(operand)) return false;
2833
2878
  const before = operands[index - 1];
2834
2879
  const after = operands[index + 1];
@@ -2921,9 +2966,11 @@ var no_dynamic_sql_default = createRule({
2921
2966
  var import_utils14 = require("@typescript-eslint/utils");
2922
2967
  var NO_ENUM_DOCUMENTATION = {
2923
2968
  summary: "Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.",
2924
- rationale: "TypeScript enums emit runtime objects and numeric enums accept values outside their declared members, adding behavior where a type-only model is sufficient.",
2925
- remediation: "Replace the enum with a string-literal union or an `as const` object and derive its value type from that object.",
2969
+ 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.",
2970
+ remediation: "Use a literal union or an `as const` object after checking runtime member access, numeric reverse mappings, serialized values, and public consumers.",
2926
2971
  category: "maintainability",
2972
+ 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."],
2973
+ references: ["https://www.typescriptlang.org/docs/handbook/enums.html"],
2927
2974
  examples: [
2928
2975
  {
2929
2976
  id: "string-literal-union",
@@ -3003,17 +3050,17 @@ var no_enum_default = createRule({
3003
3050
  // src/rules/no-fat-try-blocks.ts
3004
3051
  var import_utils15 = require("@typescript-eslint/utils");
3005
3052
  var NO_FAT_TRY_BLOCKS_DOCUMENTATION = {
3006
- summary: "Disallow `try` blocks containing more than three top-level operations that can throw.",
3053
+ summary: "Review try blocks exceeding the configured count of syntactically selected operations.",
3007
3054
  rationale: "A broad `try` block obscures which operation failed and encourages one catch clause to recover from unrelated errors.",
3008
3055
  remediation: "Keep only the operations that share one recovery policy inside the `try` block and move other work outside it.",
3009
3056
  category: "correctness",
3010
3057
  limitations: [
3011
- "The rule uses syntax to identify throwing operations and exempts generated files, finally blocks, rethrows, and terminal error boundaries."
3058
+ "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."
3012
3059
  ],
3013
3060
  examples: [
3014
3061
  {
3015
3062
  id: "focused-try-block",
3016
- title: "A try block contains three throwing operations",
3063
+ title: "Three selected operations stay within the default threshold",
3017
3064
  outcome: "no-match",
3018
3065
  files: [{
3019
3066
  path: "src/load.ts",
@@ -3025,7 +3072,7 @@ var NO_FAT_TRY_BLOCKS_DOCUMENTATION = {
3025
3072
  },
3026
3073
  {
3027
3074
  id: "broad-try-block",
3028
- title: "A try block contains four throwing operations",
3075
+ title: "Review whether four selected operations share one recovery policy",
3029
3076
  outcome: "match",
3030
3077
  files: [{
3031
3078
  path: "src/load.ts",
@@ -3395,7 +3442,7 @@ var no_fat_try_blocks_default = createRule({
3395
3442
  meta: {
3396
3443
  type: "problem",
3397
3444
  docs: {
3398
- description: "Disallow `try` blocks containing more than three top-level operations that can throw."
3445
+ description: "Review try blocks exceeding the configured count of syntactically selected operations."
3399
3446
  },
3400
3447
  schema: [
3401
3448
  {
@@ -3407,7 +3454,7 @@ var no_fat_try_blocks_default = createRule({
3407
3454
  }
3408
3455
  ],
3409
3456
  messages: {
3410
- fatTryBlock: "This `try` block has {{count}} statements that can throw (max {{max}}). Isolate the throwing statement(s); move non-throwing work outside the `try`."
3457
+ fatTryBlock: "This `try` block has {{count}} syntactically selected operations (max {{max}}). Review whether they share one recovery policy; move unrelated work outside the boundary."
3411
3458
  }
3412
3459
  },
3413
3460
  defaultOptions: [{ max: MAX_TRY_BODY_STATEMENTS }],
@@ -3454,7 +3501,8 @@ var NO_HAND_ROLLED_SLEEP_DOCUMENTATION = {
3454
3501
  remediation: "Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.",
3455
3502
  category: "correctness",
3456
3503
  limitations: [
3457
- "The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions."
3504
+ "The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions.",
3505
+ "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."
3458
3506
  ],
3459
3507
  examples: [
3460
3508
  {
@@ -3598,6 +3646,21 @@ var no_hand_rolled_sleep_default = createRule({
3598
3646
  return {};
3599
3647
  }
3600
3648
  const checkClientModules = optionsArg?.checkClientModules ?? false;
3649
+ const bindingOf = (identifier) => import_utils16.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
3650
+ const isGlobal = (identifier) => (bindingOf(identifier)?.defs.length ?? 0) === 0;
3651
+ const isBuiltinTimer = (callee) => {
3652
+ if (!isSetTimeoutCallee(callee)) return false;
3653
+ if (callee.type === import_utils16.AST_NODE_TYPES.MemberExpression && callee.object.type === import_utils16.AST_NODE_TYPES.Identifier) return isGlobal(callee.object);
3654
+ if (callee.type !== import_utils16.AST_NODE_TYPES.Identifier) return false;
3655
+ const binding = bindingOf(callee);
3656
+ return binding === null || binding.defs.length === 0 || binding.defs.every((definition) => definition.node.type === import_utils16.AST_NODE_TYPES.ImportSpecifier && definition.node.imported.type === import_utils16.AST_NODE_TYPES.Identifier && definition.node.imported.name === "setTimeout" && definition.node.parent.type === import_utils16.AST_NODE_TYPES.ImportDeclaration && ["node:timers", "timers"].includes(String(definition.node.parent.source.value)));
3657
+ };
3658
+ const settlesParameter = (callback, executor, index) => {
3659
+ const parameter = executor.params[index];
3660
+ if (parameter?.type !== import_utils16.AST_NODE_TYPES.Identifier) return false;
3661
+ const callee = callback.type === import_utils16.AST_NODE_TYPES.Identifier ? callback : callback.type === import_utils16.AST_NODE_TYPES.ArrowFunctionExpression || callback.type === import_utils16.AST_NODE_TYPES.FunctionExpression ? soleCall(callback)?.callee : null;
3662
+ return callee?.type === import_utils16.AST_NODE_TYPES.Identifier && bindingOf(callee) === bindingOf(parameter);
3663
+ };
3601
3664
  function isClientModule2() {
3602
3665
  if (/\.[cm]?[jt]sx$/.test(filename)) {
3603
3666
  return true;
@@ -3623,7 +3686,7 @@ var no_hand_rolled_sleep_default = createRule({
3623
3686
  };
3624
3687
  return {
3625
3688
  NewExpression(node) {
3626
- if (node.callee.type !== import_utils16.AST_NODE_TYPES.Identifier || node.callee.name !== "Promise") {
3689
+ if (node.callee.type !== import_utils16.AST_NODE_TYPES.Identifier || node.callee.name !== "Promise" || !isGlobal(node.callee)) {
3627
3690
  return;
3628
3691
  }
3629
3692
  const executor = node.arguments[0];
@@ -3631,7 +3694,7 @@ var no_hand_rolled_sleep_default = createRule({
3631
3694
  return;
3632
3695
  }
3633
3696
  const call = soleCall(executor);
3634
- if (call === null || !isSetTimeoutCallee(call.callee)) {
3697
+ if (call === null || !isBuiltinTimer(call.callee)) {
3635
3698
  return;
3636
3699
  }
3637
3700
  const [callback, delay] = call.arguments;
@@ -3639,14 +3702,14 @@ var no_hand_rolled_sleep_default = createRule({
3639
3702
  return;
3640
3703
  }
3641
3704
  const resolveName = parameterName(executor, 0);
3642
- if (resolveName !== null && settlesWithoutValue(callback, resolveName)) {
3705
+ if (resolveName !== null && call.arguments.length === 2 && settlesWithoutValue(callback, resolveName) && settlesParameter(callback, executor, 0)) {
3643
3706
  if (reportsSleepHere()) {
3644
3707
  context.report({ node, messageId: "handRolledSleep" });
3645
3708
  }
3646
3709
  return;
3647
3710
  }
3648
3711
  const rejectName = parameterName(executor, 1);
3649
- if (rejectName !== null && isRaceArm(node) && rejectsInCallback(callback, rejectName)) {
3712
+ if (rejectName !== null && isRaceArm(node) && settlesParameter(callback, executor, 1) && rejectsInCallback(callback, rejectName)) {
3650
3713
  context.report({ node, messageId: "handRolledTimeoutRace" });
3651
3714
  }
3652
3715
  }
@@ -3745,7 +3808,7 @@ var NO_INSECURE_RANDOM_ID_DOCUMENTATION = {
3745
3808
  rationale: "Math.random is predictable and lacks the entropy required for security-sensitive values.",
3746
3809
  remediation: "Generate the value with crypto.randomUUID or crypto.getRandomValues.",
3747
3810
  category: "security",
3748
- limitations: ["Ambiguous identifiers and test files are excluded to avoid flagging sampling and fixture data."],
3811
+ 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."],
3749
3812
  examples: [
3750
3813
  { 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 },
3751
3814
  { 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 }
@@ -3826,6 +3889,7 @@ function findEnclosingNames(node) {
3826
3889
  if (directBinding && parent.id.type === "Identifier") {
3827
3890
  names.push(parent.id.name);
3828
3891
  }
3892
+ return names;
3829
3893
  }
3830
3894
  if (parent.type === "Property" && parent.value === current) {
3831
3895
  const key = parent.key;
@@ -3860,7 +3924,7 @@ function findEnclosingNames(node) {
3860
3924
  if (directBinding && parent.id !== null) names.push(parent.id.name);
3861
3925
  return names;
3862
3926
  }
3863
- if (parent.type === "ExpressionStatement") {
3927
+ if (parent.type === "ExpressionStatement" || parent.type === "IfStatement" || parent.type === "ForStatement" || parent.type === "WhileStatement" || parent.type === "DoWhileStatement" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") {
3864
3928
  return names;
3865
3929
  }
3866
3930
  current = parent;
@@ -3952,6 +4016,7 @@ var no_insecure_random_id_default = createRule({
3952
4016
  if (!isMathRandomCall(node)) {
3953
4017
  return;
3954
4018
  }
4019
+ if ((import_utils18.ASTUtils.findVariable(context.sourceCode.getScope(node), "Math")?.defs.length ?? 0) > 0) return;
3955
4020
  const names = findEnclosingNames(node);
3956
4021
  if (names.some(isStrongSecurityName)) {
3957
4022
  context.report({ node, messageId: "insecureRandomId" });
@@ -4475,105 +4540,9 @@ var ts = __toESM(require("typescript"), 1);
4475
4540
 
4476
4541
  // src/rules/_class-private.ts
4477
4542
  var import_utils21 = require("@typescript-eslint/utils");
4478
- function symbolAt(services, checker, node) {
4479
- return checker.getSymbolAtLocation(services.esTreeNodeToTSNodeMap.get(node));
4480
- }
4481
- function sameSymbol(left, right) {
4482
- return left !== void 0 && right !== void 0 && left === right;
4483
- }
4484
- function enclosingClass(node) {
4485
- let current = node.parent;
4486
- while (current !== void 0) {
4487
- if (current.type === import_utils21.AST_NODE_TYPES.ClassDeclaration || current.type === import_utils21.AST_NODE_TYPES.ClassExpression) {
4488
- return current;
4489
- }
4490
- current = current.parent;
4491
- }
4492
- return null;
4493
- }
4494
4543
  function memberName2(member) {
4495
4544
  return !member.computed && member.key.type === import_utils21.AST_NODE_TYPES.Identifier ? member.key.name : null;
4496
4545
  }
4497
- function privateMemberFixes(context, services, owner, members, removePrivateKeyword) {
4498
- const first = members[0];
4499
- const name = first === void 0 ? null : memberName2(first);
4500
- if (first === void 0 || name === null || members.some((member) => member.static || member.decorators.length > 0)) {
4501
- return void 0;
4502
- }
4503
- if (owner.body.body.some((member) => {
4504
- if (member.type !== import_utils21.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils21.AST_NODE_TYPES.PropertyDefinition && member.type !== import_utils21.AST_NODE_TYPES.AccessorProperty) return false;
4505
- return member.key.type === import_utils21.AST_NODE_TYPES.PrivateIdentifier && member.key.name === name;
4506
- })) return void 0;
4507
- const selectedMembers = new Set(members);
4508
- if (owner.body.body.some((member) => {
4509
- if (member.type !== import_utils21.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils21.AST_NODE_TYPES.PropertyDefinition && member.type !== import_utils21.AST_NODE_TYPES.AccessorProperty) return false;
4510
- return memberName2(member) === name && !selectedMembers.has(member);
4511
- })) return void 0;
4512
- const checker = services.program.getTypeChecker();
4513
- const symbols = members.map((member) => symbolAt(services, checker, member.key)).filter(
4514
- (symbol) => symbol !== void 0
4515
- );
4516
- if (symbols.length === 0) return void 0;
4517
- const references = [];
4518
- let unsafe = false;
4519
- walk(context.sourceCode.ast, context.sourceCode.visitorKeys, (node) => {
4520
- if (node.type === import_utils21.AST_NODE_TYPES.Literal && node.value === name) {
4521
- unsafe = true;
4522
- return;
4523
- }
4524
- if (node.type !== import_utils21.AST_NODE_TYPES.MemberExpression) return;
4525
- const propertyName6 = node.property.type === import_utils21.AST_NODE_TYPES.Identifier || node.property.type === import_utils21.AST_NODE_TYPES.PrivateIdentifier ? node.property.name : node.property.type === import_utils21.AST_NODE_TYPES.Literal && typeof node.property.value === "string" ? node.property.value : null;
4526
- if (propertyName6 !== name) return;
4527
- const propertySymbol = symbolAt(services, checker, node.property);
4528
- if (node.computed || node.property.type !== import_utils21.AST_NODE_TYPES.Identifier || node.object.type !== import_utils21.AST_NODE_TYPES.ThisExpression || enclosingClass(node) !== owner || !symbols.some((symbol) => sameSymbol(symbol, propertySymbol))) {
4529
- unsafe = true;
4530
- return;
4531
- }
4532
- references.push(node);
4533
- });
4534
- if (unsafe) return void 0;
4535
- const privateKeywordRanges = /* @__PURE__ */ new Map();
4536
- if (removePrivateKeyword) {
4537
- const comments = context.sourceCode.getAllComments();
4538
- for (const member of members) {
4539
- const keyword = context.sourceCode.getTokens(member).find((token) => token.value === "private");
4540
- const next = keyword === void 0 ? void 0 : context.sourceCode.getTokenAfter(keyword);
4541
- if (keyword === void 0 || next === null || next === void 0) return void 0;
4542
- if (comments.some((comment) => comment.range[0] >= keyword.range[1] && comment.range[1] <= next.range[0])) {
4543
- return void 0;
4544
- }
4545
- privateKeywordRanges.set(member, [keyword.range[0], next.range[0]]);
4546
- }
4547
- }
4548
- return (fixer) => {
4549
- const fixes = [];
4550
- for (const member of members) {
4551
- fixes.push(fixer.replaceText(member.key, `#${name}`));
4552
- if (removePrivateKeyword) {
4553
- const range = privateKeywordRanges.get(member);
4554
- if (range === void 0) return [];
4555
- fixes.push(fixer.removeRange(range));
4556
- }
4557
- }
4558
- for (const reference of references) fixes.push(fixer.replaceText(reference.property, `#${name}`));
4559
- return fixes;
4560
- };
4561
- }
4562
- function walk(node, visitorKeys, visit) {
4563
- visit(node);
4564
- for (const key of visitorKeys[node.type] ?? []) {
4565
- const child = node[key];
4566
- if (Array.isArray(child)) {
4567
- for (const item of child) {
4568
- if (typeof item === "object" && item !== null && "type" in item) {
4569
- walk(item, visitorKeys, visit);
4570
- }
4571
- }
4572
- } else if (typeof child === "object" && child !== null && "type" in child) {
4573
- walk(child, visitorKeys, visit);
4574
- }
4575
- }
4576
- }
4577
4546
  function convertibleMemberName(member) {
4578
4547
  if (member.type !== import_utils21.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils21.AST_NODE_TYPES.PropertyDefinition && member.type !== import_utils21.AST_NODE_TYPES.AccessorProperty) return null;
4579
4548
  return memberName2(member);
@@ -4912,6 +4881,39 @@ var no_log_only_catch_default = createRule({
4912
4881
  const matcher = createLogMatcher(loggingOptions);
4913
4882
  const filename = context.filename;
4914
4883
  const sourceCode = context.sourceCode;
4884
+ function hasCoercionValidation(node) {
4885
+ const owner = node.parent;
4886
+ const statement = owner.block.body[0];
4887
+ if (node.body.body.length !== 0 || owner.finalizer !== null || owner.block.body.length !== 1 || statement?.type !== import_utils24.AST_NODE_TYPES.ExpressionStatement || statement.expression.type !== import_utils24.AST_NODE_TYPES.AssignmentExpression || statement.expression.operator !== "=") return false;
4888
+ const { left, right } = statement.expression;
4889
+ if (left.type !== import_utils24.AST_NODE_TYPES.MemberExpression || left.computed || left.object.type !== import_utils24.AST_NODE_TYPES.Identifier || right.type !== import_utils24.AST_NODE_TYPES.CallExpression || right.optional || right.callee.type !== import_utils24.AST_NODE_TYPES.Identifier || !["String", "Number", "Boolean", "BigInt"].includes(right.callee.name) || right.arguments.length !== 1) return false;
4890
+ const argument = right.arguments[0];
4891
+ if (argument === void 0 || sourceCode.getText(left) !== sourceCode.getText(argument)) return false;
4892
+ const global = import_utils24.ASTUtils.findVariable(sourceCode.getScope(right.callee), right.callee.name);
4893
+ if (global !== null && global.defs.length > 0) return false;
4894
+ const root = import_utils24.ASTUtils.findVariable(sourceCode.getScope(left.object), left.object.name);
4895
+ if (root === null || root.references.some((reference) => reference.isWrite() && !reference.init)) return false;
4896
+ let current = owner;
4897
+ let slot = statementSlot(current);
4898
+ while (slot === null && current.parent !== void 0 && !FUNCTION_TYPES2.has(current.parent.type)) {
4899
+ current = current.parent;
4900
+ slot = statementSlot(current);
4901
+ }
4902
+ let next = slot?.list[slot.index + 1];
4903
+ let target = sourceCode.getText(left);
4904
+ if (next?.type === import_utils24.AST_NODE_TYPES.VariableDeclaration && next.kind === "const" && next.declarations.length === 1) {
4905
+ const alias = next.declarations[0];
4906
+ if (alias?.id.type !== import_utils24.AST_NODE_TYPES.Identifier || alias.init === null || sourceCode.getText(alias.init) !== target) return false;
4907
+ target = alias.id.name;
4908
+ next = slot?.list[slot.index + 2];
4909
+ }
4910
+ if (next?.type !== import_utils24.AST_NODE_TYPES.IfStatement) return false;
4911
+ let condition = next.test;
4912
+ while (condition.type === import_utils24.AST_NODE_TYPES.LogicalExpression && condition.operator === "&&") condition = condition.left;
4913
+ if (condition.type !== import_utils24.AST_NODE_TYPES.BinaryExpression || !["==", "==="].includes(condition.operator)) return false;
4914
+ const test = condition.left;
4915
+ return test.type === import_utils24.AST_NODE_TYPES.UnaryExpression && test.operator === "typeof" && sourceCode.getText(test.argument) === target && condition.right.type === import_utils24.AST_NODE_TYPES.Literal && condition.right.value === right.callee.name.toLowerCase();
4916
+ }
4915
4917
  function isLoggingCallStatement(statement) {
4916
4918
  if (statement.type !== "ExpressionStatement") {
4917
4919
  return false;
@@ -4938,17 +4940,11 @@ var no_log_only_catch_default = createRule({
4938
4940
  CatchClause(node) {
4939
4941
  const statements = node.body.body;
4940
4942
  const isDocumented = sourceCode.getCommentsInside(node.body).length > 0 || hasAdjacentRationale(node);
4941
- if (statements.length === 0) {
4942
- if (isDocumented) {
4943
- return;
4944
- }
4945
- if (fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4946
- return;
4947
- }
4948
- context.report({ node, messageId: "emptyCatch" });
4943
+ if (isDocumented || hasCoercionValidation(node) || fallbackFollowsTry(node.parent) || seededFallbackHandled(node.parent, sourceCode.getScope(node))) {
4949
4944
  return;
4950
4945
  }
4951
- if (isDocumented) {
4946
+ if (statements.length === 0) {
4947
+ context.report({ node, messageId: "emptyCatch" });
4952
4948
  return;
4953
4949
  }
4954
4950
  const everyStatementIsLogging = statements.every(
@@ -4966,7 +4962,7 @@ var no_log_only_catch_default = createRule({
4966
4962
  var import_utils25 = require("@typescript-eslint/utils");
4967
4963
  var NO_BARE_RETURN_FROM_TEST_CATCH_DOCUMENTATION = {
4968
4964
  summary: "Disallow a bare return from a test catch block when it skips a later assertion.",
4969
- rationale: "The caught failure turns into a passing test without executing the assertion that follows it.",
4965
+ rationale: "An unasserted catch return can swallow a failure and skip later assertions; the complete test result also depends on other assertions and hooks.",
4970
4966
  remediation: "Rethrow the error, assert on it, or use the runner's explicit skip mechanism when the capability is optional.",
4971
4967
  category: "testing",
4972
4968
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
@@ -5053,7 +5049,7 @@ var no_bare_return_from_test_catch_default = createRule({
5053
5049
  type: "problem",
5054
5050
  docs: { description: "Disallow a bare return from a test catch block when it skips a later assertion." },
5055
5051
  schema: [],
5056
- 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." }
5052
+ 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." }
5057
5053
  },
5058
5054
  defaultOptions: [],
5059
5055
  create(context) {
@@ -5072,6 +5068,23 @@ var no_bare_return_from_test_catch_default = createRule({
5072
5068
  if (current === null || current === void 0) break;
5073
5069
  }
5074
5070
  if (catchClause === null || catchClause.parent.finalizer !== null) return;
5071
+ const parameter = catchClause.param;
5072
+ if (parameter?.type === import_utils25.AST_NODE_TYPES.Identifier && node.parent === catchClause.body) {
5073
+ const errorBinding = import_utils25.ASTUtils.findVariable(context.sourceCode.getScope(parameter), parameter.name);
5074
+ const assertedError = catchClause.body.body.some((statement) => {
5075
+ if (statement.range[1] >= node.range[0] || statement.type !== import_utils25.AST_NODE_TYPES.ExpressionStatement) return false;
5076
+ const expression = statement.expression;
5077
+ if (expression.type !== import_utils25.AST_NODE_TYPES.CallExpression || !isAssertion(expression, context)) return false;
5078
+ const root = rootIdentifier2(expression.callee);
5079
+ if (root === null) return false;
5080
+ const assertionName = importedName3(root, context, ASSERTION_MODULES);
5081
+ let operand = expression.callee.type === import_utils25.AST_NODE_TYPES.MemberExpression ? expression.callee.object : null;
5082
+ if (operand?.type === import_utils25.AST_NODE_TYPES.MemberExpression && staticMemberName2(operand) === "not") operand = operand.object;
5083
+ if (assertionName !== "assert" && (assertionName !== "expect" || operand?.type !== import_utils25.AST_NODE_TYPES.CallExpression || operand.callee !== root)) return false;
5084
+ return walkOwnScope(expression, (current) => current.type === import_utils25.AST_NODE_TYPES.Identifier && errorBinding?.references.some((reference) => reference.identifier === current) === true);
5085
+ });
5086
+ if (assertedError) return;
5087
+ }
5075
5088
  if (walkOwnScope(catchClause.body, (current) => current.type === import_utils25.AST_NODE_TYPES.ThrowStatement || isExplicitSkip(current, context))) return;
5076
5089
  if (!walkOwnScope(owner.body, (current) => current.range[0] > node.range[1] && isAssertion(current, context))) return;
5077
5090
  context.report({ node, messageId: "bareReturnFromTestCatch" });
@@ -5083,13 +5096,13 @@ var no_bare_return_from_test_catch_default = createRule({
5083
5096
  // src/rules/no-bespoke-api-case-conversion.ts
5084
5097
  var import_utils26 = require("@typescript-eslint/utils");
5085
5098
  var NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION = {
5086
- summary: "Disallow hand-written snake_case/camelCase object-key translation at a proven API adapter boundary.",
5087
- 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.",
5099
+ summary: "Review direct snake_case/camelCase mirror mappings on explicitly API-typed adapter values.",
5100
+ 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.",
5088
5101
  remediation: "Move wire-name ownership and case conversion into the generated SDK/model layer; keep application adapters on the generated typed surface.",
5089
5102
  category: "architecture",
5090
5103
  autofix: "none",
5091
5104
  limitations: [
5092
- "Only files named adapter/adapters that import an API, client, SDK, contract, or generated module are checked.",
5105
+ "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.",
5093
5106
  "Only object properties that directly translate the same identifier between snake_case and lowerCamelCase are reported.",
5094
5107
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and indirect conversions are intentionally excluded."
5095
5108
  ],
@@ -5161,7 +5174,7 @@ var no_bespoke_api_case_conversion_default = createRule({
5161
5174
  docs: { description: NO_BESPOKE_API_CASE_CONVERSION_DOCUMENTATION.summary },
5162
5175
  schema: [],
5163
5176
  messages: {
5164
- noBespokeApiCaseConversion: "This API adapter manually translates `{{wireName}}` and `{{applicationName}}`; make the generated SDK/model layer own wire-name conversion."
5177
+ noBespokeApiCaseConversion: "This API-typed adapter value mirrors `{{wireName}}` and `{{applicationName}}`. If the SDK owns application-facing names, move this conversion to its model boundary."
5165
5178
  }
5166
5179
  },
5167
5180
  defaultOptions: [],
@@ -5171,16 +5184,33 @@ var no_bespoke_api_case_conversion_default = createRule({
5171
5184
  if (!ADAPTER_BASENAME_RE.test(basename) || isGeneratedFile(filename, context.sourceCode.text) || isTestFile(filename, ["fixtureTree"])) {
5172
5185
  return {};
5173
5186
  }
5174
- const provenApiBoundary = context.sourceCode.ast.body.some(
5175
- (statement) => statement.type === import_utils26.AST_NODE_TYPES.ImportDeclaration && API_BOUNDARY_IMPORT_RE.test(statement.source.value)
5176
- );
5177
- if (!provenApiBoundary) return {};
5187
+ const hasApiReceiver = (value) => {
5188
+ let current = value;
5189
+ while (true) {
5190
+ if (current.type === import_utils26.AST_NODE_TYPES.MemberExpression) current = current.object;
5191
+ else if (current.type === import_utils26.AST_NODE_TYPES.TSAsExpression || current.type === import_utils26.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils26.AST_NODE_TYPES.TSTypeAssertion) current = current.expression;
5192
+ else break;
5193
+ }
5194
+ if (current.type !== import_utils26.AST_NODE_TYPES.Identifier) return false;
5195
+ const binding = import_utils26.ASTUtils.findVariable(context.sourceCode.getScope(current), current.name);
5196
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
5197
+ const identifier = binding.defs[0]?.name;
5198
+ if (identifier?.type !== import_utils26.AST_NODE_TYPES.Identifier) return false;
5199
+ const annotation = identifier.typeAnnotation?.typeAnnotation;
5200
+ if (annotation?.type !== import_utils26.AST_NODE_TYPES.TSTypeReference) return false;
5201
+ let typeName = annotation.typeName;
5202
+ while (typeName.type === import_utils26.AST_NODE_TYPES.TSQualifiedName) typeName = typeName.left;
5203
+ if (typeName.type !== import_utils26.AST_NODE_TYPES.Identifier) return false;
5204
+ const typeBinding = import_utils26.ASTUtils.findVariable(context.sourceCode.getScope(typeName), typeName.name);
5205
+ return typeBinding?.defs.length === 1 && typeBinding.defs[0]?.type === "ImportBinding" && typeBinding.defs[0].parent.type === import_utils26.AST_NODE_TYPES.ImportDeclaration && API_BOUNDARY_IMPORT_RE.test(typeBinding.defs[0].parent.source.value);
5206
+ };
5178
5207
  return {
5179
5208
  Property(node) {
5180
5209
  if (node.computed || node.method || node.shorthand) return;
5181
5210
  const key = propertyName(node.key);
5182
5211
  const value = memberName3(node.value);
5183
5212
  if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
5213
+ if (!hasApiReceiver(node.value)) return;
5184
5214
  const wireName = SNAKE_CASE_RE.test(key) ? key : value;
5185
5215
  const applicationName = wireName === key ? value : key;
5186
5216
  context.report({
@@ -5303,7 +5333,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5303
5333
  limitations: [
5304
5334
  "Only ESLint disable comments and TypeScript expect-error directives are checked.",
5305
5335
  "The rule uses a small anchored vocabulary and does not score prose quality generally.",
5306
- "Generated files and descriptions containing any concrete context are excluded."
5336
+ "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."
5307
5337
  ],
5308
5338
  examples: [
5309
5339
  {
@@ -5313,7 +5343,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5313
5343
  files: [
5314
5344
  {
5315
5345
  path: "src/adapter.ts",
5316
- source: "// @ts-expect-error -- vendor types omit the runtime requestId field\nreturn response.requestId;"
5346
+ source: "function requestId(response: object) {\n // @ts-expect-error -- vendor types omit the runtime requestId field\n return response.requestId;\n}"
5317
5347
  }
5318
5348
  ],
5319
5349
  focusPath: "src/adapter.ts",
@@ -5327,7 +5357,7 @@ var NO_VAGUE_SUPPRESSION_DESCRIPTION_DOCUMENTATION = {
5327
5357
  files: [
5328
5358
  {
5329
5359
  path: "src/adapter.ts",
5330
- source: "// @ts-expect-error -- false positive\nreturn response.requestId;"
5360
+ source: "function requestId(response: object) {\n // @ts-expect-error -- false positive\n return response.requestId;\n}"
5331
5361
  }
5332
5362
  ],
5333
5363
  focusPath: "src/adapter.ts",
@@ -5378,9 +5408,9 @@ var NO_GENERIC_SINGLE_EXPORT_MODULE_DOCUMENTATION = {
5378
5408
  rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
5379
5409
  remediation: "Choose a responsibility-bearing module name or colocate the export with its domain.",
5380
5410
  category: "maintainability",
5381
- limitations: ["Only configured generic stems with exactly one public runtime export are reported."],
5411
+ limitations: ["Only the fixed generic-stem vocabulary with exactly one public runtime export is checked; exported destructuring patterns are excluded rather than undercounted."],
5382
5412
  examples: [
5383
- { id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/order-parser.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/order-parser.ts", expectedCount: 0, public: true },
5413
+ { id: "responsibility-named-module", title: "Name the module after its export", outcome: "no-match", files: [{ path: "src/parse-order.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/parse-order.ts", expectedCount: 0, public: true },
5384
5414
  { id: "generic-module-name", title: "Do not hide one export in a generic module", outcome: "match", files: [{ path: "src/utils.ts", source: "export function parseOrder() { return {}; }" }], focusPath: "src/utils.ts", expectedCount: 1, public: true }
5385
5415
  ]
5386
5416
  };
@@ -5441,6 +5471,7 @@ function runtimeExports(program) {
5441
5471
  }
5442
5472
  if (statement.declaration !== null) {
5443
5473
  const declaration = statement.declaration;
5474
+ if (declaration.type === import_utils28.AST_NODE_TYPES.VariableDeclaration && declaration.declarations.some((item) => item.id.type !== import_utils28.AST_NODE_TYPES.Identifier)) ambiguous = true;
5444
5475
  exports2.push(...declaredNames(declaration).map((name) => ({ key: name, name, node: declaration })));
5445
5476
  }
5446
5477
  for (const specifier of statement.specifiers) {
@@ -5559,11 +5590,11 @@ var no_generic_single_export_module_default = createRule({
5559
5590
  // src/rules/no-offset-pagination.ts
5560
5591
  var import_utils29 = require("@typescript-eslint/utils");
5561
5592
  var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5562
- 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.",
5593
+ summary: "Prefer keyset pagination for embedded SQL queries using OFFSET.",
5563
5594
  rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
5564
- remediation: "Page with a stable ordered key and a cursor predicate.",
5595
+ remediation: "Consider a keyset cursor that preserves the query's complete ordering, tie-breakers, and filters.",
5565
5596
  category: "performance",
5566
- limitations: ["Only embedded SQL is inspected; test files and non-pagination OFFSET syntax are excluded."],
5597
+ 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."],
5567
5598
  examples: [
5568
5599
  { 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 },
5569
5600
  { 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 }
@@ -5571,17 +5602,18 @@ var NO_OFFSET_PAGINATION_DOCUMENTATION = {
5571
5602
  };
5572
5603
  var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
5573
5604
  var OFFSET_GATE = /offset/i;
5605
+ 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;
5574
5606
  var no_offset_pagination_default = createRule({
5575
5607
  name: "no-offset-pagination",
5576
5608
  documentation: NO_OFFSET_PAGINATION_DOCUMENTATION,
5577
5609
  meta: {
5578
5610
  type: "problem",
5579
5611
  docs: {
5580
- 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."
5612
+ description: "Prefer keyset pagination for embedded SQL queries using OFFSET."
5581
5613
  },
5582
5614
  schema: [],
5583
5615
  messages: {
5584
- 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 ?`."
5616
+ 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."
5585
5617
  }
5586
5618
  },
5587
5619
  defaultOptions: [],
@@ -5590,7 +5622,7 @@ var no_offset_pagination_default = createRule({
5590
5622
  return {};
5591
5623
  }
5592
5624
  return createSqlListener((sql, node) => {
5593
- if (!OFFSET_PAGINATION.test(sql)) {
5625
+ if (!PAGINATION_CONTEXT.test(sql) || !OFFSET_PAGINATION.test(sql)) {
5594
5626
  return;
5595
5627
  }
5596
5628
  context.report({ node, messageId: "noOffsetPagination" });
@@ -5629,7 +5661,7 @@ function tupleReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
5629
5661
  return argument === void 0 ? null : tupleReturnType(argument, aliases, resolving);
5630
5662
  }
5631
5663
  if (node.type === import_utils30.AST_NODE_TYPES.TSTypeReference && node.typeName.type === import_utils30.AST_NODE_TYPES.Identifier && !resolving.has(node.typeName.name)) {
5632
- const target = aliases.get(node.typeName.name);
5664
+ const target = aliases.get(node.typeName);
5633
5665
  if (target !== void 0) return tupleReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
5634
5666
  }
5635
5667
  if (node.type === import_utils30.AST_NODE_TYPES.TSTypeOperator && node.operator === "readonly") {
@@ -5721,20 +5753,27 @@ function exportedTypeNames(program) {
5721
5753
  }
5722
5754
  return names;
5723
5755
  }
5724
- function typeAliases(program) {
5756
+ function typeAliases(sourceCode) {
5725
5757
  const aliases = /* @__PURE__ */ new Map();
5726
- for (const statement of program.body) {
5758
+ for (const statement of sourceCode.ast.body) {
5727
5759
  const declaration = statement.type === import_utils30.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
5728
5760
  if (declaration?.type === import_utils30.AST_NODE_TYPES.TSTypeAliasDeclaration) {
5729
- aliases.set(declaration.id.name, declaration.typeAnnotation);
5761
+ aliases.set(declaration.id.name, declaration);
5730
5762
  }
5731
5763
  }
5732
- return aliases;
5764
+ return {
5765
+ get(identifier) {
5766
+ const declaration = aliases.get(identifier.name);
5767
+ if (declaration === void 0) return void 0;
5768
+ const binding = import_utils30.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
5769
+ return binding?.defs.length === 1 && binding.defs[0]?.node === declaration ? declaration.typeAnnotation : void 0;
5770
+ }
5771
+ };
5733
5772
  }
5734
5773
  function callableReturnType(node, aliases, resolving = /* @__PURE__ */ new Set()) {
5735
5774
  if (node.type === import_utils30.AST_NODE_TYPES.TSFunctionType) return node.returnType?.typeAnnotation ?? null;
5736
5775
  if (node.type === import_utils30.AST_NODE_TYPES.TSTypeReference && node.typeName.type === import_utils30.AST_NODE_TYPES.Identifier && !resolving.has(node.typeName.name)) {
5737
- const target = aliases.get(node.typeName.name);
5776
+ const target = aliases.get(node.typeName);
5738
5777
  if (target !== void 0) {
5739
5778
  return callableReturnType(target, aliases, /* @__PURE__ */ new Set([...resolving, node.typeName.name]));
5740
5779
  }
@@ -5863,7 +5902,7 @@ var no_positional_tuple_return_default = createRule({
5863
5902
  context.sourceCode.ast,
5864
5903
  exportedTypeNames(context.sourceCode.ast)
5865
5904
  );
5866
- const aliases = typeAliases(context.sourceCode.ast);
5905
+ const aliases = typeAliases(context.sourceCode);
5867
5906
  const reportedFunctions = /* @__PURE__ */ new WeakSet();
5868
5907
  const functionStack = [];
5869
5908
  const report2 = (annotation, name) => {
@@ -6403,11 +6442,11 @@ var no_raw_fetch_outside_clients_default = createRule({
6403
6442
  // src/rules/no-restricted-library-load.ts
6404
6443
  var import_utils33 = require("@typescript-eslint/utils");
6405
6444
  var NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION = {
6406
- summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
6407
- rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
6408
- remediation: "Load the configured replacement library instead of the restricted module.",
6445
+ summary: "Apply configured library restrictions to literal runtime loads and CommonJS resolution references.",
6446
+ rationale: "Dynamic imports, CommonJS loads, and package resolution checks can bypass library restrictions enforced for static imports.",
6447
+ remediation: "Use the configured replacement for the runtime dependency reference; resolution checks do not themselves load a module.",
6409
6448
  category: "architecture",
6410
- limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
6449
+ limitations: ["Only literal dynamic imports, unshadowed CommonJS loads/resolution calls, and runtime TypeScript import-equals declarations are checked; erased type imports are excluded. A configured restriction list is required."],
6411
6450
  examples: [
6412
6451
  { id: "static-import", title: "Static imports remain the static-import rule's responsibility", outcome: "no-match", files: [{ path: "src/client.ts", source: "import axios from 'axios';" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
6413
6452
  { id: "runtime-load", title: "Do not load a restricted library at runtime", outcome: "match", files: [{ path: "src/client.ts", source: "const client = require('axios');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
@@ -6425,7 +6464,7 @@ var no_restricted_library_load_default = createRule({
6425
6464
  meta: {
6426
6465
  type: "problem",
6427
6466
  docs: {
6428
- description: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations."
6467
+ description: NO_RESTRICTED_LIBRARY_LOAD_DOCUMENTATION.summary
6429
6468
  },
6430
6469
  schema: [
6431
6470
  {
@@ -6451,7 +6490,7 @@ var no_restricted_library_load_default = createRule({
6451
6490
  }
6452
6491
  ],
6453
6492
  messages: {
6454
- restrictedLibraryLoad: "{{id}}: Replace runtime loading of {{module}} with {{replacement}}.{{note}}"
6493
+ restrictedLibraryLoad: "{{id}}: Replace this runtime dependency reference to {{module}} with {{replacement}}.{{note}}"
6455
6494
  }
6456
6495
  },
6457
6496
  defaultOptions: [{ libraries: [] }],
@@ -6497,6 +6536,7 @@ var no_restricted_library_load_default = createRule({
6497
6536
  if (source !== null) report2(node.arguments[0], source);
6498
6537
  },
6499
6538
  TSImportEqualsDeclaration(node) {
6539
+ if (node.importKind === "type") return;
6500
6540
  if (node.moduleReference.type !== import_utils33.AST_NODE_TYPES.TSExternalModuleReference) return;
6501
6541
  const source = literalModule(node.moduleReference.expression);
6502
6542
  if (source !== null) report2(node.moduleReference.expression, source);
@@ -6613,7 +6653,7 @@ var NO_REPEATED_STRING_LITERAL_DOCUMENTATION = {
6613
6653
  ]
6614
6654
  };
6615
6655
  function isStructured(value) {
6616
- return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
6656
+ return SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
6617
6657
  }
6618
6658
  function preview(value) {
6619
6659
  const oneLine = value.replaceAll("\n", " ").trim();
@@ -6634,7 +6674,7 @@ function isScaffolding(node) {
6634
6674
  }
6635
6675
  const isNonComputedPropertyKey = (parent.type === import_utils35.AST_NODE_TYPES.Property || parent.type === import_utils35.AST_NODE_TYPES.PropertyDefinition || parent.type === import_utils35.AST_NODE_TYPES.MethodDefinition || parent.type === import_utils35.AST_NODE_TYPES.AccessorProperty) && parent.key === node && !parent.computed;
6636
6676
  const isRequireSource = parent.type === import_utils35.AST_NODE_TYPES.CallExpression && parent.callee.type === import_utils35.AST_NODE_TYPES.Identifier && parent.callee.name === "require";
6637
- return parent.type === import_utils35.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils35.AST_NODE_TYPES.ImportExpression || parent.type === import_utils35.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils35.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils35.AST_NODE_TYPES.TSImportType || parent.type === import_utils35.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils35.AST_NODE_TYPES.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
6677
+ return parent.type === import_utils35.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils35.AST_NODE_TYPES.ImportExpression || parent.type === import_utils35.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils35.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils35.AST_NODE_TYPES.TSImportType || parent.type === import_utils35.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils35.AST_NODE_TYPES.JSXExpressionContainer && parent.parent.type === import_utils35.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils35.AST_NODE_TYPES.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
6638
6678
  }
6639
6679
  var no_repeated_string_literal_default = createRule({
6640
6680
  name: "no-repeated-string-literal",
@@ -7554,11 +7594,11 @@ var no_server_env_in_client_component_default = createRule({
7554
7594
  // src/rules/no-select-star.ts
7555
7595
  var import_utils41 = require("@typescript-eslint/utils");
7556
7596
  var NO_SELECT_STAR_DOCUMENTATION = {
7557
- summary: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently.",
7597
+ summary: "Prefer explicit column projections over SELECT * in embedded SQL.",
7558
7598
  rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
7559
7599
  remediation: "List every required column explicitly in the projection.",
7560
7600
  category: "correctness",
7561
- limitations: ["Only statically visible embedded SQL is checked; function arguments such as COUNT(*) and stars inside EXISTS are excluded."],
7601
+ 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."],
7562
7602
  examples: [
7563
7603
  { 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 },
7564
7604
  { 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 }
@@ -7609,7 +7649,7 @@ var no_select_star_default = createRule({
7609
7649
  meta: {
7610
7650
  type: "problem",
7611
7651
  docs: {
7612
- description: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently."
7652
+ description: "Prefer explicit column projections over SELECT * in embedded SQL."
7613
7653
  },
7614
7654
  schema: [],
7615
7655
  messages: {
@@ -7637,7 +7677,7 @@ var NO_SENTINEL_RETURN_ON_CATCH_DOCUMENTATION = {
7637
7677
  rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
7638
7678
  remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
7639
7679
  category: "correctness",
7640
- limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
7680
+ limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded. Locally shadowed undefined bindings are not treated as sentinels; recognized handling patterns are not a proof that every control-flow path handles the error."],
7641
7681
  examples: [
7642
7682
  { id: "reported-fallback", title: "Report an error before returning a fallback", outcome: "no-match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch (error) { logger.warn('load failed', error); return null; } }" }], focusPath: "src/load.ts", expectedCount: 0, public: true },
7643
7683
  { id: "silent-fallback", title: "Do not turn an unreported error into absence", outcome: "match", files: [{ path: "src/load.ts", source: "function load() { try { return read(); } catch { return null; } }" }], focusPath: "src/load.ts", expectedCount: 1, public: true }
@@ -8052,6 +8092,8 @@ var no_sentinel_return_on_catch_default = createRule({
8052
8092
  if (!isSentinelArgument(last.argument)) {
8053
8093
  return;
8054
8094
  }
8095
+ const returned = unwrapSentinelExpression(last.argument);
8096
+ if (returned?.type === import_utils42.AST_NODE_TYPES.Identifier && returned.name === "undefined" && (import_utils42.ASTUtils.findVariable(context.sourceCode.getScope(returned), returned.name)?.defs.length ?? 0) > 0) return;
8055
8097
  if (containsThrow(node.body)) {
8056
8098
  return;
8057
8099
  }
@@ -8091,7 +8133,7 @@ var NO_SILENT_PROMISE_CATCH_DOCUMENTATION = {
8091
8133
  rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
8092
8134
  remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
8093
8135
  category: "correctness",
8094
- limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded."],
8136
+ 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."],
8095
8137
  examples: [
8096
8138
  { 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 },
8097
8139
  { 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 }
@@ -8105,6 +8147,43 @@ var BODY_PARSE_METHODS = /* @__PURE__ */ new Set([
8105
8147
  "json",
8106
8148
  "text"
8107
8149
  ]);
8150
+ var ZOD_CONSTRUCTORS = /* @__PURE__ */ new Set([
8151
+ "any",
8152
+ "array",
8153
+ "bigint",
8154
+ "boolean",
8155
+ "custom",
8156
+ "date",
8157
+ "enum",
8158
+ "literal",
8159
+ "map",
8160
+ "never",
8161
+ "null",
8162
+ "number",
8163
+ "object",
8164
+ "record",
8165
+ "set",
8166
+ "string",
8167
+ "tuple",
8168
+ "undefined",
8169
+ "union",
8170
+ "unknown"
8171
+ ]);
8172
+ var ZOD_CHAIN_METHODS = /* @__PURE__ */ new Set([
8173
+ "array",
8174
+ "catch",
8175
+ "default",
8176
+ "describe",
8177
+ "max",
8178
+ "min",
8179
+ "nullable",
8180
+ "nullish",
8181
+ "optional",
8182
+ "readonly",
8183
+ "refine",
8184
+ "superRefine",
8185
+ "transform"
8186
+ ]);
8108
8187
  function isBodyParseCall(node) {
8109
8188
  return node.type === import_utils43.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils43.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils43.AST_NODE_TYPES.Identifier && BODY_PARSE_METHODS.has(node.callee.property.name);
8110
8189
  }
@@ -8178,6 +8257,26 @@ var no_silent_promise_catch_default = createRule({
8178
8257
  if (isTestFile(context.filename) || isScriptFile(context.filename)) {
8179
8258
  return {};
8180
8259
  }
8260
+ function isZodSchema(node, seen = /* @__PURE__ */ new Set()) {
8261
+ if (seen.has(node)) return false;
8262
+ seen.add(node);
8263
+ if (node.type === import_utils43.AST_NODE_TYPES.Identifier) {
8264
+ const binding = import_utils43.ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
8265
+ if (binding === null || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
8266
+ const [definition] = binding.defs;
8267
+ return binding.defs.length === 1 && definition?.node.type === import_utils43.AST_NODE_TYPES.VariableDeclarator && definition.node.init !== null && isZodSchema(definition.node.init, seen);
8268
+ }
8269
+ if (node.type !== import_utils43.AST_NODE_TYPES.CallExpression || node.callee.type !== import_utils43.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils43.AST_NODE_TYPES.Identifier) return false;
8270
+ const { object, property } = node.callee;
8271
+ if (object.type === import_utils43.AST_NODE_TYPES.Identifier && ZOD_CONSTRUCTORS.has(property.name)) {
8272
+ const binding = import_utils43.ASTUtils.findVariable(context.sourceCode.getScope(object), object.name);
8273
+ if (binding?.defs.some((definition) => {
8274
+ const specifier = definition.node;
8275
+ return (specifier.type === import_utils43.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils43.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils43.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils43.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") && specifier.parent.type === import_utils43.AST_NODE_TYPES.ImportDeclaration && isZodModule(String(specifier.parent.source.value));
8276
+ })) return true;
8277
+ }
8278
+ return ZOD_CHAIN_METHODS.has(property.name) && isZodSchema(object, seen);
8279
+ }
8181
8280
  const hasExplanatoryComment = (call, handler) => {
8182
8281
  const sourceCode = context.sourceCode;
8183
8282
  if (sourceCode.getCommentsInside(handler).some(isExplanatory)) {
@@ -8202,6 +8301,7 @@ var no_silent_promise_catch_default = createRule({
8202
8301
  const method = node.callee.property.name;
8203
8302
  const handlerIndex = method === "catch" ? 0 : method === "then" ? 1 : null;
8204
8303
  if (handlerIndex === null) return;
8304
+ if (method === "catch" && isZodSchema(node.callee.object)) return;
8205
8305
  if (isBodyParseCall(node.callee.object)) {
8206
8306
  return;
8207
8307
  }
@@ -8236,14 +8336,14 @@ var no_silent_promise_catch_default = createRule({
8236
8336
  // src/rules/no-sleep-in-test-body.ts
8237
8337
  var import_utils44 = require("@typescript-eslint/utils");
8238
8338
  var NO_SLEEP_IN_TEST_BODY_DOCUMENTATION = {
8239
- summary: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers.",
8339
+ summary: "Avoid fixed timed sleeps directly in test bodies; synchronize on observable behavior or use controlled timers.",
8240
8340
  rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
8241
- remediation: "Await the observable signal or advance deterministic fake timers.",
8341
+ remediation: "Await the observable signal, or advance fake timers when supported and restore real timers in finally or a teardown hook.",
8242
8342
  category: "testing",
8243
8343
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
8244
- limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes and parameterized delays are excluded."],
8344
+ 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."],
8245
8345
  examples: [
8246
- { 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 },
8346
+ { 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 },
8247
8347
  { 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 }
8248
8348
  ]
8249
8349
  };
@@ -8262,9 +8362,6 @@ var FUNCTION_TYPES5 = /* @__PURE__ */ new Set([
8262
8362
  function isNonzeroNumericLiteral(node) {
8263
8363
  return node?.type === import_utils44.AST_NODE_TYPES.Literal && typeof node.value === "number" && node.value !== 0;
8264
8364
  }
8265
- function isTimedSetTimeout(node) {
8266
- return node.type === import_utils44.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils44.AST_NODE_TYPES.Identifier && node.callee.name === "setTimeout" && node.arguments.length >= 2 && isNonzeroNumericLiteral(node.arguments[1]);
8267
- }
8268
8365
  function isPromiseSleep(node) {
8269
8366
  if (node.callee.type !== import_utils44.AST_NODE_TYPES.Identifier || node.callee.name !== "Promise") {
8270
8367
  return false;
@@ -8274,12 +8371,14 @@ function isPromiseSleep(node) {
8274
8371
  return false;
8275
8372
  }
8276
8373
  const body2 = executor.body;
8277
- if (body2.type !== import_utils44.AST_NODE_TYPES.BlockStatement) {
8278
- return isTimedSetTimeout(body2);
8279
- }
8280
- return body2.body.some(
8281
- (stmt) => stmt.type === import_utils44.AST_NODE_TYPES.ExpressionStatement && isTimedSetTimeout(stmt.expression)
8282
- );
8374
+ const resolve2 = executor.params[0];
8375
+ if (executor.params.length !== 1 || resolve2?.type !== import_utils44.AST_NODE_TYPES.Identifier || resolve2.name === "setTimeout") return false;
8376
+ const statement = body2.type === import_utils44.AST_NODE_TYPES.BlockStatement && body2.body.length === 1 ? body2.body[0] : null;
8377
+ const timer = body2.type !== import_utils44.AST_NODE_TYPES.BlockStatement ? body2 : statement?.type === import_utils44.AST_NODE_TYPES.ExpressionStatement ? statement.expression : null;
8378
+ return timer?.type === import_utils44.AST_NODE_TYPES.CallExpression && isTimedSetTimeout(timer) && timer.arguments[0]?.type === import_utils44.AST_NODE_TYPES.Identifier && timer.arguments[0].name === resolve2.name;
8379
+ }
8380
+ function isTimedSetTimeout(node) {
8381
+ return node.type === import_utils44.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils44.AST_NODE_TYPES.Identifier && node.callee.name === "setTimeout" && node.arguments.length >= 2 && isNonzeroNumericLiteral(node.arguments[1]);
8283
8382
  }
8284
8383
  function isHelperSleep(node) {
8285
8384
  return node.callee.type === import_utils44.AST_NODE_TYPES.Identifier && SLEEP_HELPERS.has(node.callee.name) && node.arguments.length >= 1 && isNonzeroNumericLiteral(node.arguments[0]);
@@ -8333,11 +8432,11 @@ var no_sleep_in_test_body_default = createRule({
8333
8432
  meta: {
8334
8433
  type: "problem",
8335
8434
  docs: {
8336
- description: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers."
8435
+ description: "Avoid fixed timed sleeps directly in test bodies; synchronize on observable behavior or use controlled timers."
8337
8436
  },
8338
8437
  schema: [],
8339
8438
  messages: {
8340
- 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)`."
8439
+ 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."
8341
8440
  }
8342
8441
  },
8343
8442
  defaultOptions: [],
@@ -8358,11 +8457,17 @@ var no_sleep_in_test_body_default = createRule({
8358
8457
  return {
8359
8458
  NewExpression(node) {
8360
8459
  if (isPromiseSleep(node)) {
8460
+ const constructor = import_utils44.ASTUtils.findVariable(context.sourceCode.getScope(node), "Promise");
8461
+ const timer = import_utils44.ASTUtils.findVariable(context.sourceCode.getScope(node), "setTimeout");
8462
+ if ((constructor?.defs.length ?? 0) > 0 || (timer?.defs.length ?? 0) > 0) return;
8361
8463
  report2(node);
8362
8464
  }
8363
8465
  },
8364
8466
  CallExpression(node) {
8365
8467
  if (isHelperSleep(node)) {
8468
+ if (node.callee.type !== import_utils44.AST_NODE_TYPES.Identifier) return;
8469
+ const variable = import_utils44.ASTUtils.findVariable(context.sourceCode.getScope(node), node.callee.name);
8470
+ if (variable?.defs.some((definition) => definition.type !== "ImportBinding")) return;
8366
8471
  report2(node);
8367
8472
  }
8368
8473
  }
@@ -8383,7 +8488,7 @@ var NO_STORAGE_IN_STATELESS_MODULES_DOCUMENTATION = {
8383
8488
  rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
8384
8489
  remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
8385
8490
  category: "architecture",
8386
- 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."],
8491
+ 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."],
8387
8492
  examples: [
8388
8493
  { 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 },
8389
8494
  { 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 }
@@ -8417,6 +8522,17 @@ function storageMethodName(node, methods) {
8417
8522
  if (name === "put" && !isStorageLikeReceiver(callee.object)) {
8418
8523
  return null;
8419
8524
  }
8525
+ if (name === "prepare") {
8526
+ const argument = node.arguments[0];
8527
+ const text = argument === void 0 ? null : sqlTextOf(argument);
8528
+ if (text !== null) {
8529
+ if (!/^\s*(?:SELECT|WITH|INSERT|UPDATE|DELETE|REPLACE|CREATE|ALTER|DROP|PRAGMA|EXPLAIN)\b/iu.test(stripSqlNoise(text))) return null;
8530
+ } else {
8531
+ const receiver = callee.object;
8532
+ const receiverName = receiver.type === import_utils45.AST_NODE_TYPES.Identifier ? receiver.name : receiver.type === import_utils45.AST_NODE_TYPES.MemberExpression && !receiver.computed && receiver.property.type === import_utils45.AST_NODE_TYPES.Identifier ? receiver.property.name : "";
8533
+ if (!/^(?:db|database|connection)$/iu.test(receiverName)) return null;
8534
+ }
8535
+ }
8420
8536
  return name;
8421
8537
  }
8422
8538
  function isStorageLikeReceiver(node) {
@@ -8501,12 +8617,13 @@ var no_storage_in_stateless_modules_default = createRule({
8501
8617
  // src/rules/no-string-concat-in-loop.ts
8502
8618
  var import_utils46 = require("@typescript-eslint/utils");
8503
8619
  var NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION = {
8504
- summary: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead.",
8620
+ summary: "Prefer collecting string fragments over repeatedly accumulating a growing string inside a loop.",
8505
8621
  rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
8506
- remediation: "Collect each fragment in an array, then join the fragments after the loop.",
8622
+ remediation: "Consider collecting fragments and joining once; preserve intermediate observations and coercion timing, and measure hot paths.",
8507
8623
  category: "performance",
8508
8624
  limitations: [
8509
- "Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
8625
+ "Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected.",
8626
+ "Deferred function bodies are excluded except recognized direct forEach callbacks. Syntax does not establish engine-specific string allocation complexity."
8510
8627
  ],
8511
8628
  examples: [
8512
8629
  {
@@ -8624,6 +8741,7 @@ function enclosingLoop(node) {
8624
8741
  if ((parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression") && parent.parent.type === "CallExpression" && parent.parent.arguments[0] === parent && parent.parent.callee.type === "MemberExpression" && !parent.parent.callee.computed && parent.parent.callee.property.type === "Identifier" && parent.parent.callee.property.name === "forEach") {
8625
8742
  return parent.parent;
8626
8743
  }
8744
+ if (parent.type === "ArrowFunctionExpression" || parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") return null;
8627
8745
  if (LOOP_NODE_TYPES.has(parent.type)) {
8628
8746
  const loop = parent;
8629
8747
  if (loop.body === child) {
@@ -8635,6 +8753,19 @@ function enclosingLoop(node) {
8635
8753
  }
8636
8754
  return null;
8637
8755
  }
8756
+ function immediatelyExitsLoop(node, loop) {
8757
+ if (!LOOP_NODE_TYPES.has(loop.type) || node.parent.type !== "ExpressionStatement") return false;
8758
+ const statement = node.parent;
8759
+ const block = statement.parent;
8760
+ if (block.type !== "BlockStatement") return false;
8761
+ const next = block.body[block.body.indexOf(statement) + 1];
8762
+ if (next?.type !== "BreakStatement" && next?.type !== "ReturnStatement" && next?.type !== "ThrowStatement") return false;
8763
+ if (next.type === "BreakStatement" && next.label !== null) return false;
8764
+ for (let current = block; current !== void 0 && current !== loop; current = current.parent) {
8765
+ if (current.type === "TryStatement" || next.type === "BreakStatement" && current.type === "SwitchStatement") return false;
8766
+ }
8767
+ return true;
8768
+ }
8638
8769
  function isSmallStaticForLoop(node) {
8639
8770
  if (node.type !== "ForStatement" || node.init?.type !== "VariableDeclaration" || node.init.declarations.length !== 1 || node.test?.type !== "BinaryExpression" || node.test.operator !== "<" && node.test.operator !== "<=" || node.update?.type !== "UpdateExpression" || node.update.operator !== "++") {
8640
8771
  return false;
@@ -8673,12 +8804,12 @@ var no_string_concat_in_loop_default = createRule({
8673
8804
  meta: {
8674
8805
  type: "suggestion",
8675
8806
  docs: {
8676
- description: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead."
8807
+ description: NO_STRING_CONCAT_IN_LOOP_DOCUMENTATION.summary
8677
8808
  },
8678
8809
  schema: [],
8679
8810
  messages: {
8680
- noStringConcatInLoop: 'Avoid building a string with `+=` inside a loop \u2014 this is O(n^2). Push the parts onto an array and use `arr.join("")` after the loop.',
8681
- noStringReduce: "Avoid concatenating a growing string in `reduce` \u2014 this is O(n^2). Map the fragments and join them once instead."
8811
+ noStringConcatInLoop: "This loop repeatedly accumulates a growing string. Consider collecting fragments and joining once; preserve coercion timing and intermediate reads, and measure performance-sensitive paths.",
8812
+ noStringReduce: "This reduce repeatedly accumulates a growing string. Consider mapping fragments and joining once if coercion timing and intermediate observations are unchanged."
8682
8813
  }
8683
8814
  },
8684
8815
  defaultOptions: [],
@@ -8705,6 +8836,7 @@ var no_string_concat_in_loop_default = createRule({
8705
8836
  if (loop === null) {
8706
8837
  return;
8707
8838
  }
8839
+ if (immediatelyExitsLoop(node, loop)) return;
8708
8840
  if (isSmallStaticForLoop(loop)) {
8709
8841
  return;
8710
8842
  }
@@ -8740,12 +8872,12 @@ var no_string_concat_in_loop_default = createRule({
8740
8872
  // src/rules/no-tautological-expect.ts
8741
8873
  var import_utils47 = require("@typescript-eslint/utils");
8742
8874
  var NO_TAUTOLOGICAL_EXPECT_DOCUMENTATION = {
8743
- summary: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail.",
8875
+ summary: "Disallow supported literal-only assertions that are statically known to pass.",
8744
8876
  rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
8745
8877
  remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
8746
8878
  category: "testing",
8747
8879
  limitations: [
8748
- "Only direct supported `expect` matcher calls in recognized test files are inspected."
8880
+ "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."
8749
8881
  ],
8750
8882
  examples: [
8751
8883
  {
@@ -8782,11 +8914,11 @@ var NUMERIC_SIGNS = /* @__PURE__ */ new Set(["-", "+"]);
8782
8914
  function isLiteral(node) {
8783
8915
  switch (node.type) {
8784
8916
  case import_utils47.AST_NODE_TYPES.Literal:
8785
- return true;
8917
+ return !("regex" in node);
8786
8918
  case import_utils47.AST_NODE_TYPES.TemplateLiteral:
8787
8919
  return node.expressions.length === 0;
8788
8920
  case import_utils47.AST_NODE_TYPES.UnaryExpression:
8789
- return NUMERIC_SIGNS.has(node.operator) && isLiteral(node.argument);
8921
+ return NUMERIC_SIGNS.has(node.operator) && node.argument.type === import_utils47.AST_NODE_TYPES.Literal && typeof node.argument.value === "number";
8790
8922
  case import_utils47.AST_NODE_TYPES.ArrayExpression:
8791
8923
  return node.elements.every((element) => element !== null && isLiteral(element));
8792
8924
  case import_utils47.AST_NODE_TYPES.ObjectExpression:
@@ -8800,6 +8932,43 @@ function isLiteral(node) {
8800
8932
  function isStructuralLiteral(node) {
8801
8933
  return node.type === import_utils47.AST_NODE_TYPES.ArrayExpression || node.type === import_utils47.AST_NODE_TYPES.ObjectExpression;
8802
8934
  }
8935
+ function passesZeroArgumentMatcher(node, matcher) {
8936
+ let value;
8937
+ switch (node.type) {
8938
+ case import_utils47.AST_NODE_TYPES.Literal:
8939
+ value = node.value;
8940
+ break;
8941
+ case import_utils47.AST_NODE_TYPES.TemplateLiteral:
8942
+ value = node.quasis[0]?.value.cooked;
8943
+ break;
8944
+ case import_utils47.AST_NODE_TYPES.UnaryExpression:
8945
+ if (node.argument.type !== import_utils47.AST_NODE_TYPES.Literal || typeof node.argument.value !== "number") return false;
8946
+ value = node.operator === "-" ? -node.argument.value : node.argument.value;
8947
+ break;
8948
+ case import_utils47.AST_NODE_TYPES.ArrayExpression:
8949
+ case import_utils47.AST_NODE_TYPES.ObjectExpression:
8950
+ value = {};
8951
+ break;
8952
+ default:
8953
+ return false;
8954
+ }
8955
+ switch (matcher) {
8956
+ case "toBeDefined":
8957
+ return value !== void 0;
8958
+ case "toBeUndefined":
8959
+ return value === void 0;
8960
+ case "toBeNull":
8961
+ return value === null;
8962
+ case "toBeTruthy":
8963
+ return Boolean(value);
8964
+ case "toBeFalsy":
8965
+ return !value;
8966
+ case "toBeNaN":
8967
+ return typeof value === "number" && Number.isNaN(value);
8968
+ default:
8969
+ return false;
8970
+ }
8971
+ }
8803
8972
  function expectOperand(callee) {
8804
8973
  const receiver = callee.object;
8805
8974
  if (receiver.type !== import_utils47.AST_NODE_TYPES.CallExpression || receiver.callee.type !== import_utils47.AST_NODE_TYPES.Identifier || receiver.callee.name !== "expect" || receiver.arguments.length !== 1) {
@@ -8813,12 +8982,12 @@ var no_tautological_expect_default = createRule({
8813
8982
  meta: {
8814
8983
  type: "problem",
8815
8984
  docs: {
8816
- description: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail."
8985
+ description: "Disallow supported literal-only assertions that are statically known to pass."
8817
8986
  },
8818
8987
  schema: [],
8819
8988
  messages: {
8820
- 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.",
8821
- 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."
8989
+ 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.",
8990
+ tautologicalMatcher: "`expect({{operand}}).{{matcher}}()` is statically known to pass. Assert on a produced value or remove only the redundant assertion, preserving other coverage."
8822
8991
  }
8823
8992
  },
8824
8993
  defaultOptions: [],
@@ -8840,11 +9009,20 @@ var no_tautological_expect_default = createRule({
8840
9009
  return;
8841
9010
  }
8842
9011
  const matcher = callee.property.name;
9012
+ if (callee.object.type !== import_utils47.AST_NODE_TYPES.CallExpression || callee.object.callee.type !== import_utils47.AST_NODE_TYPES.Identifier) return;
9013
+ const expectIdentifier = callee.object.callee;
9014
+ const variable = import_utils47.ASTUtils.findVariable(context.sourceCode.getScope(expectIdentifier), expectIdentifier.name);
9015
+ if (variable !== null && variable.defs.some((definition) => {
9016
+ if (definition.node.type !== import_utils47.AST_NODE_TYPES.ImportSpecifier) return true;
9017
+ const declaration = definition.node.parent;
9018
+ const imported = definition.node.imported;
9019
+ return declaration.type !== import_utils47.AST_NODE_TYPES.ImportDeclaration || !["vitest", "@jest/globals", "@playwright/test", "bun:test"].includes(String(declaration.source.value)) || (imported.type === import_utils47.AST_NODE_TYPES.Identifier ? imported.name : imported.value) !== "expect";
9020
+ })) return;
8843
9021
  const operand = expectOperand(callee);
8844
9022
  if (operand === null || !isLiteral(operand)) {
8845
9023
  return;
8846
9024
  }
8847
- if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0) {
9025
+ if (ZERO_ARG_MATCHERS.has(matcher) && node.arguments.length === 0 && passesZeroArgumentMatcher(operand, matcher)) {
8848
9026
  context.report({
8849
9027
  node,
8850
9028
  messageId: "tautologicalMatcher",
@@ -10096,15 +10274,16 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
10096
10274
  var NO_UNSAFE_MOCK_CASTING_DOCUMENTATION = {
10097
10275
  summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
10098
10276
  rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
10099
- remediation: "Use the test framework's `mocked` helper to obtain the typed mock reference.",
10277
+ 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.",
10100
10278
  category: "testing",
10101
- limitations: ["Only mock types imported from Vitest or Jest modules are inspected."],
10279
+ 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."],
10280
+ references: ["https://vitest.dev/api/vi.html#vi-mocked"],
10102
10281
  examples: [
10103
10282
  {
10104
10283
  id: "typed-mock-helper",
10105
10284
  title: "Use the framework helper",
10106
10285
  outcome: "no-match",
10107
- files: [{ path: "src/client.test.ts", source: "const m = vi.mocked(myFn);" }],
10286
+ 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);" }],
10108
10287
  focusPath: "src/client.test.ts",
10109
10288
  expectedCount: 0,
10110
10289
  public: true
@@ -10130,7 +10309,7 @@ var no_unsafe_mock_casting_default = createRule({
10130
10309
  },
10131
10310
  schema: [],
10132
10311
  messages: {
10133
- unsafeMockCast: "Do not cast to a Mock type. Use `vi.mocked(fn)` or `jest.mocked(fn)` instead to preserve type safety."
10312
+ 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."
10134
10313
  }
10135
10314
  },
10136
10315
  defaultOptions: [],
@@ -10192,7 +10371,7 @@ var import_utils55 = require("@typescript-eslint/utils");
10192
10371
  var ts2 = __toESM(require("typescript"), 1);
10193
10372
  var NO_ZOD_NATIVE_ENUM_DOCUMENTATION = {
10194
10373
  summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
10195
- rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
10374
+ 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.",
10196
10375
  remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
10197
10376
  category: "maintainability",
10198
10377
  autofix: "none",
@@ -10370,7 +10549,7 @@ var TEST_LOOPS_OVER_LITERAL_CASES_DOCUMENTATION = {
10370
10549
  remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
10371
10550
  category: "testing",
10372
10551
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
10373
- limitations: ["Only inline literal for-of cases containing framework assertions are reported."],
10552
+ 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."],
10374
10553
  examples: [
10375
10554
  { 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 },
10376
10555
  { 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 }
@@ -10517,7 +10696,7 @@ var test_loops_over_literal_cases_default = createRule({
10517
10696
  },
10518
10697
  schema: [],
10519
10698
  messages: {
10520
- 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."
10699
+ 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."
10521
10700
  }
10522
10701
  },
10523
10702
  defaultOptions: [],
@@ -10542,6 +10721,9 @@ var test_loops_over_literal_cases_default = createRule({
10542
10721
  if (enclosing === null || !isTestBody2(enclosing, isFrameworkTest)) {
10543
10722
  return;
10544
10723
  }
10724
+ for (let current = node; current !== void 0 && current !== enclosing; current = current.parent) {
10725
+ if (current.parent?.type === import_utils56.AST_NODE_TYPES.BlockStatement && current.parent.body.at(-1) !== current) return;
10726
+ }
10545
10727
  const cases = unwrapExpression(node.right);
10546
10728
  const callbackParameters = new Set(
10547
10729
  enclosing.params.flatMap((parameter) => parameter.type === import_utils56.AST_NODE_TYPES.Identifier ? [parameter.name] : [])
@@ -10551,6 +10733,16 @@ var test_loops_over_literal_cases_default = createRule({
10551
10733
  ) || !walkOwnScope2(node.body, (current) => isAssertion2(current, isFrameworkAssertion)) || walkOwnScope2(node.body, (current) => opensSubtest(current, callbackParameters)) || walkOwnScope2(node.body, (current) => LOOP_CARRIED_CONTROL.has(current.type))) {
10552
10734
  return;
10553
10735
  }
10736
+ const capturesSetup = walkOwnScope2(node.body, (current) => {
10737
+ if (current.type !== import_utils56.AST_NODE_TYPES.Identifier) return false;
10738
+ const variable = import_utils56.ASTUtils.findVariable(context.sourceCode.getScope(current), current.name);
10739
+ if (variable === null || !variable.references.some((reference) => reference.identifier === current)) return false;
10740
+ return variable.defs.some((definition) => {
10741
+ const declaration = definition.name;
10742
+ 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]);
10743
+ });
10744
+ });
10745
+ if (capturesSetup) return;
10554
10746
  context.report({
10555
10747
  node,
10556
10748
  messageId: "literalCaseLoop",
@@ -10771,13 +10963,12 @@ var import_utils59 = require("@typescript-eslint/utils");
10771
10963
  var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
10772
10964
  summary: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members.",
10773
10965
  rationale: "ECMAScript private names enforce encapsulation at runtime instead of erasing the boundary during compilation.",
10774
- remediation: "Replace the TypeScript `private` modifier and all proven same-class references with an ECMAScript private name.",
10966
+ remediation: "Review reflection, instance escape and framework contracts before replacing TypeScript privacy with ECMAScript private names and updating references.",
10775
10967
  category: "maintainability",
10776
- autofix: "safe",
10968
+ autofix: "none",
10777
10969
  limitations: [
10778
10970
  "Ambient, abstract, computed, decorated, override, parameter-property, and generated declarations are excluded.",
10779
- "A fix is offered only for an undecorated, unexported class declaration with no references outside its body and when type information proves every use is a direct `this.name` access inside that class.",
10780
- "Overloads, modifier-adjacent comments, reflection, and any potentially cross-file or escaping class remain report-only."
10971
+ "Migration is report-only: type information cannot prove that instances or constructors never escape through this, or that reflection and framework serialization do not observe ordinary private properties."
10781
10972
  ],
10782
10973
  references: ["https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_elements"],
10783
10974
  examples: [
@@ -10797,19 +10988,11 @@ var PREFER_ECMASCRIPT_PRIVATE_MEMBERS_DOCUMENTATION = {
10797
10988
  files: [{ path: "src/vault.ts", source: "class Vault { private read() { return 1; } open() { return this.read(); } }" }],
10798
10989
  focusPath: "src/vault.ts",
10799
10990
  expectedCount: 1,
10800
- public: true,
10801
- fixedFiles: [{ path: "src/vault.ts", source: "class Vault { #read() { return 1; } open() { return this.#read(); } }" }]
10991
+ public: true
10802
10992
  }
10803
10993
  ]
10804
10994
  };
10805
- function reportClass2(context, services, owner) {
10806
- const parent = owner.parent;
10807
- const directlyExported = parent.type === import_utils59.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils59.AST_NODE_TYPES.ExportDefaultDeclaration;
10808
- const locallyClosed = !directlyExported && owner.decorators.length === 0 && owner.type === import_utils59.AST_NODE_TYPES.ClassDeclaration && context.sourceCode.getDeclaredVariables(owner).every(
10809
- (variable) => variable.references.every(
10810
- (reference) => reference.identifier.range[0] >= owner.range[0] && reference.identifier.range[1] <= owner.range[1]
10811
- )
10812
- );
10995
+ function reportClass2(context, owner) {
10813
10996
  const groups = /* @__PURE__ */ new Map();
10814
10997
  for (const member of owner.body.body) {
10815
10998
  if (!isConvertible(member)) continue;
@@ -10822,18 +11005,10 @@ function reportClass2(context, services, owner) {
10822
11005
  for (const [name, members] of groups) {
10823
11006
  const first = members[0];
10824
11007
  if (first === void 0) continue;
10825
- const fix = locallyClosed ? privateMemberFixes(
10826
- context,
10827
- services,
10828
- owner,
10829
- members,
10830
- true
10831
- ) : void 0;
10832
11008
  context.report({
10833
11009
  node: first.key,
10834
11010
  messageId: "preferEcmascriptPrivate",
10835
- data: { name },
10836
- ...fix === void 0 ? {} : { fix }
11011
+ data: { name }
10837
11012
  });
10838
11013
  }
10839
11014
  }
@@ -10847,7 +11022,6 @@ var prefer_ecmascript_private_members_default = createRule({
10847
11022
  meta: {
10848
11023
  type: "suggestion",
10849
11024
  docs: { description: "Prefer ECMAScript `#private` class members over TypeScript-only `private` members." },
10850
- fixable: "code",
10851
11025
  schema: [],
10852
11026
  messages: {
10853
11027
  preferEcmascriptPrivate: "TypeScript `private {{name}}` is erased at runtime; use the ECMAScript private name `#{{name}}`."
@@ -10864,8 +11038,8 @@ var prefer_ecmascript_private_members_default = createRule({
10864
11038
  }
10865
11039
  if (services === null) return {};
10866
11040
  return {
10867
- ClassDeclaration: (node) => reportClass2(context, services, node),
10868
- ClassExpression: (node) => reportClass2(context, services, node)
11041
+ ClassDeclaration: (node) => reportClass2(context, node),
11042
+ ClassExpression: (node) => reportClass2(context, node)
10869
11043
  };
10870
11044
  }
10871
11045
  });
@@ -10875,10 +11049,10 @@ var import_utils60 = require("@typescript-eslint/utils");
10875
11049
  var import_utils61 = require("@typescript-eslint/utils");
10876
11050
  var PREFER_DISCRIMINATED_UNION_DOCUMENTATION = {
10877
11051
  summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
10878
- rationale: "A boolean status plus optional branch data permits contradictory and incomplete states.",
10879
- remediation: "Represent each result branch as a discriminated union member with its required payload.",
11052
+ rationale: "When success and failure are mutually exclusive outcomes, a boolean plus optional branch data permits contradictory and incomplete states.",
11053
+ remediation: "If the outcomes are mutually exclusive, represent each branch as a discriminated union member with its required payload.",
10880
11054
  category: "correctness",
10881
- limitations: ["Only local object shapes with recognized positive status and payload names are inspected."],
11055
+ limitations: ["Only local object shapes with recognized non-computed status and payload names are inspected. Names do not prove that partial-success outcomes are forbidden; review the domain before changing its representation."],
10882
11056
  examples: [
10883
11057
  { id: "explicit-result-branches", title: "Use explicit result branches", outcome: "no-match", files: [{ path: "src/result.ts", source: "type Result = { ok: true; data: string } | { ok: false; error: string };" }], focusPath: "src/result.ts", expectedCount: 0, public: true },
10884
11058
  { id: "optional-result-payloads", title: "Do not make both result payloads optional", outcome: "match", files: [{ path: "src/result.ts", source: "type Result = { ok: boolean; data?: string; error?: string };" }], focusPath: "src/result.ts", expectedCount: 1, public: true }
@@ -10942,7 +11116,7 @@ function looksLikeMutuallyExclusiveState(typeLiteral) {
10942
11116
  return statusMemberCount === REQUIRED_STATUS_MEMBER_COUNT && hasFailurePayload && (hasSuccessPayload || !hasUnrecognizedMember);
10943
11117
  }
10944
11118
  function getMemberName(member) {
10945
- if (member.type !== import_utils61.AST_NODE_TYPES.TSPropertySignature) {
11119
+ if (member.type !== import_utils61.AST_NODE_TYPES.TSPropertySignature || member.computed) {
10946
11120
  return null;
10947
11121
  }
10948
11122
  const { key } = member;
@@ -10978,7 +11152,7 @@ var prefer_discriminated_union_default = createRule({
10978
11152
  },
10979
11153
  schema: [],
10980
11154
  messages: {
10981
- preferDiscriminatedUnion: "This object type uses a boolean status flag alongside several optional fields, which lets illegal states be representable. Model it as a `z.discriminatedUnion` / discriminated union (e.g. `{ ok: true; data: T } | { ok: false; error: E }`) to make illegal states unrepresentable."
11155
+ preferDiscriminatedUnion: "This object type combines a boolean status with optional payloads. If success and failure are mutually exclusive, consider a discriminated union such as `{ ok: true; data: T } | { ok: false; error: E }`."
10982
11156
  }
10983
11157
  },
10984
11158
  defaultOptions: [],
@@ -11189,7 +11363,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11189
11363
  category: "correctness",
11190
11364
  autofix: "none",
11191
11365
  limitations: [
11192
- "Only direct identifier keys in application-owned z.object/z.strictObject schemas are checked.",
11366
+ "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.",
11193
11367
  "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.",
11194
11368
  "Quoted/computed protocol keys, generated/vendor code, tests, fixtures, and non-Zod schemas are excluded."
11195
11369
  ],
@@ -11201,7 +11375,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11201
11375
  files: [
11202
11376
  {
11203
11377
  path: "src/request.ts",
11204
- source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeoutMs: z.number().int().min(1) });"
11378
+ source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeoutMs: z.number().int().min(1000).max(300000).default(30000) });"
11205
11379
  }
11206
11380
  ],
11207
11381
  focusPath: "src/request.ts",
@@ -11215,7 +11389,7 @@ var PREFER_MILLISECOND_CONTROL_DURATION_SCHEMA_DOCUMENTATION = {
11215
11389
  files: [
11216
11390
  {
11217
11391
  path: "src/request.ts",
11218
- source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeout_seconds: z.number().int().min(1) });"
11392
+ source: "import { z } from 'zod';\nexport const RequestSchema = z.object({ timeout_seconds: z.number().int().min(1).max(300).default(30) });"
11219
11393
  }
11220
11394
  ],
11221
11395
  focusPath: "src/request.ts",
@@ -11247,6 +11421,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11247
11421
  }
11248
11422
  const zodNamespaces = /* @__PURE__ */ new Set();
11249
11423
  const objectFactories = /* @__PURE__ */ new Set();
11424
+ const numberFactories = /* @__PURE__ */ new Set();
11250
11425
  function binding(identifier) {
11251
11426
  return import_utils63.ASTUtils.findVariable(context.sourceCode.getScope(identifier), identifier.name);
11252
11427
  }
@@ -11266,10 +11441,27 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11266
11441
  const variable = binding(callee.object);
11267
11442
  return variable !== null && zodNamespaces.has(variable);
11268
11443
  }
11444
+ function isNumericSchema(node) {
11445
+ if (node.type !== import_utils63.AST_NODE_TYPES.CallExpression) return false;
11446
+ const callee = node.callee;
11447
+ if (callee.type === import_utils63.AST_NODE_TYPES.Identifier) {
11448
+ const variable = binding(callee);
11449
+ return variable !== null && numberFactories.has(variable);
11450
+ }
11451
+ if (callee.type !== import_utils63.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils63.AST_NODE_TYPES.Identifier) return false;
11452
+ if (callee.object.type === import_utils63.AST_NODE_TYPES.Identifier) {
11453
+ const variable = binding(callee.object);
11454
+ return callee.property.name === "number" && variable !== null && zodNamespaces.has(variable);
11455
+ }
11456
+ return ["int", "min", "max", "positive", "nonnegative", "finite", "multipleOf", "optional", "nullable", "nullish", "default", "describe", "brand", "readonly"].includes(callee.property.name) && isNumericSchema(callee.object);
11457
+ }
11269
11458
  return {
11270
11459
  ImportDeclaration(node) {
11271
11460
  if (!isZodModule(node.source.value)) return;
11272
11461
  for (const specifier of node.specifiers) {
11462
+ if (specifier.type === import_utils63.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils63.AST_NODE_TYPES.Identifier && specifier.imported.name === "number") {
11463
+ record(numberFactories, specifier.local);
11464
+ }
11273
11465
  if (specifier.type === import_utils63.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils63.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils63.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils63.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") {
11274
11466
  record(zodNamespaces, specifier.local);
11275
11467
  } else if (specifier.type === import_utils63.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils63.AST_NODE_TYPES.Identifier && (specifier.imported.name === "object" || specifier.imported.name === "strictObject")) {
@@ -11282,7 +11474,7 @@ var prefer_millisecond_control_duration_schema_default = createRule({
11282
11474
  const shape = node.arguments[0];
11283
11475
  if (shape?.type !== import_utils63.AST_NODE_TYPES.ObjectExpression) return;
11284
11476
  for (const member of shape.properties) {
11285
- if (member.type !== import_utils63.AST_NODE_TYPES.Property) continue;
11477
+ if (member.type !== import_utils63.AST_NODE_TYPES.Property || !isNumericSchema(member.value)) continue;
11286
11478
  const key = directIdentifierKey(member);
11287
11479
  if (key === null || !CONTROL_SECONDS_RE.test(key.name) && !CONTROL_SECONDS_CAMEL_RE.test(key.name)) {
11288
11480
  continue;
@@ -11302,7 +11494,7 @@ var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
11302
11494
  remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
11303
11495
  category: "correctness",
11304
11496
  limitations: [
11305
- "The rule skips generated files, test files, JavaScript files, and collections that are deliberately mutated in their declaring module."
11497
+ "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."
11306
11498
  ],
11307
11499
  examples: [
11308
11500
  {
@@ -11456,7 +11648,7 @@ var prefer_immutable_module_constant_default = createRule({
11456
11648
  }
11457
11649
  const exportedNames2 = /* @__PURE__ */ new Set();
11458
11650
  const typeAliases2 = /* @__PURE__ */ new Map();
11459
- const mutatesThroughConstAlias = (root) => {
11651
+ const mutatesThroughAlias = (root) => {
11460
11652
  const pending = [root];
11461
11653
  const seen = /* @__PURE__ */ new Set();
11462
11654
  while (pending.length > 0) {
@@ -11468,7 +11660,7 @@ var prefer_immutable_module_constant_default = createRule({
11468
11660
  if (identifier.type !== import_utils64.AST_NODE_TYPES.Identifier) continue;
11469
11661
  if (referenceMutates(identifier, isUnshadowedGlobal3)) return true;
11470
11662
  const declarator = identifier.parent;
11471
- if (declarator.type !== import_utils64.AST_NODE_TYPES.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== import_utils64.AST_NODE_TYPES.Identifier || declarator.parent.type !== import_utils64.AST_NODE_TYPES.VariableDeclaration || declarator.parent.kind !== "const") {
11663
+ if (declarator.type !== import_utils64.AST_NODE_TYPES.VariableDeclarator || declarator.init !== identifier || declarator.id.type !== import_utils64.AST_NODE_TYPES.Identifier || declarator.parent.type !== import_utils64.AST_NODE_TYPES.VariableDeclaration) {
11472
11664
  continue;
11473
11665
  }
11474
11666
  const alias = sourceCode.getDeclaredVariables(declarator)[0];
@@ -11517,7 +11709,7 @@ var prefer_immutable_module_constant_default = createRule({
11517
11709
  return;
11518
11710
  }
11519
11711
  const variable = sourceCode.getDeclaredVariables(node)[0];
11520
- if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughConstAlias(variable)) {
11712
+ if (!directlyExported && !exportedNames2.has(node.id.name) && variable !== void 0 && mutatesThroughAlias(variable)) {
11521
11713
  return;
11522
11714
  }
11523
11715
  context.report({
@@ -11944,7 +12136,7 @@ var PREFER_MODULE_LEVEL_CONSTANT_DOCUMENTATION = {
11944
12136
  rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
11945
12137
  remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
11946
12138
  category: "performance",
11947
- limitations: ["Collections that are small, mutated, escape the function, or depend on local values are not reported."],
12139
+ 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."],
11948
12140
  examples: [
11949
12141
  { 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 },
11950
12142
  { 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 }
@@ -12099,12 +12291,16 @@ var NON_RETAINING_BUILTINS = /* @__PURE__ */ new Map(
12099
12291
  ]
12100
12292
  );
12101
12293
  function isSafeRead(identifier) {
12102
- const parent = identifier.parent;
12294
+ let parent = identifier.parent;
12103
12295
  if (parent.type === import_utils66.AST_NODE_TYPES.MemberExpression) {
12104
12296
  if (parent.object !== identifier) {
12105
12297
  return true;
12106
12298
  }
12299
+ while (parent.parent.type === import_utils66.AST_NODE_TYPES.MemberExpression && parent.parent.object === parent) {
12300
+ parent = parent.parent;
12301
+ }
12107
12302
  const grandparent = parent.parent;
12303
+ if (grandparent.type === import_utils66.AST_NODE_TYPES.VariableDeclarator || grandparent.type === import_utils66.AST_NODE_TYPES.SpreadElement) return false;
12108
12304
  if (grandparent.type === import_utils66.AST_NODE_TYPES.AssignmentExpression && grandparent.left === parent) {
12109
12305
  return false;
12110
12306
  }
@@ -12114,7 +12310,7 @@ function isSafeRead(identifier) {
12114
12310
  if (grandparent.type === import_utils66.AST_NODE_TYPES.UnaryExpression && grandparent.operator === "delete") {
12115
12311
  return false;
12116
12312
  }
12117
- if (!parent.computed && parent.property.type === import_utils66.AST_NODE_TYPES.Identifier && MUTATING_METHODS2.has(parent.property.name) && grandparent.type === import_utils66.AST_NODE_TYPES.CallExpression && grandparent.callee === parent) {
12313
+ if (grandparent.type === import_utils66.AST_NODE_TYPES.CallExpression && grandparent.callee === parent && (parent.computed ? parent.property.type !== import_utils66.AST_NODE_TYPES.Literal || typeof parent.property.value !== "string" || MUTATING_METHODS2.has(parent.property.value) : parent.property.type === import_utils66.AST_NODE_TYPES.Identifier && MUTATING_METHODS2.has(parent.property.name))) {
12118
12314
  return false;
12119
12315
  }
12120
12316
  return true;
@@ -12222,9 +12418,15 @@ var prefer_module_level_constant_default = createRule({
12222
12418
  if (node.id.type !== import_utils66.AST_NODE_TYPES.Identifier || node.init === null) {
12223
12419
  return;
12224
12420
  }
12225
- if (enclosingFunction3(node) === null) {
12421
+ const owner = enclosingFunction3(node);
12422
+ if (owner === null) {
12226
12423
  return;
12227
12424
  }
12425
+ let expression = owner;
12426
+ while (expression.parent !== void 0 && unwrap4(expression.parent) === expression) {
12427
+ expression = expression.parent;
12428
+ }
12429
+ if (expression.parent?.type === import_utils66.AST_NODE_TYPES.CallExpression && expression.parent.callee === expression) return;
12228
12430
  const candidate2 = classify(node.init, checkRegex);
12229
12431
  if (candidate2 === null) {
12230
12432
  return;
@@ -12249,10 +12451,10 @@ var prefer_module_level_constant_default = createRule({
12249
12451
  var import_utils67 = require("@typescript-eslint/utils");
12250
12452
  var PREFER_MODULE_LEVEL_SCHEMA_DOCUMENTATION = {
12251
12453
  summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
12252
- rationale: "A closed schema created inside a function is rebuilt on every call and cannot be reused or exported for inference.",
12454
+ 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.",
12253
12455
  remediation: "Move the closed schema declaration to module scope and reference it from the function.",
12254
12456
  category: "performance",
12255
- limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded."],
12457
+ 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."],
12256
12458
  examples: [
12257
12459
  { 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 },
12258
12460
  { 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 }
@@ -12269,6 +12471,36 @@ var DEFAULT_FACTORIES = [
12269
12471
  "union"
12270
12472
  ];
12271
12473
  var DEFAULT_MIN_PROPERTIES = 2;
12474
+ var CONSTRUCTION_FACTORIES = /* @__PURE__ */ new Set([
12475
+ ...DEFAULT_FACTORIES,
12476
+ "any",
12477
+ "array",
12478
+ "bigint",
12479
+ "boolean",
12480
+ "custom",
12481
+ "date",
12482
+ "enum",
12483
+ "instanceof",
12484
+ "lazy",
12485
+ "literal",
12486
+ "map",
12487
+ "nan",
12488
+ "nativeEnum",
12489
+ "never",
12490
+ "null",
12491
+ "nullable",
12492
+ "nullish",
12493
+ "number",
12494
+ "optional",
12495
+ "preprocess",
12496
+ "promise",
12497
+ "set",
12498
+ "string",
12499
+ "symbol",
12500
+ "undefined",
12501
+ "unknown",
12502
+ "void"
12503
+ ]);
12272
12504
  var MEMO_CALLEES = /* @__PURE__ */ new Set([
12273
12505
  "lazy",
12274
12506
  "memo",
@@ -12345,7 +12577,7 @@ function outermostEnclosingFunction(node) {
12345
12577
  }
12346
12578
  return outermost;
12347
12579
  }
12348
- function subtreeSome(root, predicate) {
12580
+ function subtreeSome(root, predicate, skipDeferredFunctions = false) {
12349
12581
  let found = false;
12350
12582
  const visit = (value) => {
12351
12583
  if (found || value === null || typeof value !== "object") {
@@ -12361,6 +12593,7 @@ function subtreeSome(root, predicate) {
12361
12593
  if (typeof candidate2.type !== "string") {
12362
12594
  return;
12363
12595
  }
12596
+ if (skipDeferredFunctions && FUNCTION_TYPES8.has(candidate2.type)) return;
12364
12597
  if (predicate(candidate2)) {
12365
12598
  found = true;
12366
12599
  return;
@@ -12459,6 +12692,15 @@ var prefer_module_level_schema_default = createRule({
12459
12692
  function isZodCall(node) {
12460
12693
  return node.type === import_utils67.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils67.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.object.type === import_utils67.AST_NODE_TYPES.Identifier && zodNamespaces.has(node.callee.object.name);
12461
12694
  }
12695
+ function isSchemaConstruction(node) {
12696
+ const callee = node.callee;
12697
+ if (callee.type !== import_utils67.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils67.AST_NODE_TYPES.Identifier || TERMINAL_METHODS.has(callee.property.name)) return false;
12698
+ if (callee.object.type === import_utils67.AST_NODE_TYPES.CallExpression) return isSchemaConstruction(callee.object);
12699
+ return isZodCall(node) && CONSTRUCTION_FACTORIES.has(callee.property.name);
12700
+ }
12701
+ function hasEagerComputation(node) {
12702
+ return subtreeSome(node, (inner) => inner.type === import_utils67.AST_NODE_TYPES.NewExpression || inner.type === import_utils67.AST_NODE_TYPES.TaggedTemplateExpression || inner.type === import_utils67.AST_NODE_TYPES.CallExpression && !isSchemaConstruction(inner), true);
12703
+ }
12462
12704
  function isCovered(node) {
12463
12705
  let current = node.parent ?? void 0;
12464
12706
  while (current !== void 0) {
@@ -12588,6 +12830,7 @@ var prefer_module_level_schema_default = createRule({
12588
12830
  return;
12589
12831
  }
12590
12832
  const outermost = outermostSchemaExpression(expression);
12833
+ if (hasEagerComputation(outermost)) return;
12591
12834
  if (outermost !== expression && (readsReceiver(outermost) || buildsLocalizedText(outermost) || !closesOverNothing(outermost, enclosing))) {
12592
12835
  return;
12593
12836
  }
@@ -12725,7 +12968,8 @@ var PREFER_MODULE_LEVEL_REFINED_SCHEMA_DOCUMENTATION = {
12725
12968
  limitations: [
12726
12969
  "Composite object/record/tuple/union schemas are owned by prefer-module-level-schema.",
12727
12970
  "Schemas that depend on function-local or mutable state, localized text, receiver state, lazy construction, or recognized memoization are excluded.",
12728
- "Literal string z.enum domains are owned by prefer-shared-zod-enum."
12971
+ "Literal string z.enum domains are owned by prefer-shared-zod-enum.",
12972
+ "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."
12729
12973
  ],
12730
12974
  examples: [
12731
12975
  {
@@ -12767,7 +13011,7 @@ function collectReferences2(scope, output) {
12767
13011
  output.push(...scope.references);
12768
13012
  for (const child of scope.childScopes) collectReferences2(child, output);
12769
13013
  }
12770
- function subtreeSome2(root, predicate) {
13014
+ function subtreeSome2(root, predicate, skipDeferredFunctions = false) {
12771
13015
  let found = false;
12772
13016
  const visit = (value) => {
12773
13017
  if (found || value === null || typeof value !== "object") return;
@@ -12777,6 +13021,7 @@ function subtreeSome2(root, predicate) {
12777
13021
  }
12778
13022
  const candidate2 = value;
12779
13023
  if (typeof candidate2.type !== "string") return;
13024
+ if (skipDeferredFunctions && FUNCTION_TYPES9.has(candidate2.type)) return;
12780
13025
  if (predicate(candidate2)) {
12781
13026
  found = true;
12782
13027
  return;
@@ -12897,6 +13142,15 @@ var prefer_module_level_refined_schema_default = createRule({
12897
13142
  return names[1] ?? null;
12898
13143
  return null;
12899
13144
  }
13145
+ function isSchemaConstruction(node) {
13146
+ const callee = node.callee;
13147
+ if (callee.type !== import_utils68.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils68.AST_NODE_TYPES.Identifier || NON_SCHEMA_TERMINALS.has(callee.property.name)) return false;
13148
+ if (callee.object.type === import_utils68.AST_NODE_TYPES.CallExpression) return isSchemaConstruction(callee.object);
13149
+ return factoryName(node, FACTORIES) !== null || factoryName(node, COMPOSITE_FACTORIES) !== null;
13150
+ }
13151
+ function hasEagerComputation(node) {
13152
+ return subtreeSome2(node, (inner) => inner.type === import_utils68.AST_NODE_TYPES.NewExpression || inner.type === import_utils68.AST_NODE_TYPES.TaggedTemplateExpression || inner.type === import_utils68.AST_NODE_TYPES.CallExpression && !isSchemaConstruction(inner), true);
13153
+ }
12900
13154
  function isSharedEnumDomain(node, factory) {
12901
13155
  if (factory !== "enum") return false;
12902
13156
  const [argument] = node.arguments;
@@ -12965,7 +13219,7 @@ var prefer_module_level_refined_schema_default = createRule({
12965
13219
  const enclosing = outermostEnclosingFunction2(node);
12966
13220
  if (enclosing === void 0) return;
12967
13221
  const expression = schemaExpression2(node);
12968
- if (readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
13222
+ if (hasEagerComputation(expression) || readsReceiver2(expression) || buildsLocalizedText2(expression) || !closesOverNothing(expression, enclosing))
12969
13223
  return;
12970
13224
  context.report({ node, messageId: "hoistRefinedSchema" });
12971
13225
  }
@@ -12993,7 +13247,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
12993
13247
  outcome: "no-match",
12994
13248
  files: [{
12995
13249
  path: "src/schema.ts",
12996
- source: "import { z } from 'zod'; export const Version = z.literal([1, 2, 3]);"
13250
+ source: "import { z } from 'zod/v4'; export const Version = z.literal([1, 2, 3]);"
12997
13251
  }],
12998
13252
  focusPath: "src/schema.ts",
12999
13253
  expectedCount: 0,
@@ -13005,7 +13259,7 @@ var PREFER_MULTI_VALUE_ZOD_LITERAL_DOCUMENTATION = {
13005
13259
  outcome: "match",
13006
13260
  files: [{
13007
13261
  path: "src/schema.ts",
13008
- source: "import { z } from 'zod'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
13262
+ source: "import { z } from 'zod/v4'; export const Version = z.union([z.literal(1), z.literal(2), z.literal(3)]);"
13009
13263
  }],
13010
13264
  focusPath: "src/schema.ts",
13011
13265
  expectedCount: 1,
@@ -13128,7 +13382,7 @@ function isLiteralUnion(node) {
13128
13382
  function exportedContract(node) {
13129
13383
  let current = node;
13130
13384
  while (current !== void 0) {
13131
- if (current.type === import_utils70.AST_NODE_TYPES.ExportNamedDeclaration) return true;
13385
+ if (current.type === import_utils70.AST_NODE_TYPES.TSTypeAliasDeclaration || current.type === import_utils70.AST_NODE_TYPES.TSInterfaceDeclaration) return current.parent.type === import_utils70.AST_NODE_TYPES.ExportNamedDeclaration;
13132
13386
  if (current.type === import_utils70.AST_NODE_TYPES.Program) return false;
13133
13387
  current = current.parent ?? void 0;
13134
13388
  }
@@ -13164,11 +13418,11 @@ var import_utils71 = require("@typescript-eslint/utils");
13164
13418
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
13165
13419
  summary: "Prefer a named contract for structurally complex function return types.",
13166
13420
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
13167
- remediation: "Extract the return annotation to a named type or interface and reference that contract from the signature.",
13421
+ remediation: "Name the complex nested shape while preserving its generic wrappers and type parameters; reference the named contract from the return annotation.",
13168
13422
  category: "maintainability",
13169
13423
  limitations: [
13170
13424
  "Only explicit object types with at least three members and unions with at least three object variants are reported.",
13171
- "Generic wrappers such as Promise and Readonly are unwrapped one level at a time; inferred return types are outside this rule."
13425
+ "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."
13172
13426
  ],
13173
13427
  examples: [
13174
13428
  { 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 },
@@ -13226,7 +13480,7 @@ var PREFER_NATIVE_RANDOM_UUID_DOCUMENTATION = {
13226
13480
  remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
13227
13481
  category: "maintainability",
13228
13482
  autofix: "suggestion",
13229
- limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
13483
+ limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded. Suggestions require unshadowed globalThis and comment-free calls; verify native randomUUID availability in the deployment runtime."],
13230
13484
  examples: [
13231
13485
  { id: "native-random-uuid", title: "Use the platform UUID generator", outcome: "no-match", files: [{ path: "src/id.ts", source: "const id = globalThis.crypto.randomUUID();" }], focusPath: "src/id.ts", expectedCount: 0, public: true },
13232
13486
  { id: "uuid-v4-package", title: "Do not call uuid v4 without options", outcome: "match", files: [{ path: "src/id.ts", source: "import { v4 } from 'uuid'; const id = v4();" }], focusPath: "src/id.ts", expectedCount: 1, public: true }
@@ -13246,7 +13500,7 @@ var prefer_native_random_uuid_default = createRule({
13246
13500
  hasSuggestions: true,
13247
13501
  schema: [],
13248
13502
  messages: {
13249
- preferNative: "Use the Node 22 native `globalThis.crypto.randomUUID()` instead of the `uuid` package for UUID v4.",
13503
+ preferNative: "Where supported by the deployment runtime, prefer native `globalThis.crypto.randomUUID()` over the `uuid` package for UUID v4.",
13250
13504
  replaceWithNative: "Replace this UUID v4 call with the native implementation."
13251
13505
  }
13252
13506
  },
@@ -13262,15 +13516,17 @@ var prefer_native_random_uuid_default = createRule({
13262
13516
  if (variable !== null) destination.add(variable);
13263
13517
  }
13264
13518
  function report2(node) {
13519
+ const globalBinding = import_utils72.ASTUtils.findVariable(context.sourceCode.getScope(node), "globalThis");
13520
+ const canSuggest = (globalBinding?.defs.length ?? 0) === 0 && context.sourceCode.getCommentsInside(node).length === 0;
13265
13521
  context.report({
13266
13522
  node,
13267
13523
  messageId: "preferNative",
13268
- suggest: [
13524
+ suggest: canSuggest ? [
13269
13525
  {
13270
13526
  messageId: "replaceWithNative",
13271
13527
  fix: (fixer) => fixer.replaceText(node, "globalThis.crypto.randomUUID()")
13272
13528
  }
13273
- ]
13529
+ ] : []
13274
13530
  });
13275
13531
  }
13276
13532
  return {
@@ -13322,12 +13578,14 @@ var import_utils73 = require("@typescript-eslint/utils");
13322
13578
  var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
13323
13579
  summary: "Prefer the modern one-shot node:crypto hash API when streaming state is unnecessary.",
13324
13580
  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.",
13325
- 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.",
13581
+ 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.",
13326
13582
  category: "performance",
13327
13583
  limitations: [
13328
13584
  "Only bindings and inline calls with statically proven provenance from crypto or node:crypto are analyzed; arbitrary assignments and dynamic module specifiers are excluded.",
13329
- "Only a literal algorithm with exactly one update call is reported; streaming and incremental hashes remain valid."
13585
+ "Only a literal algorithm with exactly one update call is reported; streaming and incremental hashes remain valid.",
13586
+ "Runtime support and output encoding require manual review; no autofix or guaranteed speedup is promised."
13330
13587
  ],
13588
+ references: ["https://nodejs.org/api/crypto.html#cryptohashalgorithm-data-options"],
13331
13589
  examples: [
13332
13590
  { 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 },
13333
13591
  { 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 }
@@ -13456,6 +13714,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
13456
13714
  category: "performance",
13457
13715
  limitations: [
13458
13716
  "Tests and generated files are excluded.",
13717
+ "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.",
13459
13718
  "ESLint rule implementations under src/rules are excluded because visitor creation and execution are synchronous by contract.",
13460
13719
  "Only statically identifiable node:fs loads are inspected; filesystem objects passed through arbitrary functions or assignments require type-aware analysis."
13461
13720
  ],
@@ -13474,14 +13733,14 @@ function memberName5(node) {
13474
13733
  function unwrapAwait2(node) {
13475
13734
  return node.type === import_utils74.AST_NODE_TYPES.AwaitExpression ? node.argument : node;
13476
13735
  }
13477
- function isFsLoader(node) {
13736
+ function isFsLoader(node, isGlobal) {
13478
13737
  const expression = unwrapAwait2(node);
13479
13738
  if (expression.type === import_utils74.AST_NODE_TYPES.ImportExpression) return isFsSpecifier(expression.source);
13480
13739
  if (expression.type !== import_utils74.AST_NODE_TYPES.CallExpression || expression.arguments.length !== 1) return false;
13481
13740
  const [argument] = expression.arguments;
13482
13741
  if (argument === void 0 || argument.type === import_utils74.AST_NODE_TYPES.SpreadElement || !isFsSpecifier(argument)) return false;
13483
- if (expression.callee.type === import_utils74.AST_NODE_TYPES.Identifier) return expression.callee.name === "require";
13484
- return expression.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && expression.callee.object.type === import_utils74.AST_NODE_TYPES.Identifier && expression.callee.object.name === "process" && memberName5(expression.callee) === "getBuiltinModule";
13742
+ if (expression.callee.type === import_utils74.AST_NODE_TYPES.Identifier) return expression.callee.name === "require" && isGlobal(expression.callee);
13743
+ return expression.callee.type === import_utils74.AST_NODE_TYPES.MemberExpression && expression.callee.object.type === import_utils74.AST_NODE_TYPES.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) && memberName5(expression.callee) === "getBuiltinModule";
13485
13744
  }
13486
13745
  function isFsSpecifier(node) {
13487
13746
  return node.type === import_utils74.AST_NODE_TYPES.Literal && (node.value === "node:fs" || node.value === "fs");
@@ -13508,13 +13767,26 @@ var prefer_node_fs_promises_default = createRule({
13508
13767
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text) || normalizedFilename.includes("src/rules/"))
13509
13768
  return {};
13510
13769
  const namespaces = /* @__PURE__ */ new Set();
13770
+ const bindingOf = (node) => import_utils74.ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
13771
+ const isGlobal = (node) => {
13772
+ const binding = bindingOf(node);
13773
+ return binding === null || binding.defs.length === 0;
13774
+ };
13775
+ const isNamespace = (node) => {
13776
+ const binding = bindingOf(node);
13777
+ return binding !== null && namespaces.has(binding) && !binding.references.some((reference) => reference.isWrite() && reference.init !== true);
13778
+ };
13779
+ const recordNamespace = (node) => {
13780
+ const binding = bindingOf(node);
13781
+ if (binding !== null) namespaces.add(binding);
13782
+ };
13511
13783
  return {
13512
13784
  ImportDeclaration(node) {
13513
13785
  if (node.source.value !== "node:fs" && node.source.value !== "fs") return;
13514
13786
  const synchronousImports = [];
13515
13787
  for (const specifier of node.specifiers) {
13516
13788
  if (specifier.type === import_utils74.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils74.AST_NODE_TYPES.ImportDefaultSpecifier) {
13517
- namespaces.add(specifier.local.name);
13789
+ recordNamespace(specifier.local);
13518
13790
  continue;
13519
13791
  }
13520
13792
  if (specifier.type === import_utils74.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils74.AST_NODE_TYPES.Identifier && specifier.imported.name.endsWith("Sync")) synchronousImports.push(specifier.imported.name);
@@ -13528,10 +13800,10 @@ var prefer_node_fs_promises_default = createRule({
13528
13800
  }
13529
13801
  },
13530
13802
  VariableDeclarator(node) {
13531
- if (node.init === null || !isFsLoader(node.init) && (node.init.type !== import_utils74.AST_NODE_TYPES.Identifier || !namespaces.has(node.init.name)))
13803
+ if (node.init === null || !isFsLoader(node.init, isGlobal) && (node.init.type !== import_utils74.AST_NODE_TYPES.Identifier || !isNamespace(node.init)))
13532
13804
  return;
13533
13805
  if (node.id.type === import_utils74.AST_NODE_TYPES.Identifier) {
13534
- namespaces.add(node.id.name);
13806
+ recordNamespace(node.id);
13535
13807
  return;
13536
13808
  }
13537
13809
  if (node.id.type !== import_utils74.AST_NODE_TYPES.ObjectPattern) return;
@@ -13552,7 +13824,7 @@ var prefer_node_fs_promises_default = createRule({
13552
13824
  const name = memberName5(node);
13553
13825
  if (name?.endsWith("Sync") !== true) return;
13554
13826
  const object = unwrapAwait2(node.object);
13555
- if (object.type === import_utils74.AST_NODE_TYPES.Identifier && namespaces.has(object.name) || isFsLoader(object)) {
13827
+ if (object.type === import_utils74.AST_NODE_TYPES.Identifier && isNamespace(object) || isFsLoader(object, isGlobal)) {
13556
13828
  context.report({ node, messageId: "preferAsyncFs", data: { name } });
13557
13829
  }
13558
13830
  }
@@ -13563,11 +13835,11 @@ var prefer_node_fs_promises_default = createRule({
13563
13835
  // src/rules/prefer-non-nullable-collection.ts
13564
13836
  var import_utils75 = require("@typescript-eslint/utils");
13565
13837
  var PREFER_NON_NULLABLE_COLLECTION_DOCUMENTATION = {
13566
- summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
13838
+ summary: "Suggest reviewing nullish arrays that use local empty-array defaults or a shared null-or-empty guard.",
13567
13839
  rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
13568
13840
  remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
13569
13841
  category: "maintainability",
13570
- limitations: ["The rule requires local evidence that nullish and empty values are treated identically and skips exported wire shapes."],
13842
+ 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."],
13571
13843
  examples: [
13572
13844
  { 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 },
13573
13845
  { 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 }
@@ -13636,22 +13908,6 @@ function sameAccess(node, access) {
13636
13908
  }
13637
13909
  return node.type === import_utils75.AST_NODE_TYPES.MemberExpression && !node.computed && node.object.type === import_utils75.AST_NODE_TYPES.Identifier && node.object.name === access.object && node.property.type === import_utils75.AST_NODE_TYPES.Identifier && node.property.name === access.property;
13638
13910
  }
13639
- function isNullGuard(node, access) {
13640
- if (node.type === import_utils75.AST_NODE_TYPES.UnaryExpression && node.operator === "!" && (sameAccess(node.argument, access) || optionalMemberLengthOf(node.argument, access))) return true;
13641
- if (node.type !== import_utils75.AST_NODE_TYPES.BinaryExpression || !["==", "==="].includes(node.operator)) {
13642
- return false;
13643
- }
13644
- const nullish = (value) => value.type === import_utils75.AST_NODE_TYPES.Literal && value.value === null || value.type === import_utils75.AST_NODE_TYPES.Identifier && value.name === "undefined";
13645
- return sameAccess(node.left, access) && nullish(node.right) || sameAccess(node.right, access) && nullish(node.left);
13646
- }
13647
- function isEmptyGuard(node, access) {
13648
- if (node.type === import_utils75.AST_NODE_TYPES.UnaryExpression && node.operator === "!" && memberLengthOf(node.argument, access)) return true;
13649
- if (node.type !== import_utils75.AST_NODE_TYPES.BinaryExpression || !["==", "===", "<="].includes(node.operator)) {
13650
- return false;
13651
- }
13652
- const zero = (value) => value.type === import_utils75.AST_NODE_TYPES.Literal && value.value === 0;
13653
- return memberLengthOf(node.left, access) && zero(node.right) || memberLengthOf(node.right, access) && zero(node.left);
13654
- }
13655
13911
  function memberLengthOf(node, access) {
13656
13912
  const target = node.type === import_utils75.AST_NODE_TYPES.ChainExpression ? node.expression : node;
13657
13913
  return target.type === import_utils75.AST_NODE_TYPES.MemberExpression && !target.computed && target.property.type === import_utils75.AST_NODE_TYPES.Identifier && target.property.name === "length" && sameAccess(target.object, access);
@@ -13666,7 +13922,24 @@ function hasEquivalentLeadingGuard(fn, access, visitorKeys) {
13666
13922
  const terminating = first.consequent.type === import_utils75.AST_NODE_TYPES.ReturnStatement || first.consequent.type === import_utils75.AST_NODE_TYPES.ThrowStatement || first.consequent.type === import_utils75.AST_NODE_TYPES.BlockStatement && first.consequent.body.length === 1 && (first.consequent.body[0]?.type === import_utils75.AST_NODE_TYPES.ReturnStatement || first.consequent.body[0]?.type === import_utils75.AST_NODE_TYPES.ThrowStatement);
13667
13923
  if (!terminating) return false;
13668
13924
  if (contains(first.consequent, visitorKeys, (node) => sameAccess(node, access))) return false;
13669
- return contains(first.test, visitorKeys, (node) => isNullGuard(node, access)) && contains(first.test, visitorKeys, (node) => isEmptyGuard(node, access));
13925
+ if (first.test.type === import_utils75.AST_NODE_TYPES.UnaryExpression && first.test.operator === "!" && optionalMemberLengthOf(first.test.argument, access)) return true;
13926
+ return first.test.type === import_utils75.AST_NODE_TYPES.LogicalExpression && first.test.operator === "||" && isNullGuard(first.test.left, access) && isEmptyGuard(first.test.right, access);
13927
+ }
13928
+ function isNullGuard(node, access) {
13929
+ if (node.type === import_utils75.AST_NODE_TYPES.UnaryExpression && node.operator === "!" && (sameAccess(node.argument, access) || optionalMemberLengthOf(node.argument, access))) return true;
13930
+ if (node.type !== import_utils75.AST_NODE_TYPES.BinaryExpression || !["==", "==="].includes(node.operator)) {
13931
+ return false;
13932
+ }
13933
+ const nullish = (value) => value.type === import_utils75.AST_NODE_TYPES.Literal && value.value === null || value.type === import_utils75.AST_NODE_TYPES.Identifier && value.name === "undefined";
13934
+ return sameAccess(node.left, access) && nullish(node.right) || sameAccess(node.right, access) && nullish(node.left);
13935
+ }
13936
+ function isEmptyGuard(node, access) {
13937
+ if (node.type === import_utils75.AST_NODE_TYPES.UnaryExpression && node.operator === "!" && memberLengthOf(node.argument, access)) return true;
13938
+ if (node.type !== import_utils75.AST_NODE_TYPES.BinaryExpression || !["==", "===", "<="].includes(node.operator)) {
13939
+ return false;
13940
+ }
13941
+ const zero = (value) => value.type === import_utils75.AST_NODE_TYPES.Literal && value.value === 0;
13942
+ return memberLengthOf(node.left, access) && zero(node.right) || node.operator !== "<=" && memberLengthOf(node.right, access) && zero(node.left);
13670
13943
  }
13671
13944
  function contains(node, visitorKeys, predicate) {
13672
13945
  if (predicate(node)) return true;
@@ -13704,7 +13977,7 @@ function memberIsOnlyCoalesced(context, object, property, fn) {
13704
13977
  if (!belongsToFunction(reference.identifier, fn)) return [null];
13705
13978
  const parent = reference.identifier.parent;
13706
13979
  if (parent?.type === import_utils75.AST_NODE_TYPES.MemberExpression && !parent.computed && parent.object === reference.identifier && parent.property.type === import_utils75.AST_NODE_TYPES.Identifier && parent.property.name === property) return [parent];
13707
- return [];
13980
+ return parent?.type === import_utils75.AST_NODE_TYPES.MemberExpression && parent.object === reference.identifier ? [] : [null];
13708
13981
  });
13709
13982
  return accesses.length > 0 && accesses.every((access) => access !== null && directlyCoalesced(access));
13710
13983
  }
@@ -13714,11 +13987,11 @@ var prefer_non_nullable_collection_default = createRule({
13714
13987
  meta: {
13715
13988
  type: "suggestion",
13716
13989
  docs: {
13717
- description: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection."
13990
+ description: "Suggest reviewing nullish arrays that use local empty-array defaults or a shared null-or-empty guard."
13718
13991
  },
13719
13992
  schema: [],
13720
13993
  messages: {
13721
- preferNonNullableCollection: "`{{name}}` is locally treated exactly like `[]`; make it a non-null array and normalize omitted input at the boundary."
13994
+ 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."
13722
13995
  }
13723
13996
  },
13724
13997
  defaultOptions: [],
@@ -13967,12 +14240,12 @@ var ts4 = __toESM(require("typescript"), 1);
13967
14240
  var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
13968
14241
  summary: "Prefer explicit `await` when an async function directly returns one typed Promise `.then` transform.",
13969
14242
  rationale: "Mixing a directly returned Promise callback into otherwise async control flow makes sequencing and failures harder to read.",
13970
- remediation: "Await the Promise, then return the transformed value with ordinary async statements.",
14243
+ 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.",
13971
14244
  category: "maintainability",
13972
14245
  since: "15.6.3",
13973
14246
  limitations: [
13974
14247
  "Only a single directly returned `.then` call with an inline callback is checked.",
13975
- "The receiver must be proven Promise-like by TypeScript; untyped files and larger chains are intentionally ignored.",
14248
+ "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.",
13976
14249
  "Direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports are excluded because returning the module Promise is their framework contract."
13977
14250
  ],
13978
14251
  examples: [
@@ -14113,12 +14386,17 @@ var import_utils78 = require("@typescript-eslint/utils");
14113
14386
  var PREFER_SCHEMA_FOR_API_PAYLOAD_DOCUMENTATION = {
14114
14387
  summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
14115
14388
  rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
14116
- remediation: "Parse the payload through a schema or establish a recognized runtime validation guard before reading fields.",
14389
+ 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.",
14117
14390
  category: "correctness",
14118
- limitations: ["Test fixtures, generated clients, local JSON files, and recognized validation guards are excluded."],
14391
+ limitations: [
14392
+ "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.",
14393
+ "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.",
14394
+ "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.",
14395
+ "Test fixtures, generated clients and recognized local-file reads are excluded. This is bounded local analysis, not a general control-flow or mutation proof."
14396
+ ],
14119
14397
  examples: [
14120
- { 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 },
14121
- { 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 }
14398
+ { 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 },
14399
+ { 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 }
14122
14400
  ]
14123
14401
  };
14124
14402
  var unwrap6 = (node) => {
@@ -14143,7 +14421,7 @@ var isSchemaParseReference = (node) => {
14143
14421
  const inner = unwrap6(node);
14144
14422
  return inner !== null && inner.type === import_utils78.AST_NODE_TYPES.MemberExpression && !inner.computed && inner.property.type === import_utils78.AST_NODE_TYPES.Identifier && (inner.property.name === "parse" || inner.property.name === "safeParse");
14145
14423
  };
14146
- var isRawPayloadSource = (node, isKnownLocalText) => {
14424
+ var isRawPayloadSource = (node, context, isKnownLocalText) => {
14147
14425
  let current = unwrap6(node);
14148
14426
  if (current === null) return false;
14149
14427
  if (current.type === import_utils78.AST_NODE_TYPES.AwaitExpression) {
@@ -14161,13 +14439,31 @@ var isRawPayloadSource = (node, isKnownLocalText) => {
14161
14439
  return false;
14162
14440
  }
14163
14441
  if (property.name === "json") {
14164
- return true;
14442
+ return !callee.computed && current.arguments.length === 0 && isResponseSource(callee.object, context);
14165
14443
  }
14166
14444
  if (PROMISE_CHAIN_METHODS.has(property.name)) {
14167
- return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object);
14445
+ return !current.arguments.some(isSchemaParseReference) && isRawPayloadSource(callee.object, context, isKnownLocalText);
14168
14446
  }
14169
14447
  const object = unwrap6(callee.object);
14170
- return property.name === "parse" && object !== null && object.type === import_utils78.AST_NODE_TYPES.Identifier && object.name === "JSON" && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
14448
+ const input = unwrap6(current.arguments[0]);
14449
+ if (input?.type === import_utils78.AST_NODE_TYPES.CallExpression && input.arguments.length === 1 && input.callee.type === import_utils78.AST_NODE_TYPES.MemberExpression && !input.callee.computed && input.callee.object.type === import_utils78.AST_NODE_TYPES.Identifier && input.callee.object.name === "JSON" && input.callee.property.type === import_utils78.AST_NODE_TYPES.Identifier && input.callee.property.name === "stringify" && (import_utils78.ASTUtils.findVariable(context.sourceCode.getScope(input.callee.object), "JSON")?.defs.length ?? 0) === 0) return false;
14450
+ return property.name === "parse" && object !== null && object.type === import_utils78.AST_NODE_TYPES.Identifier && object.name === "JSON" && (import_utils78.ASTUtils.findVariable(context.sourceCode.getScope(object), "JSON")?.defs.length ?? 0) === 0 && !isLocalFileRead(current.arguments[0]) && isKnownLocalText?.(current.arguments[0]) !== true;
14451
+ };
14452
+ var isResponseSource = (node, context, seen = /* @__PURE__ */ new Set()) => {
14453
+ let current = unwrap6(node);
14454
+ if (current?.type === import_utils78.AST_NODE_TYPES.AwaitExpression) current = unwrap6(current.argument);
14455
+ if (current === null || seen.has(current)) return false;
14456
+ seen.add(current);
14457
+ const isGlobal = (identifier) => (import_utils78.ASTUtils.findVariable(context.sourceCode.getScope(identifier), identifier.name)?.defs.length ?? 0) === 0;
14458
+ if (current.type === import_utils78.AST_NODE_TYPES.CallExpression) return current.callee.type === import_utils78.AST_NODE_TYPES.Identifier && current.callee.name === "fetch" && isGlobal(current.callee);
14459
+ if (current.type === import_utils78.AST_NODE_TYPES.NewExpression) return current.callee.type === import_utils78.AST_NODE_TYPES.Identifier && ["Request", "Response"].includes(current.callee.name) && isGlobal(current.callee);
14460
+ if (current.type !== import_utils78.AST_NODE_TYPES.Identifier) return false;
14461
+ const binding = import_utils78.ASTUtils.findVariable(context.sourceCode.getScope(current), current.name);
14462
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
14463
+ const definition = binding.defs[0];
14464
+ const annotation = definition?.name.type === import_utils78.AST_NODE_TYPES.Identifier ? definition.name.typeAnnotation?.typeAnnotation : void 0;
14465
+ if (annotation?.type === import_utils78.AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === import_utils78.AST_NODE_TYPES.Identifier && ["Request", "Response"].includes(annotation.typeName.name) && isGlobal(annotation.typeName)) return true;
14466
+ return definition?.type === "Variable" && definition.parent.kind === "const" && definition.node.init !== null && isResponseSource(definition.node.init, context, seen);
14171
14467
  };
14172
14468
  var FILE_READ_RE = /^(readFile|readFileSync|readJson|readJsonSync|readJSON)$/;
14173
14469
  var isDirectLocalFileRead = (node) => {
@@ -14433,7 +14729,7 @@ var prefer_schema_for_api_payload_default = createRule({
14433
14729
  },
14434
14730
  schema: [],
14435
14731
  messages: {
14436
- unparsedJsonAccess: "Property access on an unvalidated payload (`response.json()` / `JSON.parse()`) without a schema parse. Pipe through `XSchema.parse(...)` (Zod) before reading fields."
14732
+ 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."
14437
14733
  }
14438
14734
  },
14439
14735
  defaultOptions: [],
@@ -14444,6 +14740,40 @@ var prefer_schema_for_api_payload_default = createRule({
14444
14740
  const unvalidatedVariables = /* @__PURE__ */ new Set();
14445
14741
  const aliasGroups = /* @__PURE__ */ new Map();
14446
14742
  const localFileTextVariables = /* @__PURE__ */ new Set();
14743
+ const namedGuards = /* @__PURE__ */ new Map();
14744
+ const guardDominates = (use, call) => {
14745
+ if (context.sourceCode.getScope(use).variableScope !== context.sourceCode.getScope(call).variableScope) return false;
14746
+ let guard = call;
14747
+ let positive = true;
14748
+ while (guard.parent.type === import_utils78.AST_NODE_TYPES.UnaryExpression && guard.parent.operator === "!") {
14749
+ positive = !positive;
14750
+ guard = guard.parent;
14751
+ }
14752
+ while (positive && guard.parent.type === import_utils78.AST_NODE_TYPES.LogicalExpression && guard.parent.operator === "&&") guard = guard.parent;
14753
+ const branch = guard.parent;
14754
+ if (branch.type === import_utils78.AST_NODE_TYPES.IfStatement && branch.test === guard) {
14755
+ if (positive && nodeWithin2(use, branch.consequent)) return true;
14756
+ if (!positive && branch.alternate !== null && nodeWithin2(use, branch.alternate)) return true;
14757
+ const terminal = branch.consequent.type === import_utils78.AST_NODE_TYPES.BlockStatement ? branch.consequent.body.at(-1) : branch.consequent;
14758
+ if (!positive && (terminal?.type === import_utils78.AST_NODE_TYPES.ThrowStatement || terminal?.type === import_utils78.AST_NODE_TYPES.ReturnStatement)) {
14759
+ let statement2 = use;
14760
+ while (statement2.parent !== void 0 && statement2.parent !== branch.parent && statement2.parent.type !== import_utils78.AST_NODE_TYPES.Program) {
14761
+ if (statement2.type === import_utils78.AST_NODE_TYPES.FunctionDeclaration || statement2.type === import_utils78.AST_NODE_TYPES.FunctionExpression || statement2.type === import_utils78.AST_NODE_TYPES.ArrowFunctionExpression) return false;
14762
+ statement2 = statement2.parent;
14763
+ }
14764
+ return statement2.parent === branch.parent && statement2.range[0] > branch.range[1];
14765
+ }
14766
+ }
14767
+ if (branch.type === import_utils78.AST_NODE_TYPES.ConditionalExpression && branch.test === guard) return nodeWithin2(use, positive ? branch.consequent : branch.alternate);
14768
+ if (positive && branch.type === import_utils78.AST_NODE_TYPES.WhileStatement && branch.test === guard) return nodeWithin2(use, branch.body);
14769
+ if (call.parent.type !== import_utils78.AST_NODE_TYPES.ExpressionStatement || call.callee.type !== import_utils78.AST_NODE_TYPES.Identifier || /^(?:is|has)[A-Z]/u.test(call.callee.name)) return false;
14770
+ let statement = use;
14771
+ while (statement.parent !== void 0 && statement.parent !== call.parent.parent && statement.parent.type !== import_utils78.AST_NODE_TYPES.Program) {
14772
+ if (statement.type === import_utils78.AST_NODE_TYPES.FunctionDeclaration || statement.type === import_utils78.AST_NODE_TYPES.FunctionExpression || statement.type === import_utils78.AST_NODE_TYPES.ArrowFunctionExpression) return false;
14773
+ statement = statement.parent;
14774
+ }
14775
+ return statement.parent === call.parent.parent && statement.range[0] > call.parent.range[1];
14776
+ };
14447
14777
  const localFileTextRef = (node, scope) => {
14448
14778
  const unwrapped = unwrap6(node);
14449
14779
  if (unwrapped?.type !== import_utils78.AST_NODE_TYPES.Identifier) return null;
@@ -14460,6 +14790,7 @@ var prefer_schema_for_api_payload_default = createRule({
14460
14790
  };
14461
14791
  const clearBinding = (variable) => {
14462
14792
  unvalidatedVariables.delete(variable);
14793
+ namedGuards.delete(variable);
14463
14794
  const group = aliasGroups.get(variable);
14464
14795
  aliasGroups.delete(variable);
14465
14796
  group?.delete(variable);
@@ -14494,10 +14825,24 @@ var prefer_schema_for_api_payload_default = createRule({
14494
14825
  };
14495
14826
  const isFullyNarrowedPattern = (declarator) => {
14496
14827
  const declared = context.sourceCode.getDeclaredVariables(declarator);
14828
+ const statement = declarator.parent;
14829
+ const block = statement.parent;
14830
+ const followsRejectingGuard = (identifier) => {
14831
+ if (block.type !== import_utils78.AST_NODE_TYPES.BlockStatement && block.type !== import_utils78.AST_NODE_TYPES.Program) return false;
14832
+ if (context.sourceCode.getScope(identifier).variableScope !== context.sourceCode.getScope(declarator).variableScope) return false;
14833
+ return block.body.some((candidate2) => {
14834
+ if (candidate2.type !== import_utils78.AST_NODE_TYPES.IfStatement || candidate2.range[1] >= identifier.range[0] || bindingValidationPolarity(candidate2.test, identifier.name) !== "valid-when-false") return false;
14835
+ const terminal = candidate2.consequent.type === import_utils78.AST_NODE_TYPES.BlockStatement ? candidate2.consequent.body.at(-1) : candidate2.consequent;
14836
+ return terminal?.type === import_utils78.AST_NODE_TYPES.ThrowStatement || terminal?.type === import_utils78.AST_NODE_TYPES.ReturnStatement;
14837
+ });
14838
+ };
14497
14839
  return declared.length > 0 && declared.every(
14498
- (variable) => variable.references.some(
14499
- (reference) => isValidationRead(reference.identifier)
14500
- )
14840
+ (variable) => variable.references.some((reference) => isValidationRead(reference.identifier)) && variable.references.every((reference) => {
14841
+ const identifier = reference.identifier;
14842
+ if (reference.init === true) return true;
14843
+ if (reference.isWrite() || identifier.type !== import_utils78.AST_NODE_TYPES.Identifier) return false;
14844
+ return isValidationRead(identifier) || isUseWithinValidatedBranch(identifier, identifier.name) || followsRejectingGuard(identifier);
14845
+ })
14501
14846
  );
14502
14847
  };
14503
14848
  const trackInitializer = (declarator, scope) => {
@@ -14505,7 +14850,7 @@ var prefer_schema_for_api_payload_default = createRule({
14505
14850
  const variable = declaredVars[0];
14506
14851
  if (variable === void 0) return;
14507
14852
  const localText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14508
- if (isRawPayloadSource(declarator.init, localText)) {
14853
+ if (isRawPayloadSource(declarator.init, context, localText)) {
14509
14854
  trackRawBinding(variable);
14510
14855
  return;
14511
14856
  }
@@ -14526,6 +14871,7 @@ var prefer_schema_for_api_payload_default = createRule({
14526
14871
  if (node.id.type === import_utils78.AST_NODE_TYPES.ObjectPattern || node.id.type === import_utils78.AST_NODE_TYPES.ArrayPattern) {
14527
14872
  if (isRawPayloadSource(
14528
14873
  node.init,
14874
+ context,
14529
14875
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14530
14876
  )) {
14531
14877
  if (!isFullyNarrowedPattern(node)) {
@@ -14545,7 +14891,7 @@ var prefer_schema_for_api_payload_default = createRule({
14545
14891
  if (variable === null) return;
14546
14892
  const isLocalText = (candidate2) => localFileTextRef(candidate2, scope) !== null;
14547
14893
  updateLocalFileText(variable, node.right, scope);
14548
- if (isRawPayloadSource(node.right, isLocalText)) {
14894
+ if (isRawPayloadSource(node.right, context, isLocalText)) {
14549
14895
  trackRawBinding(variable);
14550
14896
  } else {
14551
14897
  const source = unvalidatedVariableRef(node.right, scope, unvalidatedVariables);
@@ -14557,6 +14903,7 @@ var prefer_schema_for_api_payload_default = createRule({
14557
14903
  if (node.left.type === import_utils78.AST_NODE_TYPES.ObjectPattern || node.left.type === import_utils78.AST_NODE_TYPES.ArrayPattern) {
14558
14904
  if (isRawPayloadSource(
14559
14905
  node.right,
14906
+ context,
14560
14907
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14561
14908
  )) {
14562
14909
  context.report({
@@ -14586,7 +14933,13 @@ var prefer_schema_for_api_payload_default = createRule({
14586
14933
  continue;
14587
14934
  }
14588
14935
  const variable = findVariable2(scope, unwrapped.name);
14589
- if (variable !== null) clearAliasGroup(variable);
14936
+ if (variable !== null) {
14937
+ for (const alias of aliasGroups.get(variable) ?? [variable]) {
14938
+ const guards = namedGuards.get(alias) ?? [];
14939
+ guards.push(node);
14940
+ namedGuards.set(alias, guards);
14941
+ }
14942
+ }
14590
14943
  }
14591
14944
  },
14592
14945
  MemberExpression(node) {
@@ -14596,6 +14949,7 @@ var prefer_schema_for_api_payload_default = createRule({
14596
14949
  const obj = unwrap6(node.object);
14597
14950
  if (isRawPayloadSource(
14598
14951
  obj,
14952
+ context,
14599
14953
  (candidate2) => localFileTextRef(candidate2, scope) !== null
14600
14954
  )) {
14601
14955
  const parent = node.parent;
@@ -14607,6 +14961,7 @@ var prefer_schema_for_api_payload_default = createRule({
14607
14961
  }
14608
14962
  const variable = obj?.type === import_utils78.AST_NODE_TYPES.Identifier ? unvalidatedVariableRef(obj, scope, unvalidatedVariables) : null;
14609
14963
  if (variable !== null && obj?.type === import_utils78.AST_NODE_TYPES.Identifier) {
14964
+ if (namedGuards.get(variable)?.some((call) => guardDominates(node, call))) return;
14610
14965
  if (isUseWithinValidatedBranch(node, obj.name)) {
14611
14966
  return;
14612
14967
  }
@@ -14632,7 +14987,7 @@ var PREFER_SHARED_ZOD_ENUM_DOCUMENTATION = {
14632
14987
  rationale: "Inline or repeated literal domains hide a reusable contract and allow equivalent fields to drift independently.",
14633
14988
  remediation: "Declare a module-level named Zod enum schema and reuse it at each field or contract site.",
14634
14989
  category: "maintainability",
14635
- limitations: ["Only direct z.enum calls with string-literal arrays are inspected; computed domains require review."],
14990
+ 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."],
14636
14991
  examples: [
14637
14992
  { 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 },
14638
14993
  { 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 }
@@ -14674,16 +15029,22 @@ var prefer_shared_zod_enum_default = createRule({
14674
15029
  create(context) {
14675
15030
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
14676
15031
  const zodBindings = /* @__PURE__ */ new Set();
15032
+ const bindingOf = (node) => import_utils79.ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
14677
15033
  const seen = /* @__PURE__ */ new Set();
14678
15034
  return {
14679
15035
  ImportDeclaration(node) {
14680
15036
  if (!isZodModule(node.source.value)) return;
14681
15037
  for (const specifier of node.specifiers) {
14682
- if (specifier.type === import_utils79.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils79.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils79.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils79.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") zodBindings.add(specifier.local.name);
15038
+ if (specifier.type === import_utils79.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils79.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils79.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils79.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") {
15039
+ const binding = bindingOf(specifier.local);
15040
+ if (binding !== null) zodBindings.add(binding);
15041
+ }
14683
15042
  }
14684
15043
  },
14685
15044
  CallExpression(node) {
14686
- if (node.callee.type !== import_utils79.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.object.type !== import_utils79.AST_NODE_TYPES.Identifier || !zodBindings.has(node.callee.object.name) || node.callee.property.type !== import_utils79.AST_NODE_TYPES.Identifier || node.callee.property.name !== "enum") return;
15045
+ if (node.callee.type !== import_utils79.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.object.type !== import_utils79.AST_NODE_TYPES.Identifier || node.callee.property.type !== import_utils79.AST_NODE_TYPES.Identifier || node.callee.property.name !== "enum") return;
15046
+ const binding = bindingOf(node.callee.object);
15047
+ if (binding === null || !zodBindings.has(binding)) return;
14687
15048
  const domain = literalDomain(node);
14688
15049
  if (domain === null) return;
14689
15050
  const key = JSON.stringify(domain);
@@ -14705,6 +15066,7 @@ var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
14705
15066
  category: "maintainability",
14706
15067
  limitations: [
14707
15068
  "Only direct if/else-if chains with at least three strict-equality tests are reported.",
15069
+ "The discriminant must be a bare identifier; calls, getters, indexed reads and other repeatedly evaluated expressions are excluded. Review case-value effects, selector mutation, branch scoping, and break/continue targets when converting manually; this is not an equivalence proof.",
14708
15070
  "Case values may be literals, enum-like member references, or upper-case named constants; dynamic expressions are excluded.",
14709
15071
  "The rule deliberately ignores compound predicates, loose equality, ranges, and chains that compare different discriminants."
14710
15072
  ],
@@ -14718,7 +15080,8 @@ function discriminantText(sourceCode, test) {
14718
15080
  const leftIsCase = isCaseValue(test.left);
14719
15081
  const rightIsCase = isCaseValue(test.right);
14720
15082
  if (leftIsCase === rightIsCase) return null;
14721
- return sourceCode.getText(leftIsCase ? test.right : test.left);
15083
+ const discriminant = leftIsCase ? test.right : test.left;
15084
+ return discriminant.type === import_utils80.AST_NODE_TYPES.Identifier ? sourceCode.getText(discriminant) : null;
14722
15085
  }
14723
15086
  function isCaseValue(node) {
14724
15087
  if (node.type === import_utils80.AST_NODE_TYPES.Literal) return true;
@@ -15659,6 +16022,13 @@ var prefer_whole_object_assertion_default = createRule({
15659
16022
  return null;
15660
16023
  }
15661
16024
  const actual = expectCall.arguments[0];
16025
+ const variable = import_utils83.ASTUtils.findVariable(sourceCode.getScope(expectCall.callee), expectCall.callee.name);
16026
+ if (variable !== null && variable.defs.some((definition) => {
16027
+ if (definition.node.type !== import_utils83.AST_NODE_TYPES.ImportSpecifier) return true;
16028
+ const declaration = definition.node.parent;
16029
+ const imported = definition.node.imported;
16030
+ return declaration.type !== import_utils83.AST_NODE_TYPES.ImportDeclaration || !["vitest", "@jest/globals", "@playwright/test", "bun:test"].includes(String(declaration.source.value)) || (imported.type === import_utils83.AST_NODE_TYPES.Identifier ? imported.name : imported.value) !== "expect";
16031
+ })) return null;
15662
16032
  if (actual === void 0 || actual.type !== import_utils83.AST_NODE_TYPES.MemberExpression || actual.optional) {
15663
16033
  return null;
15664
16034
  }
@@ -15820,12 +16190,12 @@ var prefer_whole_object_assertion_default = createRule({
15820
16190
  // src/rules/repeated-static-call-cases.ts
15821
16191
  var import_utils84 = require("@typescript-eslint/utils");
15822
16192
  var REPEATED_STATIC_CALL_CASES_DOCUMENTATION = {
15823
- summary: "Report three or more consecutive literal call assertions that should be independently named test cases.",
15824
- rationale: "Copy-pasted cases obscure the input table and stop later cases from being reported after the first failure.",
15825
- remediation: "Replace the repeated assertions with a named `test.each` or `it.each` table.",
16193
+ summary: "Review three or more consecutive static-input call assertions as potential named cases.",
16194
+ rationale: "Copy-pasted cases obscure the input table, and a thrown assertion can stop later cases from being reported.",
16195
+ remediation: "If the calls are independent, use the runner's named parameterized cases or subtests. Preserve ordered state-transition scenarios as one test.",
15826
16196
  category: "testing",
15827
16197
  filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
15828
- limitations: ["Only consecutive top-level assertions with direct calls and entirely static inputs and expected values are reported."],
16198
+ 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."],
15829
16199
  examples: [
15830
16200
  { 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 },
15831
16201
  { 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 }
@@ -15906,7 +16276,7 @@ function staticShape(node) {
15906
16276
  return "dynamic";
15907
16277
  }
15908
16278
  }
15909
- function assertionShape(statement, context) {
16279
+ function assertionShape(statement, context, callback) {
15910
16280
  if (statement.type !== import_utils84.AST_NODE_TYPES.ExpressionStatement || statement.expression.type !== import_utils84.AST_NODE_TYPES.CallExpression) return null;
15911
16281
  const matcherCall = statement.expression;
15912
16282
  if (matcherCall.callee.type !== import_utils84.AST_NODE_TYPES.MemberExpression || matcherCall.callee.computed || matcherCall.callee.property.type !== import_utils84.AST_NODE_TYPES.Identifier || matcherCall.arguments.length !== 1) return null;
@@ -15918,6 +16288,10 @@ function assertionShape(statement, context) {
15918
16288
  const expected = matcherCall.arguments[0];
15919
16289
  if (observed?.type !== import_utils84.AST_NODE_TYPES.CallExpression || observed.callee.type !== import_utils84.AST_NODE_TYPES.Identifier || observed.arguments.length === 0 || observed.arguments.some((arg) => arg.type === import_utils84.AST_NODE_TYPES.SpreadElement || !isStatic(arg)) || expected?.type === import_utils84.AST_NODE_TYPES.SpreadElement || expected === void 0 || !isStatic(expected)) return null;
15920
16290
  const skeleton = `${observed.callee.name}/${observed.arguments.map((item) => staticShape(item)).join(",")}/${chain.modifiers.join(".")}/${matcher}/${staticShape(expected)}`;
16291
+ const binding = import_utils84.ASTUtils.findVariable(context.sourceCode.getScope(observed.callee), observed.callee.name);
16292
+ if (binding?.defs.some(
16293
+ (definition) => definition.node.range[0] >= callback.range[0] && definition.node.range[1] <= callback.range[1]
16294
+ )) return null;
15921
16295
  const values = [...observed.arguments, expected].map((item) => context.sourceCode.getText(item)).join("\0");
15922
16296
  return { statement, skeleton, values };
15923
16297
  }
@@ -15937,9 +16311,9 @@ var repeated_static_call_cases_default = createRule({
15937
16311
  documentation: REPEATED_STATIC_CALL_CASES_DOCUMENTATION,
15938
16312
  meta: {
15939
16313
  type: "suggestion",
15940
- docs: { description: "Report three or more consecutive literal call assertions that should be independently named test cases." },
16314
+ docs: { description: REPEATED_STATIC_CALL_CASES_DOCUMENTATION.summary },
15941
16315
  schema: [],
15942
- 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." }
16316
+ 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." }
15943
16317
  },
15944
16318
  defaultOptions: [],
15945
16319
  create(context) {
@@ -15974,7 +16348,7 @@ var repeated_static_call_cases_default = createRule({
15974
16348
  run = [];
15975
16349
  };
15976
16350
  for (const statement of node.body.body) {
15977
- const shape = assertionShape(statement, context);
16351
+ const shape = assertionShape(statement, context, node);
15978
16352
  if (shape === null || run.length > 0 && run[0]?.skeleton !== shape.skeleton) flush();
15979
16353
  if (shape !== null) run.push(shape);
15980
16354
  }
@@ -16007,6 +16381,11 @@ var PREFER_ZOD_INFER_DOCUMENTATION = {
16007
16381
  rationale: "A derived type stays synchronized when the runtime schema changes.",
16008
16382
  remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
16009
16383
  category: "correctness",
16384
+ limitations: [
16385
+ "Only module-level const schemas and module-level type declarations are paired; local declarations are excluded rather than matched by spelling across scopes.",
16386
+ "By default every field must positively agree; collection, nested-object and referenced-schema equivalence is not inferred from outer syntax alone.",
16387
+ "This is a bounded syntactic comparison, not general type equivalence. Review schema input versus output, interface augmentation, and separately evolving domain contracts before replacing a declaration."
16388
+ ],
16010
16389
  examples: [
16011
16390
  { id: "inferred-type", title: "Infer the schema type", outcome: "no-match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); type User = z.infer<typeof UserSchema>;' }], focusPath: "src/user.ts", expectedCount: 0, public: true },
16012
16391
  { id: "handwritten-twin", title: "Do not duplicate the schema shape", outcome: "match", files: [{ path: "src/user.ts", source: 'import { z } from "zod"; const UserSchema = z.object({ id: z.string() }); interface User { id: string }' }], focusPath: "src/user.ts", expectedCount: 1, public: true }
@@ -16186,6 +16565,10 @@ function sameDomain(left, right) {
16186
16565
  function isExportedDeclaration(node) {
16187
16566
  return node.parent?.type === import_utils85.AST_NODE_TYPES.ExportNamedDeclaration;
16188
16567
  }
16568
+ function isModuleLevelDeclaration(node) {
16569
+ const parent = node.parent;
16570
+ return parent?.type === import_utils85.AST_NODE_TYPES.Program || parent?.type === import_utils85.AST_NODE_TYPES.ExportNamedDeclaration && parent.parent.type === import_utils85.AST_NODE_TYPES.Program;
16571
+ }
16189
16572
  function isModuleLevelConst(node) {
16190
16573
  const declaration = node.parent;
16191
16574
  if (declaration.type !== import_utils85.AST_NODE_TYPES.VariableDeclaration || declaration.kind !== "const") {
@@ -16236,6 +16619,9 @@ function leafAgrees(field, annotation) {
16236
16619
  return annotationDomain !== null && sameDomain(field.domain, annotationDomain);
16237
16620
  }
16238
16621
  const { leaf } = field;
16622
+ if (leaf !== null && ["array", "tuple", "object", "strictObject", "looseObject", "record", "map", "set", "promise", "intersection"].includes(leaf)) {
16623
+ return false;
16624
+ }
16239
16625
  if (leaf === null || annotation === null) {
16240
16626
  return null;
16241
16627
  }
@@ -16528,7 +16914,6 @@ var prefer_zod_infer_default = createRule({
16528
16914
  if (fields.size !== members.size) {
16529
16915
  return false;
16530
16916
  }
16531
- let agreements = 0;
16532
16917
  for (const [name, field] of fields) {
16533
16918
  const member = members.get(name);
16534
16919
  if (member === void 0) {
@@ -16547,14 +16932,11 @@ var prefer_zod_infer_default = createRule({
16547
16932
  return false;
16548
16933
  }
16549
16934
  const agrees = leafAgrees(field, member.annotation);
16550
- if (agrees === false) {
16935
+ if (agrees !== true) {
16551
16936
  return false;
16552
16937
  }
16553
- if (agrees === true) {
16554
- agreements += 1;
16555
- }
16556
16938
  }
16557
- return agreements > 0;
16939
+ return true;
16558
16940
  }
16559
16941
  return {
16560
16942
  Program(node) {
@@ -16568,7 +16950,7 @@ var prefer_zod_infer_default = createRule({
16568
16950
  recordZodImport(node);
16569
16951
  },
16570
16952
  VariableDeclarator(node) {
16571
- if (node.id.type !== import_utils85.AST_NODE_TYPES.Identifier || node.init == null) {
16953
+ if (node.id.type !== import_utils85.AST_NODE_TYPES.Identifier || node.init == null || !isModuleLevelConst(node)) {
16572
16954
  return;
16573
16955
  }
16574
16956
  const fields = schemaFields(node.init);
@@ -16601,6 +16983,7 @@ var prefer_zod_infer_default = createRule({
16601
16983
  }
16602
16984
  },
16603
16985
  TSInterfaceDeclaration(node) {
16986
+ if (!isModuleLevelDeclaration(node)) return;
16604
16987
  if (node.typeParameters !== void 0 || (node.extends?.length ?? 0) > 0) {
16605
16988
  return;
16606
16989
  }
@@ -16616,6 +16999,7 @@ var prefer_zod_infer_default = createRule({
16616
16999
  );
16617
17000
  },
16618
17001
  TSTypeAliasDeclaration(node) {
17002
+ if (!isModuleLevelDeclaration(node)) return;
16619
17003
  const schemaName = inferredSchemaName(node.typeAnnotation);
16620
17004
  if (schemaName !== null) {
16621
17005
  inferredAliases.push({
@@ -16730,6 +17114,7 @@ var REQUIRE_ASSERT_NEVER_DOCUMENTATION = {
16730
17114
  rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
16731
17115
  remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
16732
17116
  category: "correctness",
17117
+ limitations: ["Requires type information and singleton case values covering the current union. The helper must accept never to provide a compile-time check; its spelling alone is not a proof of that contract. Review the desired runtime behavior for unexpected external values."],
16733
17118
  examples: [
16734
17119
  { id: "assert-never-default", title: "Make the default exhaustive", outcome: "no-match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: assertNever(kind); }" }], focusPath: "src/render.ts", expectedCount: 0, public: true },
16735
17120
  { id: "empty-default", title: "Do not leave an exhaustive default empty", outcome: "match", files: [{ path: "src/render.ts", source: "declare const kind: 'a' | 'b';\nswitch (kind) { case 'a': break; case 'b': break; default: }" }], focusPath: "src/render.ts", expectedCount: 1, public: true }
@@ -16786,11 +17171,9 @@ function isExhaustiveFiniteSwitch(node, services) {
16786
17171
  if (caseNode.test === null) continue;
16787
17172
  const test = services.esTreeNodeToTSNodeMap.get(caseNode.test);
16788
17173
  const testType = checker.getTypeAtLocation(test);
16789
- const alternatives = testType.isUnion() ? testType.types : [testType];
16790
- for (const alternative of alternatives) {
16791
- const key = finiteTypeKey(alternative, checker);
16792
- if (key !== null) handled.add(key);
16793
- }
17174
+ if (testType.isUnion()) continue;
17175
+ const key = finiteTypeKey(testType, checker);
17176
+ if (key !== null) handled.add(key);
16794
17177
  }
16795
17178
  return [...expected].every((key) => handled.has(key));
16796
17179
  }
@@ -16984,10 +17367,12 @@ var require_fetch_timeout_default = createRule({
16984
17367
  var import_utils88 = require("@typescript-eslint/utils");
16985
17368
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
16986
17369
  summary: "Require exported concrete classes with public behavior to declare a contract.",
16987
- rationale: "Consumers coupled only to a concrete class cannot substitute implementations or state the supported public capability independently of implementation details.",
17370
+ 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.",
16988
17371
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
16989
17372
  category: "architecture",
16990
17373
  limitations: [
17374
+ "JavaScript files are excluded because implements is TypeScript syntax; imported framework base contracts still require manual policy review.",
17375
+ "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.",
16991
17376
  "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.",
16992
17377
  "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.",
16993
17378
  "Static factories and data-only classes without public instance methods are outside the contract requirement."
@@ -17059,7 +17444,7 @@ var require_interface_for_exported_class_default = createRule({
17059
17444
  },
17060
17445
  defaultOptions: [],
17061
17446
  create(context) {
17062
- if (isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
17447
+ if (/\.(?:js|jsx|mjs|cjs)$/iu.test(context.filename) || isTestFile(context.filename) || isStoryFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) return {};
17063
17448
  return {
17064
17449
  "Program:exit"(program) {
17065
17450
  const classes = /* @__PURE__ */ new Map();
@@ -17445,15 +17830,14 @@ var publicMethodNames = (body2, functionAliases) => {
17445
17830
  if (member.static || member.accessibility === "private" || member.accessibility === "protected") continue;
17446
17831
  if (member.key.type === import_utils89.AST_NODE_TYPES.PrivateIdentifier) continue;
17447
17832
  if (member.value?.type !== import_utils89.AST_NODE_TYPES.ArrowFunctionExpression && member.value?.type !== import_utils89.AST_NODE_TYPES.FunctionExpression && member.typeAnnotation?.typeAnnotation.type !== import_utils89.AST_NODE_TYPES.TSFunctionType && !(member.typeAnnotation?.typeAnnotation.type === import_utils89.AST_NODE_TYPES.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === import_utils89.AST_NODE_TYPES.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name))) continue;
17448
- names.push(member.key.type === import_utils89.AST_NODE_TYPES.Identifier ? member.key.name : "\u2026");
17833
+ names.push(declaredMemberName(member) ?? "\u2026");
17449
17834
  continue;
17450
17835
  }
17451
17836
  if (member.type !== import_utils89.AST_NODE_TYPES.MethodDefinition) continue;
17452
17837
  if (member.kind !== "method" || member.static) continue;
17453
17838
  if (member.accessibility === "private" || member.accessibility === "protected") continue;
17454
17839
  if (member.key.type === import_utils89.AST_NODE_TYPES.PrivateIdentifier) continue;
17455
- if (member.key.type === import_utils89.AST_NODE_TYPES.Identifier) names.push(member.key.name);
17456
- else names.push("\u2026");
17840
+ names.push(declaredMemberName(member) ?? "\u2026");
17457
17841
  }
17458
17842
  return names;
17459
17843
  };
@@ -17494,6 +17878,11 @@ function localClassAbstractness(program) {
17494
17878
  }
17495
17879
  return classes;
17496
17880
  }
17881
+ function declaredMemberName(member) {
17882
+ if (!member.computed && member.key.type === import_utils89.AST_NODE_TYPES.Identifier) return member.key.name;
17883
+ if (member.key.type === import_utils89.AST_NODE_TYPES.Literal && typeof member.key.value === "string") return member.key.value;
17884
+ return null;
17885
+ }
17497
17886
  function localInterfaceSurfaces(program) {
17498
17887
  const interfaces = /* @__PURE__ */ new Map();
17499
17888
  const parents = /* @__PURE__ */ new Map();
@@ -17516,14 +17905,15 @@ function localInterfaceSurfaces(program) {
17516
17905
  if (part.type !== import_utils89.AST_NODE_TYPES.TSTypeLiteral) continue;
17517
17906
  for (const member of part.members) {
17518
17907
  if (member.type !== import_utils89.AST_NODE_TYPES.TSMethodSignature && member.type !== import_utils89.AST_NODE_TYPES.TSPropertySignature) continue;
17519
- if (member.computed || member.key.type !== import_utils89.AST_NODE_TYPES.Identifier) continue;
17908
+ const name = declaredMemberName(member);
17909
+ if (name === null) continue;
17520
17910
  if (member.type === import_utils89.AST_NODE_TYPES.TSMethodSignature) {
17521
- callables2.add(member.key.name);
17911
+ callables2.add(name);
17522
17912
  continue;
17523
17913
  }
17524
17914
  if (member.type !== import_utils89.AST_NODE_TYPES.TSPropertySignature) continue;
17525
17915
  const annotation = member.typeAnnotation?.typeAnnotation;
17526
- if (annotation?.type === import_utils89.AST_NODE_TYPES.TSFunctionType || annotation?.type === import_utils89.AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === import_utils89.AST_NODE_TYPES.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(member.key.name);
17916
+ if (annotation?.type === import_utils89.AST_NODE_TYPES.TSFunctionType || annotation?.type === import_utils89.AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === import_utils89.AST_NODE_TYPES.Identifier && functionAliases.has(annotation.typeName.name)) callables2.add(name);
17527
17917
  }
17528
17918
  }
17529
17919
  interfaces.set(declaration.id.name, callables2);
@@ -17534,8 +17924,9 @@ function localInterfaceSurfaces(program) {
17534
17924
  const callables = interfaces.get(declaration.id.name) ?? /* @__PURE__ */ new Set();
17535
17925
  for (const member of declaration.body.body) {
17536
17926
  if (member.type !== import_utils89.AST_NODE_TYPES.TSMethodSignature && member.type !== import_utils89.AST_NODE_TYPES.TSPropertySignature) continue;
17537
- if (member.computed || member.key.type !== import_utils89.AST_NODE_TYPES.Identifier) continue;
17538
- if (member.type === import_utils89.AST_NODE_TYPES.TSMethodSignature || member.typeAnnotation?.typeAnnotation.type === import_utils89.AST_NODE_TYPES.TSFunctionType || member.typeAnnotation?.typeAnnotation.type === import_utils89.AST_NODE_TYPES.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === import_utils89.AST_NODE_TYPES.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name)) callables.add(member.key.name);
17927
+ const name = declaredMemberName(member);
17928
+ if (name === null) continue;
17929
+ if (member.type === import_utils89.AST_NODE_TYPES.TSMethodSignature || member.typeAnnotation?.typeAnnotation.type === import_utils89.AST_NODE_TYPES.TSFunctionType || member.typeAnnotation?.typeAnnotation.type === import_utils89.AST_NODE_TYPES.TSTypeReference && member.typeAnnotation.typeAnnotation.typeName.type === import_utils89.AST_NODE_TYPES.Identifier && functionAliases.has(member.typeAnnotation.typeAnnotation.typeName.name)) callables.add(name);
17539
17930
  }
17540
17931
  interfaces.set(declaration.id.name, callables);
17541
17932
  parents.set(
@@ -17714,12 +18105,13 @@ var MODEL_EXECUTION_METHODS = /* @__PURE__ */ new Set([
17714
18105
  var DATABASE_NAMES = /^(?:db|database|connection|pool|prisma|query|transaction|tx)$/iu;
17715
18106
  var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
17716
18107
  summary: "Keep SQL reads and writes inside a class that receives its database dependency.",
17717
- rationale: "Free-function database access hides connection ownership and makes transaction, retry, observability, and test boundaries inconsistent.",
18108
+ rationale: "An injected repository class is the preferred ownership boundary for database access under this architectural policy; free functions can also express explicit dependencies.",
17718
18109
  remediation: "Move the query into a repository or store class and inject the pool, connection, transaction, or typed database binding through its constructor.",
17719
18110
  category: "architecture",
17720
18111
  limitations: [
17721
18112
  "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.",
17722
18113
  "Query construction without a recognized execution terminal is intentionally not reported.",
18114
+ "Stable local Map, WeakMap, and URLSearchParams instances are excluded. Other conventional receiver names are heuristics, not proof of a database API.",
17723
18115
  "Constructor injection inherited from a base class or transformed through a wrapper is not inferred by this syntax-only rule."
17724
18116
  ],
17725
18117
  examples: [
@@ -17925,11 +18317,23 @@ var require_sql_access_class_default = createRule({
17925
18317
  create(context) {
17926
18318
  if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text))
17927
18319
  return {};
18320
+ function knownNonDatabase(node, seen = /* @__PURE__ */ new Set()) {
18321
+ if (seen.has(node)) return false;
18322
+ seen.add(node);
18323
+ if (node.type === import_utils90.AST_NODE_TYPES.Identifier) {
18324
+ const binding = import_utils90.ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
18325
+ if (binding?.defs.length !== 1 || binding.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
18326
+ const definition = binding.defs[0];
18327
+ return definition?.type === "Variable" && definition.node.init !== null && knownNonDatabase(definition.node.init, seen);
18328
+ }
18329
+ return node.type === import_utils90.AST_NODE_TYPES.NewExpression && node.callee.type === import_utils90.AST_NODE_TYPES.Identifier && ["Map", "WeakMap", "URLSearchParams"].includes(node.callee.name) && (import_utils90.ASTUtils.findVariable(context.sourceCode.getScope(node.callee), node.callee.name)?.defs.length ?? 0) === 0;
18330
+ }
17928
18331
  return {
17929
18332
  CallExpression(node) {
17930
18333
  if (node.callee.type !== import_utils90.AST_NODE_TYPES.MemberExpression)
17931
18334
  return;
17932
18335
  const method = memberName6(node.callee);
18336
+ if (knownNonDatabase(node.callee.object)) return;
17933
18337
  if (method === null || !isDatabaseOperation(method, node.callee.object))
17934
18338
  return;
17935
18339
  const owner = owningClass2(node);
@@ -17950,6 +18354,8 @@ var REQUIRE_STATIC_NEXT_MATCHER_DOCUMENTATION = {
17950
18354
  rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
17951
18355
  remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
17952
18356
  category: "correctness",
18357
+ 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."],
18358
+ references: ["https://nextjs.org/docs/app/api-reference/file-conventions/proxy"],
17953
18359
  examples: [
17954
18360
  { 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 },
17955
18361
  { 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 }
@@ -17997,7 +18403,7 @@ var require_static_next_matcher_default = createRule({
17997
18403
  },
17998
18404
  schema: [],
17999
18405
  messages: {
18000
- 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."
18406
+ 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."
18001
18407
  }
18002
18408
  },
18003
18409
  defaultOptions: [],
@@ -18178,7 +18584,8 @@ var REQUIRE_ZOD_FORM_VALIDATION_DOCUMENTATION = {
18178
18584
  category: "security",
18179
18585
  limitations: [
18180
18586
  "Tests are excluded; imported schema-shaped names are trusted when their implementation is outside the linted file.",
18181
- "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."
18587
+ "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.",
18588
+ "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."
18182
18589
  ],
18183
18590
  examples: [
18184
18591
  { 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 },
@@ -18293,6 +18700,15 @@ var require_zod_form_validation_default = createRule({
18293
18700
  let parent = node.parent;
18294
18701
  while (parent !== null && parent !== void 0) {
18295
18702
  if (isZodParseCall(parent)) return parent;
18703
+ if (parent.type === import_utils94.AST_NODE_TYPES.CallExpression && parent.callee.type === import_utils94.AST_NODE_TYPES.Identifier && ["Number", "String", "Boolean"].includes(parent.callee.name) && parent.arguments.length === 1 && (resolvedBinding(parent.callee)?.defs.length ?? 0) === 0) {
18704
+ parent = parent.parent;
18705
+ continue;
18706
+ }
18707
+ if (parent.type === import_utils94.AST_NODE_TYPES.CallExpression && parent.callee.type === import_utils94.AST_NODE_TYPES.MemberExpression && !parent.callee.computed && parent.callee.object.type === import_utils94.AST_NODE_TYPES.Identifier && parent.callee.object.name === "Object" && parent.callee.property.type === import_utils94.AST_NODE_TYPES.Identifier && parent.callee.property.name === "fromEntries" && (resolvedBinding(parent.callee.object)?.defs.length ?? 0) === 0) {
18708
+ parent = parent.parent;
18709
+ continue;
18710
+ }
18711
+ if (parent.type === import_utils94.AST_NODE_TYPES.CallExpression || parent.type === import_utils94.AST_NODE_TYPES.NewExpression || parent.type === import_utils94.AST_NODE_TYPES.TaggedTemplateExpression || parent.type === import_utils94.AST_NODE_TYPES.ArrowFunctionExpression || parent.type === import_utils94.AST_NODE_TYPES.FunctionExpression || parent.type === import_utils94.AST_NODE_TYPES.FunctionDeclaration) return null;
18296
18712
  parent = parent.parent;
18297
18713
  }
18298
18714
  return null;
@@ -18472,13 +18888,14 @@ var require_zod_form_validation_default = createRule({
18472
18888
  // src/rules/store-insert-requires-on-conflict.ts
18473
18889
  var import_utils95 = require("@typescript-eslint/utils");
18474
18890
  var STORE_INSERT_REQUIRES_ON_CONFLICT_DOCUMENTATION = {
18475
- summary: "Require embedded inserts in explicitly replayable callables to carry conflict handling.",
18476
- rationale: "A callable named as an enqueue, seed, migration, schedule, ensure, or upsert promises replay safety.",
18477
- remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
18891
+ summary: "Review conflict handling for embedded inserts in replay-named callables.",
18892
+ rationale: "Names such as seed, enqueue, or upsert suggest that repeated execution deserves a conflict-policy review, but do not prove a replay contract.",
18893
+ remediation: "Choose conflict handling appropriate to the schema and SQL dialect, or document why this insertion must fail on a duplicate.",
18478
18894
  category: "correctness",
18895
+ 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."],
18479
18896
  examples: [
18480
- { 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 },
18481
- { 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 }
18897
+ { 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 },
18898
+ { 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 }
18482
18899
  ]
18483
18900
  };
18484
18901
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
@@ -18490,14 +18907,18 @@ function owningCallableName(node) {
18490
18907
  return current.id?.name ?? null;
18491
18908
  }
18492
18909
  if (current.type === "MethodDefinition") {
18493
- return current.key.type === "Identifier" ? current.key.name : null;
18910
+ return !current.computed && current.key.type === "Identifier" ? current.key.name : null;
18494
18911
  }
18495
18912
  if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "VariableDeclarator" && current.parent.id.type === "Identifier") {
18496
18913
  return current.parent.id.name;
18497
18914
  }
18498
- if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && current.parent.key.type === "Identifier") {
18915
+ if ((current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") && current.parent.type === "Property" && !current.parent.computed && current.parent.key.type === "Identifier") {
18499
18916
  return current.parent.key.name;
18500
18917
  }
18918
+ if (current.type === "ArrowFunctionExpression" || current.type === "FunctionExpression") {
18919
+ const parent = current.parent;
18920
+ return parent.type === "MethodDefinition" && !parent.computed && parent.key.type === "Identifier" ? parent.key.name : null;
18921
+ }
18501
18922
  }
18502
18923
  return null;
18503
18924
  }
@@ -18508,11 +18929,11 @@ var store_insert_requires_on_conflict_default = createRule({
18508
18929
  meta: {
18509
18930
  type: "problem",
18510
18931
  docs: {
18511
- description: "Require embedded inserts in explicitly replayable callables to carry conflict handling."
18932
+ description: "Review conflict handling for embedded inserts in replay-named callables."
18512
18933
  },
18513
18934
  schema: [],
18514
18935
  messages: {
18515
- 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`)."
18936
+ 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."
18516
18937
  }
18517
18938
  },
18518
18939
  defaultOptions: [],
@@ -18525,7 +18946,7 @@ var store_insert_requires_on_conflict_default = createRule({
18525
18946
  return;
18526
18947
  }
18527
18948
  const owner = owningCallableName(node);
18528
- if (owner !== null && !REPLAY_CONTRACT_NAME.test(owner)) {
18949
+ if (owner === null || !REPLAY_CONTRACT_NAME.test(owner)) {
18529
18950
  return;
18530
18951
  }
18531
18952
  context.report({ node, messageId: "storeInsertRequiresOnConflict" });
@@ -18538,15 +18959,13 @@ var import_utils96 = require("@typescript-eslint/utils");
18538
18959
  var STEPDOWN_DOCUMENTATION = {
18539
18960
  summary: "Place a private helper below its sole direct same-scope caller.",
18540
18961
  rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
18541
- remediation: "Move the private helper below its sole caller.",
18962
+ remediation: "Consider moving the private helper below its sole caller after reviewing initialization and reflection dependencies.",
18542
18963
  category: "maintainability",
18543
- autofix: "safe",
18544
18964
  limitations: [
18545
18965
  "Generated and test files, cycles, dynamic references, overload targets, and helpers with multiple callers are excluded.",
18546
- "Class helpers must be private; their sole caller may be public, protected, or private so the fix also satisfies accessibility ordering.",
18966
+ "Class helpers must be private; their sole caller may be public, protected, or private.",
18547
18967
  "Runtime class-field, static-block, computed-member, and decorator barriers are never crossed.",
18548
- "Autofix is limited to comment-free class-member boundaries; module functions and ambiguous comment ownership remain report-only.",
18549
- "Overlapping helper chains remain report-only so ESLint never leaves a partially reordered class after exhausting its fix-pass limit."
18968
+ "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."
18550
18969
  ],
18551
18970
  examples: [
18552
18971
  { 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 },
@@ -18556,7 +18975,7 @@ var STEPDOWN_DOCUMENTATION = {
18556
18975
  function isFunction(node) {
18557
18976
  return node.type === import_utils96.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils96.AST_NODE_TYPES.FunctionDeclaration || node.type === import_utils96.AST_NODE_TYPES.FunctionExpression;
18558
18977
  }
18559
- function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true, makeFix) {
18978
+ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned, canMove = () => true) {
18560
18979
  const byName = new Map(scopeDefinitions.map((definition) => [definition.name, definition]));
18561
18980
  const cycles = cycleComponents(calls);
18562
18981
  const callers = /* @__PURE__ */ new Map();
@@ -18575,12 +18994,10 @@ function reportMisordered(context, candidates, scopeDefinitions, calls, pinned,
18575
18994
  if (callerName2 === void 0 || cycles.has(helper.name) && cycles.get(helper.name) === cycles.get(callerName2)) continue;
18576
18995
  const caller = byName.get(callerName2);
18577
18996
  if (caller === void 0 || helper.node.range[0] >= caller.node.range[0] || !canMove(helper, caller)) continue;
18578
- const fix = makeFix?.(helper, caller);
18579
18997
  context.report({
18580
18998
  node: helper.node,
18581
18999
  messageId: "helperAboveOnlyCaller",
18582
- data: { helper: helper.name, caller: callerName2 },
18583
- ...fix === void 0 ? {} : { fix }
19000
+ data: { helper: helper.name, caller: callerName2 }
18584
19001
  });
18585
19002
  }
18586
19003
  }
@@ -18751,15 +19168,15 @@ function referencedPropertyName(node) {
18751
19168
  if (!node.computed && node.property.type === import_utils96.AST_NODE_TYPES.Identifier) return node.property.name;
18752
19169
  return node.computed && node.property.type === import_utils96.AST_NODE_TYPES.Literal && typeof node.property.value === "string" ? node.property.value : null;
18753
19170
  }
18754
- function walk2(node, visitorKeys, visit, nestedFunction = false) {
19171
+ function walk(node, visitorKeys, visit, nestedFunction = false) {
18755
19172
  visit(node, nestedFunction);
18756
19173
  const nested = nestedFunction || isFunction(node);
18757
19174
  for (const key of visitorKeys[node.type] ?? []) {
18758
19175
  const child = node[key];
18759
19176
  if (Array.isArray(child)) {
18760
- for (const item of child) if (typeof item === "object" && item !== null && "type" in item) walk2(item, visitorKeys, visit, nested);
19177
+ for (const item of child) if (typeof item === "object" && item !== null && "type" in item) walk(item, visitorKeys, visit, nested);
18761
19178
  } else if (typeof child === "object" && child !== null && "type" in child) {
18762
- walk2(child, visitorKeys, visit, nested);
19179
+ walk(child, visitorKeys, visit, nested);
18763
19180
  }
18764
19181
  }
18765
19182
  }
@@ -18807,7 +19224,7 @@ function classScope(context, node, computedReferenceNames) {
18807
19224
  const parameterDecoratorNodes = /* @__PURE__ */ new Set();
18808
19225
  for (const parameter of method.value.params) {
18809
19226
  for (const decorator of parameter.decorators) {
18810
- walk2(decorator, context.sourceCode.visitorKeys, (current) => parameterDecoratorNodes.add(current));
19227
+ walk(decorator, context.sourceCode.visitorKeys, (current) => parameterDecoratorNodes.add(current));
18811
19228
  }
18812
19229
  }
18813
19230
  const thisValue = (value) => {
@@ -18839,10 +19256,10 @@ function classScope(context, node, computedReferenceNames) {
18839
19256
  }
18840
19257
  };
18841
19258
  for (const parameter of method.value.params) {
18842
- walk2(parameter, context.sourceCode.visitorKeys, collectAlias);
19259
+ walk(parameter, context.sourceCode.visitorKeys, collectAlias);
18843
19260
  }
18844
19261
  for (const statement of method.value.body.body) {
18845
- walk2(statement, context.sourceCode.visitorKeys, collectAlias);
19262
+ walk(statement, context.sourceCode.visitorKeys, collectAlias);
18846
19263
  }
18847
19264
  const visitCall = (current, nestedFunction) => {
18848
19265
  if (current.type === import_utils96.AST_NODE_TYPES.VariableDeclarator && current.id.type === import_utils96.AST_NODE_TYPES.ObjectPattern && thisValue(current.init)) {
@@ -18876,19 +19293,19 @@ function classScope(context, node, computedReferenceNames) {
18876
19293
  calls.set(caller, callees);
18877
19294
  };
18878
19295
  for (const decorator of method.decorators) {
18879
- walk2(decorator, context.sourceCode.visitorKeys, visitCall, true);
19296
+ walk(decorator, context.sourceCode.visitorKeys, visitCall, true);
18880
19297
  }
18881
- if (method.computed) walk2(method.key, context.sourceCode.visitorKeys, visitCall, true);
19298
+ if (method.computed) walk(method.key, context.sourceCode.visitorKeys, visitCall, true);
18882
19299
  for (const parameter of method.value.params) {
18883
- walk2(parameter, context.sourceCode.visitorKeys, visitCall);
19300
+ walk(parameter, context.sourceCode.visitorKeys, visitCall);
18884
19301
  }
18885
19302
  for (const statement of method.value.body.body) {
18886
- walk2(statement, context.sourceCode.visitorKeys, visitCall);
19303
+ walk(statement, context.sourceCode.visitorKeys, visitCall);
18887
19304
  }
18888
19305
  }
18889
19306
  for (const member of node.body.body) {
18890
19307
  if (member.type === import_utils96.AST_NODE_TYPES.MethodDefinition || member.type === import_utils96.AST_NODE_TYPES.TSAbstractMethodDefinition) continue;
18891
- walk2(member, context.sourceCode.visitorKeys, (current) => {
19308
+ walk(member, context.sourceCode.visitorKeys, (current) => {
18892
19309
  if (current.type !== import_utils96.AST_NODE_TYPES.MemberExpression) return;
18893
19310
  const target = referencedMethod(context, current, classVariables);
18894
19311
  const possibleTarget = target ?? referencedPropertyName(current);
@@ -18907,37 +19324,7 @@ function classScope(context, node, computedReferenceNames) {
18907
19324
  if (helperIndex === void 0 || callerIndex === void 0) return false;
18908
19325
  return runtimeBarrierPrefix[callerIndex + 1] === runtimeBarrierPrefix[helperIndex + 1];
18909
19326
  };
18910
- const incoming = /* @__PURE__ */ new Map();
18911
- for (const [caller, callees] of calls) {
18912
- for (const callee of callees) {
18913
- if (callee === caller) continue;
18914
- const callers = incoming.get(callee) ?? /* @__PURE__ */ new Set();
18915
- callers.add(caller);
18916
- incoming.set(callee, callers);
18917
- }
18918
- }
18919
- reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove, (helper, caller) => {
18920
- if (!canMove(helper, caller)) return void 0;
18921
- const helperCallsAnother = [...calls.get(helper.name) ?? []].some((callee) => callee !== helper.name);
18922
- const callerIsAnotherHelper = [...incoming.get(caller.name) ?? []].some((name) => name !== helper.name);
18923
- if (helperCallsAnother || callerIsAnotherHelper) return void 0;
18924
- const helperMember = helper.node;
18925
- const callerMember = caller.node;
18926
- const helperIndex = memberIndexes.get(helperMember);
18927
- if (helperIndex === void 0) return void 0;
18928
- const next = node.body.body[helperIndex + 1];
18929
- const suffixEnd = next?.range[0] ?? node.body.range[1] - 1;
18930
- const suffix = context.sourceCode.text.slice(helperMember.range[1], suffixEnd);
18931
- if (!/^\s*$/u.test(suffix)) return void 0;
18932
- const previous = node.body.body[helperIndex - 1];
18933
- const prefixStart = previous?.range[1] ?? node.body.range[0] + 1;
18934
- if (!/^\s*$/u.test(context.sourceCode.text.slice(prefixStart, helperMember.range[0]))) return void 0;
18935
- const helperText = context.sourceCode.getText(helperMember);
18936
- return (fixer) => [
18937
- fixer.removeRange([helperMember.range[0], suffixEnd]),
18938
- fixer.insertTextAfter(callerMember, `${suffix}${helperText}`)
18939
- ];
18940
- });
19327
+ reportMisordered(context, definitions, scopeDefinitions, calls, pinned, canMove);
18941
19328
  }
18942
19329
  function isClassRuntimeBarrier(member) {
18943
19330
  switch (member.type) {
@@ -18959,7 +19346,6 @@ var stepdown_default = createRule({
18959
19346
  type: "suggestion",
18960
19347
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
18961
19348
  schema: [],
18962
- fixable: "code",
18963
19349
  messages: {
18964
19350
  helperAboveOnlyCaller: "Private helper `{{helper}}` is defined above its only caller `{{caller}}`; move it below the caller."
18965
19351
  }
@@ -18978,7 +19364,7 @@ var stepdown_default = createRule({
18978
19364
  "Program:exit": (program) => {
18979
19365
  moduleScope(context, program);
18980
19366
  const computedReferenceNames = /* @__PURE__ */ new Set();
18981
- walk2(program, context.sourceCode.visitorKeys, (node) => {
19367
+ walk(program, context.sourceCode.visitorKeys, (node) => {
18982
19368
  if (node.type === import_utils96.AST_NODE_TYPES.MemberExpression && node.computed && node.property.type === import_utils96.AST_NODE_TYPES.Literal && typeof node.property.value === "string") computedReferenceNames.add(node.property.value);
18983
19369
  });
18984
19370
  for (const node of classes) classScope(context, node, computedReferenceNames);
@@ -19032,7 +19418,7 @@ var SOURCE_COUPLED_TEST_DOCUMENTATION = {
19032
19418
  remediation: "Parse the artifact, execute its validator, or assert on another runtime contract.",
19033
19419
  category: "testing",
19034
19420
  limitations: [
19035
- "The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
19421
+ "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.",
19036
19422
  "When raw representation is genuinely the contract (for example a golden or compatibility sentinel), use an exact line suppression with the reason."
19037
19423
  ],
19038
19424
  examples: [
@@ -19071,6 +19457,11 @@ function stringValue(node) {
19071
19457
  const current = unwrap7(node);
19072
19458
  if (current.type === import_utils97.AST_NODE_TYPES.Literal && typeof current.value === "string") return current.value;
19073
19459
  if (current.type === import_utils97.AST_NODE_TYPES.TemplateLiteral && current.expressions.length === 0) return current.quasis[0]?.value.cooked ?? null;
19460
+ if (current.type === import_utils97.AST_NODE_TYPES.BinaryExpression && current.operator === "+") {
19461
+ const left = stringValue(current.left);
19462
+ const right = stringValue(current.right);
19463
+ return left === null || right === null ? null : left + right;
19464
+ }
19074
19465
  return null;
19075
19466
  }
19076
19467
  function importSource(node) {
@@ -19100,14 +19491,19 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19100
19491
  const scopes = [newScope()];
19101
19492
  const reportedOrigins = /* @__PURE__ */ new Set();
19102
19493
  const currentScope = () => scopes.at(-1) ?? scopes[0];
19103
- const visible = (kind, name2) => {
19494
+ const bindingOf = (node) => import_utils97.ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
19495
+ const visible = (kind, node) => {
19496
+ const name2 = bindingOf(node);
19497
+ if (name2 === null || name2.references.some((reference) => reference.isWrite() && reference.init !== true)) return false;
19104
19498
  for (let index = scopes.length - 1; index >= 0; index--) {
19105
19499
  const scope = scopes[index];
19106
19500
  if (scope.declared.has(name2)) return scope[kind].has(name2);
19107
19501
  }
19108
19502
  return false;
19109
19503
  };
19110
- const visibleRawOrigins = (name2) => {
19504
+ const visibleRawOrigins = (node) => {
19505
+ const name2 = bindingOf(node);
19506
+ if (name2 === null || name2.references.some((reference) => reference.isWrite() && reference.init !== true)) return /* @__PURE__ */ new Set();
19111
19507
  for (let index = scopes.length - 1; index >= 0; index--) {
19112
19508
  const scope = scopes[index];
19113
19509
  if (scope.declared.has(name2)) return scope.rawOrigins.get(name2) ?? /* @__PURE__ */ new Set();
@@ -19118,15 +19514,14 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19118
19514
  const current = unwrap7(node);
19119
19515
  const value = stringValue(current);
19120
19516
  if (value !== null) return sourceSuffixRe.test(value);
19121
- if (current.type === import_utils97.AST_NODE_TYPES.Identifier) return visible("paths", current.name);
19122
- if (current.type === import_utils97.AST_NODE_TYPES.BinaryExpression && current.operator === "+") {
19123
- return sourcePath(current.left) || sourcePath(current.right);
19124
- }
19125
- if (current.type === import_utils97.AST_NODE_TYPES.TemplateLiteral) return current.expressions.some(sourcePath);
19517
+ if (current.type === import_utils97.AST_NODE_TYPES.Identifier) return visible("paths", current);
19126
19518
  if (current.type === import_utils97.AST_NODE_TYPES.CallExpression || current.type === import_utils97.AST_NODE_TYPES.NewExpression) {
19127
- return current.arguments.some((argument) => argument.type !== import_utils97.AST_NODE_TYPES.SpreadElement && sourcePath(argument));
19519
+ const callee = current.callee;
19520
+ const first = current.arguments[0];
19521
+ if (first === void 0 || first.type === import_utils97.AST_NODE_TYPES.SpreadElement) return false;
19522
+ if (current.type === import_utils97.AST_NODE_TYPES.NewExpression && callee.type === import_utils97.AST_NODE_TYPES.Identifier && callee.name === "URL" && (bindingOf(callee)?.defs.length ?? 0) === 0) return sourcePath(first);
19523
+ if (callee.type === import_utils97.AST_NODE_TYPES.Identifier && bindingOf(callee)?.defs.some((definition) => definition.node.type === import_utils97.AST_NODE_TYPES.ImportSpecifier && definition.node.imported.type === import_utils97.AST_NODE_TYPES.Identifier && definition.node.imported.name === "fileURLToPath" && definition.node.parent.type === import_utils97.AST_NODE_TYPES.ImportDeclaration && ["node:url", "url"].includes(String(definition.node.parent.source.value)))) return sourcePath(first);
19128
19524
  }
19129
- if (current.type === import_utils97.AST_NODE_TYPES.MemberExpression) return sourcePath(current.object);
19130
19525
  return false;
19131
19526
  };
19132
19527
  const rawRead = (node) => {
@@ -19134,16 +19529,16 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19134
19529
  if (current.type !== import_utils97.AST_NODE_TYPES.CallExpression || current.arguments.length === 0) return false;
19135
19530
  const callee = unwrap7(current.callee);
19136
19531
  if (callee.type === import_utils97.AST_NODE_TYPES.Identifier) {
19137
- return visible("fsReaders", callee.name) && sourcePath(current.arguments[0]);
19532
+ return visible("fsReaders", callee) && sourcePath(current.arguments[0]);
19138
19533
  }
19139
19534
  if (callee.type !== import_utils97.AST_NODE_TYPES.MemberExpression) return false;
19140
19535
  const name2 = staticMemberName7(callee);
19141
19536
  const object = unwrap7(callee.object);
19142
- return name2 !== null && FS_READERS.has(name2) && object.type === import_utils97.AST_NODE_TYPES.Identifier && visible("fsObjects", object.name) && sourcePath(current.arguments[0]);
19537
+ return name2 !== null && FS_READERS.has(name2) && object.type === import_utils97.AST_NODE_TYPES.Identifier && visible("fsObjects", object) && sourcePath(current.arguments[0]);
19143
19538
  };
19144
19539
  const rawOrigins = (node) => {
19145
19540
  const current = unwrap7(node);
19146
- if (current.type === import_utils97.AST_NODE_TYPES.Identifier) return visibleRawOrigins(current.name);
19541
+ if (current.type === import_utils97.AST_NODE_TYPES.Identifier) return visibleRawOrigins(current);
19147
19542
  if (rawRead(current)) return /* @__PURE__ */ new Set([`${current.range[0]}:${current.range[1]}`]);
19148
19543
  if (current.type === import_utils97.AST_NODE_TYPES.BinaryExpression && current.operator === "+") return /* @__PURE__ */ new Set([...rawOrigins(current.left), ...rawOrigins(current.right)]);
19149
19544
  if (current.type === import_utils97.AST_NODE_TYPES.MemberExpression && staticMemberName7(current) === "length") return rawOrigins(current.object);
@@ -19184,14 +19579,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19184
19579
  if (receiver.type !== import_utils97.AST_NODE_TYPES.Identifier || receiver.name !== "assert" || !ASSERT_MATCHERS.has(matcher)) return /* @__PURE__ */ new Set();
19185
19580
  return new Set(node.arguments.flatMap((argument) => argument.type === import_utils97.AST_NODE_TYPES.SpreadElement ? [] : [...evidenceOrigins(argument)]));
19186
19581
  };
19187
- const rawRegexExtractionOrigins = (node) => {
19188
- const callee = unwrap7(node.callee);
19189
- if (callee.type !== import_utils97.AST_NODE_TYPES.MemberExpression || staticMemberName7(callee) !== "matchAll" || node.arguments.length !== 1) return /* @__PURE__ */ new Set();
19190
- const argument = node.arguments[0];
19191
- if (argument?.type !== import_utils97.AST_NODE_TYPES.Literal || !(argument.value instanceof RegExp)) return /* @__PURE__ */ new Set();
19192
- return rawOrigins(callee.object);
19193
- };
19194
- const declare = (name2, state) => {
19582
+ const declare = (node, state) => {
19583
+ const name2 = bindingOf(node);
19584
+ if (name2 === null) return;
19195
19585
  const scope = currentScope();
19196
19586
  scope.declared.add(name2);
19197
19587
  scope.collections.delete(name2);
@@ -19211,18 +19601,8 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19211
19601
  const current = unwrap7(node);
19212
19602
  return current.type === import_utils97.AST_NODE_TYPES.ArrayExpression && current.elements.length > 0 && current.elements.every((element) => element !== null && element.type !== import_utils97.AST_NODE_TYPES.SpreadElement && sourcePath(element));
19213
19603
  };
19214
- const declaredNames2 = (node) => {
19215
- const current = unwrap7(node);
19216
- if (current.type === import_utils97.AST_NODE_TYPES.Identifier) return [current.name];
19217
- if (current.type === import_utils97.AST_NODE_TYPES.AssignmentPattern) return declaredNames2(current.left);
19218
- if (current.type === import_utils97.AST_NODE_TYPES.RestElement) return declaredNames2(current.argument);
19219
- if (current.type === import_utils97.AST_NODE_TYPES.ArrayPattern) return current.elements.flatMap((element) => element === null ? [] : declaredNames2(element));
19220
- if (current.type === import_utils97.AST_NODE_TYPES.ObjectPattern) return current.properties.flatMap((property) => property.type === import_utils97.AST_NODE_TYPES.RestElement ? declaredNames2(property.argument) : declaredNames2(property.value));
19221
- return [];
19222
- };
19223
- const enterFunction = (node) => {
19604
+ const enterFunction = () => {
19224
19605
  scopes.push(newScope());
19225
- for (const parameter of node.params) for (const name2 of declaredNames2(parameter)) declare(name2, {});
19226
19606
  };
19227
19607
  const exitFunction = () => {
19228
19608
  scopes.pop();
@@ -19234,9 +19614,9 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19234
19614
  for (const specifier of node.specifiers) {
19235
19615
  if (specifier.type === import_utils97.AST_NODE_TYPES.ImportSpecifier) {
19236
19616
  const imported = specifier.imported.type === import_utils97.AST_NODE_TYPES.Identifier ? specifier.imported.name : String(specifier.imported.value);
19237
- if (FS_READERS.has(imported)) declare(specifier.local.name, { fsReader: true });
19617
+ if (FS_READERS.has(imported)) declare(specifier.local, { fsReader: true });
19238
19618
  } else {
19239
- declare(specifier.local.name, { fsObject: true });
19619
+ declare(specifier.local, { fsObject: true });
19240
19620
  }
19241
19621
  }
19242
19622
  },
@@ -19245,35 +19625,34 @@ function createSourceCoupledRule(name, documentation, sourceSuffixRe) {
19245
19625
  VariableDeclarator(node) {
19246
19626
  if (node.init === null) return;
19247
19627
  const required = requireSource(node.init);
19628
+ const initializer = unwrap7(node.init);
19629
+ if (required !== null && initializer.type === import_utils97.AST_NODE_TYPES.CallExpression && initializer.callee.type === import_utils97.AST_NODE_TYPES.Identifier && (bindingOf(initializer.callee)?.defs.length ?? 0) > 0) return;
19248
19630
  if (required !== null && FS_MODULES.has(required) && node.id.type === import_utils97.AST_NODE_TYPES.Identifier) {
19249
- declare(node.id.name, { fsObject: true });
19631
+ declare(node.id, { fsObject: true });
19250
19632
  return;
19251
19633
  }
19252
19634
  if (node.id.type === import_utils97.AST_NODE_TYPES.ObjectPattern && required !== null && FS_MODULES.has(required)) {
19253
19635
  for (const property of node.id.properties) {
19254
19636
  if (property.type !== import_utils97.AST_NODE_TYPES.Property || property.value.type !== import_utils97.AST_NODE_TYPES.Identifier) continue;
19255
19637
  const key = property.key.type === import_utils97.AST_NODE_TYPES.Identifier ? property.key.name : property.key.type === import_utils97.AST_NODE_TYPES.Literal ? String(property.key.value) : "";
19256
- if (FS_READERS.has(key)) declare(property.value.name, { fsReader: true });
19638
+ if (FS_READERS.has(key)) declare(property.value, { fsReader: true });
19257
19639
  }
19258
19640
  return;
19259
19641
  }
19260
19642
  if (node.id.type !== import_utils97.AST_NODE_TYPES.Identifier) return;
19261
- declare(node.id.name, { collection: sourceCollection(node.init), path: sourcePath(node.init), rawOrigins: rawOrigins(node.init) });
19643
+ declare(node.id, { collection: sourceCollection(node.init), path: sourcePath(node.init), rawOrigins: rawOrigins(node.init) });
19262
19644
  },
19263
19645
  AssignmentExpression(node) {
19264
- if (node.left.type === import_utils97.AST_NODE_TYPES.Identifier) declare(node.left.name, { path: sourcePath(node.right), rawOrigins: rawOrigins(node.right) });
19646
+ if (node.left.type === import_utils97.AST_NODE_TYPES.Identifier) declare(node.left, {});
19265
19647
  },
19266
19648
  ForOfStatement(node) {
19267
19649
  const right = unwrap7(node.right);
19268
- const collection = right.type === import_utils97.AST_NODE_TYPES.Identifier && visible("collections", right.name);
19650
+ const collection = right.type === import_utils97.AST_NODE_TYPES.Identifier && visible("collections", right);
19269
19651
  const left = node.left.type === import_utils97.AST_NODE_TYPES.VariableDeclaration ? node.left.declarations[0]?.id : node.left;
19270
- if (collection && left?.type === import_utils97.AST_NODE_TYPES.Identifier) declare(left.name, { path: true });
19652
+ if (collection && left?.type === import_utils97.AST_NODE_TYPES.Identifier) declare(left, { path: true });
19271
19653
  },
19272
19654
  CallExpression(node) {
19273
- const origins = /* @__PURE__ */ new Set([
19274
- ...rawAssertionOrigins(node),
19275
- ...rawRegexExtractionOrigins(node)
19276
- ]);
19655
+ const origins = rawAssertionOrigins(node);
19277
19656
  if (origins.size === 0 || [...origins].every((origin) => reportedOrigins.has(origin))) return;
19278
19657
  for (const origin of origins) reportedOrigins.add(origin);
19279
19658
  context.report({ node, messageId: "rawSourceOracle" });
@@ -19416,8 +19795,8 @@ var IAC_SOURCE_COUPLED_TEST_DOCUMENTATION = {
19416
19795
  remediation: "Parse rendered plan JSON, query the provider, or exercise the deployed runtime contract.",
19417
19796
  category: "testing",
19418
19797
  limitations: [
19419
- "The rule follows lexical aliases, source-path collections, awaited reads, and common text operations; interprocedural flows remain unreported.",
19420
- "The warning-stage rule remains suppressible for calibration; promotion may make the locked policy non-suppressible."
19798
+ "The rule follows stable lexical bindings and static source paths. Reassigned bindings, dynamic paths, unknown path wrappers, iterator pipelines, and interprocedural flows remain unreported.",
19799
+ "When raw representation is genuinely the contract, use an exact line suppression explaining that contract."
19421
19800
  ],
19422
19801
  examples: [
19423
19802
  {
@@ -19892,7 +20271,7 @@ var RULES = {
19892
20271
  };
19893
20272
  var meta = {
19894
20273
  name: "@sarj/eslint-plugin",
19895
- version: "15.17.9"
20274
+ version: "15.17.11"
19896
20275
  };
19897
20276
  var APPLICATION_ONLY_RULES = [];
19898
20277
  var LIBRARY_IMPORT_POLICY = ["error", {