@sarj/eslint-plugin 12.0.0 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  applicationOnlyRules: () => applicationOnlyRules,
34
34
  default: () => index_default,
35
+ publicDocumentation: () => publicDocumentation,
35
36
  recommendedRules: () => recommendedRules,
36
37
  renamedRules: () => renamedRules,
37
38
  retiredRules: () => retiredRules,
@@ -49,7 +50,193 @@ var REPO_BLOB = "https://github.com/sarj-ai/standards/blob/main";
49
50
  var TESTS_DIR = "packages/typescript/tests/rules";
50
51
  var examplesPath = (name) => `${TESTS_DIR}/${name}.test.ts`;
51
52
  var examplesUrl = (name) => `${REPO_BLOB}/${examplesPath(name)}`;
52
- var createRule = import_utils.ESLintUtils.RuleCreator(examplesUrl);
53
+ var KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
54
+ var MAX_SUMMARY_LENGTH = 160;
55
+ var eslintCreateRule = import_utils.ESLintUtils.RuleCreator(examplesUrl);
56
+ function createRule(config) {
57
+ const { documentation, ...eslintConfig } = config;
58
+ const rule = eslintCreateRule(eslintConfig);
59
+ if (documentation !== void 0) {
60
+ Object.defineProperty(rule, "documentation", {
61
+ configurable: false,
62
+ enumerable: false,
63
+ value: nativeSpec(eslintConfig, documentation),
64
+ writable: false
65
+ });
66
+ }
67
+ return rule;
68
+ }
69
+ function documentationWarnings(rules2) {
70
+ return Object.entries(rules2).filter(([, rule]) => rule.documentation === void 0).map(([name]) => `${name}: source-owned documentation has not been migrated`).sort();
71
+ }
72
+ function publicDocumentation(rules2) {
73
+ const missing = documentationWarnings(rules2);
74
+ if (missing.length > 0) {
75
+ throw new TypeError(`cannot publish an incomplete rule catalog:
76
+ ${missing.join("\n")}`);
77
+ }
78
+ return Object.entries(rules2).sort(([left], [right]) => left.localeCompare(right)).map(([ruleId, rule]) => {
79
+ const spec = rule.documentation;
80
+ if (spec === void 0 || spec.ruleId !== ruleId) {
81
+ throw new TypeError(`${ruleId}: native documentation identity mismatch`);
82
+ }
83
+ return deepFreeze({
84
+ engine: spec.engine,
85
+ ruleId: spec.ruleId,
86
+ code: spec.code,
87
+ summary: spec.summary,
88
+ rationale: spec.rationale,
89
+ remediation: spec.remediation,
90
+ category: spec.category,
91
+ languages: spec.languages,
92
+ autofix: spec.autofix,
93
+ aliases: spec.aliases,
94
+ limitations: spec.limitations,
95
+ filePatterns: spec.filePatterns,
96
+ references: spec.references,
97
+ since: spec.since,
98
+ messageIds: spec.messageIds,
99
+ optionsSchema: spec.optionsSchema,
100
+ examples: spec.publicExamples.map(publicExample)
101
+ });
102
+ });
103
+ }
104
+ function publicExample(example) {
105
+ return {
106
+ id: example.id,
107
+ title: example.title,
108
+ outcome: example.outcome,
109
+ files: example.files.map(publicFile),
110
+ focusPath: example.focusPath,
111
+ expectedCount: example.expectedCount,
112
+ fixedFiles: (example.fixedFiles ?? []).map(publicFile)
113
+ };
114
+ }
115
+ function publicFile(file) {
116
+ return { path: file.path, source: file.source };
117
+ }
118
+ function nativeSpec(config, documentation) {
119
+ const { name, meta: meta2 } = config;
120
+ if (!KEBAB_CASE.test(name)) throw new TypeError("rule ID must be lowercase kebab-case");
121
+ for (const [label, value] of [
122
+ ["summary", documentation.summary],
123
+ ["rationale", documentation.rationale],
124
+ ["remediation", documentation.remediation]
125
+ ]) {
126
+ if (value.trim().length === 0) throw new TypeError(`rule ${label} must not be empty`);
127
+ }
128
+ if (documentation.summary.includes("\n") || documentation.summary.length > MAX_SUMMARY_LENGTH) {
129
+ throw new TypeError(`rule summary must be one line of at most ${MAX_SUMMARY_LENGTH} characters`);
130
+ }
131
+ if (documentation.summary !== meta2.docs?.description) {
132
+ throw new TypeError(`${name}: ESLint description must equal the authored documentation summary`);
133
+ }
134
+ const aliases = [...documentation.aliases ?? []];
135
+ assertUnique(aliases, "rule aliases");
136
+ if (aliases.some((alias) => !KEBAB_CASE.test(alias) || alias === name)) {
137
+ throw new TypeError("rule aliases must be historical lowercase kebab-case IDs");
138
+ }
139
+ const limitations = [...documentation.limitations ?? []];
140
+ const filePatterns = [...documentation.filePatterns ?? []];
141
+ if ([...limitations, ...filePatterns].some((value) => value.trim().length === 0)) {
142
+ throw new TypeError("rule limitations and file patterns must not be empty");
143
+ }
144
+ const references = [...documentation.references ?? []];
145
+ if (references.some((reference) => !reference.startsWith("https://"))) {
146
+ throw new TypeError("rule references must use https");
147
+ }
148
+ const examples = [...documentation.examples ?? []];
149
+ examples.forEach(validateExample);
150
+ assertUnique(examples.map((example) => example.id), "rule example IDs");
151
+ const publicOutcomes = new Set(examples.filter((example) => example.public === true).map((example) => example.outcome));
152
+ if (publicOutcomes.size > 0 && !(publicOutcomes.has("match") && publicOutcomes.has("no-match"))) {
153
+ throw new TypeError("published rule examples must include matching and non-matching cases");
154
+ }
155
+ const messageIds = Object.keys(meta2.messages).sort();
156
+ const schema = optionsSchema(meta2.schema);
157
+ const spec = {
158
+ engine: "eslint",
159
+ ruleId: name,
160
+ code: null,
161
+ key: `eslint:${name}`,
162
+ summary: documentation.summary,
163
+ rationale: documentation.rationale,
164
+ remediation: documentation.remediation,
165
+ category: documentation.category,
166
+ languages: [...documentation.languages ?? ["typescript"]],
167
+ autofix: documentation.autofix ?? "none",
168
+ aliases,
169
+ limitations,
170
+ filePatterns,
171
+ references,
172
+ since: documentation.since ?? null,
173
+ examples,
174
+ publicExamples: examples.filter((example) => example.public === true),
175
+ messageIds,
176
+ optionsSchema: schema
177
+ };
178
+ return deepFreeze(spec);
179
+ }
180
+ function optionsSchema(value) {
181
+ if (!Array.isArray(value)) return isObject(value) ? value : null;
182
+ const items = value;
183
+ if (items.length === 0) return null;
184
+ const [only] = items;
185
+ return items.length === 1 && isObject(only) ? only : { type: "array", items };
186
+ }
187
+ function isObject(value) {
188
+ return value !== null && typeof value === "object";
189
+ }
190
+ function validateExample(example) {
191
+ if (!KEBAB_CASE.test(example.id)) {
192
+ throw new TypeError("example ID must be lowercase kebab-case");
193
+ }
194
+ if (example.title.trim().length === 0) {
195
+ throw new TypeError("example title must not be empty");
196
+ }
197
+ if (!Number.isSafeInteger(example.expectedCount) || example.expectedCount < 0) {
198
+ throw new TypeError("example expected count must be a non-negative integer");
199
+ }
200
+ if (example.outcome === "match" && example.expectedCount < 1) {
201
+ throw new TypeError("matching examples must expect at least one diagnostic");
202
+ }
203
+ if (example.outcome === "no-match" && example.expectedCount !== 0) {
204
+ throw new TypeError("non-matching examples must expect zero diagnostics");
205
+ }
206
+ if (example.files.length === 0) {
207
+ throw new TypeError("example files must not be empty");
208
+ }
209
+ const paths = example.files.map((file) => file.path);
210
+ assertUnique(paths, "example file paths");
211
+ for (const file of [...example.files, ...example.fixedFiles ?? []]) {
212
+ assertSafeRelativePath(file.path, "example file path");
213
+ if (file.source.length === 0) {
214
+ throw new TypeError("example file source must not be empty");
215
+ }
216
+ }
217
+ assertSafeRelativePath(example.focusPath, "example focus path");
218
+ if (!paths.includes(example.focusPath)) {
219
+ throw new TypeError("example focus path must name one example file");
220
+ }
221
+ assertUnique((example.fixedFiles ?? []).map((file) => file.path), "fixed example file paths");
222
+ }
223
+ function assertSafeRelativePath(path, label) {
224
+ if (path.length === 0 || path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:[\\/]/u.test(path) || path.split(/[\\/]/u).includes("..")) {
225
+ throw new TypeError(`${label} must be a safe relative path`);
226
+ }
227
+ }
228
+ function assertUnique(values, label) {
229
+ if (new Set(values).size !== values.length) {
230
+ throw new TypeError(`${label} must be unique`);
231
+ }
232
+ }
233
+ function deepFreeze(value) {
234
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
235
+ for (const child of Object.values(value)) deepFreeze(child);
236
+ Object.freeze(value);
237
+ }
238
+ return value;
239
+ }
53
240
 
54
241
  // src/rules/_paths.ts
55
242
  var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
@@ -138,6 +325,35 @@ function isScriptFile(filename) {
138
325
  }
139
326
 
140
327
  // src/rules/enforce-file-structure.ts
328
+ var enforceFileStructureDocumentation = {
329
+ summary: "Require imports before body statements and require `use server` to be the first statement.",
330
+ rationale: "Interleaved imports obscure module dependencies, while a displaced `use server` string is not an active directive.",
331
+ remediation: "Move `use server` to the first statement when present, then place imports before declarations and executable statements.",
332
+ category: "correctness",
333
+ limitations: [
334
+ "The rule skips tests and generated files, treats re-exports as neutral, and does not order body declarations."
335
+ ],
336
+ examples: [
337
+ {
338
+ id: "imports-first",
339
+ title: "Imports precede module declarations",
340
+ outcome: "no-match",
341
+ files: [{ path: "src/component.ts", source: "import { z } from 'zod';\nexport const schema = z.string();" }],
342
+ focusPath: "src/component.ts",
343
+ expectedCount: 0,
344
+ public: true
345
+ },
346
+ {
347
+ id: "import-after-declaration",
348
+ title: "An import follows a module declaration",
349
+ outcome: "match",
350
+ files: [{ path: "src/component.ts", source: "export const x = 1;\nimport { z } from 'zod';" }],
351
+ focusPath: "src/component.ts",
352
+ expectedCount: 1,
353
+ public: true
354
+ }
355
+ ]
356
+ };
141
357
  var classifyStatement = (statement) => {
142
358
  switch (statement.type) {
143
359
  case import_utils2.AST_NODE_TYPES.ImportDeclaration:
@@ -159,10 +375,11 @@ var isUseServerDirective = (statement) => {
159
375
  };
160
376
  var enforce_file_structure_default = createRule({
161
377
  name: "enforce-file-structure",
378
+ documentation: enforceFileStructureDocumentation,
162
379
  meta: {
163
380
  type: "suggestion",
164
381
  docs: {
165
- description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. When a module contains a `use server` directive, it must be the first statement in the file."
382
+ description: "Require imports before body statements and require `use server` to be the first statement."
166
383
  },
167
384
  schema: [],
168
385
  messages: {
@@ -233,6 +450,41 @@ var OMITTED_AST_KEYS = /* @__PURE__ */ new Set([
233
450
  var MIN_STATEMENTS = 3;
234
451
  var MAX_NORMALIZED_STRING_LENGTH = 64;
235
452
  var TEST_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
453
+ var duplicateTestBodyDocumentation = {
454
+ summary: "Disallow substantial sibling tests with the same body shape; express their differing inputs as a parameterized case table.",
455
+ rationale: "Copy-pasted test bodies hide the cases that differ and allow equivalent assertions to drift independently.",
456
+ remediation: "Move the varying inputs and expected values into a case table consumed by `test.each(...)` or `it.each(...)`.",
457
+ category: "testing",
458
+ limitations: [
459
+ "The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments."
460
+ ],
461
+ examples: [
462
+ {
463
+ id: "parameterized-cases",
464
+ title: "A case table shares one test body",
465
+ outcome: "no-match",
466
+ files: [{
467
+ path: "src/user.test.ts",
468
+ source: "test.each(['a', 'b'])('parses %s', (value) => { const x = parse(value); expect(x.ok).toBe(true); expect(x.value).toBe(value); });"
469
+ }],
470
+ focusPath: "src/user.test.ts",
471
+ expectedCount: 0,
472
+ public: true
473
+ },
474
+ {
475
+ id: "copied-sibling-tests",
476
+ title: "Sibling tests repeat the same body",
477
+ outcome: "match",
478
+ files: [{
479
+ path: "src/user.test.ts",
480
+ source: "test('accepts a', () => { const result = parse('a'); expect(result.ok).toBe(true); expect(result.value).toBe('a'); });\ntest('accepts b', () => { const result = parse('b'); expect(result.ok).toBe(true); expect(result.value).toBe('b'); });"
481
+ }],
482
+ focusPath: "src/user.test.ts",
483
+ expectedCount: 1,
484
+ public: true
485
+ }
486
+ ]
487
+ };
236
488
  function rootIdentifier(callee) {
237
489
  if (callee.type === import_utils3.AST_NODE_TYPES.Identifier) return callee;
238
490
  if (callee.type === import_utils3.AST_NODE_TYPES.MemberExpression) return rootIdentifier(callee.object);
@@ -348,6 +600,7 @@ function normalizedLiteral(node) {
348
600
  }
349
601
  var duplicate_test_body_default = createRule({
350
602
  name: "duplicate-test-body",
603
+ documentation: duplicateTestBodyDocumentation,
351
604
  meta: {
352
605
  type: "suggestion",
353
606
  docs: {
@@ -414,12 +667,43 @@ var duplicate_test_body_default = createRule({
414
667
 
415
668
  // src/rules/no-async-callback-in-wait-for.ts
416
669
  var import_utils4 = require("@typescript-eslint/utils");
670
+ var noAsyncCallbackInWaitForDocumentation = {
671
+ summary: "Disallow async callbacks in `waitFor` to prevent swallowed promise rejections.",
672
+ rationale: "`waitFor` retries synchronous assertions; an async callback changes that contract and can hide a rejected assertion promise.",
673
+ remediation: "Remove `async` and keep the assertions inside `waitFor` synchronous.",
674
+ category: "testing",
675
+ aliases: ["no-async-callback-in-waitfor"],
676
+ limitations: [
677
+ "The rule checks inline first-argument callbacks to bare or non-computed `.waitFor` calls in test files."
678
+ ],
679
+ examples: [
680
+ {
681
+ id: "synchronous-wait-for-callback",
682
+ title: "waitFor retries a synchronous assertion",
683
+ outcome: "no-match",
684
+ files: [{ path: "src/component.test.ts", source: "it('works', async () => { await waitFor(() => expect(foo).toBe(true)); });" }],
685
+ focusPath: "src/component.test.ts",
686
+ expectedCount: 0,
687
+ public: true
688
+ },
689
+ {
690
+ id: "async-wait-for-callback",
691
+ title: "waitFor receives an async callback",
692
+ outcome: "match",
693
+ files: [{ path: "src/component.test.ts", source: "it('fails', async () => { await waitFor(async () => expect(foo).toBe(true)); });" }],
694
+ focusPath: "src/component.test.ts",
695
+ expectedCount: 1,
696
+ public: true
697
+ }
698
+ ]
699
+ };
417
700
  var isWaitForCallee = (callee) => {
418
701
  if (callee.type === import_utils4.AST_NODE_TYPES.Identifier) return callee.name === "waitFor";
419
702
  return callee.type === import_utils4.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils4.AST_NODE_TYPES.Identifier && callee.property.name === "waitFor";
420
703
  };
421
704
  var no_async_callback_in_wait_for_default = createRule({
422
705
  name: "no-async-callback-in-wait-for",
706
+ documentation: noAsyncCallbackInWaitForDocumentation,
423
707
  meta: {
424
708
  type: "problem",
425
709
  docs: {
@@ -452,6 +736,35 @@ var no_async_callback_in_wait_for_default = createRule({
452
736
 
453
737
  // src/rules/no-client-side-data-fetching.ts
454
738
  var import_utils5 = require("@typescript-eslint/utils");
739
+ var noClientSideDataFetchingDocumentation = {
740
+ summary: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`.",
741
+ rationale: "Effect-driven reads begin after rendering and can create request waterfalls, duplicate fetches, and loading-state layout shifts.",
742
+ remediation: "Fetch in a React Server Component or Server Action, or use a client cache such as SWR or React Query.",
743
+ category: "performance",
744
+ limitations: [
745
+ "The rule recognizes common fetch clients syntactically and exempts analytics endpoints and non-GET `fetch` calls."
746
+ ],
747
+ examples: [
748
+ {
749
+ id: "effect-without-fetch",
750
+ title: "An effect performs no data request",
751
+ outcome: "no-match",
752
+ files: [{ path: "src/users.tsx", source: "import { useEffect } from 'react'; useEffect(() => { console.log('mounted'); }, []);" }],
753
+ focusPath: "src/users.tsx",
754
+ expectedCount: 0,
755
+ public: true
756
+ },
757
+ {
758
+ id: "fetch-inside-effect",
759
+ title: "An effect starts a data request",
760
+ outcome: "match",
761
+ files: [{ path: "src/users.tsx", source: "useEffect(() => { fetch('/api/users'); }, []);" }],
762
+ focusPath: "src/users.tsx",
763
+ expectedCount: 1,
764
+ public: true
765
+ }
766
+ ]
767
+ };
455
768
  var FETCH_LIBS = /* @__PURE__ */ new Set(["axios", "ky", "superagent"]);
456
769
  var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
457
770
  "get",
@@ -550,10 +863,11 @@ function extractUrlString(node) {
550
863
  }
551
864
  var no_client_side_data_fetching_default = createRule({
552
865
  name: "no-client-side-data-fetching",
866
+ documentation: noClientSideDataFetchingDocumentation,
553
867
  meta: {
554
868
  type: "problem",
555
869
  docs: {
556
- description: "Disallow data fetching inside `useEffect` / `useLayoutEffect`; prefer React Server Components, Server Actions, or a client-side cache (SWR / React Query)."
870
+ description: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`."
557
871
  },
558
872
  schema: [],
559
873
  messages: {
@@ -774,6 +1088,35 @@ function headTokens(source) {
774
1088
  }
775
1089
 
776
1090
  // src/rules/no-comment-cruft.ts
1091
+ var noCommentCruftDocumentation = {
1092
+ summary: "Flag commented-out code, section-banner comments, and leading file-header comment preambles.",
1093
+ rationale: "Decorative, narrated, or dead-code comments obscure the constraints and rationale that comments should preserve.",
1094
+ remediation: "Delete dead code and narration; express boundaries with named code and retain only comments that explain constraints or intent.",
1095
+ category: "maintainability",
1096
+ limitations: [
1097
+ "The rule skips generated files and conservatively preserves prose, issue references, licenses, examples, and tool directives."
1098
+ ],
1099
+ examples: [
1100
+ {
1101
+ id: "rationale-comment",
1102
+ title: "A comment explains why retry is required",
1103
+ outcome: "no-match",
1104
+ files: [{ path: "src/retry.ts", source: "// retry because the upstream API is flaky\nconst x = retry();" }],
1105
+ focusPath: "src/retry.ts",
1106
+ expectedCount: 0,
1107
+ public: true
1108
+ },
1109
+ {
1110
+ id: "region-banner",
1111
+ title: "A region comment decorates a code boundary",
1112
+ outcome: "match",
1113
+ files: [{ path: "src/helpers.ts", source: "const x = 1;\n// region helpers\nconst y = 2;" }],
1114
+ focusPath: "src/helpers.ts",
1115
+ expectedCount: 1,
1116
+ public: true
1117
+ }
1118
+ ]
1119
+ };
777
1120
  var LEADING_PREAMBLE_MIN = 4;
778
1121
  var WALL_MIN_STATEMENTS = 4;
779
1122
  var WALL_MIN_COMMENTS = 3;
@@ -1142,6 +1485,7 @@ function hasCommentedOutCode(texts, precedingProse, allowCall) {
1142
1485
  }
1143
1486
  var no_comment_cruft_default = createRule({
1144
1487
  name: "no-comment-cruft",
1488
+ documentation: noCommentCruftDocumentation,
1145
1489
  meta: {
1146
1490
  type: "suggestion",
1147
1491
  docs: {
@@ -1348,6 +1692,35 @@ var no_comment_cruft_default = createRule({
1348
1692
 
1349
1693
  // src/rules/no-conditional-in-test.ts
1350
1694
  var import_utils8 = require("@typescript-eslint/utils");
1695
+ var noConditionalInTestDocumentation = {
1696
+ summary: "Disallow test conditionals that can skip a runtime assertion or exit the test before one runs.",
1697
+ rationale: "A branch can skip the assertion that gives a test its meaning, allowing unexpected inputs to pass silently.",
1698
+ remediation: "Split each path into a separate test or use a parameterized case table with unconditional assertions.",
1699
+ category: "testing",
1700
+ limitations: [
1701
+ "The rule exempts lifecycle hooks, nested helpers, and narrow guards whose outcome is pinned by a preceding assertion."
1702
+ ],
1703
+ examples: [
1704
+ {
1705
+ id: "unconditional-assertion",
1706
+ title: "A test always executes its assertion",
1707
+ outcome: "no-match",
1708
+ files: [{ path: "src/component.test.ts", source: "it('works', () => { expect(1).toBe(1); });" }],
1709
+ focusPath: "src/component.test.ts",
1710
+ expectedCount: 0,
1711
+ public: true
1712
+ },
1713
+ {
1714
+ id: "conditional-assertion",
1715
+ title: "A branch can skip the assertion",
1716
+ outcome: "match",
1717
+ files: [{ path: "src/component.test.ts", source: "it('fails with if', () => { if (ready) { expect(value).toBe(1); } });" }],
1718
+ focusPath: "src/component.test.ts",
1719
+ expectedCount: 1,
1720
+ public: true
1721
+ }
1722
+ ]
1723
+ };
1351
1724
  var TEST_CALLERS2 = /* @__PURE__ */ new Set(["it", "test"]);
1352
1725
  var NON_TEST_MEMBERS = /* @__PURE__ */ new Set([
1353
1726
  "afterAll",
@@ -1628,6 +2001,7 @@ function isShortCircuitedAssertion(node) {
1628
2001
  }
1629
2002
  var no_conditional_in_test_default = createRule({
1630
2003
  name: "no-conditional-in-test",
2004
+ documentation: noConditionalInTestDocumentation,
1631
2005
  meta: {
1632
2006
  type: "problem",
1633
2007
  docs: {
@@ -1675,6 +2049,35 @@ var no_conditional_in_test_default = createRule({
1675
2049
 
1676
2050
  // src/rules/no-cors-wildcard-with-credentials.ts
1677
2051
  var import_utils9 = require("@typescript-eslint/utils");
2052
+ var noCorsWildcardWithCredentialsDocumentation = {
2053
+ summary: "Disallow wildcard CORS origins when credentials are enabled.",
2054
+ rationale: "Reflecting every origin while allowing credentials can let an untrusted site read authenticated cross-origin responses.",
2055
+ remediation: "Enumerate the trusted origins that may receive credentialed responses.",
2056
+ category: "security",
2057
+ limitations: [
2058
+ "The rule detects literal CORS option and header combinations within the same syntactic scope; it does not resolve runtime configuration."
2059
+ ],
2060
+ examples: [
2061
+ {
2062
+ id: "trusted-origin-with-credentials",
2063
+ title: "Credentials are limited to a trusted origin",
2064
+ outcome: "no-match",
2065
+ files: [{ path: "src/server.ts", source: "app.use(cors({ origin: 'https://app.example.com', credentials: true }));" }],
2066
+ focusPath: "src/server.ts",
2067
+ expectedCount: 0,
2068
+ public: true
2069
+ },
2070
+ {
2071
+ id: "wildcard-origin-with-credentials",
2072
+ title: "Credentials are enabled for every origin",
2073
+ outcome: "match",
2074
+ files: [{ path: "src/server.ts", source: "app.use(cors({ origin: '*', credentials: true }));" }],
2075
+ focusPath: "src/server.ts",
2076
+ expectedCount: 1,
2077
+ public: true
2078
+ }
2079
+ ]
2080
+ };
1678
2081
  var ACAO_HEADER = "access-control-allow-origin";
1679
2082
  var ACAC_HEADER = "access-control-allow-credentials";
1680
2083
  var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
@@ -1818,10 +2221,11 @@ function enclosingScope(node) {
1818
2221
  }
1819
2222
  var no_cors_wildcard_with_credentials_default = createRule({
1820
2223
  name: "no-cors-wildcard-with-credentials",
2224
+ documentation: noCorsWildcardWithCredentialsDocumentation,
1821
2225
  meta: {
1822
2226
  type: "problem",
1823
2227
  docs: {
1824
- description: 'Disallow CORS that reflects any Origin (`"*"`) while allowing credentials; any site could then read authenticated responses. Enumerate explicit trusted origins instead.'
2228
+ description: "Disallow wildcard CORS origins when credentials are enabled."
1825
2229
  },
1826
2230
  schema: [],
1827
2231
  messages: {
@@ -2028,6 +2432,35 @@ function createSqlListener(handler) {
2028
2432
  }
2029
2433
 
2030
2434
  // src/rules/no-dynamic-sql.ts
2435
+ var noDynamicSqlDocumentation = {
2436
+ summary: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods.",
2437
+ rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
2438
+ remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
2439
+ category: "security",
2440
+ limitations: [
2441
+ "The rule recognizes SQL by syntax and configured method names; static fragments and parameterizing tagged templates are exempt."
2442
+ ],
2443
+ examples: [
2444
+ {
2445
+ id: "bound-sql-parameter",
2446
+ title: "A runtime value is bound separately",
2447
+ outcome: "no-match",
2448
+ files: [{ path: "src/users.ts", source: "db.prepare('select * from users where id = ?').bind(userId);" }],
2449
+ focusPath: "src/users.ts",
2450
+ expectedCount: 0,
2451
+ public: true
2452
+ },
2453
+ {
2454
+ id: "interpolated-sql-value",
2455
+ title: "A runtime value is interpolated into SQL",
2456
+ outcome: "match",
2457
+ files: [{ path: "src/users.ts", source: "db.prepare(`select * from users where id = '${userId}'`);" }],
2458
+ focusPath: "src/users.ts",
2459
+ expectedCount: 1,
2460
+ public: true
2461
+ }
2462
+ ]
2463
+ };
2031
2464
  var DEFAULT_METHODS = ["prepare", "exec", "query"];
2032
2465
  var CONSTANT_CASE_RE = /^[A-Z][A-Z0-9_]*$/;
2033
2466
  function isStaticFragment(expression) {
@@ -2095,10 +2528,11 @@ function statementMethodName(node, methods) {
2095
2528
  }
2096
2529
  var no_dynamic_sql_default = createRule({
2097
2530
  name: "no-dynamic-sql",
2531
+ documentation: noDynamicSqlDocumentation,
2098
2532
  meta: {
2099
2533
  type: "problem",
2100
2534
  docs: {
2101
- description: "Disallow interpolating or concatenating a runtime value into a SQL statement passed to `prepare`/`exec`/`query`; use a placeholder and bind the value."
2535
+ description: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods."
2102
2536
  },
2103
2537
  schema: [
2104
2538
  {
@@ -2145,6 +2579,32 @@ var no_dynamic_sql_default = createRule({
2145
2579
 
2146
2580
  // src/rules/no-enum.ts
2147
2581
  var import_utils12 = require("@typescript-eslint/utils");
2582
+ var noEnumDocumentation = {
2583
+ summary: "Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.",
2584
+ rationale: "TypeScript enums emit runtime objects and numeric enums accept values outside their declared members, adding behavior where a type-only model is sufficient.",
2585
+ remediation: "Replace the enum with a string-literal union or an `as const` object and derive its value type from that object.",
2586
+ category: "maintainability",
2587
+ examples: [
2588
+ {
2589
+ id: "string-literal-union",
2590
+ title: "A string-literal union has no emitted runtime enum",
2591
+ outcome: "no-match",
2592
+ files: [{ path: "src/status.ts", source: 'type Status = "active" | "inactive";' }],
2593
+ focusPath: "src/status.ts",
2594
+ expectedCount: 0,
2595
+ public: true
2596
+ },
2597
+ {
2598
+ id: "numeric-enum",
2599
+ title: "A numeric enum emits a mutable runtime object",
2600
+ outcome: "match",
2601
+ files: [{ path: "src/status.ts", source: "enum Status { Active, Inactive }" }],
2602
+ focusPath: "src/status.ts",
2603
+ expectedCount: 1,
2604
+ public: true
2605
+ }
2606
+ ]
2607
+ };
2148
2608
  function matchesAnyPattern(filename, patterns) {
2149
2609
  for (const pattern of patterns) {
2150
2610
  const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
@@ -2156,6 +2616,7 @@ function matchesAnyPattern(filename, patterns) {
2156
2616
  }
2157
2617
  var no_enum_default = createRule({
2158
2618
  name: "no-enum",
2619
+ documentation: noEnumDocumentation,
2159
2620
  meta: {
2160
2621
  type: "suggestion",
2161
2622
  docs: {
@@ -2201,6 +2662,41 @@ var no_enum_default = createRule({
2201
2662
 
2202
2663
  // src/rules/no-fat-try-blocks.ts
2203
2664
  var import_utils13 = require("@typescript-eslint/utils");
2665
+ var noFatTryBlocksDocumentation = {
2666
+ summary: "Disallow `try` blocks containing more than three top-level operations that can throw.",
2667
+ rationale: "A broad `try` block obscures which operation failed and encourages one catch clause to recover from unrelated errors.",
2668
+ remediation: "Keep only the operations that share one recovery policy inside the `try` block and move other work outside it.",
2669
+ category: "correctness",
2670
+ limitations: [
2671
+ "The rule uses syntax to identify throwing operations and exempts generated files, finally blocks, rethrows, and terminal error boundaries."
2672
+ ],
2673
+ examples: [
2674
+ {
2675
+ id: "focused-try-block",
2676
+ title: "A try block contains three throwing operations",
2677
+ outcome: "no-match",
2678
+ files: [{
2679
+ path: "src/load.ts",
2680
+ source: "function f() { try { const a = one(); const b = two(); const c = three(); } catch (error) { handle(error); } finish(); }"
2681
+ }],
2682
+ focusPath: "src/load.ts",
2683
+ expectedCount: 0,
2684
+ public: true
2685
+ },
2686
+ {
2687
+ id: "broad-try-block",
2688
+ title: "A try block contains four throwing operations",
2689
+ outcome: "match",
2690
+ files: [{
2691
+ path: "src/load.ts",
2692
+ source: "function f() { try { const a = one(); const b = two(); const c = three(); const d = four(); } catch (error) { handle(error); } finish(); }"
2693
+ }],
2694
+ focusPath: "src/load.ts",
2695
+ expectedCount: 1,
2696
+ public: true
2697
+ }
2698
+ ]
2699
+ };
2204
2700
  var MAX_TRY_BODY_STATEMENTS = 3;
2205
2701
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2206
2702
  import_utils13.AST_NODE_TYPES.FunctionDeclaration,
@@ -2536,10 +3032,11 @@ var handlerReturnsSuccessShaped = (handler) => subtreeMatches2(
2536
3032
  );
2537
3033
  var no_fat_try_blocks_default = createRule({
2538
3034
  name: "no-fat-try-blocks",
3035
+ documentation: noFatTryBlocksDocumentation,
2539
3036
  meta: {
2540
3037
  type: "problem",
2541
3038
  docs: {
2542
- description: "Disallow `try` blocks with more than three top-level statements that can throw \u2014 isolate the throwing statement and move non-throwing work outside."
3039
+ description: "Disallow `try` blocks containing more than three top-level operations that can throw."
2543
3040
  },
2544
3041
  schema: [],
2545
3042
  messages: {
@@ -2580,6 +3077,38 @@ var no_fat_try_blocks_default = createRule({
2580
3077
 
2581
3078
  // src/rules/no-hand-rolled-sleep.ts
2582
3079
  var import_utils14 = require("@typescript-eslint/utils");
3080
+ var noHandRolledSleepDocumentation = {
3081
+ summary: "Disallow uncancellable promisified timers and timeout arms.",
3082
+ rationale: "A timer that outlives an aborted operation or a lost promise race retains work and can keep the process alive until it fires.",
3083
+ remediation: "Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.",
3084
+ category: "correctness",
3085
+ limitations: [
3086
+ "The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions."
3087
+ ],
3088
+ examples: [
3089
+ {
3090
+ id: "cancellable-node-timer",
3091
+ title: "A standard-library timer accepts an abort signal",
3092
+ outcome: "no-match",
3093
+ files: [{
3094
+ path: "src/lib/queue.ts",
3095
+ source: 'import { setTimeout as sleep } from "node:timers/promises";\nawait sleep(500, undefined, { signal });'
3096
+ }],
3097
+ focusPath: "src/lib/queue.ts",
3098
+ expectedCount: 0,
3099
+ public: true
3100
+ },
3101
+ {
3102
+ id: "uncancellable-sleep",
3103
+ title: "A Promise wraps a timer without cancellation",
3104
+ outcome: "match",
3105
+ files: [{ path: "src/lib/queue.ts", source: "await new Promise((resolve) => setTimeout(resolve, 500));" }],
3106
+ focusPath: "src/lib/queue.ts",
3107
+ expectedCount: 1,
3108
+ public: true
3109
+ }
3110
+ ]
3111
+ };
2583
3112
  var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
2584
3113
  "globalThis",
2585
3114
  "window",
@@ -2659,10 +3188,11 @@ function isRaceArm(node) {
2659
3188
  }
2660
3189
  var no_hand_rolled_sleep_default = createRule({
2661
3190
  name: "no-hand-rolled-sleep",
3191
+ documentation: noHandRolledSleepDocumentation,
2662
3192
  meta: {
2663
3193
  type: "problem",
2664
3194
  docs: {
2665
- description: "Disallow hand-rolled promisified timers (`new Promise((r) => setTimeout(r, ms))`) and hand-rolled `Promise.race` timeout arms; the stdlib forms are cancellable, these are not."
3195
+ description: "Disallow uncancellable promisified timers and timeout arms."
2666
3196
  },
2667
3197
  schema: [
2668
3198
  {
@@ -2755,6 +3285,17 @@ var no_hand_rolled_sleep_default = createRule({
2755
3285
 
2756
3286
  // src/rules/no-hand-rolled-spinner.ts
2757
3287
  var import_utils15 = require("@typescript-eslint/utils");
3288
+ var noHandRolledSpinnerDocumentation = {
3289
+ summary: "Disallow intrinsic elements styled as Tailwind border-ring spinners outside the design-system implementation.",
3290
+ rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
3291
+ remediation: "Render the design-system Spinner component instead.",
3292
+ category: "maintainability",
3293
+ limitations: ["Only static className values on div and span elements are inspected."],
3294
+ examples: [
3295
+ { id: "design-system-spinner", title: "Use the shared spinner", outcome: "no-match", files: [{ path: "src/loading-state.tsx", source: '<Spinner className="size-4" />' }], focusPath: "src/loading-state.tsx", expectedCount: 0, public: true },
3296
+ { id: "border-ring-spinner", title: "Do not rebuild a spinner", outcome: "match", files: [{ path: "src/loading-state.tsx", source: '<div className="size-4 animate-spin rounded-full border-2 border-t-transparent" />' }], focusPath: "src/loading-state.tsx", expectedCount: 1, public: true }
3297
+ ]
3298
+ };
2758
3299
  var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
2759
3300
  var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
2760
3301
  var TRANSPARENT_EDGE = /^border-[trbl]-transparent$/u;
@@ -2770,6 +3311,7 @@ function staticClassName(attribute) {
2770
3311
  }
2771
3312
  var no_hand_rolled_spinner_default = createRule({
2772
3313
  name: "no-hand-rolled-spinner",
3314
+ documentation: noHandRolledSpinnerDocumentation,
2773
3315
  meta: {
2774
3316
  type: "suggestion",
2775
3317
  docs: {
@@ -2807,6 +3349,17 @@ var no_hand_rolled_spinner_default = createRule({
2807
3349
 
2808
3350
  // src/rules/no-insecure-random-id.ts
2809
3351
  var import_utils16 = require("@typescript-eslint/utils");
3352
+ var noInsecureRandomIdDocumentation = {
3353
+ summary: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead.",
3354
+ rationale: "Math.random is predictable and lacks the entropy required for security-sensitive values.",
3355
+ remediation: "Generate the value with crypto.randomUUID or crypto.getRandomValues.",
3356
+ category: "security",
3357
+ limitations: ["Ambiguous identifiers and test files are excluded to avoid flagging sampling and fixture data."],
3358
+ examples: [
3359
+ { 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 },
3360
+ { 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 }
3361
+ ]
3362
+ };
2810
3363
  var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
2811
3364
  var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
2812
3365
  var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
@@ -2949,6 +3502,7 @@ function collectStaticStringParts(node, out) {
2949
3502
  }
2950
3503
  var no_insecure_random_id_default = createRule({
2951
3504
  name: "no-insecure-random-id",
3505
+ documentation: noInsecureRandomIdDocumentation,
2952
3506
  meta: {
2953
3507
  type: "problem",
2954
3508
  docs: {
@@ -2990,6 +3544,17 @@ var no_insecure_random_id_default = createRule({
2990
3544
 
2991
3545
  // src/rules/no-json-stringify-error.ts
2992
3546
  var import_utils17 = require("@typescript-eslint/utils");
3547
+ var noJsonStringifyErrorDocumentation = {
3548
+ summary: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable.",
3549
+ rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
3550
+ remediation: "Serialize explicit error fields or use an error-aware serializer.",
3551
+ category: "correctness",
3552
+ limitations: ["The rule uses local syntax and naming evidence rather than type information."],
3553
+ examples: [
3554
+ { id: "explicit-error-message", title: "Serialize an enumerable error field", outcome: "no-match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err.message }); }" }], focusPath: "src/report.ts", expectedCount: 0, public: true },
3555
+ { id: "stringified-error", title: "Do not stringify an Error object", outcome: "match", files: [{ path: "src/report.ts", source: "try { f(); } catch (err) { JSON.stringify({ error: err }); }" }], focusPath: "src/report.ts", expectedCount: 1, public: true }
3556
+ ]
3557
+ };
2993
3558
  var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
2994
3559
  var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
2995
3560
  var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
@@ -3177,6 +3742,7 @@ function nestedExpressionSuggestsError(expression, scope) {
3177
3742
  }
3178
3743
  var no_json_stringify_error_default = createRule({
3179
3744
  name: "no-json-stringify-error",
3745
+ documentation: noJsonStringifyErrorDocumentation,
3180
3746
  meta: {
3181
3747
  type: "problem",
3182
3748
  docs: {
@@ -3227,6 +3793,47 @@ function isZodModule(source) {
3227
3793
  }
3228
3794
 
3229
3795
  // src/rules/no-impossible-zod-literal-bounds.ts
3796
+ var noImpossibleZodLiteralBoundsDocumentation = {
3797
+ summary: "Disallow same-chain literal Zod bounds whose accepted set is mathematically empty.",
3798
+ rationale: "A schema with contradictory literal bounds rejects every input, turning validation into an unreachable contract that usually reflects a typo.",
3799
+ remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
3800
+ category: "correctness",
3801
+ limitations: [
3802
+ "Only finite numeric literals in a single number, string, or array schema chain are compared.",
3803
+ "Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
3804
+ "Test and generated files are excluded."
3805
+ ],
3806
+ examples: [
3807
+ {
3808
+ id: "compatible-number-bounds",
3809
+ title: "Allow a number admitted by both bounds",
3810
+ outcome: "no-match",
3811
+ files: [
3812
+ {
3813
+ path: "src/schema.ts",
3814
+ source: 'import { z } from "zod"; const S = z.number().gte(3).lte(3);'
3815
+ }
3816
+ ],
3817
+ focusPath: "src/schema.ts",
3818
+ expectedCount: 0,
3819
+ public: true
3820
+ },
3821
+ {
3822
+ id: "contradictory-number-bounds",
3823
+ title: "Reject an empty numeric interval",
3824
+ outcome: "match",
3825
+ files: [
3826
+ {
3827
+ path: "src/schema.ts",
3828
+ source: 'import { z } from "zod"; const S = z.number().min(5).max(4);'
3829
+ }
3830
+ ],
3831
+ focusPath: "src/schema.ts",
3832
+ expectedCount: 1,
3833
+ public: true
3834
+ }
3835
+ ]
3836
+ };
3230
3837
  var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
3231
3838
  var NUMBER_METHODS = /* @__PURE__ */ new Set([
3232
3839
  "gt",
@@ -3286,6 +3893,7 @@ function isOutermostCall(node) {
3286
3893
  }
3287
3894
  var no_impossible_zod_literal_bounds_default = createRule({
3288
3895
  name: "no-impossible-zod-literal-bounds",
3896
+ documentation: noImpossibleZodLiteralBoundsDocumentation,
3289
3897
  meta: {
3290
3898
  type: "problem",
3291
3899
  docs: {
@@ -3543,6 +4151,17 @@ function createLogMatcher(options = {}) {
3543
4151
  }
3544
4152
 
3545
4153
  // src/rules/no-log-only-catch.ts
4154
+ var noLogOnlyCatchDocumentation = {
4155
+ summary: "Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead.",
4156
+ rationale: "Swallowing an exception after logging lets execution continue as if the operation succeeded.",
4157
+ remediation: "Rethrow the error, return an explicit fallback, or perform concrete recovery.",
4158
+ category: "correctness",
4159
+ limitations: ["Documented intentional ignores, tests, and catches with observable recovery are excluded."],
4160
+ examples: [
4161
+ { id: "rethrow-after-log", title: "Preserve failure after logging", outcome: "no-match", files: [{ path: "src/task.ts", source: "try { run(); } catch (error) { console.error(error); throw error; }" }], focusPath: "src/task.ts", expectedCount: 0, public: true },
4162
+ { id: "log-and-swallow", title: "Do not only log a failure", outcome: "match", files: [{ path: "src/task.ts", source: "try { run(); } catch (error) { console.error(error); }" }], focusPath: "src/task.ts", expectedCount: 1, public: true }
4163
+ ]
4164
+ };
3546
4165
  var BENCHMARK_DIR_RE = /(?:^|[\\/])benchmarks?[\\/]/;
3547
4166
  var SINGLE_STATEMENT_HOSTS = /* @__PURE__ */ new Set([
3548
4167
  import_utils20.AST_NODE_TYPES.DoWhileStatement,
@@ -3630,6 +4249,7 @@ function isSeedValue(node) {
3630
4249
  }
3631
4250
  var no_log_only_catch_default = createRule({
3632
4251
  name: "no-log-only-catch",
4252
+ documentation: noLogOnlyCatchDocumentation,
3633
4253
  meta: {
3634
4254
  type: "problem",
3635
4255
  docs: {
@@ -3811,6 +4431,17 @@ function typedFunction(node) {
3811
4431
  }
3812
4432
 
3813
4433
  // src/rules/no-long-comment.ts
4434
+ var noLongCommentDocumentation = {
4435
+ summary: "Flag unusually large unstructured prose blocks in implementation code.",
4436
+ rationale: "Large narrative comments become stale and obscure the local facts that belong beside the code.",
4437
+ remediation: "Keep only durable local constraints and express the remaining behavior in code.",
4438
+ category: "maintainability",
4439
+ limitations: ["Structured API docs, tests, scripts, generated files, and versioned dependencies are excluded."],
4440
+ examples: [
4441
+ { id: "local-fact", title: "Keep a concise local fact", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// The cache is process local.\nconst cache = new Map();" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
4442
+ { id: "prose-wall", title: "Avoid an unstructured prose wall", outcome: "match", files: [{ path: "src/chart.ts", source: "/** One. Two. Three. Four. Five. Six. Seven. Eight. */\nconst chart = createChart();" }], focusPath: "src/chart.ts", expectedCount: 1, public: true }
4443
+ ]
4444
+ };
3814
4445
  var EXCESSIVE_SENTENCE_COUNT = 8;
3815
4446
  var EXCESSIVE_WORD_COUNT = 120;
3816
4447
  var PROSE_WORD_RE = /[\p{L}\p{N}][\p{L}\p{N}'’-]*/gu;
@@ -3830,6 +4461,7 @@ function wordUnits(text) {
3830
4461
  }
3831
4462
  var no_long_comment_default = createRule({
3832
4463
  name: "no-long-comment",
4464
+ documentation: noLongCommentDocumentation,
3833
4465
  meta: {
3834
4466
  type: "suggestion",
3835
4467
  docs: { description: "Flag unusually large unstructured prose blocks in implementation code." },
@@ -3855,6 +4487,17 @@ var no_long_comment_default = createRule({
3855
4487
 
3856
4488
  // src/rules/no-generic-single-export-module.ts
3857
4489
  var import_utils23 = require("@typescript-eslint/utils");
4490
+ var noGenericSingleExportModuleDocumentation = {
4491
+ summary: "Disallow generic module stems when one runtime export already names the responsibility.",
4492
+ rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
4493
+ remediation: "Rename the module after its single runtime export.",
4494
+ category: "maintainability",
4495
+ limitations: ["Only configured generic stems with exactly one public runtime export are reported."],
4496
+ examples: [
4497
+ { 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 },
4498
+ { 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 }
4499
+ ]
4500
+ };
3858
4501
  var GENERIC_STEMS = /* @__PURE__ */ new Set([
3859
4502
  "base",
3860
4503
  "common",
@@ -4017,6 +4660,7 @@ function memberPropertyName(node) {
4017
4660
  }
4018
4661
  var no_generic_single_export_module_default = createRule({
4019
4662
  name: "no-generic-single-export-module",
4663
+ documentation: noGenericSingleExportModuleDocumentation,
4020
4664
  meta: {
4021
4665
  type: "suggestion",
4022
4666
  docs: { description: "Disallow generic module stems when one runtime export already names the responsibility." },
@@ -4069,10 +4713,22 @@ var no_generic_single_export_module_default = createRule({
4069
4713
 
4070
4714
  // src/rules/no-offset-pagination.ts
4071
4715
  var import_utils24 = require("@typescript-eslint/utils");
4716
+ var noOffsetPaginationDocumentation = {
4717
+ 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.",
4718
+ rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
4719
+ remediation: "Page with a stable ordered key and a cursor predicate.",
4720
+ category: "performance",
4721
+ limitations: ["Only embedded SQL is inspected; test files and non-pagination OFFSET syntax are excluded."],
4722
+ examples: [
4723
+ { 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 },
4724
+ { 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 }
4725
+ ]
4726
+ };
4072
4727
  var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
4073
4728
  var OFFSET_GATE = /offset/i;
4074
4729
  var no_offset_pagination_default = createRule({
4075
4730
  name: "no-offset-pagination",
4731
+ documentation: noOffsetPaginationDocumentation,
4076
4732
  meta: {
4077
4733
  type: "problem",
4078
4734
  docs: {
@@ -4099,6 +4755,17 @@ var no_offset_pagination_default = createRule({
4099
4755
 
4100
4756
  // src/rules/no-positional-tuple-return.ts
4101
4757
  var import_utils25 = require("@typescript-eslint/utils");
4758
+ var noPositionalTupleReturnDocumentation = {
4759
+ summary: "Disallow returning a multi-field tuple from an exported function; return a named object so call sites cannot mismatch slots.",
4760
+ rationale: "Public tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
4761
+ remediation: "Return an object whose property names describe each value.",
4762
+ category: "maintainability",
4763
+ limitations: ["Only declared multi-field tuple returns on public TypeScript surfaces are inspected."],
4764
+ examples: [
4765
+ { id: "named-object-return", title: "Return named fields", outcome: "no-match", files: [{ path: "src/download.ts", source: "export function download(): { body: string; status: number } { return impl(); }" }], focusPath: "src/download.ts", expectedCount: 0, public: true },
4766
+ { id: "tuple-return", title: "Do not expose positional fields", outcome: "match", files: [{ path: "src/download.ts", source: "export function download(): [string, number] { return impl(); }" }], focusPath: "src/download.ts", expectedCount: 1, public: true }
4767
+ ]
4768
+ };
4102
4769
  var MIN_ELEMENTS = 2;
4103
4770
  var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited", "Readonly"]);
4104
4771
  function staticMemberName2(key) {
@@ -4323,6 +4990,7 @@ function isExported(node, specifierExports) {
4323
4990
  }
4324
4991
  var no_positional_tuple_return_default = createRule({
4325
4992
  name: "no-positional-tuple-return",
4993
+ documentation: noPositionalTupleReturnDocumentation,
4326
4994
  meta: {
4327
4995
  type: "suggestion",
4328
4996
  docs: {
@@ -4419,6 +5087,17 @@ var no_positional_tuple_return_default = createRule({
4419
5087
 
4420
5088
  // src/rules/no-raw-env.ts
4421
5089
  var import_utils26 = require("@typescript-eslint/utils");
5090
+ var noRawEnvDocumentation = {
5091
+ summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
5092
+ rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
5093
+ remediation: "Validate environment values at startup and import the typed configuration object.",
5094
+ category: "correctness",
5095
+ limitations: ["Host markers, assignment targets, tests, scripts, build config, and validated boundaries are excluded."],
5096
+ examples: [
5097
+ { id: "validated-environment", title: "Read validated configuration", outcome: "no-match", files: [{ path: "src/database.ts", source: "import { env } from './env.js'; const url = env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 0, public: true },
5098
+ { id: "raw-environment-read", title: "Do not read raw configuration", outcome: "match", files: [{ path: "src/database.ts", source: "const url = process.env.DATABASE_URL;" }], focusPath: "src/database.ts", expectedCount: 1, public: true }
5099
+ ]
5100
+ };
4422
5101
  var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
4423
5102
  var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
4424
5103
  var ENV_VALIDATION_MARKER_RE = /\bcreateEnv\s*\(|\bz\.object\s*\(|\.(?:safeParse|parse)\s*\(/;
@@ -4465,6 +5144,7 @@ function isWholeEnvSpread(node) {
4465
5144
  }
4466
5145
  var no_raw_env_default = createRule({
4467
5146
  name: "no-raw-env",
5147
+ documentation: noRawEnvDocumentation,
4468
5148
  meta: {
4469
5149
  type: "problem",
4470
5150
  docs: {
@@ -4496,6 +5176,17 @@ var no_raw_env_default = createRule({
4496
5176
 
4497
5177
  // src/rules/no-raw-fetch-outside-clients.ts
4498
5178
  var import_utils27 = require("@typescript-eslint/utils");
5179
+ var noRawFetchOutsideClientsDocumentation = {
5180
+ summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
5181
+ rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
5182
+ remediation: "Move the request into a client module and call that abstraction from application code.",
5183
+ category: "architecture",
5184
+ limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
5185
+ examples: [
5186
+ { id: "client-call", title: "Use a client abstraction", outcome: "no-match", files: [{ path: "src/routes/handler.ts", source: "const response = await billingClient.getInvoice(id);" }], focusPath: "src/routes/handler.ts", expectedCount: 0, public: true },
5187
+ { id: "raw-fetch", title: "Do not call global fetch here", outcome: "match", files: [{ path: "src/routes/handler.ts", source: "const response = await fetch('/api/invoices');" }], focusPath: "src/routes/handler.ts", expectedCount: 1, public: true }
5188
+ ]
5189
+ };
4499
5190
  var DEFAULT_ALLOW = [
4500
5191
  "[\\\\/]clients?[\\\\/]",
4501
5192
  "-client\\.[cm]?[jt]sx?$",
@@ -4559,6 +5250,7 @@ function compile(patterns) {
4559
5250
  }
4560
5251
  var no_raw_fetch_outside_clients_default = createRule({
4561
5252
  name: "no-raw-fetch-outside-clients",
5253
+ documentation: noRawFetchOutsideClientsDocumentation,
4562
5254
  meta: {
4563
5255
  type: "problem",
4564
5256
  docs: {
@@ -4607,6 +5299,17 @@ var no_raw_fetch_outside_clients_default = createRule({
4607
5299
 
4608
5300
  // src/rules/no-restricted-library-load.ts
4609
5301
  var import_utils28 = require("@typescript-eslint/utils");
5302
+ var noRestrictedLibraryLoadDocumentation = {
5303
+ summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
5304
+ rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
5305
+ remediation: "Load the configured replacement library instead of the restricted module.",
5306
+ category: "architecture",
5307
+ limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
5308
+ examples: [
5309
+ { 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 },
5310
+ { 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 }
5311
+ ]
5312
+ };
4610
5313
  function literalModule(node) {
4611
5314
  return node?.type === import_utils28.AST_NODE_TYPES.Literal && typeof node.value === "string" ? node.value : null;
4612
5315
  }
@@ -4615,6 +5318,7 @@ function matchesModule(source, module2) {
4615
5318
  }
4616
5319
  var no_restricted_library_load_default = createRule({
4617
5320
  name: "no-restricted-library-load",
5321
+ documentation: noRestrictedLibraryLoadDocumentation,
4618
5322
  meta: {
4619
5323
  type: "problem",
4620
5324
  docs: {
@@ -4710,6 +5414,17 @@ var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
4710
5414
  import_utils29.AST_NODE_TYPES.FunctionExpression,
4711
5415
  import_utils29.AST_NODE_TYPES.ArrowFunctionExpression
4712
5416
  ]);
5417
+ var noRepeatedStringLiteralDocumentation = {
5418
+ summary: "Disallow a long structured string literal repeated across functions; the copies drift when one is edited. Extract a module-level constant.",
5419
+ rationale: "Independent copies of a structured value can diverge and silently change behavior.",
5420
+ remediation: "Extract the repeated value to one module-level constant and reference it from each function.",
5421
+ category: "maintainability",
5422
+ limitations: ["Test files, short strings, prose, substitutions, module sources, JSX attributes, and repetition within one function are excluded."],
5423
+ examples: [
5424
+ { id: "shared-constant", title: "Share one structured value", outcome: "no-match", files: [{ path: "src/queries.ts", source: "const QUERY = 'SELECT id, status, created_at FROM candidates';\nfunction one() { return QUERY; }\nfunction two() { return QUERY; }" }], focusPath: "src/queries.ts", expectedCount: 0, public: true },
5425
+ { id: "repeated-query", title: "Do not copy a structured value across functions", outcome: "match", files: [{ path: "src/queries.ts", source: "function one() { return 'SELECT id, status, created_at FROM candidates'; }\nfunction two() { return 'SELECT id, status, created_at FROM candidates'; }" }], focusPath: "src/queries.ts", expectedCount: 1, public: true }
5426
+ ]
5427
+ };
4713
5428
  function isStructured(value) {
4714
5429
  return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value);
4715
5430
  }
@@ -4735,6 +5450,7 @@ function isScaffolding(node) {
4735
5450
  }
4736
5451
  var no_repeated_string_literal_default = createRule({
4737
5452
  name: "no-repeated-string-literal",
5453
+ documentation: noRepeatedStringLiteralDocumentation,
4738
5454
  meta: {
4739
5455
  type: "suggestion",
4740
5456
  docs: {
@@ -4820,6 +5536,17 @@ var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
4820
5536
  var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|build|call|check|compute|copy|count|create|fetch|filter|find|get|handle|load|map|merge|parse|process|read|remove|return|save|send|set|sort|store|update|validate|write)(?:s|es|d|ed|ing)?\b/i;
4821
5537
  var WALL_CLUSTER_MAX_LINE_GAP = 8;
4822
5538
  var WALL_CLUSTER_MIN_COMMENTS = 3;
5539
+ var noRestatedCommentDocumentation = {
5540
+ summary: "Flag a single-line comment whose every word already appears on the statement below it.",
5541
+ rationale: "A comment that only repeats code adds no context and can become stale independently.",
5542
+ remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
5543
+ category: "maintainability",
5544
+ limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
5545
+ examples: [
5546
+ { id: "reason-comment", title: "Keep the reason the code cannot express", outcome: "no-match", files: [{ path: "src/cache.ts", source: "// Serialize because the cache key is stable across deploys.\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 0, public: true },
5547
+ { id: "restated-comment", title: "Remove a comment that repeats the statement", outcome: "match", files: [{ path: "src/cache.ts", source: "// Serialize key\nconst key = serialize(input);" }], focusPath: "src/cache.ts", expectedCount: 1, public: true }
5548
+ ]
5549
+ };
4823
5550
  function areAdjacentLineComments2(a, b) {
4824
5551
  return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
4825
5552
  }
@@ -4834,6 +5561,7 @@ function headsSiblingRun(node) {
4834
5561
  }
4835
5562
  var no_restated_comment_default = createRule({
4836
5563
  name: "no-restated-comment",
5564
+ documentation: noRestatedCommentDocumentation,
4837
5565
  meta: {
4838
5566
  type: "suggestion",
4839
5567
  docs: {
@@ -4919,6 +5647,19 @@ var no_restated_comment_default = createRule({
4919
5647
 
4920
5648
  // src/rules/no-restated-jsdoc.ts
4921
5649
  var import_utils31 = require("@typescript-eslint/utils");
5650
+ var noRestatedJsdocDocumentation = {
5651
+ summary: "Flag a JSDoc block whose description and tags only re-spell the signature they document.",
5652
+ rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
5653
+ remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
5654
+ category: "maintainability",
5655
+ aliases: ["jsdoc-restates-signature"],
5656
+ autofix: "suggestion",
5657
+ limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
5658
+ examples: [
5659
+ { id: "behavioral-jsdoc", title: "Document behavior absent from the signature", outcome: "no-match", files: [{ path: "src/users.ts", source: "/** Get the user while bypassing the read replica. */\nexport function getUser(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 0, public: true },
5660
+ { id: "signature-jsdoc", title: "Remove JSDoc that only repeats the signature", outcome: "match", files: [{ path: "src/users.ts", source: "/** Get the user by id. */\nexport function getUserById(id: string) { return id; }" }], focusPath: "src/users.ts", expectedCount: 1, public: true }
5661
+ ]
5662
+ };
4922
5663
  var MODELLED_TAGS = /* @__PURE__ */ new Set([
4923
5664
  "arg",
4924
5665
  "argument",
@@ -5018,6 +5759,7 @@ function tokensOf(names) {
5018
5759
  }
5019
5760
  var no_restated_jsdoc_default = createRule({
5020
5761
  name: "no-restated-jsdoc",
5762
+ documentation: noRestatedJsdocDocumentation,
5021
5763
  meta: {
5022
5764
  type: "suggestion",
5023
5765
  hasSuggestions: true,
@@ -5249,6 +5991,17 @@ function isAuthSecretName(identifier) {
5249
5991
  }
5250
5992
 
5251
5993
  // src/rules/no-secret-in-log.ts
5994
+ var noSecretInLogDocumentation = {
5995
+ summary: "Disallow passing a secret-named value or a raw request/response blob to a logging call; both leak to log sinks. Redact or omit.",
5996
+ rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
5997
+ remediation: "Omit the value or log an explicitly redacted, truncated, or derived non-sensitive field.",
5998
+ category: "security",
5999
+ limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
6000
+ examples: [
6001
+ { id: "redacted-secret", title: "Log an explicitly redacted value", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { tokenPrefix });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
6002
+ { id: "logged-secret", title: "Do not send a secret to logs", outcome: "match", files: [{ path: "src/auth.ts", source: "logger.error('auth failed', { token });" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
6003
+ ]
6004
+ };
5252
6005
  var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
5253
6006
  ...INNOCUOUS_WORDS,
5254
6007
  "name",
@@ -5368,6 +6121,7 @@ function propertyKeyName2(prop) {
5368
6121
  }
5369
6122
  var no_secret_in_log_default = createRule({
5370
6123
  name: "no-secret-in-log",
6124
+ documentation: noSecretInLogDocumentation,
5371
6125
  meta: {
5372
6126
  type: "problem",
5373
6127
  docs: {
@@ -5442,6 +6196,17 @@ var no_secret_in_log_default = createRule({
5442
6196
 
5443
6197
  // src/rules/no-select-star.ts
5444
6198
  var import_utils33 = require("@typescript-eslint/utils");
6199
+ var noSelectStarDocumentation = {
6200
+ summary: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently.",
6201
+ rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
6202
+ remediation: "List every required column explicitly in the projection.",
6203
+ category: "correctness",
6204
+ limitations: ["Only statically visible embedded SQL is checked; function arguments such as COUNT(*) and stars inside EXISTS are excluded."],
6205
+ examples: [
6206
+ { 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 },
6207
+ { 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 }
6208
+ ]
6209
+ };
5445
6210
  var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
5446
6211
  var SELECT_KEYWORD = /\bSELECT\b/gi;
5447
6212
  var FROM_KEYWORD = /^FROM\b/i;
@@ -5483,6 +6248,7 @@ function isProjectionStar(sql, pos) {
5483
6248
  }
5484
6249
  var no_select_star_default = createRule({
5485
6250
  name: "no-select-star",
6251
+ documentation: noSelectStarDocumentation,
5486
6252
  meta: {
5487
6253
  type: "problem",
5488
6254
  docs: {
@@ -5509,6 +6275,17 @@ var no_select_star_default = createRule({
5509
6275
 
5510
6276
  // src/rules/no-sentinel-return-on-catch.ts
5511
6277
  var import_utils34 = require("@typescript-eslint/utils");
6278
+ var noSentinelReturnOnCatchDocumentation = {
6279
+ summary: "Disallow swallowing a caught error by returning an empty sentinel unless the error is handled or the sentinel is part of the function contract.",
6280
+ rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
6281
+ remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
6282
+ category: "correctness",
6283
+ limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
6284
+ examples: [
6285
+ { 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 },
6286
+ { 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 }
6287
+ ]
6288
+ };
5512
6289
  function unwrapSentinelExpression(arg) {
5513
6290
  let current = arg;
5514
6291
  while (current?.type === import_utils34.AST_NODE_TYPES.TSAsExpression || current?.type === import_utils34.AST_NODE_TYPES.TSTypeAssertion || current?.type === import_utils34.AST_NODE_TYPES.TSSatisfiesExpression) {
@@ -5832,10 +6609,11 @@ function isWithin(node, ancestor) {
5832
6609
  }
5833
6610
  var no_sentinel_return_on_catch_default = createRule({
5834
6611
  name: "no-sentinel-return-on-catch",
6612
+ documentation: noSentinelReturnOnCatchDocumentation,
5835
6613
  meta: {
5836
6614
  type: "problem",
5837
6615
  docs: {
5838
- description: "Disallow swallowing a caught error by returning an empty sentinel (`null`, `undefined`, `false`, `[]`, `{}`) as the final statement of a `catch` block, unless the error is logged/reported or the sentinel is the declared safe-parse/predicate contract."
6616
+ description: "Disallow swallowing a caught error by returning an empty sentinel unless the error is handled or the sentinel is part of the function contract."
5839
6617
  },
5840
6618
  schema: [
5841
6619
  {
@@ -5913,6 +6691,17 @@ var no_sentinel_return_on_catch_default = createRule({
5913
6691
 
5914
6692
  // src/rules/no-silent-promise-catch.ts
5915
6693
  var import_utils35 = require("@typescript-eslint/utils");
6694
+ var noSilentPromiseCatchDocumentation = {
6695
+ summary: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error.",
6696
+ rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
6697
+ remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
6698
+ category: "correctness",
6699
+ limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded."],
6700
+ examples: [
6701
+ { 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 },
6702
+ { 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 }
6703
+ ]
6704
+ };
5916
6705
  function isBodyParseCall(node) {
5917
6706
  return node.type === import_utils35.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils35.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils35.AST_NODE_TYPES.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
5918
6707
  }
@@ -5967,6 +6756,7 @@ function isSilentExpression(node) {
5967
6756
  }
5968
6757
  var no_silent_promise_catch_default = createRule({
5969
6758
  name: "no-silent-promise-catch",
6759
+ documentation: noSilentPromiseCatchDocumentation,
5970
6760
  meta: {
5971
6761
  type: "problem",
5972
6762
  docs: {
@@ -6036,6 +6826,18 @@ var no_silent_promise_catch_default = createRule({
6036
6826
 
6037
6827
  // src/rules/no-sleep-in-test-body.ts
6038
6828
  var import_utils36 = require("@typescript-eslint/utils");
6829
+ var noSleepInTestBodyDocumentation = {
6830
+ summary: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers.",
6831
+ rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
6832
+ remediation: "Await the observable signal or advance deterministic fake timers.",
6833
+ category: "testing",
6834
+ filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
6835
+ limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes and parameterized delays are excluded."],
6836
+ examples: [
6837
+ { 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 },
6838
+ { 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 }
6839
+ ]
6840
+ };
6039
6841
  var SLEEP_HELPERS = /* @__PURE__ */ new Set(["sleep", "delay", "wait", "pause"]);
6040
6842
  var TEST_CALLERS3 = /* @__PURE__ */ new Set([
6041
6843
  "it",
@@ -6111,6 +6913,7 @@ function testCallerName2(callee) {
6111
6913
  }
6112
6914
  var no_sleep_in_test_body_default = createRule({
6113
6915
  name: "no-sleep-in-test-body",
6916
+ documentation: noSleepInTestBodyDocumentation,
6114
6917
  meta: {
6115
6918
  type: "problem",
6116
6919
  docs: {
@@ -6156,6 +6959,17 @@ var DEFAULT_METHODS2 = [
6156
6959
  "getWithMetadata"
6157
6960
  ];
6158
6961
  var MIN_ARGUMENTS = /* @__PURE__ */ new Map([["put", 2]]);
6962
+ var noStorageInStatelessModulesDocumentation = {
6963
+ summary: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead.",
6964
+ rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
6965
+ remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
6966
+ category: "architecture",
6967
+ limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
6968
+ examples: [
6969
+ { 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 },
6970
+ { 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 }
6971
+ ]
6972
+ };
6159
6973
  function compile2(patterns) {
6160
6974
  const compiled = [];
6161
6975
  for (const pattern of patterns) {
@@ -6182,10 +6996,11 @@ function storageMethodName(node, methods) {
6182
6996
  }
6183
6997
  var no_storage_in_stateless_modules_default = createRule({
6184
6998
  name: "no-storage-in-stateless-modules",
6999
+ documentation: noStorageInStatelessModulesDocumentation,
6185
7000
  meta: {
6186
7001
  type: "problem",
6187
7002
  docs: {
6188
- description: "Disallow SQL or key/value access inside modules a team has declared stateless; derive state from the systems of record instead. No-op until `modules` is configured."
7003
+ description: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead."
6189
7004
  },
6190
7005
  schema: [
6191
7006
  {
@@ -6237,6 +7052,35 @@ var no_storage_in_stateless_modules_default = createRule({
6237
7052
 
6238
7053
  // src/rules/no-string-concat-in-loop.ts
6239
7054
  var import_utils38 = require("@typescript-eslint/utils");
7055
+ var noStringConcatInLoopDocumentation = {
7056
+ summary: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead.",
7057
+ rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
7058
+ remediation: "Collect each fragment in an array, then join the fragments after the loop.",
7059
+ category: "performance",
7060
+ limitations: [
7061
+ "Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
7062
+ ],
7063
+ examples: [
7064
+ {
7065
+ id: "join-fragments",
7066
+ title: "Join collected fragments after the loop",
7067
+ outcome: "no-match",
7068
+ files: [{ path: "src/render.ts", source: 'const parts = []; for (const item of items) { parts.push(item); } const output = parts.join("");' }],
7069
+ focusPath: "src/render.ts",
7070
+ expectedCount: 0,
7071
+ public: true
7072
+ },
7073
+ {
7074
+ id: "rebuild-string",
7075
+ title: "Do not rebuild a growing string in a loop",
7076
+ outcome: "match",
7077
+ files: [{ path: "src/render.ts", source: "let output = ''; for (const item of items) { output = `${output}${item}`; }" }],
7078
+ focusPath: "src/render.ts",
7079
+ expectedCount: 1,
7080
+ public: true
7081
+ }
7082
+ ]
7083
+ };
6240
7084
  var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
6241
7085
  "ForStatement",
6242
7086
  "ForOfStatement",
@@ -6328,6 +7172,7 @@ function enclosingLoop(node) {
6328
7172
  }
6329
7173
  var no_string_concat_in_loop_default = createRule({
6330
7174
  name: "no-string-concat-in-loop",
7175
+ documentation: noStringConcatInLoopDocumentation,
6331
7176
  meta: {
6332
7177
  type: "suggestion",
6333
7178
  docs: {
@@ -6388,6 +7233,35 @@ var no_string_concat_in_loop_default = createRule({
6388
7233
 
6389
7234
  // src/rules/no-tautological-expect.ts
6390
7235
  var import_utils39 = require("@typescript-eslint/utils");
7236
+ var noTautologicalExpectDocumentation = {
7237
+ summary: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail.",
7238
+ rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
7239
+ remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
7240
+ category: "testing",
7241
+ limitations: [
7242
+ "Only direct supported `expect` matcher calls in recognized test files are inspected."
7243
+ ],
7244
+ examples: [
7245
+ {
7246
+ id: "produced-value",
7247
+ title: "Assert on a produced value",
7248
+ outcome: "no-match",
7249
+ files: [{ path: "src/add.test.ts", source: "it('adds', () => { expect(add(1, 1)).toBe(2); });" }],
7250
+ focusPath: "src/add.test.ts",
7251
+ expectedCount: 0,
7252
+ public: true
7253
+ },
7254
+ {
7255
+ id: "literal-only-assertion",
7256
+ title: "Do not compare identical literals",
7257
+ outcome: "match",
7258
+ files: [{ path: "src/add.test.ts", source: "it('works', () => { expect(true).toBe(true); });" }],
7259
+ focusPath: "src/add.test.ts",
7260
+ expectedCount: 1,
7261
+ public: true
7262
+ }
7263
+ ]
7264
+ };
6391
7265
  var EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
6392
7266
  var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
6393
7267
  "toBeDefined",
@@ -6426,6 +7300,7 @@ function expectOperand(callee) {
6426
7300
  }
6427
7301
  var no_tautological_expect_default = createRule({
6428
7302
  name: "no-tautological-expect",
7303
+ documentation: noTautologicalExpectDocumentation,
6429
7304
  meta: {
6430
7305
  type: "problem",
6431
7306
  docs: {
@@ -6486,8 +7361,36 @@ var no_tautological_expect_default = createRule({
6486
7361
  });
6487
7362
 
6488
7363
  // src/rules/no-typed-doc-sections.ts
7364
+ var noTypedDocSectionsDocumentation = {
7365
+ summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
7366
+ rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
7367
+ remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
7368
+ category: "maintainability",
7369
+ limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
7370
+ examples: [
7371
+ {
7372
+ id: "behavioral-documentation",
7373
+ title: "Keep behavior that the signature cannot express",
7374
+ outcome: "no-match",
7375
+ files: [{ path: "src/client.ts", source: "/** Retries when the vendor returns 429. */\nexport function fetchValue(id: string): number { return 1; }" }],
7376
+ focusPath: "src/client.ts",
7377
+ expectedCount: 0,
7378
+ public: true
7379
+ },
7380
+ {
7381
+ id: "repeated-typed-sections",
7382
+ title: "Do not restate typed parameters and returns",
7383
+ outcome: "match",
7384
+ files: [{ path: "src/client.ts", source: "/** @param id external identifier\n * @returns the value\n */\nexport function fetchValue(id: string): number { return 1; }" }],
7385
+ focusPath: "src/client.ts",
7386
+ expectedCount: 1,
7387
+ public: true
7388
+ }
7389
+ ]
7390
+ };
6489
7391
  var no_typed_doc_sections_default = createRule({
6490
7392
  name: "no-typed-doc-sections",
7393
+ documentation: noTypedDocSectionsDocumentation,
6491
7394
  meta: {
6492
7395
  type: "suggestion",
6493
7396
  docs: { description: "Reject typed-signature repetition while preserving behavior that types cannot express." },
@@ -6512,6 +7415,34 @@ var no_typed_doc_sections_default = createRule({
6512
7415
 
6513
7416
  // src/rules/no-trailing-value-narration.ts
6514
7417
  var import_utils40 = require("@typescript-eslint/utils");
7418
+ var noTrailingValueNarrationDocumentation = {
7419
+ summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
7420
+ rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
7421
+ remediation: "Put the unit in the identifier and keep comments only when they explain a constraint or non-obvious conversion.",
7422
+ category: "maintainability",
7423
+ aliases: ["trailing-value-narration"],
7424
+ limitations: ["Only trailing comments with numeric values and recognized unit words are inspected."],
7425
+ examples: [
7426
+ {
7427
+ id: "explain-constraint",
7428
+ title: "Explain a domain constraint",
7429
+ outcome: "no-match",
7430
+ files: [{ path: "src/timeouts.ts", source: "const timeout = 5 * 60; // 5 minutes for cold starts" }],
7431
+ focusPath: "src/timeouts.ts",
7432
+ expectedCount: 0,
7433
+ public: true
7434
+ },
7435
+ {
7436
+ id: "repeat-duration",
7437
+ title: "Do not narrate the numeric duration",
7438
+ outcome: "match",
7439
+ files: [{ path: "src/timeouts.ts", source: "const staleTime = 5 * 60 * 1000; // 5 minutes" }],
7440
+ focusPath: "src/timeouts.ts",
7441
+ expectedCount: 1,
7442
+ public: true
7443
+ }
7444
+ ]
7445
+ };
6515
7446
  var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
6516
7447
  var WORD_RE3 = /[A-Za-z]+(?:'[a-z]+)?|\d+(?:\.\d+)?/g;
6517
7448
  var UNIT_WORDS = /* @__PURE__ */ new Set([
@@ -6598,6 +7529,7 @@ function numbersIn(text) {
6598
7529
  }
6599
7530
  var no_trailing_value_narration_default = createRule({
6600
7531
  name: "no-trailing-value-narration",
7532
+ documentation: noTrailingValueNarrationDocumentation,
6601
7533
  meta: {
6602
7534
  type: "suggestion",
6603
7535
  docs: {
@@ -6774,6 +7706,33 @@ function bodyOf(member) {
6774
7706
  }
6775
7707
 
6776
7708
  // src/rules/no-declaration-comment-wall.ts
7709
+ var noDeclarationCommentWallDocumentation = {
7710
+ summary: "Flag an enum body or class body whose member comments mostly re-spell the members' own names.",
7711
+ rationale: "A dense block of repetitive member comments obscures the few comments that add information and drifts with renamed members.",
7712
+ remediation: "Delete comments that restate member names and retain comments that explain constraints, lifecycle, or behavior.",
7713
+ category: "maintainability",
7714
+ limitations: ["Only enum and class bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
7715
+ examples: [
7716
+ {
7717
+ id: "uncommented-members",
7718
+ title: "Let clear member names stand alone",
7719
+ outcome: "no-match",
7720
+ files: [{ path: "src/status.ts", source: "enum Status { Pending = 'pending', Done = 'done', Failed = 'failed' }" }],
7721
+ focusPath: "src/status.ts",
7722
+ expectedCount: 0,
7723
+ public: true
7724
+ },
7725
+ {
7726
+ id: "restated-enum-members",
7727
+ title: "Do not restate every enum member",
7728
+ outcome: "match",
7729
+ files: [{ path: "src/status.ts", source: "enum Status {\n /** The pending status. */\n Pending = 'pending',\n /** The finished status. */\n Finished = 'finished',\n /** The failed status. */\n Failed = 'failed',\n}" }],
7730
+ focusPath: "src/status.ts",
7731
+ expectedCount: 1,
7732
+ public: true
7733
+ }
7734
+ ]
7735
+ };
6777
7736
  function named(node) {
6778
7737
  switch (node.type) {
6779
7738
  case import_utils42.AST_NODE_TYPES.TSEnumMember:
@@ -6789,6 +7748,7 @@ function named(node) {
6789
7748
  }
6790
7749
  var no_declaration_comment_wall_default = createRule({
6791
7750
  name: "no-declaration-comment-wall",
7751
+ documentation: noDeclarationCommentWallDocumentation,
6792
7752
  meta: {
6793
7753
  type: "suggestion",
6794
7754
  docs: {
@@ -6882,6 +7842,33 @@ var no_declaration_comment_wall_default = createRule({
6882
7842
 
6883
7843
  // src/rules/no-union-in-comment.ts
6884
7844
  var import_utils43 = require("@typescript-eslint/utils");
7845
+ var noUnionInCommentDocumentation = {
7846
+ summary: "Flag a comment that lists a `string` field's allowed values instead of the type listing them.",
7847
+ rationale: "A comment cannot prevent callers from supplying strings outside the listed set, and the list can drift from runtime behavior.",
7848
+ remediation: "Move the allowed values into a string-literal union and remove the redundant comment.",
7849
+ category: "correctness",
7850
+ limitations: ["Only bare quoted-value lists attached to supported string declarations and schema-builder fields are inspected."],
7851
+ examples: [
7852
+ {
7853
+ id: "literal-union",
7854
+ title: "Encode allowed values in the type",
7855
+ outcome: "no-match",
7856
+ files: [{ path: "src/record.ts", source: "interface R { kind: 'aa' | 'bb'; }" }],
7857
+ focusPath: "src/record.ts",
7858
+ expectedCount: 0,
7859
+ public: true
7860
+ },
7861
+ {
7862
+ id: "comment-only-union",
7863
+ title: "Do not leave allowed values in a comment",
7864
+ outcome: "match",
7865
+ files: [{ path: "src/record.ts", source: "interface R {\n kind: string; // 'aa' | 'bb'\n}" }],
7866
+ focusPath: "src/record.ts",
7867
+ expectedCount: 1,
7868
+ public: true
7869
+ }
7870
+ ]
7871
+ };
6885
7872
  var MAX_LITERAL_LENGTH = 28;
6886
7873
  var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
6887
7874
  var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
@@ -6970,6 +7957,7 @@ function unionLiterals(body2) {
6970
7957
  }
6971
7958
  var no_union_in_comment_default = createRule({
6972
7959
  name: "no-union-in-comment",
7960
+ documentation: noUnionInCommentDocumentation,
6973
7961
  meta: {
6974
7962
  type: "suggestion",
6975
7963
  docs: {
@@ -7030,11 +8018,39 @@ var no_union_in_comment_default = createRule({
7030
8018
 
7031
8019
  // src/rules/no-type-member-comment-wall.ts
7032
8020
  var import_utils44 = require("@typescript-eslint/utils");
8021
+ var noTypeMemberCommentWallDocumentation = {
8022
+ summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
8023
+ rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
8024
+ remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
8025
+ category: "maintainability",
8026
+ limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
8027
+ examples: [
8028
+ {
8029
+ id: "uncommented-members",
8030
+ title: "Let clear member names and types stand alone",
8031
+ outcome: "no-match",
8032
+ files: [{ path: "src/credentials.ts", source: "interface Credentials { host: string; port: number; username: string; }" }],
8033
+ focusPath: "src/credentials.ts",
8034
+ expectedCount: 0,
8035
+ public: true
8036
+ },
8037
+ {
8038
+ id: "restated-type-members",
8039
+ title: "Do not restate member names and types",
8040
+ outcome: "match",
8041
+ files: [{ path: "src/credentials.ts", source: "interface Credentials {\n // Database host.\n host?: string;\n // Database host port.\n port?: number;\n // Database username.\n username?: string;\n // Database password.\n password?: string;\n}" }],
8042
+ focusPath: "src/credentials.ts",
8043
+ expectedCount: 1,
8044
+ public: true
8045
+ }
8046
+ ]
8047
+ };
7033
8048
  function isNamedMember(node) {
7034
8049
  return (node.type === import_utils44.AST_NODE_TYPES.TSPropertySignature || node.type === import_utils44.AST_NODE_TYPES.TSMethodSignature) && !node.computed;
7035
8050
  }
7036
8051
  var no_type_member_comment_wall_default = createRule({
7037
8052
  name: "no-type-member-comment-wall",
8053
+ documentation: noTypeMemberCommentWallDocumentation,
7038
8054
  meta: {
7039
8055
  type: "suggestion",
7040
8056
  docs: {
@@ -7119,6 +8135,33 @@ var no_type_member_comment_wall_default = createRule({
7119
8135
 
7120
8136
  // src/rules/no-unnecessary-use-client.ts
7121
8137
  var import_utils45 = require("@typescript-eslint/utils");
8138
+ var noUnnecessaryUseClientDocumentation = {
8139
+ summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
8140
+ rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
8141
+ remediation: "Remove the directive, or keep it only when the module uses a supported client-side API or boundary dependency.",
8142
+ category: "performance",
8143
+ limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
8144
+ examples: [
8145
+ {
8146
+ id: "interactive-component",
8147
+ title: "Keep the directive for interactive components",
8148
+ outcome: "no-match",
8149
+ files: [{ path: "src/counter.tsx", source: "'use client'; import { useState } from 'react'; export default function X() { const [n] = useState(0); return <div>{n}</div>; }" }],
8150
+ focusPath: "src/counter.tsx",
8151
+ expectedCount: 0,
8152
+ public: true
8153
+ },
8154
+ {
8155
+ id: "static-component",
8156
+ title: "Remove the directive from static components",
8157
+ outcome: "match",
8158
+ files: [{ path: "src/banner.tsx", source: "'use client'; export default function X() { return <div>hello</div>; }" }],
8159
+ focusPath: "src/banner.tsx",
8160
+ expectedCount: 1,
8161
+ public: true
8162
+ }
8163
+ ]
8164
+ };
7122
8165
  var HOOK_REGEX = /^use([A-Z]|$)/;
7123
8166
  var EVENT_PROP_REGEX = /^on[A-Z]/;
7124
8167
  var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
@@ -7193,6 +8236,7 @@ var isGlobalReference = (node, context) => {
7193
8236
  };
7194
8237
  var no_unnecessary_use_client_default = createRule({
7195
8238
  name: "no-unnecessary-use-client",
8239
+ documentation: noUnnecessaryUseClientDocumentation,
7196
8240
  meta: {
7197
8241
  type: "suggestion",
7198
8242
  docs: {
@@ -7323,8 +8367,36 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
7323
8367
  "jest-mock",
7324
8368
  "@jest/globals"
7325
8369
  ]);
8370
+ var noUnsafeMockCastingDocumentation = {
8371
+ summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
8372
+ rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
8373
+ remediation: "Use the test framework's `mocked` helper to obtain the typed mock reference.",
8374
+ category: "testing",
8375
+ limitations: ["Only mock types imported from Vitest or Jest modules are inspected."],
8376
+ examples: [
8377
+ {
8378
+ id: "typed-mock-helper",
8379
+ title: "Use the framework helper",
8380
+ outcome: "no-match",
8381
+ files: [{ path: "src/client.test.ts", source: "const m = vi.mocked(myFn);" }],
8382
+ focusPath: "src/client.test.ts",
8383
+ expectedCount: 0,
8384
+ public: true
8385
+ },
8386
+ {
8387
+ id: "mock-type-assertion",
8388
+ title: "Do not assert that a value is a mock",
8389
+ outcome: "match",
8390
+ files: [{ path: "src/client.test.ts", source: 'import type * as vi from "vitest"; const m = myFn as vi.Mock;' }],
8391
+ focusPath: "src/client.test.ts",
8392
+ expectedCount: 1,
8393
+ public: true
8394
+ }
8395
+ ]
8396
+ };
7326
8397
  var no_unsafe_mock_casting_default = createRule({
7327
8398
  name: "no-unsafe-mock-casting",
8399
+ documentation: noUnsafeMockCastingDocumentation,
7328
8400
  meta: {
7329
8401
  type: "problem",
7330
8402
  docs: {
@@ -7392,6 +8464,35 @@ var no_unsafe_mock_casting_default = createRule({
7392
8464
  // src/rules/no-zod-native-enum.ts
7393
8465
  var import_utils47 = require("@typescript-eslint/utils");
7394
8466
  var ts = __toESM(require("typescript"), 1);
8467
+ var noZodNativeEnumDocumentation = {
8468
+ summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
8469
+ rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
8470
+ remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
8471
+ category: "maintainability",
8472
+ autofix: "safe",
8473
+ limitations: ["Automatic fixes are limited to inline object literals whose unique values are all string literals."],
8474
+ examples: [
8475
+ {
8476
+ id: "zod-literal-enum",
8477
+ title: "Declare string values directly in Zod",
8478
+ outcome: "no-match",
8479
+ files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }],
8480
+ focusPath: "src/status.ts",
8481
+ expectedCount: 0,
8482
+ public: true
8483
+ },
8484
+ {
8485
+ id: "zod-native-enum",
8486
+ title: "Do not wrap a TypeScript enum",
8487
+ outcome: "match",
8488
+ files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
8489
+ focusPath: "src/status.ts",
8490
+ expectedCount: 1,
8491
+ public: true,
8492
+ fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
8493
+ }
8494
+ ]
8495
+ };
7395
8496
  var IGNORE_PATTERNS = [
7396
8497
  /[\\/]generated[\\/]/,
7397
8498
  /\.gen\.tsx?$/,
@@ -7461,6 +8562,7 @@ function resolvesToImportedEnum(node, services) {
7461
8562
  }
7462
8563
  var no_zod_native_enum_default = createRule({
7463
8564
  name: "no-zod-native-enum",
8565
+ documentation: noZodNativeEnumDocumentation,
7464
8566
  meta: {
7465
8567
  type: "suggestion",
7466
8568
  fixable: "code",
@@ -7572,6 +8674,18 @@ var no_zod_native_enum_default = createRule({
7572
8674
 
7573
8675
  // src/rules/test-loops-over-literal-cases.ts
7574
8676
  var import_utils48 = require("@typescript-eslint/utils");
8677
+ var testLoopsOverLiteralCasesDocumentation = {
8678
+ summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
8679
+ rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
8680
+ remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
8681
+ category: "testing",
8682
+ filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
8683
+ limitations: ["Only inline literal for-of cases containing framework assertions are reported."],
8684
+ examples: [
8685
+ { 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 },
8686
+ { 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 }
8687
+ ]
8688
+ };
7575
8689
  var TEST_CALLERS4 = /* @__PURE__ */ new Set(["it", "test"]);
7576
8690
  var TEST_MODIFIERS2 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
7577
8691
  var ASSERTION_ROOTS2 = /* @__PURE__ */ new Set([
@@ -7705,6 +8819,7 @@ var LOOP_CARRIED_CONTROL = /* @__PURE__ */ new Set([
7705
8819
  ]);
7706
8820
  var test_loops_over_literal_cases_default = createRule({
7707
8821
  name: "test-loops-over-literal-cases",
8822
+ documentation: testLoopsOverLiteralCasesDocumentation,
7708
8823
  meta: {
7709
8824
  type: "suggestion",
7710
8825
  docs: {
@@ -7764,6 +8879,17 @@ function unwrapExpression(node) {
7764
8879
 
7765
8880
  // src/rules/prefer-constant-time-secret-compare.ts
7766
8881
  var import_utils49 = require("@typescript-eslint/utils");
8882
+ var preferConstantTimeSecretCompareDocumentation = {
8883
+ summary: "Disallow `===`/`!==` on a secret-like value; short-circuiting comparison leaks the secret through timing. Use a constant-time compare.",
8884
+ rationale: "Ordinary equality stops at the first differing byte, allowing repeated measurements to reveal secret material.",
8885
+ remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
8886
+ category: "security",
8887
+ limitations: ["Secret-like values are identified conservatively from their names; test files and public sentinel comparisons are excluded."],
8888
+ examples: [
8889
+ { id: "constant-time-compare", title: "Use a constant-time comparison", outcome: "no-match", files: [{ path: "src/auth.ts", source: "if (await constantTimeEqual(presentedToken, expectedToken)) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
8890
+ { id: "secret-equality", title: "Do not compare secrets with equality", outcome: "match", files: [{ path: "src/auth.ts", source: "if (presentedToken === expectedToken) { allow(); }" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
8891
+ ]
8892
+ };
7767
8893
  var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
7768
8894
  var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
7769
8895
  var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
@@ -7818,6 +8944,7 @@ function secretNameOf(node) {
7818
8944
  }
7819
8945
  var prefer_constant_time_secret_compare_default = createRule({
7820
8946
  name: "prefer-constant-time-secret-compare",
8947
+ documentation: preferConstantTimeSecretCompareDocumentation,
7821
8948
  meta: {
7822
8949
  type: "problem",
7823
8950
  docs: {
@@ -7859,6 +8986,17 @@ var prefer_constant_time_secret_compare_default = createRule({
7859
8986
  // src/rules/prefer-discriminated-union.ts
7860
8987
  var import_utils50 = require("@typescript-eslint/utils");
7861
8988
  var import_utils51 = require("@typescript-eslint/utils");
8989
+ var preferDiscriminatedUnionDocumentation = {
8990
+ summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
8991
+ rationale: "A boolean status plus optional branch data permits contradictory and incomplete states.",
8992
+ remediation: "Represent each result branch as a discriminated union member with its required payload.",
8993
+ category: "correctness",
8994
+ limitations: ["Only local object shapes with recognized positive status and payload names are inspected."],
8995
+ examples: [
8996
+ { 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 },
8997
+ { 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 }
8998
+ ]
8999
+ };
7862
9000
  var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
7863
9001
  "success",
7864
9002
  "ok"
@@ -7940,6 +9078,7 @@ function inlineReturnTypeLiteral(node) {
7940
9078
  }
7941
9079
  var prefer_discriminated_union_default = createRule({
7942
9080
  name: "prefer-discriminated-union",
9081
+ documentation: preferDiscriminatedUnionDocumentation,
7943
9082
  meta: {
7944
9083
  type: "suggestion",
7945
9084
  docs: {
@@ -7988,6 +9127,17 @@ var prefer_discriminated_union_default = createRule({
7988
9127
 
7989
9128
  // src/rules/prefer-input-group-search.ts
7990
9129
  var import_utils52 = require("@typescript-eslint/utils");
9130
+ var preferInputGroupSearchDocumentation = {
9131
+ summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
9132
+ rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
9133
+ remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
9134
+ category: "style",
9135
+ limitations: ["Only Search and Input bindings imported from the recognized shared modules are paired."],
9136
+ examples: [
9137
+ { id: "grouped-search", title: "Use the shared input group", outcome: "no-match", files: [{ path: "src/search.tsx", source: "import { Search } from 'lucide-react'; import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'; const field = <InputGroup><InputGroupAddon><Search /></InputGroupAddon><InputGroupInput /></InputGroup>;" }], focusPath: "src/search.tsx", expectedCount: 0, public: true },
9138
+ { id: "loose-search-input", title: "Do not pair loose search controls", outcome: "match", files: [{ path: "src/search.tsx", source: "import { Search } from 'lucide-react'; import { Input } from '@/components/ui/input'; const field = <div><Search /><Input /></div>;" }], focusPath: "src/search.tsx", expectedCount: 1, public: true }
9139
+ ]
9140
+ };
7991
9141
  var INPUT_MODULE = /(?:^|\/)components\/ui\/input$/u;
7992
9142
  var INPUT_GROUP_MODULE = /(?:^|\/)components\/ui\/input-group$/u;
7993
9143
  var MAX_JSX_DISTANCE = 2;
@@ -8031,6 +9181,7 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
8031
9181
  }
8032
9182
  var prefer_input_group_search_default = createRule({
8033
9183
  name: "prefer-input-group-search",
9184
+ documentation: preferInputGroupSearchDocumentation,
8034
9185
  meta: {
8035
9186
  type: "suggestion",
8036
9187
  docs: {
@@ -8101,6 +9252,35 @@ var prefer_input_group_search_default = createRule({
8101
9252
 
8102
9253
  // src/rules/prefer-immutable-module-constant.ts
8103
9254
  var import_utils53 = require("@typescript-eslint/utils");
9255
+ var preferImmutableModuleConstantDocumentation = {
9256
+ summary: "Require module-level constant collections to expose readonly state.",
9257
+ rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
9258
+ remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
9259
+ category: "correctness",
9260
+ limitations: [
9261
+ "The rule skips generated files, test files, JavaScript files, and collections that are deliberately mutated in their declaring module."
9262
+ ],
9263
+ examples: [
9264
+ {
9265
+ id: "readonly-array-literal",
9266
+ title: "A module constant exposes a readonly literal",
9267
+ outcome: "no-match",
9268
+ files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3] as const;" }],
9269
+ focusPath: "src/constants.ts",
9270
+ expectedCount: 0,
9271
+ public: true
9272
+ },
9273
+ {
9274
+ id: "mutable-array-literal",
9275
+ title: "A module constant exposes a mutable array",
9276
+ outcome: "match",
9277
+ files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3];" }],
9278
+ focusPath: "src/constants.ts",
9279
+ expectedCount: 1,
9280
+ public: true
9281
+ }
9282
+ ]
9283
+ };
8104
9284
  var CONSTANT_NAME = /^_?[A-Z][A-Z0-9_]*$/;
8105
9285
  var JAVASCRIPT_FILE_RE = /\.[cm]?jsx?$/i;
8106
9286
  var MUTATING_METHODS = /* @__PURE__ */ new Set([
@@ -8208,6 +9388,7 @@ function referenceMutates(identifier, isUnshadowedGlobal) {
8208
9388
  }
8209
9389
  var prefer_immutable_module_constant_default = createRule({
8210
9390
  name: "prefer-immutable-module-constant",
9391
+ documentation: preferImmutableModuleConstantDocumentation,
8211
9392
  meta: {
8212
9393
  type: "suggestion",
8213
9394
  docs: {
@@ -8294,6 +9475,17 @@ function unwrapTransparentExport(node) {
8294
9475
 
8295
9476
  // src/rules/prefer-shadcn-primitives.ts
8296
9477
  var import_utils54 = require("@typescript-eslint/utils");
9478
+ var preferShadcnPrimitivesDocumentation = {
9479
+ summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
9480
+ rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
9481
+ remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
9482
+ category: "style",
9483
+ limitations: ["Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded."],
9484
+ examples: [
9485
+ { id: "shared-button", title: "Use a shared button", outcome: "no-match", files: [{ path: "src/form.tsx", source: "import { Button } from '@/components/ui/button'; const action = <Button>Save</Button>;" }], focusPath: "src/form.tsx", expectedCount: 0, public: true },
9486
+ { id: "raw-button", title: "Do not use a raw button", outcome: "match", files: [{ path: "src/form.tsx", source: "const action = <button>Save</button>;" }], focusPath: "src/form.tsx", expectedCount: 1, public: true }
9487
+ ]
9488
+ };
8297
9489
  var SHADCN_PRIMITIVES = {
8298
9490
  button: "Button",
8299
9491
  dialog: "Dialog or AlertDialog family",
@@ -8396,6 +9588,7 @@ function replacementFor(node, element) {
8396
9588
  }
8397
9589
  var prefer_shadcn_primitives_default = createRule({
8398
9590
  name: "prefer-shadcn-primitives",
9591
+ documentation: preferShadcnPrimitivesDocumentation,
8399
9592
  meta: {
8400
9593
  type: "suggestion",
8401
9594
  docs: {
@@ -8427,6 +9620,17 @@ var prefer_shadcn_primitives_default = createRule({
8427
9620
 
8428
9621
  // src/rules/prefer-module-level-constant.ts
8429
9622
  var import_utils55 = require("@typescript-eslint/utils");
9623
+ var preferModuleLevelConstantDocumentation = {
9624
+ summary: "Hoist literal-only constant collections and regexes out of function bodies to module scope so they are allocated once.",
9625
+ rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
9626
+ remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
9627
+ category: "performance",
9628
+ limitations: ["Collections that are small, mutated, escape the function, or depend on local values are not reported."],
9629
+ examples: [
9630
+ { 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 },
9631
+ { 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 }
9632
+ ]
9633
+ };
8430
9634
  var DEFAULT_MIN_ELEMENTS = 3;
8431
9635
  var MAX_LITERAL_DEPTH = 4;
8432
9636
  var IGNORE_PATTERNS2 = [
@@ -8632,6 +9836,7 @@ function isNonRetainingBuiltinCall(node, argument) {
8632
9836
  }
8633
9837
  var prefer_module_level_constant_default = createRule({
8634
9838
  name: "prefer-module-level-constant",
9839
+ documentation: preferModuleLevelConstantDocumentation,
8635
9840
  meta: {
8636
9841
  type: "suggestion",
8637
9842
  docs: {
@@ -8723,6 +9928,17 @@ var prefer_module_level_constant_default = createRule({
8723
9928
 
8724
9929
  // src/rules/prefer-module-level-schema.ts
8725
9930
  var import_utils56 = require("@typescript-eslint/utils");
9931
+ var preferModuleLevelSchemaDocumentation = {
9932
+ summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
9933
+ rationale: "A closed schema created inside a function is rebuilt on every call and cannot be reused or exported for inference.",
9934
+ remediation: "Move the closed schema declaration to module scope and reference it from the function.",
9935
+ category: "performance",
9936
+ limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded."],
9937
+ examples: [
9938
+ { 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 },
9939
+ { 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 }
9940
+ ]
9941
+ };
8726
9942
  var DEFAULT_FACTORIES = [
8727
9943
  "discriminatedUnion",
8728
9944
  "intersection",
@@ -8869,6 +10085,7 @@ function collectReferences(scope, out) {
8869
10085
  }
8870
10086
  var prefer_module_level_schema_default = createRule({
8871
10087
  name: "prefer-module-level-schema",
10088
+ documentation: preferModuleLevelSchemaDocumentation,
8872
10089
  meta: {
8873
10090
  type: "problem",
8874
10091
  docs: {
@@ -9066,11 +10283,24 @@ var prefer_module_level_schema_default = createRule({
9066
10283
 
9067
10284
  // src/rules/prefer-native-random-uuid.ts
9068
10285
  var import_utils57 = require("@typescript-eslint/utils");
10286
+ var preferNativeRandomUuidDocumentation = {
10287
+ summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
10288
+ rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
10289
+ remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
10290
+ category: "maintainability",
10291
+ autofix: "suggestion",
10292
+ limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
10293
+ examples: [
10294
+ { 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 },
10295
+ { 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 }
10296
+ ]
10297
+ };
9069
10298
  function requireUuid(node) {
9070
10299
  return node?.type === import_utils57.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils57.AST_NODE_TYPES.Identifier && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0]?.type === import_utils57.AST_NODE_TYPES.Literal && node.arguments[0].value === "uuid";
9071
10300
  }
9072
10301
  var prefer_native_random_uuid_default = createRule({
9073
10302
  name: "prefer-native-random-uuid",
10303
+ documentation: preferNativeRandomUuidDocumentation,
9074
10304
  meta: {
9075
10305
  type: "suggestion",
9076
10306
  docs: {
@@ -9152,6 +10382,17 @@ var prefer_native_random_uuid_default = createRule({
9152
10382
 
9153
10383
  // src/rules/prefer-non-nullable-collection.ts
9154
10384
  var import_utils58 = require("@typescript-eslint/utils");
10385
+ var preferNonNullableCollectionDocumentation = {
10386
+ summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
10387
+ rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
10388
+ remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
10389
+ category: "maintainability",
10390
+ limitations: ["The rule requires local evidence that nullish and empty values are treated identically and skips exported wire shapes."],
10391
+ examples: [
10392
+ { 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 },
10393
+ { 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 }
10394
+ ]
10395
+ };
9155
10396
  var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
9156
10397
  function propertyName(node) {
9157
10398
  const key = node.key;
@@ -9289,6 +10530,7 @@ function memberIsOnlyCoalesced(context, object, property, fn) {
9289
10530
  }
9290
10531
  var prefer_non_nullable_collection_default = createRule({
9291
10532
  name: "prefer-non-nullable-collection",
10533
+ documentation: preferNonNullableCollectionDocumentation,
9292
10534
  meta: {
9293
10535
  type: "suggestion",
9294
10536
  docs: {
@@ -9385,6 +10627,17 @@ var prefer_non_nullable_collection_default = createRule({
9385
10627
 
9386
10628
  // src/rules/prefer-schema-for-api-payload.ts
9387
10629
  var import_utils59 = require("@typescript-eslint/utils");
10630
+ var preferSchemaForApiPayloadDocumentation = {
10631
+ summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
10632
+ rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
10633
+ remediation: "Parse the payload through a schema or establish a recognized runtime validation guard before reading fields.",
10634
+ category: "correctness",
10635
+ limitations: ["Test fixtures, generated clients, local JSON files, and recognized validation guards are excluded."],
10636
+ examples: [
10637
+ { 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 },
10638
+ { 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 }
10639
+ ]
10640
+ };
9388
10641
  var unwrap4 = (node) => {
9389
10642
  let current = node;
9390
10643
  while (current !== null && current !== void 0) {
@@ -9626,6 +10879,7 @@ var unvalidatedVariableRef = (node, scope, tracked) => {
9626
10879
  };
9627
10880
  var prefer_schema_for_api_payload_default = createRule({
9628
10881
  name: "prefer-schema-for-api-payload",
10882
+ documentation: preferSchemaForApiPayloadDocumentation,
9629
10883
  meta: {
9630
10884
  type: "problem",
9631
10885
  docs: {
@@ -9828,6 +11082,17 @@ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^
9828
11082
  var classTokens = (value) => value.split(/\s+/).filter(Boolean);
9829
11083
 
9830
11084
  // src/rules/prefer-semantic-colors.ts
11085
+ var preferSemanticColorsDocumentation = {
11086
+ summary: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals.",
11087
+ rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
11088
+ remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
11089
+ category: "style",
11090
+ limitations: ["Email, PDF, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions."],
11091
+ examples: [
11092
+ { id: "semantic-text-color", title: "Use a semantic color token", outcome: "no-match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-destructive" />;' }], focusPath: "src/notice.tsx", expectedCount: 0, public: true },
11093
+ { id: "raw-text-color", title: "Do not use a raw palette color", outcome: "match", files: [{ path: "src/notice.tsx", source: 'const notice = <div className="text-red-500" />;' }], focusPath: "src/notice.tsx", expectedCount: 1, public: true }
11094
+ ]
11095
+ };
9831
11096
  var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
9832
11097
  var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
9833
11098
  var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
@@ -10106,10 +11371,11 @@ var staticallyImportsEmailOrPdfRenderer = (program) => program.body.some((statem
10106
11371
  });
10107
11372
  var prefer_semantic_colors_default = createRule({
10108
11373
  name: "prefer-semantic-colors",
11374
+ documentation: preferSemanticColorsDocumentation,
10109
11375
  meta: {
10110
11376
  type: "suggestion",
10111
11377
  docs: {
10112
- description: "Enforce design-system semantic color tokens (bg-primary, text-destructive, \u2026) over raw Tailwind palette classes (text-red-500), arbitrary color values (bg-[#fff]), and inline color literals."
11378
+ description: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals."
10113
11379
  },
10114
11380
  schema: [
10115
11381
  {
@@ -10244,6 +11510,17 @@ var prefer_semantic_colors_default = createRule({
10244
11510
 
10245
11511
  // src/rules/prefer-server-actions.ts
10246
11512
  var import_utils61 = require("@typescript-eslint/utils");
11513
+ var preferServerActionsDocumentation = {
11514
+ summary: "Prefer Next.js Server Actions over /api/* mutations.",
11515
+ rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
11516
+ remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
11517
+ category: "architecture",
11518
+ limitations: ["Only statically recognizable /api/ mutations in applicable React modules are reported."],
11519
+ examples: [
11520
+ { id: "server-action-call", title: "Call a Server Action", outcome: "no-match", files: [{ path: "app/tasks/page.tsx", source: "import { createTask } from './actions'; await createTask(input);" }], focusPath: "app/tasks/page.tsx", expectedCount: 0, public: true },
11521
+ { id: "api-mutation", title: "Do not mutate through an API route", outcome: "match", files: [{ path: "app/tasks/page.tsx", source: "await fetch('/api/tasks', { method: 'POST', body });" }], focusPath: "app/tasks/page.tsx", expectedCount: 1, public: true }
11522
+ ]
11523
+ };
10247
11524
  var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
10248
11525
  var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
10249
11526
  var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
@@ -10341,6 +11618,7 @@ function getPropertyNode(objNode, propName2) {
10341
11618
  }
10342
11619
  var prefer_server_actions_default = createRule({
10343
11620
  name: "prefer-server-actions",
11621
+ documentation: preferServerActionsDocumentation,
10344
11622
  meta: {
10345
11623
  type: "suggestion",
10346
11624
  docs: {
@@ -10416,6 +11694,18 @@ var COLLECTION_PROPERTIES = /* @__PURE__ */ new Set(["length", "size"]);
10416
11694
  var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
10417
11695
  var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
10418
11696
  var MIN_RUN_LENGTH = 2;
11697
+ var preferWholeObjectAssertionDocumentation = {
11698
+ summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
11699
+ rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
11700
+ remediation: "Replace consecutive member assertions with one `toMatchObject` assertion.",
11701
+ category: "testing",
11702
+ aliases: ["strict-test-assertions"],
11703
+ autofix: "safe",
11704
+ examples: [
11705
+ { id: "whole-object", title: "Assert the object once", outcome: "no-match", files: [{ path: "src/user.test.ts", source: "expect(user).toMatchObject({ id: 1, name: 'Ada' });" }], focusPath: "src/user.test.ts", expectedCount: 0, public: true },
11706
+ { id: "member-run", title: "Do not split one object across assertions", outcome: "match", files: [{ path: "src/user.test.ts", source: "expect(user.id).toBe(1);\nexpect(user.name).toBe('Ada');" }], focusPath: "src/user.test.ts", expectedCount: 1, public: true, fixedFiles: [{ path: "src/user.test.ts", source: "expect(user).toMatchObject({ id: 1, name: 'Ada' });\n" }] }
11707
+ ]
11708
+ };
10419
11709
  function literalText(node, getText) {
10420
11710
  switch (node.type) {
10421
11711
  case import_utils62.AST_NODE_TYPES.Literal:
@@ -10453,10 +11743,11 @@ function literalIndex(node) {
10453
11743
  }
10454
11744
  var prefer_whole_object_assertion_default = createRule({
10455
11745
  name: "prefer-whole-object-assertion",
11746
+ documentation: preferWholeObjectAssertionDocumentation,
10456
11747
  meta: {
10457
11748
  type: "suggestion",
10458
11749
  docs: {
10459
- description: "Collapse a run of consecutive assertions on the same object into one assertion about the whole object, so every mismatch is reported and nothing outside the asserted keys goes unchecked."
11750
+ description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
10460
11751
  },
10461
11752
  fixable: "code",
10462
11753
  messages: {
@@ -10631,6 +11922,16 @@ var prefer_whole_object_assertion_default = createRule({
10631
11922
 
10632
11923
  // src/rules/prefer-zod-infer.ts
10633
11924
  var import_utils63 = require("@typescript-eslint/utils");
11925
+ var preferZodInferDocumentation = {
11926
+ summary: "Derive a type from its Zod schema with `z.infer` instead of hand-writing a twin declaration beside it.",
11927
+ rationale: "A derived type stays synchronized when the runtime schema changes.",
11928
+ remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
11929
+ category: "correctness",
11930
+ examples: [
11931
+ { 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 },
11932
+ { 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 }
11933
+ ]
11934
+ };
10634
11935
  var SHAPE_PRESERVING_METHODS = /* @__PURE__ */ new Set([
10635
11936
  "describe",
10636
11937
  "refine",
@@ -10756,6 +12057,7 @@ function leafAgrees(leaf, annotation) {
10756
12057
  }
10757
12058
  var prefer_zod_infer_default = createRule({
10758
12059
  name: "prefer-zod-infer",
12060
+ documentation: preferZodInferDocumentation,
10759
12061
  meta: {
10760
12062
  type: "problem",
10761
12063
  docs: {
@@ -11041,6 +12343,16 @@ var prefer_zod_infer_default = createRule({
11041
12343
  // src/rules/require-assert-never.ts
11042
12344
  var import_utils64 = require("@typescript-eslint/utils");
11043
12345
  var import_typescript = __toESM(require("typescript"), 1);
12346
+ var requireAssertNeverDocumentation = {
12347
+ summary: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time.",
12348
+ rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
12349
+ remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
12350
+ category: "correctness",
12351
+ examples: [
12352
+ { 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 },
12353
+ { 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 }
12354
+ ]
12355
+ };
11044
12356
  var isRuntimeHandlingStatement = (statement) => {
11045
12357
  if (statement.type === import_utils64.AST_NODE_TYPES.EmptyStatement) return false;
11046
12358
  if (statement.type === import_utils64.AST_NODE_TYPES.TSTypeAliasDeclaration || statement.type === import_utils64.AST_NODE_TYPES.TSInterfaceDeclaration) {
@@ -11098,10 +12410,11 @@ function finiteTypeKey(type, checker) {
11098
12410
  }
11099
12411
  var require_assert_never_default = createRule({
11100
12412
  name: "require-assert-never",
12413
+ documentation: requireAssertNeverDocumentation,
11101
12414
  meta: {
11102
12415
  type: "problem",
11103
12416
  docs: {
11104
- description: "Require an exhaustive-style switch whose `default` case does no runtime work to call `assertNever(_)` so that discriminated unions are exhaustively checked at compile time. Switches with a legitimate runtime default (a reducer's `return state`, an HTTP-status `return fallback()`, a `break`, a `throw`, etc.) are left alone."
12417
+ description: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time."
11105
12418
  },
11106
12419
  schema: [],
11107
12420
  messages: {
@@ -11139,6 +12452,16 @@ var require_assert_never_default = createRule({
11139
12452
 
11140
12453
  // src/rules/require-fetch-timeout.ts
11141
12454
  var import_utils65 = require("@typescript-eslint/utils");
12455
+ var requireFetchTimeoutDocumentation = {
12456
+ summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
12457
+ rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
12458
+ remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
12459
+ category: "correctness",
12460
+ examples: [
12461
+ { id: "bounded-fetch", title: "Bound the request", outcome: "no-match", files: [{ path: "src/client.ts", source: "await fetch(url, { signal: AbortSignal.timeout(5000) });" }], focusPath: "src/client.ts", expectedCount: 0, public: true },
12462
+ { id: "unbounded-fetch", title: "Do not leave fetch unbounded", outcome: "match", files: [{ path: "src/client.ts", source: "await fetch('https://api.example.com/items');" }], focusPath: "src/client.ts", expectedCount: 1, public: true }
12463
+ ]
12464
+ };
11142
12465
  var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
11143
12466
  "globalThis",
11144
12467
  "window",
@@ -11175,6 +12498,7 @@ function isInlineUrl(node, resolvesToGlobal) {
11175
12498
  }
11176
12499
  var require_fetch_timeout_default = createRule({
11177
12500
  name: "require-fetch-timeout",
12501
+ documentation: requireFetchTimeoutDocumentation,
11178
12502
  meta: {
11179
12503
  type: "problem",
11180
12504
  docs: {
@@ -11234,8 +12558,19 @@ var require_fetch_timeout_default = createRule({
11234
12558
  }
11235
12559
  });
11236
12560
 
11237
- // src/rules/require-interface-for-injected-service.ts
12561
+ // src/rules/require-port-for-service.ts
11238
12562
  var import_utils66 = require("@typescript-eslint/utils");
12563
+ var requirePortForServiceDocumentation = {
12564
+ summary: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
12565
+ rationale: "A declared port keeps consumers coupled to the service capability instead of its concrete implementation.",
12566
+ remediation: "Declare and implement an interface covering the service's public methods.",
12567
+ category: "architecture",
12568
+ aliases: ["require-interface-for-injected-service"],
12569
+ examples: [
12570
+ { id: "declared-service-port", title: "Implement the service port", outcome: "no-match", files: [{ path: "src/service.ts", source: "interface Handler { handle(): void }\nexport class RequestHandler implements Handler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 0, public: true },
12571
+ { id: "concrete-injected-service", title: "Do not expose only the concrete service", outcome: "match", files: [{ path: "src/service.ts", source: "export class RequestHandler { constructor(private readonly store: TaskStore) {} handle(): void { this.store.handle(); } }" }], focusPath: "src/service.ts", expectedCount: 1, public: true }
12572
+ ]
12573
+ };
11239
12574
  var CONFIGISH_TYPE_RE = /(?:Options|Opts|Config|Configuration|Settings|Params|Props|Args|Env|Environment|Callbacks|Flags)$/;
11240
12575
  var CONFIGISH_NAME_RE = /^(?:options|opts|config|configuration|settings|params|props|args|env|environment|callbacks|flags|logger|log|clock)$/i;
11241
12576
  var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
@@ -11674,8 +13009,9 @@ function hasServicePort(node, methods, classes, interfaces) {
11674
13009
  }
11675
13010
  return methods.every((method) => combined.has(method));
11676
13011
  }
11677
- var require_interface_for_injected_service_default = createRule({
11678
- name: "require-interface-for-injected-service",
13012
+ var require_port_for_service_default = createRule({
13013
+ name: "require-port-for-service",
13014
+ documentation: requirePortForServiceDocumentation,
11679
13015
  meta: {
11680
13016
  type: "suggestion",
11681
13017
  docs: {
@@ -11740,6 +13076,16 @@ var require_interface_for_injected_service_default = createRule({
11740
13076
 
11741
13077
  // src/rules/require-static-next-matcher.ts
11742
13078
  var import_utils67 = require("@typescript-eslint/utils");
13079
+ var requireStaticNextMatcherDocumentation = {
13080
+ summary: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
13081
+ rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
13082
+ remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
13083
+ category: "correctness",
13084
+ examples: [
13085
+ { 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 },
13086
+ { 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 }
13087
+ ]
13088
+ };
11743
13089
  var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
11744
13090
  function unwrapExpression3(node) {
11745
13091
  if (node.type === import_utils67.AST_NODE_TYPES.TSAsExpression || node.type === import_utils67.AST_NODE_TYPES.TSSatisfiesExpression || node.type === import_utils67.AST_NODE_TYPES.TSNonNullExpression || node.type === import_utils67.AST_NODE_TYPES.TSTypeAssertion) {
@@ -11774,6 +13120,7 @@ function propertyName2(property) {
11774
13120
  }
11775
13121
  var require_static_next_matcher_default = createRule({
11776
13122
  name: "require-static-next-matcher",
13123
+ documentation: requireStaticNextMatcherDocumentation,
11777
13124
  meta: {
11778
13125
  type: "problem",
11779
13126
  docs: {
@@ -11818,6 +13165,16 @@ var require_static_next_matcher_default = createRule({
11818
13165
 
11819
13166
  // src/rules/require-zod-form-validation.ts
11820
13167
  var import_utils68 = require("@typescript-eslint/utils");
13168
+ var requireZodFormValidationDocumentation = {
13169
+ summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
13170
+ rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
13171
+ remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
13172
+ category: "security",
13173
+ examples: [
13174
+ { 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 },
13175
+ { id: "raw-form-value", title: "Do not use a raw form value", outcome: "match", files: [{ path: "src/action.ts", source: "const name = formData.get('name');" }], focusPath: "src/action.ts", expectedCount: 1, public: true }
13176
+ ]
13177
+ };
11821
13178
  var isZodParseCall = (node) => {
11822
13179
  if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression) return false;
11823
13180
  const callee = node.callee;
@@ -11858,6 +13215,7 @@ var isFormDataMethodCall = (node) => {
11858
13215
  };
11859
13216
  var require_zod_form_validation_default = createRule({
11860
13217
  name: "require-zod-form-validation",
13218
+ documentation: requireZodFormValidationDocumentation,
11861
13219
  meta: {
11862
13220
  type: "problem",
11863
13221
  docs: {
@@ -11946,11 +13304,22 @@ var require_zod_form_validation_default = createRule({
11946
13304
 
11947
13305
  // src/rules/store-insert-requires-on-conflict.ts
11948
13306
  var import_utils69 = require("@typescript-eslint/utils");
13307
+ var storeInsertRequiresOnConflictDocumentation = {
13308
+ summary: "Require an embedded SQL INSERT to carry ON CONFLICT; store writes replay under cron re-runs and queue redelivery and must be idempotent upserts.",
13309
+ rationale: "A replayed bare insert can duplicate data or fail on a uniqueness constraint.",
13310
+ remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
13311
+ category: "correctness",
13312
+ examples: [
13313
+ { 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 },
13314
+ { 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 }
13315
+ ]
13316
+ };
11949
13317
  var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
11950
13318
  var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b/i;
11951
13319
  var INSERT_GATE = /insert/i;
11952
13320
  var store_insert_requires_on_conflict_default = createRule({
11953
13321
  name: "store-insert-requires-on-conflict",
13322
+ documentation: storeInsertRequiresOnConflictDocumentation,
11954
13323
  meta: {
11955
13324
  type: "problem",
11956
13325
  docs: {
@@ -11977,6 +13346,16 @@ var store_insert_requires_on_conflict_default = createRule({
11977
13346
 
11978
13347
  // src/rules/stepdown.ts
11979
13348
  var import_utils70 = require("@typescript-eslint/utils");
13349
+ var stepdownDocumentation = {
13350
+ summary: "Place a private helper below its sole direct same-scope caller.",
13351
+ rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
13352
+ remediation: "Move the private helper immediately below its sole caller.",
13353
+ category: "maintainability",
13354
+ examples: [
13355
+ { 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 },
13356
+ { id: "helper-before-caller", title: "Do not lead with a sole-caller helper", outcome: "match", files: [{ path: "src/run.ts", source: "function load() { return 1; }\nfunction run() { return load(); }" }], focusPath: "src/run.ts", expectedCount: 1, public: true }
13357
+ ]
13358
+ };
11980
13359
  function isFunction(node) {
11981
13360
  return node.type === import_utils70.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils70.AST_NODE_TYPES.FunctionDeclaration || node.type === import_utils70.AST_NODE_TYPES.FunctionExpression;
11982
13361
  }
@@ -12331,6 +13710,7 @@ function classScope(context, node, computedReferenceNames) {
12331
13710
  }
12332
13711
  var stepdown_default = createRule({
12333
13712
  name: "stepdown",
13713
+ documentation: stepdownDocumentation,
12334
13714
  meta: {
12335
13715
  type: "suggestion",
12336
13716
  docs: { description: "Place a private helper below its sole direct same-scope caller." },
@@ -12364,6 +13744,16 @@ var stepdown_default = createRule({
12364
13744
 
12365
13745
  // src/rules/zod-naming-convention.ts
12366
13746
  var import_utils71 = require("@typescript-eslint/utils");
13747
+ var zodNamingConventionDocumentation = {
13748
+ summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
13749
+ rationale: "A recognizable schema name distinguishes runtime validators from ordinary values at each use site.",
13750
+ remediation: "Rename the schema with a `Z` prefix or `Schema` suffix, according to the configured convention.",
13751
+ category: "style",
13752
+ examples: [
13753
+ { id: "recognizable-schema-name", title: "Mark the value as a schema", outcome: "no-match", files: [{ path: "src/user.ts", source: "import { z } from 'zod';\nconst userSchema = z.object({ id: z.string() });" }], focusPath: "src/user.ts", expectedCount: 0, public: true },
13754
+ { id: "unmarked-schema-name", title: "Do not hide the schema behind a value name", outcome: "match", files: [{ path: "src/user.ts", source: "import { z } from 'zod';\nconst user = z.object({ id: z.string() });" }], focusPath: "src/user.ts", expectedCount: 1, public: true }
13755
+ ]
13756
+ };
12367
13757
  var CONVENTIONS = {
12368
13758
  prefix: { test: ZOD_PREFIX_RE, messageId: "zPrefix" },
12369
13759
  suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
@@ -12408,6 +13798,7 @@ var calleeChainRoot = (node) => {
12408
13798
  };
12409
13799
  var zod_naming_convention_default = createRule({
12410
13800
  name: "zod-naming-convention",
13801
+ documentation: zodNamingConventionDocumentation,
12411
13802
  meta: {
12412
13803
  type: "suggestion",
12413
13804
  docs: {
@@ -12490,6 +13881,7 @@ var zod_naming_convention_default = createRule({
12490
13881
  var renamedRules = {
12491
13882
  "jsdoc-restates-signature": "no-restated-jsdoc",
12492
13883
  "no-async-callback-in-waitfor": "no-async-callback-in-wait-for",
13884
+ "require-interface-for-injected-service": "require-port-for-service",
12493
13885
  "strict-test-assertions": "prefer-whole-object-assertion",
12494
13886
  "trailing-value-narration": "no-trailing-value-narration"
12495
13887
  };
@@ -12615,7 +14007,7 @@ var rules = {
12615
14007
  "prefer-zod-infer": prefer_zod_infer_default,
12616
14008
  "require-assert-never": require_assert_never_default,
12617
14009
  "require-fetch-timeout": require_fetch_timeout_default,
12618
- "require-interface-for-injected-service": require_interface_for_injected_service_default,
14010
+ "require-port-for-service": require_port_for_service_default,
12619
14011
  "require-static-next-matcher": require_static_next_matcher_default,
12620
14012
  "require-zod-form-validation": require_zod_form_validation_default,
12621
14013
  "store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
@@ -12624,7 +14016,7 @@ var rules = {
12624
14016
  };
12625
14017
  var meta = {
12626
14018
  name: "@sarj/eslint-plugin",
12627
- version: "12.0.0"
14019
+ version: "13.0.0"
12628
14020
  };
12629
14021
  var applicationOnlyRules = [
12630
14022
  "no-restricted-library-load",
@@ -12684,7 +14076,7 @@ var recommendedRules = {
12684
14076
  "@sarj/prefer-zod-infer": "error",
12685
14077
  "@sarj/require-assert-never": "error",
12686
14078
  "@sarj/require-fetch-timeout": "error",
12687
- "@sarj/require-interface-for-injected-service": "error",
14079
+ "@sarj/require-port-for-service": "error",
12688
14080
  "@sarj/require-static-next-matcher": "error",
12689
14081
  "@sarj/require-zod-form-validation": "error",
12690
14082
  "@sarj/store-insert-requires-on-conflict": "error",
@@ -12748,7 +14140,7 @@ var strictRules = {
12748
14140
  "@sarj/prefer-zod-infer": "error",
12749
14141
  "@sarj/require-assert-never": "error",
12750
14142
  "@sarj/require-fetch-timeout": "error",
12751
- "@sarj/require-interface-for-injected-service": "error",
14143
+ "@sarj/require-port-for-service": "error",
12752
14144
  "@sarj/require-static-next-matcher": "error",
12753
14145
  "@sarj/require-zod-form-validation": "error",
12754
14146
  "@sarj/store-insert-requires-on-conflict": "error",
@@ -12778,6 +14170,7 @@ var index_default = plugin;
12778
14170
  // Annotate the CommonJS export names for ESM import in node:
12779
14171
  0 && (module.exports = {
12780
14172
  applicationOnlyRules,
14173
+ publicDocumentation,
12781
14174
  recommendedRules,
12782
14175
  renamedRules,
12783
14176
  retiredRules,