@sarj/eslint-plugin 12.0.0 → 13.1.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/README.md +91 -38
- package/dist/index.cjs +1445 -31
- package/dist/index.d.cts +235 -413
- package/dist/index.d.ts +235 -413
- package/dist/index.js +1444 -31
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7,7 +7,193 @@ var REPO_BLOB = "https://github.com/sarj-ai/standards/blob/main";
|
|
|
7
7
|
var TESTS_DIR = "packages/typescript/tests/rules";
|
|
8
8
|
var examplesPath = (name) => `${TESTS_DIR}/${name}.test.ts`;
|
|
9
9
|
var examplesUrl = (name) => `${REPO_BLOB}/${examplesPath(name)}`;
|
|
10
|
-
var
|
|
10
|
+
var KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
11
|
+
var MAX_SUMMARY_LENGTH = 160;
|
|
12
|
+
var eslintCreateRule = ESLintUtils.RuleCreator(examplesUrl);
|
|
13
|
+
function createRule(config) {
|
|
14
|
+
const { documentation, ...eslintConfig } = config;
|
|
15
|
+
const rule = eslintCreateRule(eslintConfig);
|
|
16
|
+
if (documentation !== void 0) {
|
|
17
|
+
Object.defineProperty(rule, "documentation", {
|
|
18
|
+
configurable: false,
|
|
19
|
+
enumerable: false,
|
|
20
|
+
value: nativeSpec(eslintConfig, documentation),
|
|
21
|
+
writable: false
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return rule;
|
|
25
|
+
}
|
|
26
|
+
function documentationWarnings(rules2) {
|
|
27
|
+
return Object.entries(rules2).filter(([, rule]) => rule.documentation === void 0).map(([name]) => `${name}: source-owned documentation has not been migrated`).sort();
|
|
28
|
+
}
|
|
29
|
+
function publicDocumentation(rules2) {
|
|
30
|
+
const missing = documentationWarnings(rules2);
|
|
31
|
+
if (missing.length > 0) {
|
|
32
|
+
throw new TypeError(`cannot publish an incomplete rule catalog:
|
|
33
|
+
${missing.join("\n")}`);
|
|
34
|
+
}
|
|
35
|
+
return Object.entries(rules2).sort(([left], [right]) => left.localeCompare(right)).map(([ruleId, rule]) => {
|
|
36
|
+
const spec = rule.documentation;
|
|
37
|
+
if (spec === void 0 || spec.ruleId !== ruleId) {
|
|
38
|
+
throw new TypeError(`${ruleId}: native documentation identity mismatch`);
|
|
39
|
+
}
|
|
40
|
+
return deepFreeze({
|
|
41
|
+
engine: spec.engine,
|
|
42
|
+
ruleId: spec.ruleId,
|
|
43
|
+
code: spec.code,
|
|
44
|
+
summary: spec.summary,
|
|
45
|
+
rationale: spec.rationale,
|
|
46
|
+
remediation: spec.remediation,
|
|
47
|
+
category: spec.category,
|
|
48
|
+
languages: spec.languages,
|
|
49
|
+
autofix: spec.autofix,
|
|
50
|
+
aliases: spec.aliases,
|
|
51
|
+
limitations: spec.limitations,
|
|
52
|
+
filePatterns: spec.filePatterns,
|
|
53
|
+
references: spec.references,
|
|
54
|
+
since: spec.since,
|
|
55
|
+
messageIds: spec.messageIds,
|
|
56
|
+
optionsSchema: spec.optionsSchema,
|
|
57
|
+
examples: spec.publicExamples.map(publicExample)
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function publicExample(example) {
|
|
62
|
+
return {
|
|
63
|
+
id: example.id,
|
|
64
|
+
title: example.title,
|
|
65
|
+
outcome: example.outcome,
|
|
66
|
+
files: example.files.map(publicFile),
|
|
67
|
+
focusPath: example.focusPath,
|
|
68
|
+
expectedCount: example.expectedCount,
|
|
69
|
+
fixedFiles: (example.fixedFiles ?? []).map(publicFile)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function publicFile(file) {
|
|
73
|
+
return { path: file.path, source: file.source };
|
|
74
|
+
}
|
|
75
|
+
function nativeSpec(config, documentation) {
|
|
76
|
+
const { name, meta: meta2 } = config;
|
|
77
|
+
if (!KEBAB_CASE.test(name)) throw new TypeError("rule ID must be lowercase kebab-case");
|
|
78
|
+
for (const [label, value] of [
|
|
79
|
+
["summary", documentation.summary],
|
|
80
|
+
["rationale", documentation.rationale],
|
|
81
|
+
["remediation", documentation.remediation]
|
|
82
|
+
]) {
|
|
83
|
+
if (value.trim().length === 0) throw new TypeError(`rule ${label} must not be empty`);
|
|
84
|
+
}
|
|
85
|
+
if (documentation.summary.includes("\n") || documentation.summary.length > MAX_SUMMARY_LENGTH) {
|
|
86
|
+
throw new TypeError(`rule summary must be one line of at most ${MAX_SUMMARY_LENGTH} characters`);
|
|
87
|
+
}
|
|
88
|
+
if (documentation.summary !== meta2.docs?.description) {
|
|
89
|
+
throw new TypeError(`${name}: ESLint description must equal the authored documentation summary`);
|
|
90
|
+
}
|
|
91
|
+
const aliases = [...documentation.aliases ?? []];
|
|
92
|
+
assertUnique(aliases, "rule aliases");
|
|
93
|
+
if (aliases.some((alias) => !KEBAB_CASE.test(alias) || alias === name)) {
|
|
94
|
+
throw new TypeError("rule aliases must be historical lowercase kebab-case IDs");
|
|
95
|
+
}
|
|
96
|
+
const limitations = [...documentation.limitations ?? []];
|
|
97
|
+
const filePatterns = [...documentation.filePatterns ?? []];
|
|
98
|
+
if ([...limitations, ...filePatterns].some((value) => value.trim().length === 0)) {
|
|
99
|
+
throw new TypeError("rule limitations and file patterns must not be empty");
|
|
100
|
+
}
|
|
101
|
+
const references = [...documentation.references ?? []];
|
|
102
|
+
if (references.some((reference) => !reference.startsWith("https://"))) {
|
|
103
|
+
throw new TypeError("rule references must use https");
|
|
104
|
+
}
|
|
105
|
+
const examples = [...documentation.examples ?? []];
|
|
106
|
+
examples.forEach(validateExample);
|
|
107
|
+
assertUnique(examples.map((example) => example.id), "rule example IDs");
|
|
108
|
+
const publicOutcomes = new Set(examples.filter((example) => example.public === true).map((example) => example.outcome));
|
|
109
|
+
if (publicOutcomes.size > 0 && !(publicOutcomes.has("match") && publicOutcomes.has("no-match"))) {
|
|
110
|
+
throw new TypeError("published rule examples must include matching and non-matching cases");
|
|
111
|
+
}
|
|
112
|
+
const messageIds = Object.keys(meta2.messages).sort();
|
|
113
|
+
const schema = optionsSchema(meta2.schema);
|
|
114
|
+
const spec = {
|
|
115
|
+
engine: "eslint",
|
|
116
|
+
ruleId: name,
|
|
117
|
+
code: null,
|
|
118
|
+
key: `eslint:${name}`,
|
|
119
|
+
summary: documentation.summary,
|
|
120
|
+
rationale: documentation.rationale,
|
|
121
|
+
remediation: documentation.remediation,
|
|
122
|
+
category: documentation.category,
|
|
123
|
+
languages: [...documentation.languages ?? ["typescript"]],
|
|
124
|
+
autofix: documentation.autofix ?? "none",
|
|
125
|
+
aliases,
|
|
126
|
+
limitations,
|
|
127
|
+
filePatterns,
|
|
128
|
+
references,
|
|
129
|
+
since: documentation.since ?? null,
|
|
130
|
+
examples,
|
|
131
|
+
publicExamples: examples.filter((example) => example.public === true),
|
|
132
|
+
messageIds,
|
|
133
|
+
optionsSchema: schema
|
|
134
|
+
};
|
|
135
|
+
return deepFreeze(spec);
|
|
136
|
+
}
|
|
137
|
+
function optionsSchema(value) {
|
|
138
|
+
if (!Array.isArray(value)) return isObject(value) ? value : null;
|
|
139
|
+
const items = value;
|
|
140
|
+
if (items.length === 0) return null;
|
|
141
|
+
const [only] = items;
|
|
142
|
+
return items.length === 1 && isObject(only) ? only : { type: "array", items };
|
|
143
|
+
}
|
|
144
|
+
function isObject(value) {
|
|
145
|
+
return value !== null && typeof value === "object";
|
|
146
|
+
}
|
|
147
|
+
function validateExample(example) {
|
|
148
|
+
if (!KEBAB_CASE.test(example.id)) {
|
|
149
|
+
throw new TypeError("example ID must be lowercase kebab-case");
|
|
150
|
+
}
|
|
151
|
+
if (example.title.trim().length === 0) {
|
|
152
|
+
throw new TypeError("example title must not be empty");
|
|
153
|
+
}
|
|
154
|
+
if (!Number.isSafeInteger(example.expectedCount) || example.expectedCount < 0) {
|
|
155
|
+
throw new TypeError("example expected count must be a non-negative integer");
|
|
156
|
+
}
|
|
157
|
+
if (example.outcome === "match" && example.expectedCount < 1) {
|
|
158
|
+
throw new TypeError("matching examples must expect at least one diagnostic");
|
|
159
|
+
}
|
|
160
|
+
if (example.outcome === "no-match" && example.expectedCount !== 0) {
|
|
161
|
+
throw new TypeError("non-matching examples must expect zero diagnostics");
|
|
162
|
+
}
|
|
163
|
+
if (example.files.length === 0) {
|
|
164
|
+
throw new TypeError("example files must not be empty");
|
|
165
|
+
}
|
|
166
|
+
const paths = example.files.map((file) => file.path);
|
|
167
|
+
assertUnique(paths, "example file paths");
|
|
168
|
+
for (const file of [...example.files, ...example.fixedFiles ?? []]) {
|
|
169
|
+
assertSafeRelativePath(file.path, "example file path");
|
|
170
|
+
if (file.source.length === 0) {
|
|
171
|
+
throw new TypeError("example file source must not be empty");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
assertSafeRelativePath(example.focusPath, "example focus path");
|
|
175
|
+
if (!paths.includes(example.focusPath)) {
|
|
176
|
+
throw new TypeError("example focus path must name one example file");
|
|
177
|
+
}
|
|
178
|
+
assertUnique((example.fixedFiles ?? []).map((file) => file.path), "fixed example file paths");
|
|
179
|
+
}
|
|
180
|
+
function assertSafeRelativePath(path, label) {
|
|
181
|
+
if (path.length === 0 || path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:[\\/]/u.test(path) || path.split(/[\\/]/u).includes("..")) {
|
|
182
|
+
throw new TypeError(`${label} must be a safe relative path`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function assertUnique(values, label) {
|
|
186
|
+
if (new Set(values).size !== values.length) {
|
|
187
|
+
throw new TypeError(`${label} must be unique`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function deepFreeze(value) {
|
|
191
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
192
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
193
|
+
Object.freeze(value);
|
|
194
|
+
}
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
11
197
|
|
|
12
198
|
// src/rules/_paths.ts
|
|
13
199
|
var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
|
|
@@ -96,6 +282,35 @@ function isScriptFile(filename) {
|
|
|
96
282
|
}
|
|
97
283
|
|
|
98
284
|
// src/rules/enforce-file-structure.ts
|
|
285
|
+
var enforceFileStructureDocumentation = {
|
|
286
|
+
summary: "Require imports before body statements and require `use server` to be the first statement.",
|
|
287
|
+
rationale: "Interleaved imports obscure module dependencies, while a displaced `use server` string is not an active directive.",
|
|
288
|
+
remediation: "Move `use server` to the first statement when present, then place imports before declarations and executable statements.",
|
|
289
|
+
category: "correctness",
|
|
290
|
+
limitations: [
|
|
291
|
+
"The rule skips tests and generated files, treats re-exports as neutral, and does not order body declarations."
|
|
292
|
+
],
|
|
293
|
+
examples: [
|
|
294
|
+
{
|
|
295
|
+
id: "imports-first",
|
|
296
|
+
title: "Imports precede module declarations",
|
|
297
|
+
outcome: "no-match",
|
|
298
|
+
files: [{ path: "src/component.ts", source: "import { z } from 'zod';\nexport const schema = z.string();" }],
|
|
299
|
+
focusPath: "src/component.ts",
|
|
300
|
+
expectedCount: 0,
|
|
301
|
+
public: true
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "import-after-declaration",
|
|
305
|
+
title: "An import follows a module declaration",
|
|
306
|
+
outcome: "match",
|
|
307
|
+
files: [{ path: "src/component.ts", source: "export const x = 1;\nimport { z } from 'zod';" }],
|
|
308
|
+
focusPath: "src/component.ts",
|
|
309
|
+
expectedCount: 1,
|
|
310
|
+
public: true
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
};
|
|
99
314
|
var classifyStatement = (statement) => {
|
|
100
315
|
switch (statement.type) {
|
|
101
316
|
case AST_NODE_TYPES.ImportDeclaration:
|
|
@@ -117,10 +332,11 @@ var isUseServerDirective = (statement) => {
|
|
|
117
332
|
};
|
|
118
333
|
var enforce_file_structure_default = createRule({
|
|
119
334
|
name: "enforce-file-structure",
|
|
335
|
+
documentation: enforceFileStructureDocumentation,
|
|
120
336
|
meta: {
|
|
121
337
|
type: "suggestion",
|
|
122
338
|
docs: {
|
|
123
|
-
description: "Require
|
|
339
|
+
description: "Require imports before body statements and require `use server` to be the first statement."
|
|
124
340
|
},
|
|
125
341
|
schema: [],
|
|
126
342
|
messages: {
|
|
@@ -191,6 +407,41 @@ var OMITTED_AST_KEYS = /* @__PURE__ */ new Set([
|
|
|
191
407
|
var MIN_STATEMENTS = 3;
|
|
192
408
|
var MAX_NORMALIZED_STRING_LENGTH = 64;
|
|
193
409
|
var TEST_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
|
|
410
|
+
var duplicateTestBodyDocumentation = {
|
|
411
|
+
summary: "Disallow substantial sibling tests with the same body shape; express their differing inputs as a parameterized case table.",
|
|
412
|
+
rationale: "Copy-pasted test bodies hide the cases that differ and allow equivalent assertions to drift independently.",
|
|
413
|
+
remediation: "Move the varying inputs and expected values into a case table consumed by `test.each(...)` or `it.each(...)`.",
|
|
414
|
+
category: "testing",
|
|
415
|
+
limitations: [
|
|
416
|
+
"The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments."
|
|
417
|
+
],
|
|
418
|
+
examples: [
|
|
419
|
+
{
|
|
420
|
+
id: "parameterized-cases",
|
|
421
|
+
title: "A case table shares one test body",
|
|
422
|
+
outcome: "no-match",
|
|
423
|
+
files: [{
|
|
424
|
+
path: "src/user.test.ts",
|
|
425
|
+
source: "test.each(['a', 'b'])('parses %s', (value) => { const x = parse(value); expect(x.ok).toBe(true); expect(x.value).toBe(value); });"
|
|
426
|
+
}],
|
|
427
|
+
focusPath: "src/user.test.ts",
|
|
428
|
+
expectedCount: 0,
|
|
429
|
+
public: true
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "copied-sibling-tests",
|
|
433
|
+
title: "Sibling tests repeat the same body",
|
|
434
|
+
outcome: "match",
|
|
435
|
+
files: [{
|
|
436
|
+
path: "src/user.test.ts",
|
|
437
|
+
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'); });"
|
|
438
|
+
}],
|
|
439
|
+
focusPath: "src/user.test.ts",
|
|
440
|
+
expectedCount: 1,
|
|
441
|
+
public: true
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
};
|
|
194
445
|
function rootIdentifier(callee) {
|
|
195
446
|
if (callee.type === AST_NODE_TYPES2.Identifier) return callee;
|
|
196
447
|
if (callee.type === AST_NODE_TYPES2.MemberExpression) return rootIdentifier(callee.object);
|
|
@@ -306,6 +557,7 @@ function normalizedLiteral(node) {
|
|
|
306
557
|
}
|
|
307
558
|
var duplicate_test_body_default = createRule({
|
|
308
559
|
name: "duplicate-test-body",
|
|
560
|
+
documentation: duplicateTestBodyDocumentation,
|
|
309
561
|
meta: {
|
|
310
562
|
type: "suggestion",
|
|
311
563
|
docs: {
|
|
@@ -372,12 +624,43 @@ var duplicate_test_body_default = createRule({
|
|
|
372
624
|
|
|
373
625
|
// src/rules/no-async-callback-in-wait-for.ts
|
|
374
626
|
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
|
|
627
|
+
var noAsyncCallbackInWaitForDocumentation = {
|
|
628
|
+
summary: "Disallow async callbacks in `waitFor` to prevent swallowed promise rejections.",
|
|
629
|
+
rationale: "`waitFor` retries synchronous assertions; an async callback changes that contract and can hide a rejected assertion promise.",
|
|
630
|
+
remediation: "Remove `async` and keep the assertions inside `waitFor` synchronous.",
|
|
631
|
+
category: "testing",
|
|
632
|
+
aliases: ["no-async-callback-in-waitfor"],
|
|
633
|
+
limitations: [
|
|
634
|
+
"The rule checks inline first-argument callbacks to bare or non-computed `.waitFor` calls in test files."
|
|
635
|
+
],
|
|
636
|
+
examples: [
|
|
637
|
+
{
|
|
638
|
+
id: "synchronous-wait-for-callback",
|
|
639
|
+
title: "waitFor retries a synchronous assertion",
|
|
640
|
+
outcome: "no-match",
|
|
641
|
+
files: [{ path: "src/component.test.ts", source: "it('works', async () => { await waitFor(() => expect(foo).toBe(true)); });" }],
|
|
642
|
+
focusPath: "src/component.test.ts",
|
|
643
|
+
expectedCount: 0,
|
|
644
|
+
public: true
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
id: "async-wait-for-callback",
|
|
648
|
+
title: "waitFor receives an async callback",
|
|
649
|
+
outcome: "match",
|
|
650
|
+
files: [{ path: "src/component.test.ts", source: "it('fails', async () => { await waitFor(async () => expect(foo).toBe(true)); });" }],
|
|
651
|
+
focusPath: "src/component.test.ts",
|
|
652
|
+
expectedCount: 1,
|
|
653
|
+
public: true
|
|
654
|
+
}
|
|
655
|
+
]
|
|
656
|
+
};
|
|
375
657
|
var isWaitForCallee = (callee) => {
|
|
376
658
|
if (callee.type === AST_NODE_TYPES3.Identifier) return callee.name === "waitFor";
|
|
377
659
|
return callee.type === AST_NODE_TYPES3.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES3.Identifier && callee.property.name === "waitFor";
|
|
378
660
|
};
|
|
379
661
|
var no_async_callback_in_wait_for_default = createRule({
|
|
380
662
|
name: "no-async-callback-in-wait-for",
|
|
663
|
+
documentation: noAsyncCallbackInWaitForDocumentation,
|
|
381
664
|
meta: {
|
|
382
665
|
type: "problem",
|
|
383
666
|
docs: {
|
|
@@ -410,6 +693,35 @@ var no_async_callback_in_wait_for_default = createRule({
|
|
|
410
693
|
|
|
411
694
|
// src/rules/no-client-side-data-fetching.ts
|
|
412
695
|
import { AST_NODE_TYPES as AST_NODE_TYPES4 } from "@typescript-eslint/utils";
|
|
696
|
+
var noClientSideDataFetchingDocumentation = {
|
|
697
|
+
summary: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`.",
|
|
698
|
+
rationale: "Effect-driven reads begin after rendering and can create request waterfalls, duplicate fetches, and loading-state layout shifts.",
|
|
699
|
+
remediation: "Fetch in a React Server Component or Server Action, or use a client cache such as SWR or React Query.",
|
|
700
|
+
category: "performance",
|
|
701
|
+
limitations: [
|
|
702
|
+
"The rule recognizes common fetch clients syntactically and exempts analytics endpoints and non-GET `fetch` calls."
|
|
703
|
+
],
|
|
704
|
+
examples: [
|
|
705
|
+
{
|
|
706
|
+
id: "effect-without-fetch",
|
|
707
|
+
title: "An effect performs no data request",
|
|
708
|
+
outcome: "no-match",
|
|
709
|
+
files: [{ path: "src/users.tsx", source: "import { useEffect } from 'react'; useEffect(() => { console.log('mounted'); }, []);" }],
|
|
710
|
+
focusPath: "src/users.tsx",
|
|
711
|
+
expectedCount: 0,
|
|
712
|
+
public: true
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
id: "fetch-inside-effect",
|
|
716
|
+
title: "An effect starts a data request",
|
|
717
|
+
outcome: "match",
|
|
718
|
+
files: [{ path: "src/users.tsx", source: "useEffect(() => { fetch('/api/users'); }, []);" }],
|
|
719
|
+
focusPath: "src/users.tsx",
|
|
720
|
+
expectedCount: 1,
|
|
721
|
+
public: true
|
|
722
|
+
}
|
|
723
|
+
]
|
|
724
|
+
};
|
|
413
725
|
var FETCH_LIBS = /* @__PURE__ */ new Set(["axios", "ky", "superagent"]);
|
|
414
726
|
var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
415
727
|
"get",
|
|
@@ -508,10 +820,11 @@ function extractUrlString(node) {
|
|
|
508
820
|
}
|
|
509
821
|
var no_client_side_data_fetching_default = createRule({
|
|
510
822
|
name: "no-client-side-data-fetching",
|
|
823
|
+
documentation: noClientSideDataFetchingDocumentation,
|
|
511
824
|
meta: {
|
|
512
825
|
type: "problem",
|
|
513
826
|
docs: {
|
|
514
|
-
description: "Disallow data fetching inside `useEffect`
|
|
827
|
+
description: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`."
|
|
515
828
|
},
|
|
516
829
|
schema: [],
|
|
517
830
|
messages: {
|
|
@@ -732,6 +1045,35 @@ function headTokens(source) {
|
|
|
732
1045
|
}
|
|
733
1046
|
|
|
734
1047
|
// src/rules/no-comment-cruft.ts
|
|
1048
|
+
var noCommentCruftDocumentation = {
|
|
1049
|
+
summary: "Flag commented-out code, section-banner comments, and leading file-header comment preambles.",
|
|
1050
|
+
rationale: "Decorative, narrated, or dead-code comments obscure the constraints and rationale that comments should preserve.",
|
|
1051
|
+
remediation: "Delete dead code and narration; express boundaries with named code and retain only comments that explain constraints or intent.",
|
|
1052
|
+
category: "maintainability",
|
|
1053
|
+
limitations: [
|
|
1054
|
+
"The rule skips generated files and conservatively preserves prose, issue references, licenses, examples, and tool directives."
|
|
1055
|
+
],
|
|
1056
|
+
examples: [
|
|
1057
|
+
{
|
|
1058
|
+
id: "rationale-comment",
|
|
1059
|
+
title: "A comment explains why retry is required",
|
|
1060
|
+
outcome: "no-match",
|
|
1061
|
+
files: [{ path: "src/retry.ts", source: "// retry because the upstream API is flaky\nconst x = retry();" }],
|
|
1062
|
+
focusPath: "src/retry.ts",
|
|
1063
|
+
expectedCount: 0,
|
|
1064
|
+
public: true
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
id: "region-banner",
|
|
1068
|
+
title: "A region comment decorates a code boundary",
|
|
1069
|
+
outcome: "match",
|
|
1070
|
+
files: [{ path: "src/helpers.ts", source: "const x = 1;\n// region helpers\nconst y = 2;" }],
|
|
1071
|
+
focusPath: "src/helpers.ts",
|
|
1072
|
+
expectedCount: 1,
|
|
1073
|
+
public: true
|
|
1074
|
+
}
|
|
1075
|
+
]
|
|
1076
|
+
};
|
|
735
1077
|
var LEADING_PREAMBLE_MIN = 4;
|
|
736
1078
|
var WALL_MIN_STATEMENTS = 4;
|
|
737
1079
|
var WALL_MIN_COMMENTS = 3;
|
|
@@ -757,6 +1099,7 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
|
|
|
757
1099
|
var META_COMMENTARY_RE = /\b(?:keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
|
|
758
1100
|
var EDITORIAL_PLACEHOLDER_RE = /^(?:(?:implementation omitted|existing code here|your code here|rest of (?:the )?code (?:is )?unchanged|same as above|placeholder implementation)\s*[.!]?|in a real (?:app(?:lication)?|implementation),?\s+(?:this|we|you|it)\s+would\s+(?:call|fetch|generate|download|persist|save|send|store|write)\b[^,;]*[.!]?)$/i;
|
|
759
1101
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
1102
|
+
var JSDOC_DEBT_RE = /^@?(?:todo|fixme)\b/i;
|
|
760
1103
|
var DEFERRAL_STOPWORDS = /* @__PURE__ */ new Set([
|
|
761
1104
|
"a",
|
|
762
1105
|
"an",
|
|
@@ -885,6 +1228,7 @@ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
|
|
|
885
1228
|
var CODE_KEYWORD_RE = /^(import |export |const |let |var |function\b|class |interface |type \w|enum |return\b|throw |await |async |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|new |console\.)/;
|
|
886
1229
|
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
887
1230
|
var ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$/;
|
|
1231
|
+
var DECLARATION_RE = /^(?:export\s+)?(?:declare\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*(?:\s*:\s*[^=]+)?\s*=\s*(?:[A-Za-z_$][\w.$]*(?:\s*\(|\s*$)|["'`]|\[|\{|\d|true\b|false\b|null\b|undefined\b|new\b|await\b|async\b|function\b|class\b)/;
|
|
888
1232
|
var CALL_RE = /^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
889
1233
|
var ASSERTION_CODE_RE = /^(?:await\s+)?(?:expect(?:TypeOf)?|assert(?:\.\w+)?)\s*\(/;
|
|
890
1234
|
var HTTP_CONTRACT_RE = /\b(?:GET|HEAD|OPTIONS|PATCH|POST|PUT|DELETE)\s+(?:https?:\/\/|\/|\{[A-Za-z_$])/;
|
|
@@ -912,6 +1256,7 @@ function looksLikeCode(text, allowCall = true) {
|
|
|
912
1256
|
if (!t) return false;
|
|
913
1257
|
if (PROSE_ASSIGNMENT_RE.test(t)) return false;
|
|
914
1258
|
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
1259
|
+
if (DECLARATION_RE.test(t)) return true;
|
|
915
1260
|
if (ASSIGN_RE.test(t)) return true;
|
|
916
1261
|
if (ASSERTION_CODE_RE.test(t)) return true;
|
|
917
1262
|
return allowCall && CALL_RE.test(t);
|
|
@@ -1100,6 +1445,7 @@ function hasCommentedOutCode(texts, precedingProse, allowCall) {
|
|
|
1100
1445
|
}
|
|
1101
1446
|
var no_comment_cruft_default = createRule({
|
|
1102
1447
|
name: "no-comment-cruft",
|
|
1448
|
+
documentation: noCommentCruftDocumentation,
|
|
1103
1449
|
meta: {
|
|
1104
1450
|
type: "suggestion",
|
|
1105
1451
|
docs: {
|
|
@@ -1240,6 +1586,11 @@ var no_comment_cruft_default = createRule({
|
|
|
1240
1586
|
}
|
|
1241
1587
|
if (wallMembers.has(comment)) continue;
|
|
1242
1588
|
if (isJsDoc(comment)) {
|
|
1589
|
+
const debt = comment.value.split("\n").map(stripCommentMarker).find((line) => JSDOC_DEBT_RE.test(line));
|
|
1590
|
+
if (debt !== void 0 && !runCitesAReference(comments, i)) {
|
|
1591
|
+
context.report({ node: comment, messageId: "untrackedTodo" });
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1243
1594
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
1244
1595
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
1245
1596
|
}
|
|
@@ -1306,6 +1657,35 @@ var no_comment_cruft_default = createRule({
|
|
|
1306
1657
|
|
|
1307
1658
|
// src/rules/no-conditional-in-test.ts
|
|
1308
1659
|
import { AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
|
|
1660
|
+
var noConditionalInTestDocumentation = {
|
|
1661
|
+
summary: "Disallow test conditionals that can skip a runtime assertion or exit the test before one runs.",
|
|
1662
|
+
rationale: "A branch can skip the assertion that gives a test its meaning, allowing unexpected inputs to pass silently.",
|
|
1663
|
+
remediation: "Split each path into a separate test or use a parameterized case table with unconditional assertions.",
|
|
1664
|
+
category: "testing",
|
|
1665
|
+
limitations: [
|
|
1666
|
+
"The rule exempts lifecycle hooks, nested helpers, and narrow guards whose outcome is pinned by a preceding assertion."
|
|
1667
|
+
],
|
|
1668
|
+
examples: [
|
|
1669
|
+
{
|
|
1670
|
+
id: "unconditional-assertion",
|
|
1671
|
+
title: "A test always executes its assertion",
|
|
1672
|
+
outcome: "no-match",
|
|
1673
|
+
files: [{ path: "src/component.test.ts", source: "it('works', () => { expect(1).toBe(1); });" }],
|
|
1674
|
+
focusPath: "src/component.test.ts",
|
|
1675
|
+
expectedCount: 0,
|
|
1676
|
+
public: true
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
id: "conditional-assertion",
|
|
1680
|
+
title: "A branch can skip the assertion",
|
|
1681
|
+
outcome: "match",
|
|
1682
|
+
files: [{ path: "src/component.test.ts", source: "it('fails with if', () => { if (ready) { expect(value).toBe(1); } });" }],
|
|
1683
|
+
focusPath: "src/component.test.ts",
|
|
1684
|
+
expectedCount: 1,
|
|
1685
|
+
public: true
|
|
1686
|
+
}
|
|
1687
|
+
]
|
|
1688
|
+
};
|
|
1309
1689
|
var TEST_CALLERS2 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
1310
1690
|
var NON_TEST_MEMBERS = /* @__PURE__ */ new Set([
|
|
1311
1691
|
"afterAll",
|
|
@@ -1586,6 +1966,7 @@ function isShortCircuitedAssertion(node) {
|
|
|
1586
1966
|
}
|
|
1587
1967
|
var no_conditional_in_test_default = createRule({
|
|
1588
1968
|
name: "no-conditional-in-test",
|
|
1969
|
+
documentation: noConditionalInTestDocumentation,
|
|
1589
1970
|
meta: {
|
|
1590
1971
|
type: "problem",
|
|
1591
1972
|
docs: {
|
|
@@ -1633,6 +2014,35 @@ var no_conditional_in_test_default = createRule({
|
|
|
1633
2014
|
|
|
1634
2015
|
// src/rules/no-cors-wildcard-with-credentials.ts
|
|
1635
2016
|
import "@typescript-eslint/utils";
|
|
2017
|
+
var noCorsWildcardWithCredentialsDocumentation = {
|
|
2018
|
+
summary: "Disallow wildcard CORS origins when credentials are enabled.",
|
|
2019
|
+
rationale: "Reflecting every origin while allowing credentials can let an untrusted site read authenticated cross-origin responses.",
|
|
2020
|
+
remediation: "Enumerate the trusted origins that may receive credentialed responses.",
|
|
2021
|
+
category: "security",
|
|
2022
|
+
limitations: [
|
|
2023
|
+
"The rule detects literal CORS option and header combinations within the same syntactic scope; it does not resolve runtime configuration."
|
|
2024
|
+
],
|
|
2025
|
+
examples: [
|
|
2026
|
+
{
|
|
2027
|
+
id: "trusted-origin-with-credentials",
|
|
2028
|
+
title: "Credentials are limited to a trusted origin",
|
|
2029
|
+
outcome: "no-match",
|
|
2030
|
+
files: [{ path: "src/server.ts", source: "app.use(cors({ origin: 'https://app.example.com', credentials: true }));" }],
|
|
2031
|
+
focusPath: "src/server.ts",
|
|
2032
|
+
expectedCount: 0,
|
|
2033
|
+
public: true
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
id: "wildcard-origin-with-credentials",
|
|
2037
|
+
title: "Credentials are enabled for every origin",
|
|
2038
|
+
outcome: "match",
|
|
2039
|
+
files: [{ path: "src/server.ts", source: "app.use(cors({ origin: '*', credentials: true }));" }],
|
|
2040
|
+
focusPath: "src/server.ts",
|
|
2041
|
+
expectedCount: 1,
|
|
2042
|
+
public: true
|
|
2043
|
+
}
|
|
2044
|
+
]
|
|
2045
|
+
};
|
|
1636
2046
|
var ACAO_HEADER = "access-control-allow-origin";
|
|
1637
2047
|
var ACAC_HEADER = "access-control-allow-credentials";
|
|
1638
2048
|
var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
|
|
@@ -1776,10 +2186,11 @@ function enclosingScope(node) {
|
|
|
1776
2186
|
}
|
|
1777
2187
|
var no_cors_wildcard_with_credentials_default = createRule({
|
|
1778
2188
|
name: "no-cors-wildcard-with-credentials",
|
|
2189
|
+
documentation: noCorsWildcardWithCredentialsDocumentation,
|
|
1779
2190
|
meta: {
|
|
1780
2191
|
type: "problem",
|
|
1781
2192
|
docs: {
|
|
1782
|
-
description:
|
|
2193
|
+
description: "Disallow wildcard CORS origins when credentials are enabled."
|
|
1783
2194
|
},
|
|
1784
2195
|
schema: [],
|
|
1785
2196
|
messages: {
|
|
@@ -1986,6 +2397,35 @@ function createSqlListener(handler) {
|
|
|
1986
2397
|
}
|
|
1987
2398
|
|
|
1988
2399
|
// src/rules/no-dynamic-sql.ts
|
|
2400
|
+
var noDynamicSqlDocumentation = {
|
|
2401
|
+
summary: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods.",
|
|
2402
|
+
rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
|
|
2403
|
+
remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
|
|
2404
|
+
category: "security",
|
|
2405
|
+
limitations: [
|
|
2406
|
+
"The rule recognizes SQL by syntax and configured method names; static fragments and parameterizing tagged templates are exempt."
|
|
2407
|
+
],
|
|
2408
|
+
examples: [
|
|
2409
|
+
{
|
|
2410
|
+
id: "bound-sql-parameter",
|
|
2411
|
+
title: "A runtime value is bound separately",
|
|
2412
|
+
outcome: "no-match",
|
|
2413
|
+
files: [{ path: "src/users.ts", source: "db.prepare('select * from users where id = ?').bind(userId);" }],
|
|
2414
|
+
focusPath: "src/users.ts",
|
|
2415
|
+
expectedCount: 0,
|
|
2416
|
+
public: true
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
id: "interpolated-sql-value",
|
|
2420
|
+
title: "A runtime value is interpolated into SQL",
|
|
2421
|
+
outcome: "match",
|
|
2422
|
+
files: [{ path: "src/users.ts", source: "db.prepare(`select * from users where id = '${userId}'`);" }],
|
|
2423
|
+
focusPath: "src/users.ts",
|
|
2424
|
+
expectedCount: 1,
|
|
2425
|
+
public: true
|
|
2426
|
+
}
|
|
2427
|
+
]
|
|
2428
|
+
};
|
|
1989
2429
|
var DEFAULT_METHODS = ["prepare", "exec", "query"];
|
|
1990
2430
|
var CONSTANT_CASE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
1991
2431
|
function isStaticFragment(expression) {
|
|
@@ -2053,10 +2493,11 @@ function statementMethodName(node, methods) {
|
|
|
2053
2493
|
}
|
|
2054
2494
|
var no_dynamic_sql_default = createRule({
|
|
2055
2495
|
name: "no-dynamic-sql",
|
|
2496
|
+
documentation: noDynamicSqlDocumentation,
|
|
2056
2497
|
meta: {
|
|
2057
2498
|
type: "problem",
|
|
2058
2499
|
docs: {
|
|
2059
|
-
description: "Disallow
|
|
2500
|
+
description: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods."
|
|
2060
2501
|
},
|
|
2061
2502
|
schema: [
|
|
2062
2503
|
{
|
|
@@ -2103,6 +2544,32 @@ var no_dynamic_sql_default = createRule({
|
|
|
2103
2544
|
|
|
2104
2545
|
// src/rules/no-enum.ts
|
|
2105
2546
|
import "@typescript-eslint/utils";
|
|
2547
|
+
var noEnumDocumentation = {
|
|
2548
|
+
summary: "Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.",
|
|
2549
|
+
rationale: "TypeScript enums emit runtime objects and numeric enums accept values outside their declared members, adding behavior where a type-only model is sufficient.",
|
|
2550
|
+
remediation: "Replace the enum with a string-literal union or an `as const` object and derive its value type from that object.",
|
|
2551
|
+
category: "maintainability",
|
|
2552
|
+
examples: [
|
|
2553
|
+
{
|
|
2554
|
+
id: "string-literal-union",
|
|
2555
|
+
title: "A string-literal union has no emitted runtime enum",
|
|
2556
|
+
outcome: "no-match",
|
|
2557
|
+
files: [{ path: "src/status.ts", source: 'type Status = "active" | "inactive";' }],
|
|
2558
|
+
focusPath: "src/status.ts",
|
|
2559
|
+
expectedCount: 0,
|
|
2560
|
+
public: true
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
id: "numeric-enum",
|
|
2564
|
+
title: "A numeric enum emits a mutable runtime object",
|
|
2565
|
+
outcome: "match",
|
|
2566
|
+
files: [{ path: "src/status.ts", source: "enum Status { Active, Inactive }" }],
|
|
2567
|
+
focusPath: "src/status.ts",
|
|
2568
|
+
expectedCount: 1,
|
|
2569
|
+
public: true
|
|
2570
|
+
}
|
|
2571
|
+
]
|
|
2572
|
+
};
|
|
2106
2573
|
function matchesAnyPattern(filename, patterns) {
|
|
2107
2574
|
for (const pattern of patterns) {
|
|
2108
2575
|
const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
|
|
@@ -2114,6 +2581,7 @@ function matchesAnyPattern(filename, patterns) {
|
|
|
2114
2581
|
}
|
|
2115
2582
|
var no_enum_default = createRule({
|
|
2116
2583
|
name: "no-enum",
|
|
2584
|
+
documentation: noEnumDocumentation,
|
|
2117
2585
|
meta: {
|
|
2118
2586
|
type: "suggestion",
|
|
2119
2587
|
docs: {
|
|
@@ -2159,6 +2627,41 @@ var no_enum_default = createRule({
|
|
|
2159
2627
|
|
|
2160
2628
|
// src/rules/no-fat-try-blocks.ts
|
|
2161
2629
|
import { AST_NODE_TYPES as AST_NODE_TYPES10 } from "@typescript-eslint/utils";
|
|
2630
|
+
var noFatTryBlocksDocumentation = {
|
|
2631
|
+
summary: "Disallow `try` blocks containing more than three top-level operations that can throw.",
|
|
2632
|
+
rationale: "A broad `try` block obscures which operation failed and encourages one catch clause to recover from unrelated errors.",
|
|
2633
|
+
remediation: "Keep only the operations that share one recovery policy inside the `try` block and move other work outside it.",
|
|
2634
|
+
category: "correctness",
|
|
2635
|
+
limitations: [
|
|
2636
|
+
"The rule uses syntax to identify throwing operations and exempts generated files, finally blocks, rethrows, and terminal error boundaries."
|
|
2637
|
+
],
|
|
2638
|
+
examples: [
|
|
2639
|
+
{
|
|
2640
|
+
id: "focused-try-block",
|
|
2641
|
+
title: "A try block contains three throwing operations",
|
|
2642
|
+
outcome: "no-match",
|
|
2643
|
+
files: [{
|
|
2644
|
+
path: "src/load.ts",
|
|
2645
|
+
source: "function f() { try { const a = one(); const b = two(); const c = three(); } catch (error) { handle(error); } finish(); }"
|
|
2646
|
+
}],
|
|
2647
|
+
focusPath: "src/load.ts",
|
|
2648
|
+
expectedCount: 0,
|
|
2649
|
+
public: true
|
|
2650
|
+
},
|
|
2651
|
+
{
|
|
2652
|
+
id: "broad-try-block",
|
|
2653
|
+
title: "A try block contains four throwing operations",
|
|
2654
|
+
outcome: "match",
|
|
2655
|
+
files: [{
|
|
2656
|
+
path: "src/load.ts",
|
|
2657
|
+
source: "function f() { try { const a = one(); const b = two(); const c = three(); const d = four(); } catch (error) { handle(error); } finish(); }"
|
|
2658
|
+
}],
|
|
2659
|
+
focusPath: "src/load.ts",
|
|
2660
|
+
expectedCount: 1,
|
|
2661
|
+
public: true
|
|
2662
|
+
}
|
|
2663
|
+
]
|
|
2664
|
+
};
|
|
2162
2665
|
var MAX_TRY_BODY_STATEMENTS = 3;
|
|
2163
2666
|
var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
2164
2667
|
AST_NODE_TYPES10.FunctionDeclaration,
|
|
@@ -2494,10 +2997,11 @@ var handlerReturnsSuccessShaped = (handler) => subtreeMatches2(
|
|
|
2494
2997
|
);
|
|
2495
2998
|
var no_fat_try_blocks_default = createRule({
|
|
2496
2999
|
name: "no-fat-try-blocks",
|
|
3000
|
+
documentation: noFatTryBlocksDocumentation,
|
|
2497
3001
|
meta: {
|
|
2498
3002
|
type: "problem",
|
|
2499
3003
|
docs: {
|
|
2500
|
-
description: "Disallow `try` blocks
|
|
3004
|
+
description: "Disallow `try` blocks containing more than three top-level operations that can throw."
|
|
2501
3005
|
},
|
|
2502
3006
|
schema: [],
|
|
2503
3007
|
messages: {
|
|
@@ -2538,6 +3042,38 @@ var no_fat_try_blocks_default = createRule({
|
|
|
2538
3042
|
|
|
2539
3043
|
// src/rules/no-hand-rolled-sleep.ts
|
|
2540
3044
|
import { AST_NODE_TYPES as AST_NODE_TYPES11 } from "@typescript-eslint/utils";
|
|
3045
|
+
var noHandRolledSleepDocumentation = {
|
|
3046
|
+
summary: "Disallow uncancellable promisified timers and timeout arms.",
|
|
3047
|
+
rationale: "A timer that outlives an aborted operation or a lost promise race retains work and can keep the process alive until it fires.",
|
|
3048
|
+
remediation: "Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.",
|
|
3049
|
+
category: "correctness",
|
|
3050
|
+
limitations: [
|
|
3051
|
+
"The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions."
|
|
3052
|
+
],
|
|
3053
|
+
examples: [
|
|
3054
|
+
{
|
|
3055
|
+
id: "cancellable-node-timer",
|
|
3056
|
+
title: "A standard-library timer accepts an abort signal",
|
|
3057
|
+
outcome: "no-match",
|
|
3058
|
+
files: [{
|
|
3059
|
+
path: "src/lib/queue.ts",
|
|
3060
|
+
source: 'import { setTimeout as sleep } from "node:timers/promises";\nawait sleep(500, undefined, { signal });'
|
|
3061
|
+
}],
|
|
3062
|
+
focusPath: "src/lib/queue.ts",
|
|
3063
|
+
expectedCount: 0,
|
|
3064
|
+
public: true
|
|
3065
|
+
},
|
|
3066
|
+
{
|
|
3067
|
+
id: "uncancellable-sleep",
|
|
3068
|
+
title: "A Promise wraps a timer without cancellation",
|
|
3069
|
+
outcome: "match",
|
|
3070
|
+
files: [{ path: "src/lib/queue.ts", source: "await new Promise((resolve) => setTimeout(resolve, 500));" }],
|
|
3071
|
+
focusPath: "src/lib/queue.ts",
|
|
3072
|
+
expectedCount: 1,
|
|
3073
|
+
public: true
|
|
3074
|
+
}
|
|
3075
|
+
]
|
|
3076
|
+
};
|
|
2541
3077
|
var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
|
|
2542
3078
|
"globalThis",
|
|
2543
3079
|
"window",
|
|
@@ -2617,10 +3153,11 @@ function isRaceArm(node) {
|
|
|
2617
3153
|
}
|
|
2618
3154
|
var no_hand_rolled_sleep_default = createRule({
|
|
2619
3155
|
name: "no-hand-rolled-sleep",
|
|
3156
|
+
documentation: noHandRolledSleepDocumentation,
|
|
2620
3157
|
meta: {
|
|
2621
3158
|
type: "problem",
|
|
2622
3159
|
docs: {
|
|
2623
|
-
description: "Disallow
|
|
3160
|
+
description: "Disallow uncancellable promisified timers and timeout arms."
|
|
2624
3161
|
},
|
|
2625
3162
|
schema: [
|
|
2626
3163
|
{
|
|
@@ -2713,6 +3250,17 @@ var no_hand_rolled_sleep_default = createRule({
|
|
|
2713
3250
|
|
|
2714
3251
|
// src/rules/no-hand-rolled-spinner.ts
|
|
2715
3252
|
import { AST_NODE_TYPES as AST_NODE_TYPES12 } from "@typescript-eslint/utils";
|
|
3253
|
+
var noHandRolledSpinnerDocumentation = {
|
|
3254
|
+
summary: "Disallow intrinsic elements styled as Tailwind border-ring spinners outside the design-system implementation.",
|
|
3255
|
+
rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
|
|
3256
|
+
remediation: "Render the design-system Spinner component instead.",
|
|
3257
|
+
category: "maintainability",
|
|
3258
|
+
limitations: ["Only static className values on div and span elements are inspected."],
|
|
3259
|
+
examples: [
|
|
3260
|
+
{ 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 },
|
|
3261
|
+
{ 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 }
|
|
3262
|
+
]
|
|
3263
|
+
};
|
|
2716
3264
|
var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
|
|
2717
3265
|
var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
|
|
2718
3266
|
var TRANSPARENT_EDGE = /^border-[trbl]-transparent$/u;
|
|
@@ -2728,6 +3276,7 @@ function staticClassName(attribute) {
|
|
|
2728
3276
|
}
|
|
2729
3277
|
var no_hand_rolled_spinner_default = createRule({
|
|
2730
3278
|
name: "no-hand-rolled-spinner",
|
|
3279
|
+
documentation: noHandRolledSpinnerDocumentation,
|
|
2731
3280
|
meta: {
|
|
2732
3281
|
type: "suggestion",
|
|
2733
3282
|
docs: {
|
|
@@ -2765,6 +3314,17 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2765
3314
|
|
|
2766
3315
|
// src/rules/no-insecure-random-id.ts
|
|
2767
3316
|
import "@typescript-eslint/utils";
|
|
3317
|
+
var noInsecureRandomIdDocumentation = {
|
|
3318
|
+
summary: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead.",
|
|
3319
|
+
rationale: "Math.random is predictable and lacks the entropy required for security-sensitive values.",
|
|
3320
|
+
remediation: "Generate the value with crypto.randomUUID or crypto.getRandomValues.",
|
|
3321
|
+
category: "security",
|
|
3322
|
+
limitations: ["Ambiguous identifiers and test files are excluded to avoid flagging sampling and fixture data."],
|
|
3323
|
+
examples: [
|
|
3324
|
+
{ 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 },
|
|
3325
|
+
{ 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 }
|
|
3326
|
+
]
|
|
3327
|
+
};
|
|
2768
3328
|
var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
|
|
2769
3329
|
var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
|
|
2770
3330
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
@@ -2907,6 +3467,7 @@ function collectStaticStringParts(node, out) {
|
|
|
2907
3467
|
}
|
|
2908
3468
|
var no_insecure_random_id_default = createRule({
|
|
2909
3469
|
name: "no-insecure-random-id",
|
|
3470
|
+
documentation: noInsecureRandomIdDocumentation,
|
|
2910
3471
|
meta: {
|
|
2911
3472
|
type: "problem",
|
|
2912
3473
|
docs: {
|
|
@@ -2948,6 +3509,17 @@ var no_insecure_random_id_default = createRule({
|
|
|
2948
3509
|
|
|
2949
3510
|
// src/rules/no-json-stringify-error.ts
|
|
2950
3511
|
import "@typescript-eslint/utils";
|
|
3512
|
+
var noJsonStringifyErrorDocumentation = {
|
|
3513
|
+
summary: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable.",
|
|
3514
|
+
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3515
|
+
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3516
|
+
category: "correctness",
|
|
3517
|
+
limitations: ["The rule uses local syntax and naming evidence rather than type information."],
|
|
3518
|
+
examples: [
|
|
3519
|
+
{ 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 },
|
|
3520
|
+
{ 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 }
|
|
3521
|
+
]
|
|
3522
|
+
};
|
|
2951
3523
|
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
2952
3524
|
var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
|
|
2953
3525
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
@@ -3135,6 +3707,7 @@ function nestedExpressionSuggestsError(expression, scope) {
|
|
|
3135
3707
|
}
|
|
3136
3708
|
var no_json_stringify_error_default = createRule({
|
|
3137
3709
|
name: "no-json-stringify-error",
|
|
3710
|
+
documentation: noJsonStringifyErrorDocumentation,
|
|
3138
3711
|
meta: {
|
|
3139
3712
|
type: "problem",
|
|
3140
3713
|
docs: {
|
|
@@ -3187,6 +3760,47 @@ function isZodModule(source) {
|
|
|
3187
3760
|
}
|
|
3188
3761
|
|
|
3189
3762
|
// src/rules/no-impossible-zod-literal-bounds.ts
|
|
3763
|
+
var noImpossibleZodLiteralBoundsDocumentation = {
|
|
3764
|
+
summary: "Disallow same-chain literal Zod bounds whose accepted set is mathematically empty.",
|
|
3765
|
+
rationale: "A schema with contradictory literal bounds rejects every input, turning validation into an unreachable contract that usually reflects a typo.",
|
|
3766
|
+
remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
|
|
3767
|
+
category: "correctness",
|
|
3768
|
+
limitations: [
|
|
3769
|
+
"Only finite numeric literals in a single number, string, or array schema chain are compared.",
|
|
3770
|
+
"Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
|
|
3771
|
+
"Test and generated files are excluded."
|
|
3772
|
+
],
|
|
3773
|
+
examples: [
|
|
3774
|
+
{
|
|
3775
|
+
id: "compatible-number-bounds",
|
|
3776
|
+
title: "Allow a number admitted by both bounds",
|
|
3777
|
+
outcome: "no-match",
|
|
3778
|
+
files: [
|
|
3779
|
+
{
|
|
3780
|
+
path: "src/schema.ts",
|
|
3781
|
+
source: 'import { z } from "zod"; const S = z.number().gte(3).lte(3);'
|
|
3782
|
+
}
|
|
3783
|
+
],
|
|
3784
|
+
focusPath: "src/schema.ts",
|
|
3785
|
+
expectedCount: 0,
|
|
3786
|
+
public: true
|
|
3787
|
+
},
|
|
3788
|
+
{
|
|
3789
|
+
id: "contradictory-number-bounds",
|
|
3790
|
+
title: "Reject an empty numeric interval",
|
|
3791
|
+
outcome: "match",
|
|
3792
|
+
files: [
|
|
3793
|
+
{
|
|
3794
|
+
path: "src/schema.ts",
|
|
3795
|
+
source: 'import { z } from "zod"; const S = z.number().min(5).max(4);'
|
|
3796
|
+
}
|
|
3797
|
+
],
|
|
3798
|
+
focusPath: "src/schema.ts",
|
|
3799
|
+
expectedCount: 1,
|
|
3800
|
+
public: true
|
|
3801
|
+
}
|
|
3802
|
+
]
|
|
3803
|
+
};
|
|
3190
3804
|
var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
|
|
3191
3805
|
var NUMBER_METHODS = /* @__PURE__ */ new Set([
|
|
3192
3806
|
"gt",
|
|
@@ -3246,6 +3860,7 @@ function isOutermostCall(node) {
|
|
|
3246
3860
|
}
|
|
3247
3861
|
var no_impossible_zod_literal_bounds_default = createRule({
|
|
3248
3862
|
name: "no-impossible-zod-literal-bounds",
|
|
3863
|
+
documentation: noImpossibleZodLiteralBoundsDocumentation,
|
|
3249
3864
|
meta: {
|
|
3250
3865
|
type: "problem",
|
|
3251
3866
|
docs: {
|
|
@@ -3503,6 +4118,17 @@ function createLogMatcher(options = {}) {
|
|
|
3503
4118
|
}
|
|
3504
4119
|
|
|
3505
4120
|
// src/rules/no-log-only-catch.ts
|
|
4121
|
+
var noLogOnlyCatchDocumentation = {
|
|
4122
|
+
summary: "Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead.",
|
|
4123
|
+
rationale: "Swallowing an exception after logging lets execution continue as if the operation succeeded.",
|
|
4124
|
+
remediation: "Rethrow the error, return an explicit fallback, or perform concrete recovery.",
|
|
4125
|
+
category: "correctness",
|
|
4126
|
+
limitations: ["Documented intentional ignores, tests, and catches with observable recovery are excluded."],
|
|
4127
|
+
examples: [
|
|
4128
|
+
{ 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 },
|
|
4129
|
+
{ 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 }
|
|
4130
|
+
]
|
|
4131
|
+
};
|
|
3506
4132
|
var BENCHMARK_DIR_RE = /(?:^|[\\/])benchmarks?[\\/]/;
|
|
3507
4133
|
var SINGLE_STATEMENT_HOSTS = /* @__PURE__ */ new Set([
|
|
3508
4134
|
AST_NODE_TYPES14.DoWhileStatement,
|
|
@@ -3590,6 +4216,7 @@ function isSeedValue(node) {
|
|
|
3590
4216
|
}
|
|
3591
4217
|
var no_log_only_catch_default = createRule({
|
|
3592
4218
|
name: "no-log-only-catch",
|
|
4219
|
+
documentation: noLogOnlyCatchDocumentation,
|
|
3593
4220
|
meta: {
|
|
3594
4221
|
type: "problem",
|
|
3595
4222
|
docs: {
|
|
@@ -3771,6 +4398,17 @@ function typedFunction(node) {
|
|
|
3771
4398
|
}
|
|
3772
4399
|
|
|
3773
4400
|
// src/rules/no-long-comment.ts
|
|
4401
|
+
var noLongCommentDocumentation = {
|
|
4402
|
+
summary: "Flag unusually large unstructured JSDoc blocks in implementation code.",
|
|
4403
|
+
rationale: "Large narrative comments become stale and obscure the local facts that belong beside the code.",
|
|
4404
|
+
remediation: "Keep only durable local constraints and express the remaining behavior in code.",
|
|
4405
|
+
category: "maintainability",
|
|
4406
|
+
limitations: ["Only JSDoc blocks are inspected; structured API docs, tests, scripts, generated files, and versioned dependencies are excluded."],
|
|
4407
|
+
examples: [
|
|
4408
|
+
{ 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 },
|
|
4409
|
+
{ 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 }
|
|
4410
|
+
]
|
|
4411
|
+
};
|
|
3774
4412
|
var EXCESSIVE_SENTENCE_COUNT = 8;
|
|
3775
4413
|
var EXCESSIVE_WORD_COUNT = 120;
|
|
3776
4414
|
var PROSE_WORD_RE = /[\p{L}\p{N}][\p{L}\p{N}'’-]*/gu;
|
|
@@ -3790,9 +4428,10 @@ function wordUnits(text) {
|
|
|
3790
4428
|
}
|
|
3791
4429
|
var no_long_comment_default = createRule({
|
|
3792
4430
|
name: "no-long-comment",
|
|
4431
|
+
documentation: noLongCommentDocumentation,
|
|
3793
4432
|
meta: {
|
|
3794
4433
|
type: "suggestion",
|
|
3795
|
-
docs: { description: "Flag unusually large unstructured
|
|
4434
|
+
docs: { description: "Flag unusually large unstructured JSDoc blocks in implementation code." },
|
|
3796
4435
|
schema: [],
|
|
3797
4436
|
messages: {
|
|
3798
4437
|
tooLong: "Comment is an unusually large prose block \u2014 keep the local facts and clarify the code itself."
|
|
@@ -3815,6 +4454,17 @@ var no_long_comment_default = createRule({
|
|
|
3815
4454
|
|
|
3816
4455
|
// src/rules/no-generic-single-export-module.ts
|
|
3817
4456
|
import { AST_NODE_TYPES as AST_NODE_TYPES17, ASTUtils as ASTUtils3 } from "@typescript-eslint/utils";
|
|
4457
|
+
var noGenericSingleExportModuleDocumentation = {
|
|
4458
|
+
summary: "Disallow generic module stems when one runtime export already names the responsibility.",
|
|
4459
|
+
rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
|
|
4460
|
+
remediation: "Rename the module after its single runtime export.",
|
|
4461
|
+
category: "maintainability",
|
|
4462
|
+
limitations: ["Only configured generic stems with exactly one public runtime export are reported."],
|
|
4463
|
+
examples: [
|
|
4464
|
+
{ 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 },
|
|
4465
|
+
{ 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 }
|
|
4466
|
+
]
|
|
4467
|
+
};
|
|
3818
4468
|
var GENERIC_STEMS = /* @__PURE__ */ new Set([
|
|
3819
4469
|
"base",
|
|
3820
4470
|
"common",
|
|
@@ -3977,6 +4627,7 @@ function memberPropertyName(node) {
|
|
|
3977
4627
|
}
|
|
3978
4628
|
var no_generic_single_export_module_default = createRule({
|
|
3979
4629
|
name: "no-generic-single-export-module",
|
|
4630
|
+
documentation: noGenericSingleExportModuleDocumentation,
|
|
3980
4631
|
meta: {
|
|
3981
4632
|
type: "suggestion",
|
|
3982
4633
|
docs: { description: "Disallow generic module stems when one runtime export already names the responsibility." },
|
|
@@ -4029,10 +4680,22 @@ var no_generic_single_export_module_default = createRule({
|
|
|
4029
4680
|
|
|
4030
4681
|
// src/rules/no-offset-pagination.ts
|
|
4031
4682
|
import "@typescript-eslint/utils";
|
|
4683
|
+
var noOffsetPaginationDocumentation = {
|
|
4684
|
+
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.",
|
|
4685
|
+
rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
|
|
4686
|
+
remediation: "Page with a stable ordered key and a cursor predicate.",
|
|
4687
|
+
category: "performance",
|
|
4688
|
+
limitations: ["Only embedded SQL is inspected; test files and non-pagination OFFSET syntax are excluded."],
|
|
4689
|
+
examples: [
|
|
4690
|
+
{ 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 },
|
|
4691
|
+
{ 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 }
|
|
4692
|
+
]
|
|
4693
|
+
};
|
|
4032
4694
|
var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
|
|
4033
4695
|
var OFFSET_GATE = /offset/i;
|
|
4034
4696
|
var no_offset_pagination_default = createRule({
|
|
4035
4697
|
name: "no-offset-pagination",
|
|
4698
|
+
documentation: noOffsetPaginationDocumentation,
|
|
4036
4699
|
meta: {
|
|
4037
4700
|
type: "problem",
|
|
4038
4701
|
docs: {
|
|
@@ -4059,6 +4722,17 @@ var no_offset_pagination_default = createRule({
|
|
|
4059
4722
|
|
|
4060
4723
|
// src/rules/no-positional-tuple-return.ts
|
|
4061
4724
|
import { AST_NODE_TYPES as AST_NODE_TYPES18 } from "@typescript-eslint/utils";
|
|
4725
|
+
var noPositionalTupleReturnDocumentation = {
|
|
4726
|
+
summary: "Disallow returning a multi-field tuple from an exported function; return a named object so call sites cannot mismatch slots.",
|
|
4727
|
+
rationale: "Public tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
|
|
4728
|
+
remediation: "Return an object whose property names describe each value.",
|
|
4729
|
+
category: "maintainability",
|
|
4730
|
+
limitations: ["Only declared multi-field tuple returns on public TypeScript surfaces are inspected."],
|
|
4731
|
+
examples: [
|
|
4732
|
+
{ 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 },
|
|
4733
|
+
{ 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 }
|
|
4734
|
+
]
|
|
4735
|
+
};
|
|
4062
4736
|
var MIN_ELEMENTS = 2;
|
|
4063
4737
|
var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited", "Readonly"]);
|
|
4064
4738
|
function staticMemberName2(key) {
|
|
@@ -4283,6 +4957,7 @@ function isExported(node, specifierExports) {
|
|
|
4283
4957
|
}
|
|
4284
4958
|
var no_positional_tuple_return_default = createRule({
|
|
4285
4959
|
name: "no-positional-tuple-return",
|
|
4960
|
+
documentation: noPositionalTupleReturnDocumentation,
|
|
4286
4961
|
meta: {
|
|
4287
4962
|
type: "suggestion",
|
|
4288
4963
|
docs: {
|
|
@@ -4379,6 +5054,17 @@ var no_positional_tuple_return_default = createRule({
|
|
|
4379
5054
|
|
|
4380
5055
|
// src/rules/no-raw-env.ts
|
|
4381
5056
|
import "@typescript-eslint/utils";
|
|
5057
|
+
var noRawEnvDocumentation = {
|
|
5058
|
+
summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
|
|
5059
|
+
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
|
|
5060
|
+
remediation: "Validate environment values at startup and import the typed configuration object.",
|
|
5061
|
+
category: "correctness",
|
|
5062
|
+
limitations: ["Host markers, assignment targets, tests, scripts, build config, and validated boundaries are excluded."],
|
|
5063
|
+
examples: [
|
|
5064
|
+
{ 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 },
|
|
5065
|
+
{ 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 }
|
|
5066
|
+
]
|
|
5067
|
+
};
|
|
4382
5068
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
4383
5069
|
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
4384
5070
|
var ENV_VALIDATION_MARKER_RE = /\bcreateEnv\s*\(|\bz\.object\s*\(|\.(?:safeParse|parse)\s*\(/;
|
|
@@ -4425,6 +5111,7 @@ function isWholeEnvSpread(node) {
|
|
|
4425
5111
|
}
|
|
4426
5112
|
var no_raw_env_default = createRule({
|
|
4427
5113
|
name: "no-raw-env",
|
|
5114
|
+
documentation: noRawEnvDocumentation,
|
|
4428
5115
|
meta: {
|
|
4429
5116
|
type: "problem",
|
|
4430
5117
|
docs: {
|
|
@@ -4456,6 +5143,17 @@ var no_raw_env_default = createRule({
|
|
|
4456
5143
|
|
|
4457
5144
|
// src/rules/no-raw-fetch-outside-clients.ts
|
|
4458
5145
|
import { AST_NODE_TYPES as AST_NODE_TYPES19 } from "@typescript-eslint/utils";
|
|
5146
|
+
var noRawFetchOutsideClientsDocumentation = {
|
|
5147
|
+
summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
|
|
5148
|
+
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
5149
|
+
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
5150
|
+
category: "architecture",
|
|
5151
|
+
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
|
|
5152
|
+
examples: [
|
|
5153
|
+
{ 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 },
|
|
5154
|
+
{ 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 }
|
|
5155
|
+
]
|
|
5156
|
+
};
|
|
4459
5157
|
var DEFAULT_ALLOW = [
|
|
4460
5158
|
"[\\\\/]clients?[\\\\/]",
|
|
4461
5159
|
"-client\\.[cm]?[jt]sx?$",
|
|
@@ -4519,6 +5217,7 @@ function compile(patterns) {
|
|
|
4519
5217
|
}
|
|
4520
5218
|
var no_raw_fetch_outside_clients_default = createRule({
|
|
4521
5219
|
name: "no-raw-fetch-outside-clients",
|
|
5220
|
+
documentation: noRawFetchOutsideClientsDocumentation,
|
|
4522
5221
|
meta: {
|
|
4523
5222
|
type: "problem",
|
|
4524
5223
|
docs: {
|
|
@@ -4567,6 +5266,17 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4567
5266
|
|
|
4568
5267
|
// src/rules/no-restricted-library-load.ts
|
|
4569
5268
|
import { AST_NODE_TYPES as AST_NODE_TYPES20, ASTUtils as ASTUtils4 } from "@typescript-eslint/utils";
|
|
5269
|
+
var noRestrictedLibraryLoadDocumentation = {
|
|
5270
|
+
summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
|
|
5271
|
+
rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
|
|
5272
|
+
remediation: "Load the configured replacement library instead of the restricted module.",
|
|
5273
|
+
category: "architecture",
|
|
5274
|
+
limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
|
|
5275
|
+
examples: [
|
|
5276
|
+
{ 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 },
|
|
5277
|
+
{ 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 }
|
|
5278
|
+
]
|
|
5279
|
+
};
|
|
4570
5280
|
function literalModule(node) {
|
|
4571
5281
|
return node?.type === AST_NODE_TYPES20.Literal && typeof node.value === "string" ? node.value : null;
|
|
4572
5282
|
}
|
|
@@ -4575,6 +5285,7 @@ function matchesModule(source, module) {
|
|
|
4575
5285
|
}
|
|
4576
5286
|
var no_restricted_library_load_default = createRule({
|
|
4577
5287
|
name: "no-restricted-library-load",
|
|
5288
|
+
documentation: noRestrictedLibraryLoadDocumentation,
|
|
4578
5289
|
meta: {
|
|
4579
5290
|
type: "problem",
|
|
4580
5291
|
docs: {
|
|
@@ -4665,13 +5376,25 @@ var MIN_DISTINCT_SCOPES = 2;
|
|
|
4665
5376
|
var PREVIEW_LENGTH = 40;
|
|
4666
5377
|
var SQL_KEYWORD_RE = /\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|VALUES|ON CONFLICT|RETURNING|GROUP BY|ORDER BY)\b/;
|
|
4667
5378
|
var IDENTIFIER_RE = /^[a-z_][a-z0-9_.]*$/;
|
|
5379
|
+
var URL_PATH_RE = /^\/(?=[^\s]*[A-Za-z0-9])[A-Za-z0-9._~!$&'()*+,;=:@%/?#{}\u005B\u005D-]+$/;
|
|
4668
5380
|
var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
4669
5381
|
AST_NODE_TYPES21.FunctionDeclaration,
|
|
4670
5382
|
AST_NODE_TYPES21.FunctionExpression,
|
|
4671
5383
|
AST_NODE_TYPES21.ArrowFunctionExpression
|
|
4672
5384
|
]);
|
|
5385
|
+
var noRepeatedStringLiteralDocumentation = {
|
|
5386
|
+
summary: "Disallow a long structured string literal repeated across functions; the copies drift when one is edited. Extract a module-level constant.",
|
|
5387
|
+
rationale: "Independent copies of a query, route template, or identifier can diverge and silently change behavior.",
|
|
5388
|
+
remediation: "Extract the repeated value to one module-level constant and reference it from each function.",
|
|
5389
|
+
category: "maintainability",
|
|
5390
|
+
limitations: ["Test files, short strings, prose, substitutions, module sources, JSX attributes, and repetition within one function are excluded."],
|
|
5391
|
+
examples: [
|
|
5392
|
+
{ 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 },
|
|
5393
|
+
{ 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 }
|
|
5394
|
+
]
|
|
5395
|
+
};
|
|
4673
5396
|
function isStructured(value) {
|
|
4674
|
-
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value);
|
|
5397
|
+
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
|
|
4675
5398
|
}
|
|
4676
5399
|
function preview(value) {
|
|
4677
5400
|
const oneLine = value.replaceAll("\n", " ").trim();
|
|
@@ -4690,11 +5413,13 @@ function isScaffolding(node) {
|
|
|
4690
5413
|
if (parent === void 0) {
|
|
4691
5414
|
return true;
|
|
4692
5415
|
}
|
|
5416
|
+
const isNonComputedPropertyKey = (parent.type === AST_NODE_TYPES21.Property || parent.type === AST_NODE_TYPES21.PropertyDefinition || parent.type === AST_NODE_TYPES21.MethodDefinition || parent.type === AST_NODE_TYPES21.AccessorProperty) && parent.key === node && !parent.computed;
|
|
4693
5417
|
const isRequireSource = parent.type === AST_NODE_TYPES21.CallExpression && parent.callee.type === AST_NODE_TYPES21.Identifier && parent.callee.name === "require";
|
|
4694
|
-
return parent.type === AST_NODE_TYPES21.ImportDeclaration || parent.type === AST_NODE_TYPES21.ImportExpression || parent.type === AST_NODE_TYPES21.ExportNamedDeclaration || parent.type === AST_NODE_TYPES21.ExportAllDeclaration || parent.type === AST_NODE_TYPES21.TSImportType || parent.type === AST_NODE_TYPES21.JSXAttribute || parent.type === AST_NODE_TYPES21.TSLiteralType || isRequireSource;
|
|
5418
|
+
return parent.type === AST_NODE_TYPES21.ImportDeclaration || parent.type === AST_NODE_TYPES21.ImportExpression || parent.type === AST_NODE_TYPES21.ExportNamedDeclaration || parent.type === AST_NODE_TYPES21.ExportAllDeclaration || parent.type === AST_NODE_TYPES21.TSImportType || parent.type === AST_NODE_TYPES21.JSXAttribute || parent.type === AST_NODE_TYPES21.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
|
|
4695
5419
|
}
|
|
4696
5420
|
var no_repeated_string_literal_default = createRule({
|
|
4697
5421
|
name: "no-repeated-string-literal",
|
|
5422
|
+
documentation: noRepeatedStringLiteralDocumentation,
|
|
4698
5423
|
meta: {
|
|
4699
5424
|
type: "suggestion",
|
|
4700
5425
|
docs: {
|
|
@@ -4707,7 +5432,7 @@ var no_repeated_string_literal_default = createRule({
|
|
|
4707
5432
|
},
|
|
4708
5433
|
defaultOptions: [],
|
|
4709
5434
|
create(context) {
|
|
4710
|
-
if (isTestFile(context.filename)) {
|
|
5435
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
4711
5436
|
return {};
|
|
4712
5437
|
}
|
|
4713
5438
|
const occurrences = /* @__PURE__ */ new Map();
|
|
@@ -4780,6 +5505,17 @@ var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
|
|
|
4780
5505
|
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;
|
|
4781
5506
|
var WALL_CLUSTER_MAX_LINE_GAP = 8;
|
|
4782
5507
|
var WALL_CLUSTER_MIN_COMMENTS = 3;
|
|
5508
|
+
var noRestatedCommentDocumentation = {
|
|
5509
|
+
summary: "Flag a single-line comment whose every word already appears on the statement below it.",
|
|
5510
|
+
rationale: "A comment that only repeats code adds no context and can become stale independently.",
|
|
5511
|
+
remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
|
|
5512
|
+
category: "maintainability",
|
|
5513
|
+
limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
|
|
5514
|
+
examples: [
|
|
5515
|
+
{ 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 },
|
|
5516
|
+
{ 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 }
|
|
5517
|
+
]
|
|
5518
|
+
};
|
|
4783
5519
|
function areAdjacentLineComments2(a, b) {
|
|
4784
5520
|
return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
|
|
4785
5521
|
}
|
|
@@ -4794,6 +5530,7 @@ function headsSiblingRun(node) {
|
|
|
4794
5530
|
}
|
|
4795
5531
|
var no_restated_comment_default = createRule({
|
|
4796
5532
|
name: "no-restated-comment",
|
|
5533
|
+
documentation: noRestatedCommentDocumentation,
|
|
4797
5534
|
meta: {
|
|
4798
5535
|
type: "suggestion",
|
|
4799
5536
|
docs: {
|
|
@@ -4879,6 +5616,19 @@ var no_restated_comment_default = createRule({
|
|
|
4879
5616
|
|
|
4880
5617
|
// src/rules/no-restated-jsdoc.ts
|
|
4881
5618
|
import { AST_NODE_TYPES as AST_NODE_TYPES23 } from "@typescript-eslint/utils";
|
|
5619
|
+
var noRestatedJsdocDocumentation = {
|
|
5620
|
+
summary: "Flag a JSDoc block whose description and tags only re-spell the signature they document.",
|
|
5621
|
+
rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
|
|
5622
|
+
remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
|
|
5623
|
+
category: "maintainability",
|
|
5624
|
+
aliases: ["jsdoc-restates-signature"],
|
|
5625
|
+
autofix: "suggestion",
|
|
5626
|
+
limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
|
|
5627
|
+
examples: [
|
|
5628
|
+
{ 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 },
|
|
5629
|
+
{ 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 }
|
|
5630
|
+
]
|
|
5631
|
+
};
|
|
4882
5632
|
var MODELLED_TAGS = /* @__PURE__ */ new Set([
|
|
4883
5633
|
"arg",
|
|
4884
5634
|
"argument",
|
|
@@ -4978,6 +5728,7 @@ function tokensOf(names) {
|
|
|
4978
5728
|
}
|
|
4979
5729
|
var no_restated_jsdoc_default = createRule({
|
|
4980
5730
|
name: "no-restated-jsdoc",
|
|
5731
|
+
documentation: noRestatedJsdocDocumentation,
|
|
4981
5732
|
meta: {
|
|
4982
5733
|
type: "suggestion",
|
|
4983
5734
|
hasSuggestions: true,
|
|
@@ -5001,10 +5752,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5001
5752
|
for (const comment of sourceCode.getAllComments()) {
|
|
5002
5753
|
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue;
|
|
5003
5754
|
const { description, tags } = parseJsDoc(comment.value);
|
|
5004
|
-
|
|
5755
|
+
const describedText = [
|
|
5756
|
+
description,
|
|
5757
|
+
...tags.filter((tag) => tag.name === "description").map((tag) => tag.text)
|
|
5758
|
+
].filter((text) => text.length > 0).join("\n");
|
|
5759
|
+
if (DIRECTIVE_RE4.test(describedText)) continue;
|
|
5005
5760
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
5006
5761
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
5007
|
-
if (isProtected(
|
|
5762
|
+
if (isProtected(describedText)) continue;
|
|
5008
5763
|
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
5009
5764
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
5010
5765
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
@@ -5017,13 +5772,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5017
5772
|
if (declaration === null) continue;
|
|
5018
5773
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
5019
5774
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
5020
|
-
if (
|
|
5775
|
+
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
5021
5776
|
continue;
|
|
5022
5777
|
}
|
|
5778
|
+
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5023
5779
|
const nameTokens = tokensOf([declaration.name]);
|
|
5024
5780
|
const paramTokens = tokensOf(declaration.params);
|
|
5025
5781
|
const known = /* @__PURE__ */ new Set([...nameTokens, ...paramTokens]);
|
|
5026
|
-
let addsNothing = covered(
|
|
5782
|
+
let addsNothing = covered(describedText, known);
|
|
5027
5783
|
for (const tag of paramTags) {
|
|
5028
5784
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
5029
5785
|
const match = /^\[?([A-Za-z_$][\w.$]*)\]?\s*-?\s*([\s\S]*)$/.exec(text);
|
|
@@ -5031,7 +5787,13 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5031
5787
|
addsNothing = false;
|
|
5032
5788
|
break;
|
|
5033
5789
|
}
|
|
5034
|
-
const
|
|
5790
|
+
const path = match[1] ?? "";
|
|
5791
|
+
const root = path.split(".")[0] ?? "";
|
|
5792
|
+
if (!declaration.params.includes(root)) {
|
|
5793
|
+
addsNothing = false;
|
|
5794
|
+
break;
|
|
5795
|
+
}
|
|
5796
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens]);
|
|
5035
5797
|
if (!covered(match[2] ?? "", own)) {
|
|
5036
5798
|
addsNothing = false;
|
|
5037
5799
|
break;
|
|
@@ -5209,6 +5971,17 @@ function isAuthSecretName(identifier) {
|
|
|
5209
5971
|
}
|
|
5210
5972
|
|
|
5211
5973
|
// src/rules/no-secret-in-log.ts
|
|
5974
|
+
var noSecretInLogDocumentation = {
|
|
5975
|
+
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.",
|
|
5976
|
+
rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
|
|
5977
|
+
remediation: "Omit the value or log an explicitly redacted, truncated, or derived non-sensitive field.",
|
|
5978
|
+
category: "security",
|
|
5979
|
+
limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
|
|
5980
|
+
examples: [
|
|
5981
|
+
{ 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 },
|
|
5982
|
+
{ 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 }
|
|
5983
|
+
]
|
|
5984
|
+
};
|
|
5212
5985
|
var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
|
|
5213
5986
|
...INNOCUOUS_WORDS,
|
|
5214
5987
|
"name",
|
|
@@ -5328,6 +6101,7 @@ function propertyKeyName2(prop) {
|
|
|
5328
6101
|
}
|
|
5329
6102
|
var no_secret_in_log_default = createRule({
|
|
5330
6103
|
name: "no-secret-in-log",
|
|
6104
|
+
documentation: noSecretInLogDocumentation,
|
|
5331
6105
|
meta: {
|
|
5332
6106
|
type: "problem",
|
|
5333
6107
|
docs: {
|
|
@@ -5402,6 +6176,17 @@ var no_secret_in_log_default = createRule({
|
|
|
5402
6176
|
|
|
5403
6177
|
// src/rules/no-select-star.ts
|
|
5404
6178
|
import "@typescript-eslint/utils";
|
|
6179
|
+
var noSelectStarDocumentation = {
|
|
6180
|
+
summary: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently.",
|
|
6181
|
+
rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
|
|
6182
|
+
remediation: "List every required column explicitly in the projection.",
|
|
6183
|
+
category: "correctness",
|
|
6184
|
+
limitations: ["Only statically visible embedded SQL is checked; function arguments such as COUNT(*) and stars inside EXISTS are excluded."],
|
|
6185
|
+
examples: [
|
|
6186
|
+
{ 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 },
|
|
6187
|
+
{ 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 }
|
|
6188
|
+
]
|
|
6189
|
+
};
|
|
5405
6190
|
var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
|
|
5406
6191
|
var SELECT_KEYWORD = /\bSELECT\b/gi;
|
|
5407
6192
|
var FROM_KEYWORD = /^FROM\b/i;
|
|
@@ -5443,6 +6228,7 @@ function isProjectionStar(sql, pos) {
|
|
|
5443
6228
|
}
|
|
5444
6229
|
var no_select_star_default = createRule({
|
|
5445
6230
|
name: "no-select-star",
|
|
6231
|
+
documentation: noSelectStarDocumentation,
|
|
5446
6232
|
meta: {
|
|
5447
6233
|
type: "problem",
|
|
5448
6234
|
docs: {
|
|
@@ -5469,6 +6255,17 @@ var no_select_star_default = createRule({
|
|
|
5469
6255
|
|
|
5470
6256
|
// src/rules/no-sentinel-return-on-catch.ts
|
|
5471
6257
|
import { AST_NODE_TYPES as AST_NODE_TYPES24 } from "@typescript-eslint/utils";
|
|
6258
|
+
var noSentinelReturnOnCatchDocumentation = {
|
|
6259
|
+
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.",
|
|
6260
|
+
rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
|
|
6261
|
+
remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
|
|
6262
|
+
category: "correctness",
|
|
6263
|
+
limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
|
|
6264
|
+
examples: [
|
|
6265
|
+
{ 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 },
|
|
6266
|
+
{ 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 }
|
|
6267
|
+
]
|
|
6268
|
+
};
|
|
5472
6269
|
function unwrapSentinelExpression(arg) {
|
|
5473
6270
|
let current = arg;
|
|
5474
6271
|
while (current?.type === AST_NODE_TYPES24.TSAsExpression || current?.type === AST_NODE_TYPES24.TSTypeAssertion || current?.type === AST_NODE_TYPES24.TSSatisfiesExpression) {
|
|
@@ -5792,10 +6589,11 @@ function isWithin(node, ancestor) {
|
|
|
5792
6589
|
}
|
|
5793
6590
|
var no_sentinel_return_on_catch_default = createRule({
|
|
5794
6591
|
name: "no-sentinel-return-on-catch",
|
|
6592
|
+
documentation: noSentinelReturnOnCatchDocumentation,
|
|
5795
6593
|
meta: {
|
|
5796
6594
|
type: "problem",
|
|
5797
6595
|
docs: {
|
|
5798
|
-
description: "Disallow swallowing a caught error by returning an empty sentinel
|
|
6596
|
+
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."
|
|
5799
6597
|
},
|
|
5800
6598
|
schema: [
|
|
5801
6599
|
{
|
|
@@ -5820,10 +6618,10 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
5820
6618
|
return false;
|
|
5821
6619
|
}
|
|
5822
6620
|
if (matcher.isLoggingCall(current)) {
|
|
5823
|
-
return
|
|
6621
|
+
return caughtName === null || argsIncludeBinding(current.arguments, caughtName);
|
|
5824
6622
|
}
|
|
5825
6623
|
const name = calleeName2(current.callee);
|
|
5826
|
-
return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
|
|
6624
|
+
return name !== null && REPORT_NAME_RE.test(name) && (caughtName === null || argsIncludeBinding(current.arguments, caughtName));
|
|
5827
6625
|
});
|
|
5828
6626
|
}
|
|
5829
6627
|
return {
|
|
@@ -5873,6 +6671,17 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
5873
6671
|
|
|
5874
6672
|
// src/rules/no-silent-promise-catch.ts
|
|
5875
6673
|
import { AST_NODE_TYPES as AST_NODE_TYPES25 } from "@typescript-eslint/utils";
|
|
6674
|
+
var noSilentPromiseCatchDocumentation = {
|
|
6675
|
+
summary: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error.",
|
|
6676
|
+
rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
|
|
6677
|
+
remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
|
|
6678
|
+
category: "correctness",
|
|
6679
|
+
limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded."],
|
|
6680
|
+
examples: [
|
|
6681
|
+
{ 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 },
|
|
6682
|
+
{ 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 }
|
|
6683
|
+
]
|
|
6684
|
+
};
|
|
5876
6685
|
function isBodyParseCall(node) {
|
|
5877
6686
|
return node.type === AST_NODE_TYPES25.CallExpression && node.arguments.length === 0 && node.callee.type === AST_NODE_TYPES25.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES25.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
|
|
5878
6687
|
}
|
|
@@ -5927,6 +6736,7 @@ function isSilentExpression(node) {
|
|
|
5927
6736
|
}
|
|
5928
6737
|
var no_silent_promise_catch_default = createRule({
|
|
5929
6738
|
name: "no-silent-promise-catch",
|
|
6739
|
+
documentation: noSilentPromiseCatchDocumentation,
|
|
5930
6740
|
meta: {
|
|
5931
6741
|
type: "problem",
|
|
5932
6742
|
docs: {
|
|
@@ -5996,6 +6806,18 @@ var no_silent_promise_catch_default = createRule({
|
|
|
5996
6806
|
|
|
5997
6807
|
// src/rules/no-sleep-in-test-body.ts
|
|
5998
6808
|
import { AST_NODE_TYPES as AST_NODE_TYPES26 } from "@typescript-eslint/utils";
|
|
6809
|
+
var noSleepInTestBodyDocumentation = {
|
|
6810
|
+
summary: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers.",
|
|
6811
|
+
rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
|
|
6812
|
+
remediation: "Await the observable signal or advance deterministic fake timers.",
|
|
6813
|
+
category: "testing",
|
|
6814
|
+
filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
|
|
6815
|
+
limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes and parameterized delays are excluded."],
|
|
6816
|
+
examples: [
|
|
6817
|
+
{ 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 },
|
|
6818
|
+
{ 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 }
|
|
6819
|
+
]
|
|
6820
|
+
};
|
|
5999
6821
|
var SLEEP_HELPERS = /* @__PURE__ */ new Set(["sleep", "delay", "wait", "pause"]);
|
|
6000
6822
|
var TEST_CALLERS3 = /* @__PURE__ */ new Set([
|
|
6001
6823
|
"it",
|
|
@@ -6071,6 +6893,7 @@ function testCallerName2(callee) {
|
|
|
6071
6893
|
}
|
|
6072
6894
|
var no_sleep_in_test_body_default = createRule({
|
|
6073
6895
|
name: "no-sleep-in-test-body",
|
|
6896
|
+
documentation: noSleepInTestBodyDocumentation,
|
|
6074
6897
|
meta: {
|
|
6075
6898
|
type: "problem",
|
|
6076
6899
|
docs: {
|
|
@@ -6116,6 +6939,17 @@ var DEFAULT_METHODS2 = [
|
|
|
6116
6939
|
"getWithMetadata"
|
|
6117
6940
|
];
|
|
6118
6941
|
var MIN_ARGUMENTS = /* @__PURE__ */ new Map([["put", 2]]);
|
|
6942
|
+
var noStorageInStatelessModulesDocumentation = {
|
|
6943
|
+
summary: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead.",
|
|
6944
|
+
rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
|
|
6945
|
+
remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
|
|
6946
|
+
category: "architecture",
|
|
6947
|
+
limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
|
|
6948
|
+
examples: [
|
|
6949
|
+
{ 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 },
|
|
6950
|
+
{ 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 }
|
|
6951
|
+
]
|
|
6952
|
+
};
|
|
6119
6953
|
function compile2(patterns) {
|
|
6120
6954
|
const compiled = [];
|
|
6121
6955
|
for (const pattern of patterns) {
|
|
@@ -6142,10 +6976,11 @@ function storageMethodName(node, methods) {
|
|
|
6142
6976
|
}
|
|
6143
6977
|
var no_storage_in_stateless_modules_default = createRule({
|
|
6144
6978
|
name: "no-storage-in-stateless-modules",
|
|
6979
|
+
documentation: noStorageInStatelessModulesDocumentation,
|
|
6145
6980
|
meta: {
|
|
6146
6981
|
type: "problem",
|
|
6147
6982
|
docs: {
|
|
6148
|
-
description: "Disallow SQL or key/value access inside
|
|
6983
|
+
description: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead."
|
|
6149
6984
|
},
|
|
6150
6985
|
schema: [
|
|
6151
6986
|
{
|
|
@@ -6197,6 +7032,35 @@ var no_storage_in_stateless_modules_default = createRule({
|
|
|
6197
7032
|
|
|
6198
7033
|
// src/rules/no-string-concat-in-loop.ts
|
|
6199
7034
|
import "@typescript-eslint/utils";
|
|
7035
|
+
var noStringConcatInLoopDocumentation = {
|
|
7036
|
+
summary: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead.",
|
|
7037
|
+
rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
|
|
7038
|
+
remediation: "Collect each fragment in an array, then join the fragments after the loop.",
|
|
7039
|
+
category: "performance",
|
|
7040
|
+
limitations: [
|
|
7041
|
+
"Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
|
|
7042
|
+
],
|
|
7043
|
+
examples: [
|
|
7044
|
+
{
|
|
7045
|
+
id: "join-fragments",
|
|
7046
|
+
title: "Join collected fragments after the loop",
|
|
7047
|
+
outcome: "no-match",
|
|
7048
|
+
files: [{ path: "src/render.ts", source: 'const parts = []; for (const item of items) { parts.push(item); } const output = parts.join("");' }],
|
|
7049
|
+
focusPath: "src/render.ts",
|
|
7050
|
+
expectedCount: 0,
|
|
7051
|
+
public: true
|
|
7052
|
+
},
|
|
7053
|
+
{
|
|
7054
|
+
id: "rebuild-string",
|
|
7055
|
+
title: "Do not rebuild a growing string in a loop",
|
|
7056
|
+
outcome: "match",
|
|
7057
|
+
files: [{ path: "src/render.ts", source: "let output = ''; for (const item of items) { output = `${output}${item}`; }" }],
|
|
7058
|
+
focusPath: "src/render.ts",
|
|
7059
|
+
expectedCount: 1,
|
|
7060
|
+
public: true
|
|
7061
|
+
}
|
|
7062
|
+
]
|
|
7063
|
+
};
|
|
6200
7064
|
var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
6201
7065
|
"ForStatement",
|
|
6202
7066
|
"ForOfStatement",
|
|
@@ -6288,6 +7152,7 @@ function enclosingLoop(node) {
|
|
|
6288
7152
|
}
|
|
6289
7153
|
var no_string_concat_in_loop_default = createRule({
|
|
6290
7154
|
name: "no-string-concat-in-loop",
|
|
7155
|
+
documentation: noStringConcatInLoopDocumentation,
|
|
6291
7156
|
meta: {
|
|
6292
7157
|
type: "suggestion",
|
|
6293
7158
|
docs: {
|
|
@@ -6348,6 +7213,35 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
6348
7213
|
|
|
6349
7214
|
// src/rules/no-tautological-expect.ts
|
|
6350
7215
|
import { AST_NODE_TYPES as AST_NODE_TYPES28 } from "@typescript-eslint/utils";
|
|
7216
|
+
var noTautologicalExpectDocumentation = {
|
|
7217
|
+
summary: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail.",
|
|
7218
|
+
rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
|
|
7219
|
+
remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
|
|
7220
|
+
category: "testing",
|
|
7221
|
+
limitations: [
|
|
7222
|
+
"Only direct supported `expect` matcher calls in recognized test files are inspected."
|
|
7223
|
+
],
|
|
7224
|
+
examples: [
|
|
7225
|
+
{
|
|
7226
|
+
id: "produced-value",
|
|
7227
|
+
title: "Assert on a produced value",
|
|
7228
|
+
outcome: "no-match",
|
|
7229
|
+
files: [{ path: "src/add.test.ts", source: "it('adds', () => { expect(add(1, 1)).toBe(2); });" }],
|
|
7230
|
+
focusPath: "src/add.test.ts",
|
|
7231
|
+
expectedCount: 0,
|
|
7232
|
+
public: true
|
|
7233
|
+
},
|
|
7234
|
+
{
|
|
7235
|
+
id: "literal-only-assertion",
|
|
7236
|
+
title: "Do not compare identical literals",
|
|
7237
|
+
outcome: "match",
|
|
7238
|
+
files: [{ path: "src/add.test.ts", source: "it('works', () => { expect(true).toBe(true); });" }],
|
|
7239
|
+
focusPath: "src/add.test.ts",
|
|
7240
|
+
expectedCount: 1,
|
|
7241
|
+
public: true
|
|
7242
|
+
}
|
|
7243
|
+
]
|
|
7244
|
+
};
|
|
6351
7245
|
var EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
6352
7246
|
var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
|
|
6353
7247
|
"toBeDefined",
|
|
@@ -6386,6 +7280,7 @@ function expectOperand(callee) {
|
|
|
6386
7280
|
}
|
|
6387
7281
|
var no_tautological_expect_default = createRule({
|
|
6388
7282
|
name: "no-tautological-expect",
|
|
7283
|
+
documentation: noTautologicalExpectDocumentation,
|
|
6389
7284
|
meta: {
|
|
6390
7285
|
type: "problem",
|
|
6391
7286
|
docs: {
|
|
@@ -6446,8 +7341,36 @@ var no_tautological_expect_default = createRule({
|
|
|
6446
7341
|
});
|
|
6447
7342
|
|
|
6448
7343
|
// src/rules/no-typed-doc-sections.ts
|
|
7344
|
+
var noTypedDocSectionsDocumentation = {
|
|
7345
|
+
summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
|
|
7346
|
+
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
7347
|
+
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
7348
|
+
category: "maintainability",
|
|
7349
|
+
limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
|
|
7350
|
+
examples: [
|
|
7351
|
+
{
|
|
7352
|
+
id: "behavioral-documentation",
|
|
7353
|
+
title: "Keep behavior that the signature cannot express",
|
|
7354
|
+
outcome: "no-match",
|
|
7355
|
+
files: [{ path: "src/client.ts", source: "/** Retries when the vendor returns 429. */\nexport function fetchValue(id: string): number { return 1; }" }],
|
|
7356
|
+
focusPath: "src/client.ts",
|
|
7357
|
+
expectedCount: 0,
|
|
7358
|
+
public: true
|
|
7359
|
+
},
|
|
7360
|
+
{
|
|
7361
|
+
id: "repeated-typed-sections",
|
|
7362
|
+
title: "Do not restate typed parameters and returns",
|
|
7363
|
+
outcome: "match",
|
|
7364
|
+
files: [{ path: "src/client.ts", source: "/** @param id external identifier\n * @returns the value\n */\nexport function fetchValue(id: string): number { return 1; }" }],
|
|
7365
|
+
focusPath: "src/client.ts",
|
|
7366
|
+
expectedCount: 1,
|
|
7367
|
+
public: true
|
|
7368
|
+
}
|
|
7369
|
+
]
|
|
7370
|
+
};
|
|
6449
7371
|
var no_typed_doc_sections_default = createRule({
|
|
6450
7372
|
name: "no-typed-doc-sections",
|
|
7373
|
+
documentation: noTypedDocSectionsDocumentation,
|
|
6451
7374
|
meta: {
|
|
6452
7375
|
type: "suggestion",
|
|
6453
7376
|
docs: { description: "Reject typed-signature repetition while preserving behavior that types cannot express." },
|
|
@@ -6472,6 +7395,34 @@ var no_typed_doc_sections_default = createRule({
|
|
|
6472
7395
|
|
|
6473
7396
|
// src/rules/no-trailing-value-narration.ts
|
|
6474
7397
|
import "@typescript-eslint/utils";
|
|
7398
|
+
var noTrailingValueNarrationDocumentation = {
|
|
7399
|
+
summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
|
|
7400
|
+
rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
|
|
7401
|
+
remediation: "Put the unit in the identifier and keep comments only when they explain a constraint or non-obvious conversion.",
|
|
7402
|
+
category: "maintainability",
|
|
7403
|
+
aliases: ["trailing-value-narration"],
|
|
7404
|
+
limitations: ["Only trailing comments with numeric values and recognized unit words are inspected."],
|
|
7405
|
+
examples: [
|
|
7406
|
+
{
|
|
7407
|
+
id: "explain-constraint",
|
|
7408
|
+
title: "Explain a domain constraint",
|
|
7409
|
+
outcome: "no-match",
|
|
7410
|
+
files: [{ path: "src/timeouts.ts", source: "const timeout = 5 * 60; // 5 minutes for cold starts" }],
|
|
7411
|
+
focusPath: "src/timeouts.ts",
|
|
7412
|
+
expectedCount: 0,
|
|
7413
|
+
public: true
|
|
7414
|
+
},
|
|
7415
|
+
{
|
|
7416
|
+
id: "repeat-duration",
|
|
7417
|
+
title: "Do not narrate the numeric duration",
|
|
7418
|
+
outcome: "match",
|
|
7419
|
+
files: [{ path: "src/timeouts.ts", source: "const staleTime = 5 * 60 * 1000; // 5 minutes" }],
|
|
7420
|
+
focusPath: "src/timeouts.ts",
|
|
7421
|
+
expectedCount: 1,
|
|
7422
|
+
public: true
|
|
7423
|
+
}
|
|
7424
|
+
]
|
|
7425
|
+
};
|
|
6475
7426
|
var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
|
|
6476
7427
|
var WORD_RE3 = /[A-Za-z]+(?:'[a-z]+)?|\d+(?:\.\d+)?/g;
|
|
6477
7428
|
var UNIT_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -6558,6 +7509,7 @@ function numbersIn(text) {
|
|
|
6558
7509
|
}
|
|
6559
7510
|
var no_trailing_value_narration_default = createRule({
|
|
6560
7511
|
name: "no-trailing-value-narration",
|
|
7512
|
+
documentation: noTrailingValueNarrationDocumentation,
|
|
6561
7513
|
meta: {
|
|
6562
7514
|
type: "suggestion",
|
|
6563
7515
|
docs: {
|
|
@@ -6734,6 +7686,33 @@ function bodyOf(member) {
|
|
|
6734
7686
|
}
|
|
6735
7687
|
|
|
6736
7688
|
// src/rules/no-declaration-comment-wall.ts
|
|
7689
|
+
var noDeclarationCommentWallDocumentation = {
|
|
7690
|
+
summary: "Flag an enum body or class body whose member comments mostly re-spell the members' own names.",
|
|
7691
|
+
rationale: "A dense block of repetitive member comments obscures the few comments that add information and drifts with renamed members.",
|
|
7692
|
+
remediation: "Delete comments that restate member names and retain comments that explain constraints, lifecycle, or behavior.",
|
|
7693
|
+
category: "maintainability",
|
|
7694
|
+
limitations: ["Only enum and class bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
7695
|
+
examples: [
|
|
7696
|
+
{
|
|
7697
|
+
id: "uncommented-members",
|
|
7698
|
+
title: "Let clear member names stand alone",
|
|
7699
|
+
outcome: "no-match",
|
|
7700
|
+
files: [{ path: "src/status.ts", source: "enum Status { Pending = 'pending', Done = 'done', Failed = 'failed' }" }],
|
|
7701
|
+
focusPath: "src/status.ts",
|
|
7702
|
+
expectedCount: 0,
|
|
7703
|
+
public: true
|
|
7704
|
+
},
|
|
7705
|
+
{
|
|
7706
|
+
id: "restated-enum-members",
|
|
7707
|
+
title: "Do not restate every enum member",
|
|
7708
|
+
outcome: "match",
|
|
7709
|
+
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}" }],
|
|
7710
|
+
focusPath: "src/status.ts",
|
|
7711
|
+
expectedCount: 1,
|
|
7712
|
+
public: true
|
|
7713
|
+
}
|
|
7714
|
+
]
|
|
7715
|
+
};
|
|
6737
7716
|
function named(node) {
|
|
6738
7717
|
switch (node.type) {
|
|
6739
7718
|
case AST_NODE_TYPES30.TSEnumMember:
|
|
@@ -6749,6 +7728,7 @@ function named(node) {
|
|
|
6749
7728
|
}
|
|
6750
7729
|
var no_declaration_comment_wall_default = createRule({
|
|
6751
7730
|
name: "no-declaration-comment-wall",
|
|
7731
|
+
documentation: noDeclarationCommentWallDocumentation,
|
|
6752
7732
|
meta: {
|
|
6753
7733
|
type: "suggestion",
|
|
6754
7734
|
docs: {
|
|
@@ -6842,6 +7822,33 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
6842
7822
|
|
|
6843
7823
|
// src/rules/no-union-in-comment.ts
|
|
6844
7824
|
import { AST_NODE_TYPES as AST_NODE_TYPES31 } from "@typescript-eslint/utils";
|
|
7825
|
+
var noUnionInCommentDocumentation = {
|
|
7826
|
+
summary: "Flag a comment that lists a `string` field's allowed values instead of the type listing them.",
|
|
7827
|
+
rationale: "A comment cannot prevent callers from supplying strings outside the listed set, and the list can drift from runtime behavior.",
|
|
7828
|
+
remediation: "Move the allowed values into a string-literal union and remove the redundant comment.",
|
|
7829
|
+
category: "correctness",
|
|
7830
|
+
limitations: ["Only bare quoted-value lists attached to supported string declarations and schema-builder fields are inspected."],
|
|
7831
|
+
examples: [
|
|
7832
|
+
{
|
|
7833
|
+
id: "literal-union",
|
|
7834
|
+
title: "Encode allowed values in the type",
|
|
7835
|
+
outcome: "no-match",
|
|
7836
|
+
files: [{ path: "src/record.ts", source: "interface R { kind: 'aa' | 'bb'; }" }],
|
|
7837
|
+
focusPath: "src/record.ts",
|
|
7838
|
+
expectedCount: 0,
|
|
7839
|
+
public: true
|
|
7840
|
+
},
|
|
7841
|
+
{
|
|
7842
|
+
id: "comment-only-union",
|
|
7843
|
+
title: "Do not leave allowed values in a comment",
|
|
7844
|
+
outcome: "match",
|
|
7845
|
+
files: [{ path: "src/record.ts", source: "interface R {\n kind: string; // 'aa' | 'bb'\n}" }],
|
|
7846
|
+
focusPath: "src/record.ts",
|
|
7847
|
+
expectedCount: 1,
|
|
7848
|
+
public: true
|
|
7849
|
+
}
|
|
7850
|
+
]
|
|
7851
|
+
};
|
|
6845
7852
|
var MAX_LITERAL_LENGTH = 28;
|
|
6846
7853
|
var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
|
|
6847
7854
|
var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
|
|
@@ -6930,6 +7937,7 @@ function unionLiterals(body2) {
|
|
|
6930
7937
|
}
|
|
6931
7938
|
var no_union_in_comment_default = createRule({
|
|
6932
7939
|
name: "no-union-in-comment",
|
|
7940
|
+
documentation: noUnionInCommentDocumentation,
|
|
6933
7941
|
meta: {
|
|
6934
7942
|
type: "suggestion",
|
|
6935
7943
|
docs: {
|
|
@@ -6990,11 +7998,39 @@ var no_union_in_comment_default = createRule({
|
|
|
6990
7998
|
|
|
6991
7999
|
// src/rules/no-type-member-comment-wall.ts
|
|
6992
8000
|
import { AST_NODE_TYPES as AST_NODE_TYPES32 } from "@typescript-eslint/utils";
|
|
8001
|
+
var noTypeMemberCommentWallDocumentation = {
|
|
8002
|
+
summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
|
|
8003
|
+
rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
|
|
8004
|
+
remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
|
|
8005
|
+
category: "maintainability",
|
|
8006
|
+
limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
8007
|
+
examples: [
|
|
8008
|
+
{
|
|
8009
|
+
id: "uncommented-members",
|
|
8010
|
+
title: "Let clear member names and types stand alone",
|
|
8011
|
+
outcome: "no-match",
|
|
8012
|
+
files: [{ path: "src/credentials.ts", source: "interface Credentials { host: string; port: number; username: string; }" }],
|
|
8013
|
+
focusPath: "src/credentials.ts",
|
|
8014
|
+
expectedCount: 0,
|
|
8015
|
+
public: true
|
|
8016
|
+
},
|
|
8017
|
+
{
|
|
8018
|
+
id: "restated-type-members",
|
|
8019
|
+
title: "Do not restate member names and types",
|
|
8020
|
+
outcome: "match",
|
|
8021
|
+
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}" }],
|
|
8022
|
+
focusPath: "src/credentials.ts",
|
|
8023
|
+
expectedCount: 1,
|
|
8024
|
+
public: true
|
|
8025
|
+
}
|
|
8026
|
+
]
|
|
8027
|
+
};
|
|
6993
8028
|
function isNamedMember(node) {
|
|
6994
8029
|
return (node.type === AST_NODE_TYPES32.TSPropertySignature || node.type === AST_NODE_TYPES32.TSMethodSignature) && !node.computed;
|
|
6995
8030
|
}
|
|
6996
8031
|
var no_type_member_comment_wall_default = createRule({
|
|
6997
8032
|
name: "no-type-member-comment-wall",
|
|
8033
|
+
documentation: noTypeMemberCommentWallDocumentation,
|
|
6998
8034
|
meta: {
|
|
6999
8035
|
type: "suggestion",
|
|
7000
8036
|
docs: {
|
|
@@ -7054,7 +8090,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
7054
8090
|
claimed.add(comment);
|
|
7055
8091
|
commented += 1;
|
|
7056
8092
|
const body2 = commentBody(comment);
|
|
7057
|
-
if (body2.length === 0 || carriesValue(body2)) continue;
|
|
8093
|
+
if (body2.length === 0 || carriesValue(body2) || isTagsOnly(body2)) continue;
|
|
7058
8094
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
7059
8095
|
restated += 1;
|
|
7060
8096
|
}
|
|
@@ -7079,6 +8115,33 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
7079
8115
|
|
|
7080
8116
|
// src/rules/no-unnecessary-use-client.ts
|
|
7081
8117
|
import { AST_NODE_TYPES as AST_NODE_TYPES33 } from "@typescript-eslint/utils";
|
|
8118
|
+
var noUnnecessaryUseClientDocumentation = {
|
|
8119
|
+
summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
|
|
8120
|
+
rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
|
|
8121
|
+
remediation: "Remove the directive, or keep it only when the module uses a supported client-side API or boundary dependency.",
|
|
8122
|
+
category: "performance",
|
|
8123
|
+
limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
|
|
8124
|
+
examples: [
|
|
8125
|
+
{
|
|
8126
|
+
id: "interactive-component",
|
|
8127
|
+
title: "Keep the directive for interactive components",
|
|
8128
|
+
outcome: "no-match",
|
|
8129
|
+
files: [{ path: "src/counter.tsx", source: "'use client'; import { useState } from 'react'; export default function X() { const [n] = useState(0); return <div>{n}</div>; }" }],
|
|
8130
|
+
focusPath: "src/counter.tsx",
|
|
8131
|
+
expectedCount: 0,
|
|
8132
|
+
public: true
|
|
8133
|
+
},
|
|
8134
|
+
{
|
|
8135
|
+
id: "static-component",
|
|
8136
|
+
title: "Remove the directive from static components",
|
|
8137
|
+
outcome: "match",
|
|
8138
|
+
files: [{ path: "src/banner.tsx", source: "'use client'; export default function X() { return <div>hello</div>; }" }],
|
|
8139
|
+
focusPath: "src/banner.tsx",
|
|
8140
|
+
expectedCount: 1,
|
|
8141
|
+
public: true
|
|
8142
|
+
}
|
|
8143
|
+
]
|
|
8144
|
+
};
|
|
7082
8145
|
var HOOK_REGEX = /^use([A-Z]|$)/;
|
|
7083
8146
|
var EVENT_PROP_REGEX = /^on[A-Z]/;
|
|
7084
8147
|
var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
|
|
@@ -7153,6 +8216,7 @@ var isGlobalReference = (node, context) => {
|
|
|
7153
8216
|
};
|
|
7154
8217
|
var no_unnecessary_use_client_default = createRule({
|
|
7155
8218
|
name: "no-unnecessary-use-client",
|
|
8219
|
+
documentation: noUnnecessaryUseClientDocumentation,
|
|
7156
8220
|
meta: {
|
|
7157
8221
|
type: "suggestion",
|
|
7158
8222
|
docs: {
|
|
@@ -7286,8 +8350,36 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
|
|
|
7286
8350
|
"jest-mock",
|
|
7287
8351
|
"@jest/globals"
|
|
7288
8352
|
]);
|
|
8353
|
+
var noUnsafeMockCastingDocumentation = {
|
|
8354
|
+
summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
|
|
8355
|
+
rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
|
|
8356
|
+
remediation: "Use the test framework's `mocked` helper to obtain the typed mock reference.",
|
|
8357
|
+
category: "testing",
|
|
8358
|
+
limitations: ["Only mock types imported from Vitest or Jest modules are inspected."],
|
|
8359
|
+
examples: [
|
|
8360
|
+
{
|
|
8361
|
+
id: "typed-mock-helper",
|
|
8362
|
+
title: "Use the framework helper",
|
|
8363
|
+
outcome: "no-match",
|
|
8364
|
+
files: [{ path: "src/client.test.ts", source: "const m = vi.mocked(myFn);" }],
|
|
8365
|
+
focusPath: "src/client.test.ts",
|
|
8366
|
+
expectedCount: 0,
|
|
8367
|
+
public: true
|
|
8368
|
+
},
|
|
8369
|
+
{
|
|
8370
|
+
id: "mock-type-assertion",
|
|
8371
|
+
title: "Do not assert that a value is a mock",
|
|
8372
|
+
outcome: "match",
|
|
8373
|
+
files: [{ path: "src/client.test.ts", source: 'import type * as vi from "vitest"; const m = myFn as vi.Mock;' }],
|
|
8374
|
+
focusPath: "src/client.test.ts",
|
|
8375
|
+
expectedCount: 1,
|
|
8376
|
+
public: true
|
|
8377
|
+
}
|
|
8378
|
+
]
|
|
8379
|
+
};
|
|
7289
8380
|
var no_unsafe_mock_casting_default = createRule({
|
|
7290
8381
|
name: "no-unsafe-mock-casting",
|
|
8382
|
+
documentation: noUnsafeMockCastingDocumentation,
|
|
7291
8383
|
meta: {
|
|
7292
8384
|
type: "problem",
|
|
7293
8385
|
docs: {
|
|
@@ -7358,6 +8450,35 @@ import {
|
|
|
7358
8450
|
AST_NODE_TYPES as AST_NODE_TYPES35
|
|
7359
8451
|
} from "@typescript-eslint/utils";
|
|
7360
8452
|
import * as ts from "typescript";
|
|
8453
|
+
var noZodNativeEnumDocumentation = {
|
|
8454
|
+
summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
|
|
8455
|
+
rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
|
|
8456
|
+
remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
|
|
8457
|
+
category: "maintainability",
|
|
8458
|
+
autofix: "safe",
|
|
8459
|
+
limitations: ["Automatic fixes are limited to inline object literals whose unique values are all string literals."],
|
|
8460
|
+
examples: [
|
|
8461
|
+
{
|
|
8462
|
+
id: "zod-literal-enum",
|
|
8463
|
+
title: "Declare string values directly in Zod",
|
|
8464
|
+
outcome: "no-match",
|
|
8465
|
+
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }],
|
|
8466
|
+
focusPath: "src/status.ts",
|
|
8467
|
+
expectedCount: 0,
|
|
8468
|
+
public: true
|
|
8469
|
+
},
|
|
8470
|
+
{
|
|
8471
|
+
id: "zod-native-enum",
|
|
8472
|
+
title: "Do not wrap a TypeScript enum",
|
|
8473
|
+
outcome: "match",
|
|
8474
|
+
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
|
|
8475
|
+
focusPath: "src/status.ts",
|
|
8476
|
+
expectedCount: 1,
|
|
8477
|
+
public: true,
|
|
8478
|
+
fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
|
|
8479
|
+
}
|
|
8480
|
+
]
|
|
8481
|
+
};
|
|
7361
8482
|
var IGNORE_PATTERNS = [
|
|
7362
8483
|
/[\\/]generated[\\/]/,
|
|
7363
8484
|
/\.gen\.tsx?$/,
|
|
@@ -7427,6 +8548,7 @@ function resolvesToImportedEnum(node, services) {
|
|
|
7427
8548
|
}
|
|
7428
8549
|
var no_zod_native_enum_default = createRule({
|
|
7429
8550
|
name: "no-zod-native-enum",
|
|
8551
|
+
documentation: noZodNativeEnumDocumentation,
|
|
7430
8552
|
meta: {
|
|
7431
8553
|
type: "suggestion",
|
|
7432
8554
|
fixable: "code",
|
|
@@ -7538,6 +8660,18 @@ var no_zod_native_enum_default = createRule({
|
|
|
7538
8660
|
|
|
7539
8661
|
// src/rules/test-loops-over-literal-cases.ts
|
|
7540
8662
|
import { AST_NODE_TYPES as AST_NODE_TYPES36, ASTUtils as ASTUtils6 } from "@typescript-eslint/utils";
|
|
8663
|
+
var testLoopsOverLiteralCasesDocumentation = {
|
|
8664
|
+
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
8665
|
+
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
8666
|
+
remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
|
|
8667
|
+
category: "testing",
|
|
8668
|
+
filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
|
|
8669
|
+
limitations: ["Only inline literal for-of cases containing framework assertions are reported."],
|
|
8670
|
+
examples: [
|
|
8671
|
+
{ 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 },
|
|
8672
|
+
{ 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 }
|
|
8673
|
+
]
|
|
8674
|
+
};
|
|
7541
8675
|
var TEST_CALLERS4 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
7542
8676
|
var TEST_MODIFIERS2 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
|
|
7543
8677
|
var ASSERTION_ROOTS2 = /* @__PURE__ */ new Set([
|
|
@@ -7671,6 +8805,7 @@ var LOOP_CARRIED_CONTROL = /* @__PURE__ */ new Set([
|
|
|
7671
8805
|
]);
|
|
7672
8806
|
var test_loops_over_literal_cases_default = createRule({
|
|
7673
8807
|
name: "test-loops-over-literal-cases",
|
|
8808
|
+
documentation: testLoopsOverLiteralCasesDocumentation,
|
|
7674
8809
|
meta: {
|
|
7675
8810
|
type: "suggestion",
|
|
7676
8811
|
docs: {
|
|
@@ -7730,6 +8865,17 @@ function unwrapExpression(node) {
|
|
|
7730
8865
|
|
|
7731
8866
|
// src/rules/prefer-constant-time-secret-compare.ts
|
|
7732
8867
|
import { AST_NODE_TYPES as AST_NODE_TYPES37 } from "@typescript-eslint/utils";
|
|
8868
|
+
var preferConstantTimeSecretCompareDocumentation = {
|
|
8869
|
+
summary: "Disallow `===`/`!==` on a secret-like value; short-circuiting comparison leaks the secret through timing. Use a constant-time compare.",
|
|
8870
|
+
rationale: "Ordinary equality stops at the first differing byte, allowing repeated measurements to reveal secret material.",
|
|
8871
|
+
remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
|
|
8872
|
+
category: "security",
|
|
8873
|
+
limitations: ["Secret-like values are identified conservatively from their names; test files and public sentinel comparisons are excluded."],
|
|
8874
|
+
examples: [
|
|
8875
|
+
{ 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 },
|
|
8876
|
+
{ 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 }
|
|
8877
|
+
]
|
|
8878
|
+
};
|
|
7733
8879
|
var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
|
|
7734
8880
|
var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
|
|
7735
8881
|
var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
|
|
@@ -7784,6 +8930,7 @@ function secretNameOf(node) {
|
|
|
7784
8930
|
}
|
|
7785
8931
|
var prefer_constant_time_secret_compare_default = createRule({
|
|
7786
8932
|
name: "prefer-constant-time-secret-compare",
|
|
8933
|
+
documentation: preferConstantTimeSecretCompareDocumentation,
|
|
7787
8934
|
meta: {
|
|
7788
8935
|
type: "problem",
|
|
7789
8936
|
docs: {
|
|
@@ -7825,6 +8972,17 @@ var prefer_constant_time_secret_compare_default = createRule({
|
|
|
7825
8972
|
// src/rules/prefer-discriminated-union.ts
|
|
7826
8973
|
import "@typescript-eslint/utils";
|
|
7827
8974
|
import { AST_NODE_TYPES as AST_NODE_TYPES38 } from "@typescript-eslint/utils";
|
|
8975
|
+
var preferDiscriminatedUnionDocumentation = {
|
|
8976
|
+
summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
|
|
8977
|
+
rationale: "A boolean status plus optional branch data permits contradictory and incomplete states.",
|
|
8978
|
+
remediation: "Represent each result branch as a discriminated union member with its required payload.",
|
|
8979
|
+
category: "correctness",
|
|
8980
|
+
limitations: ["Only local object shapes with recognized positive status and payload names are inspected."],
|
|
8981
|
+
examples: [
|
|
8982
|
+
{ 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 },
|
|
8983
|
+
{ 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 }
|
|
8984
|
+
]
|
|
8985
|
+
};
|
|
7828
8986
|
var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
7829
8987
|
"success",
|
|
7830
8988
|
"ok"
|
|
@@ -7906,6 +9064,7 @@ function inlineReturnTypeLiteral(node) {
|
|
|
7906
9064
|
}
|
|
7907
9065
|
var prefer_discriminated_union_default = createRule({
|
|
7908
9066
|
name: "prefer-discriminated-union",
|
|
9067
|
+
documentation: preferDiscriminatedUnionDocumentation,
|
|
7909
9068
|
meta: {
|
|
7910
9069
|
type: "suggestion",
|
|
7911
9070
|
docs: {
|
|
@@ -7954,6 +9113,17 @@ var prefer_discriminated_union_default = createRule({
|
|
|
7954
9113
|
|
|
7955
9114
|
// src/rules/prefer-input-group-search.ts
|
|
7956
9115
|
import { AST_NODE_TYPES as AST_NODE_TYPES39 } from "@typescript-eslint/utils";
|
|
9116
|
+
var preferInputGroupSearchDocumentation = {
|
|
9117
|
+
summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
|
|
9118
|
+
rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
|
|
9119
|
+
remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
|
|
9120
|
+
category: "style",
|
|
9121
|
+
limitations: ["Only Search and Input bindings imported from the recognized shared modules are paired."],
|
|
9122
|
+
examples: [
|
|
9123
|
+
{ 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 },
|
|
9124
|
+
{ 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 }
|
|
9125
|
+
]
|
|
9126
|
+
};
|
|
7957
9127
|
var INPUT_MODULE = /(?:^|\/)components\/ui\/input$/u;
|
|
7958
9128
|
var INPUT_GROUP_MODULE = /(?:^|\/)components\/ui\/input-group$/u;
|
|
7959
9129
|
var MAX_JSX_DISTANCE = 2;
|
|
@@ -7997,6 +9167,7 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
|
|
|
7997
9167
|
}
|
|
7998
9168
|
var prefer_input_group_search_default = createRule({
|
|
7999
9169
|
name: "prefer-input-group-search",
|
|
9170
|
+
documentation: preferInputGroupSearchDocumentation,
|
|
8000
9171
|
meta: {
|
|
8001
9172
|
type: "suggestion",
|
|
8002
9173
|
docs: {
|
|
@@ -8067,6 +9238,35 @@ var prefer_input_group_search_default = createRule({
|
|
|
8067
9238
|
|
|
8068
9239
|
// src/rules/prefer-immutable-module-constant.ts
|
|
8069
9240
|
import { AST_NODE_TYPES as AST_NODE_TYPES40, ASTUtils as ASTUtils7 } from "@typescript-eslint/utils";
|
|
9241
|
+
var preferImmutableModuleConstantDocumentation = {
|
|
9242
|
+
summary: "Require module-level constant collections to expose readonly state.",
|
|
9243
|
+
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
9244
|
+
remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
|
|
9245
|
+
category: "correctness",
|
|
9246
|
+
limitations: [
|
|
9247
|
+
"The rule skips generated files, test files, JavaScript files, and collections that are deliberately mutated in their declaring module."
|
|
9248
|
+
],
|
|
9249
|
+
examples: [
|
|
9250
|
+
{
|
|
9251
|
+
id: "readonly-array-literal",
|
|
9252
|
+
title: "A module constant exposes a readonly literal",
|
|
9253
|
+
outcome: "no-match",
|
|
9254
|
+
files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3] as const;" }],
|
|
9255
|
+
focusPath: "src/constants.ts",
|
|
9256
|
+
expectedCount: 0,
|
|
9257
|
+
public: true
|
|
9258
|
+
},
|
|
9259
|
+
{
|
|
9260
|
+
id: "mutable-array-literal",
|
|
9261
|
+
title: "A module constant exposes a mutable array",
|
|
9262
|
+
outcome: "match",
|
|
9263
|
+
files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3];" }],
|
|
9264
|
+
focusPath: "src/constants.ts",
|
|
9265
|
+
expectedCount: 1,
|
|
9266
|
+
public: true
|
|
9267
|
+
}
|
|
9268
|
+
]
|
|
9269
|
+
};
|
|
8070
9270
|
var CONSTANT_NAME = /^_?[A-Z][A-Z0-9_]*$/;
|
|
8071
9271
|
var JAVASCRIPT_FILE_RE = /\.[cm]?jsx?$/i;
|
|
8072
9272
|
var MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -8174,6 +9374,7 @@ function referenceMutates(identifier, isUnshadowedGlobal) {
|
|
|
8174
9374
|
}
|
|
8175
9375
|
var prefer_immutable_module_constant_default = createRule({
|
|
8176
9376
|
name: "prefer-immutable-module-constant",
|
|
9377
|
+
documentation: preferImmutableModuleConstantDocumentation,
|
|
8177
9378
|
meta: {
|
|
8178
9379
|
type: "suggestion",
|
|
8179
9380
|
docs: {
|
|
@@ -8260,6 +9461,17 @@ function unwrapTransparentExport(node) {
|
|
|
8260
9461
|
|
|
8261
9462
|
// src/rules/prefer-shadcn-primitives.ts
|
|
8262
9463
|
import { AST_NODE_TYPES as AST_NODE_TYPES41 } from "@typescript-eslint/utils";
|
|
9464
|
+
var preferShadcnPrimitivesDocumentation = {
|
|
9465
|
+
summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
|
|
9466
|
+
rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
|
|
9467
|
+
remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
|
|
9468
|
+
category: "style",
|
|
9469
|
+
limitations: ["Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded."],
|
|
9470
|
+
examples: [
|
|
9471
|
+
{ 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 },
|
|
9472
|
+
{ 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 }
|
|
9473
|
+
]
|
|
9474
|
+
};
|
|
8263
9475
|
var SHADCN_PRIMITIVES = {
|
|
8264
9476
|
button: "Button",
|
|
8265
9477
|
dialog: "Dialog or AlertDialog family",
|
|
@@ -8362,6 +9574,7 @@ function replacementFor(node, element) {
|
|
|
8362
9574
|
}
|
|
8363
9575
|
var prefer_shadcn_primitives_default = createRule({
|
|
8364
9576
|
name: "prefer-shadcn-primitives",
|
|
9577
|
+
documentation: preferShadcnPrimitivesDocumentation,
|
|
8365
9578
|
meta: {
|
|
8366
9579
|
type: "suggestion",
|
|
8367
9580
|
docs: {
|
|
@@ -8393,6 +9606,17 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
8393
9606
|
|
|
8394
9607
|
// src/rules/prefer-module-level-constant.ts
|
|
8395
9608
|
import { AST_NODE_TYPES as AST_NODE_TYPES42 } from "@typescript-eslint/utils";
|
|
9609
|
+
var preferModuleLevelConstantDocumentation = {
|
|
9610
|
+
summary: "Hoist literal-only constant collections and regexes out of function bodies to module scope so they are allocated once.",
|
|
9611
|
+
rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
|
|
9612
|
+
remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
|
|
9613
|
+
category: "performance",
|
|
9614
|
+
limitations: ["Collections that are small, mutated, escape the function, or depend on local values are not reported."],
|
|
9615
|
+
examples: [
|
|
9616
|
+
{ 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 },
|
|
9617
|
+
{ 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 }
|
|
9618
|
+
]
|
|
9619
|
+
};
|
|
8396
9620
|
var DEFAULT_MIN_ELEMENTS = 3;
|
|
8397
9621
|
var MAX_LITERAL_DEPTH = 4;
|
|
8398
9622
|
var IGNORE_PATTERNS2 = [
|
|
@@ -8598,6 +9822,7 @@ function isNonRetainingBuiltinCall(node, argument) {
|
|
|
8598
9822
|
}
|
|
8599
9823
|
var prefer_module_level_constant_default = createRule({
|
|
8600
9824
|
name: "prefer-module-level-constant",
|
|
9825
|
+
documentation: preferModuleLevelConstantDocumentation,
|
|
8601
9826
|
meta: {
|
|
8602
9827
|
type: "suggestion",
|
|
8603
9828
|
docs: {
|
|
@@ -8689,6 +9914,17 @@ var prefer_module_level_constant_default = createRule({
|
|
|
8689
9914
|
|
|
8690
9915
|
// src/rules/prefer-module-level-schema.ts
|
|
8691
9916
|
import { AST_NODE_TYPES as AST_NODE_TYPES43 } from "@typescript-eslint/utils";
|
|
9917
|
+
var preferModuleLevelSchemaDocumentation = {
|
|
9918
|
+
summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
|
|
9919
|
+
rationale: "A closed schema created inside a function is rebuilt on every call and cannot be reused or exported for inference.",
|
|
9920
|
+
remediation: "Move the closed schema declaration to module scope and reference it from the function.",
|
|
9921
|
+
category: "performance",
|
|
9922
|
+
limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded."],
|
|
9923
|
+
examples: [
|
|
9924
|
+
{ 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 },
|
|
9925
|
+
{ 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 }
|
|
9926
|
+
]
|
|
9927
|
+
};
|
|
8692
9928
|
var DEFAULT_FACTORIES = [
|
|
8693
9929
|
"discriminatedUnion",
|
|
8694
9930
|
"intersection",
|
|
@@ -8835,6 +10071,7 @@ function collectReferences(scope, out) {
|
|
|
8835
10071
|
}
|
|
8836
10072
|
var prefer_module_level_schema_default = createRule({
|
|
8837
10073
|
name: "prefer-module-level-schema",
|
|
10074
|
+
documentation: preferModuleLevelSchemaDocumentation,
|
|
8838
10075
|
meta: {
|
|
8839
10076
|
type: "problem",
|
|
8840
10077
|
docs: {
|
|
@@ -9032,11 +10269,24 @@ var prefer_module_level_schema_default = createRule({
|
|
|
9032
10269
|
|
|
9033
10270
|
// src/rules/prefer-native-random-uuid.ts
|
|
9034
10271
|
import { AST_NODE_TYPES as AST_NODE_TYPES44, ASTUtils as ASTUtils8 } from "@typescript-eslint/utils";
|
|
10272
|
+
var preferNativeRandomUuidDocumentation = {
|
|
10273
|
+
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
10274
|
+
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
10275
|
+
remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
|
|
10276
|
+
category: "maintainability",
|
|
10277
|
+
autofix: "suggestion",
|
|
10278
|
+
limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
|
|
10279
|
+
examples: [
|
|
10280
|
+
{ 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 },
|
|
10281
|
+
{ 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 }
|
|
10282
|
+
]
|
|
10283
|
+
};
|
|
9035
10284
|
function requireUuid(node) {
|
|
9036
10285
|
return node?.type === AST_NODE_TYPES44.CallExpression && node.callee.type === AST_NODE_TYPES44.Identifier && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0]?.type === AST_NODE_TYPES44.Literal && node.arguments[0].value === "uuid";
|
|
9037
10286
|
}
|
|
9038
10287
|
var prefer_native_random_uuid_default = createRule({
|
|
9039
10288
|
name: "prefer-native-random-uuid",
|
|
10289
|
+
documentation: preferNativeRandomUuidDocumentation,
|
|
9040
10290
|
meta: {
|
|
9041
10291
|
type: "suggestion",
|
|
9042
10292
|
docs: {
|
|
@@ -9118,6 +10368,17 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
9118
10368
|
|
|
9119
10369
|
// src/rules/prefer-non-nullable-collection.ts
|
|
9120
10370
|
import { AST_NODE_TYPES as AST_NODE_TYPES45, ASTUtils as ASTUtils9 } from "@typescript-eslint/utils";
|
|
10371
|
+
var preferNonNullableCollectionDocumentation = {
|
|
10372
|
+
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
10373
|
+
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
10374
|
+
remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
|
|
10375
|
+
category: "maintainability",
|
|
10376
|
+
limitations: ["The rule requires local evidence that nullish and empty values are treated identically and skips exported wire shapes."],
|
|
10377
|
+
examples: [
|
|
10378
|
+
{ 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 },
|
|
10379
|
+
{ 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 }
|
|
10380
|
+
]
|
|
10381
|
+
};
|
|
9121
10382
|
var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
9122
10383
|
function propertyName(node) {
|
|
9123
10384
|
const key = node.key;
|
|
@@ -9255,6 +10516,7 @@ function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
|
9255
10516
|
}
|
|
9256
10517
|
var prefer_non_nullable_collection_default = createRule({
|
|
9257
10518
|
name: "prefer-non-nullable-collection",
|
|
10519
|
+
documentation: preferNonNullableCollectionDocumentation,
|
|
9258
10520
|
meta: {
|
|
9259
10521
|
type: "suggestion",
|
|
9260
10522
|
docs: {
|
|
@@ -9351,6 +10613,17 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
9351
10613
|
|
|
9352
10614
|
// src/rules/prefer-schema-for-api-payload.ts
|
|
9353
10615
|
import { AST_NODE_TYPES as AST_NODE_TYPES46 } from "@typescript-eslint/utils";
|
|
10616
|
+
var preferSchemaForApiPayloadDocumentation = {
|
|
10617
|
+
summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
|
|
10618
|
+
rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
|
|
10619
|
+
remediation: "Parse the payload through a schema or establish a recognized runtime validation guard before reading fields.",
|
|
10620
|
+
category: "correctness",
|
|
10621
|
+
limitations: ["Test fixtures, generated clients, local JSON files, and recognized validation guards are excluded."],
|
|
10622
|
+
examples: [
|
|
10623
|
+
{ 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 },
|
|
10624
|
+
{ 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 }
|
|
10625
|
+
]
|
|
10626
|
+
};
|
|
9354
10627
|
var unwrap4 = (node) => {
|
|
9355
10628
|
let current = node;
|
|
9356
10629
|
while (current !== null && current !== void 0) {
|
|
@@ -9592,6 +10865,7 @@ var unvalidatedVariableRef = (node, scope, tracked) => {
|
|
|
9592
10865
|
};
|
|
9593
10866
|
var prefer_schema_for_api_payload_default = createRule({
|
|
9594
10867
|
name: "prefer-schema-for-api-payload",
|
|
10868
|
+
documentation: preferSchemaForApiPayloadDocumentation,
|
|
9595
10869
|
meta: {
|
|
9596
10870
|
type: "problem",
|
|
9597
10871
|
docs: {
|
|
@@ -9794,6 +11068,17 @@ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^
|
|
|
9794
11068
|
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
9795
11069
|
|
|
9796
11070
|
// src/rules/prefer-semantic-colors.ts
|
|
11071
|
+
var preferSemanticColorsDocumentation = {
|
|
11072
|
+
summary: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals.",
|
|
11073
|
+
rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
|
|
11074
|
+
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
11075
|
+
category: "style",
|
|
11076
|
+
limitations: ["Email, PDF, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions."],
|
|
11077
|
+
examples: [
|
|
11078
|
+
{ 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 },
|
|
11079
|
+
{ 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 }
|
|
11080
|
+
]
|
|
11081
|
+
};
|
|
9797
11082
|
var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
|
|
9798
11083
|
var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
|
|
9799
11084
|
var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
|
|
@@ -10072,10 +11357,11 @@ var staticallyImportsEmailOrPdfRenderer = (program) => program.body.some((statem
|
|
|
10072
11357
|
});
|
|
10073
11358
|
var prefer_semantic_colors_default = createRule({
|
|
10074
11359
|
name: "prefer-semantic-colors",
|
|
11360
|
+
documentation: preferSemanticColorsDocumentation,
|
|
10075
11361
|
meta: {
|
|
10076
11362
|
type: "suggestion",
|
|
10077
11363
|
docs: {
|
|
10078
|
-
description: "Enforce
|
|
11364
|
+
description: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals."
|
|
10079
11365
|
},
|
|
10080
11366
|
schema: [
|
|
10081
11367
|
{
|
|
@@ -10210,6 +11496,17 @@ var prefer_semantic_colors_default = createRule({
|
|
|
10210
11496
|
|
|
10211
11497
|
// src/rules/prefer-server-actions.ts
|
|
10212
11498
|
import "@typescript-eslint/utils";
|
|
11499
|
+
var preferServerActionsDocumentation = {
|
|
11500
|
+
summary: "Prefer Next.js Server Actions over /api/* mutations.",
|
|
11501
|
+
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
11502
|
+
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
11503
|
+
category: "architecture",
|
|
11504
|
+
limitations: ["Only statically recognizable /api/ mutations in applicable React modules are reported."],
|
|
11505
|
+
examples: [
|
|
11506
|
+
{ 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 },
|
|
11507
|
+
{ 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 }
|
|
11508
|
+
]
|
|
11509
|
+
};
|
|
10213
11510
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
10214
11511
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
10215
11512
|
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\/)/;
|
|
@@ -10307,6 +11604,7 @@ function getPropertyNode(objNode, propName2) {
|
|
|
10307
11604
|
}
|
|
10308
11605
|
var prefer_server_actions_default = createRule({
|
|
10309
11606
|
name: "prefer-server-actions",
|
|
11607
|
+
documentation: preferServerActionsDocumentation,
|
|
10310
11608
|
meta: {
|
|
10311
11609
|
type: "suggestion",
|
|
10312
11610
|
docs: {
|
|
@@ -10382,6 +11680,18 @@ var COLLECTION_PROPERTIES = /* @__PURE__ */ new Set(["length", "size"]);
|
|
|
10382
11680
|
var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
|
|
10383
11681
|
var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
|
|
10384
11682
|
var MIN_RUN_LENGTH = 2;
|
|
11683
|
+
var preferWholeObjectAssertionDocumentation = {
|
|
11684
|
+
summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
|
|
11685
|
+
rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
|
|
11686
|
+
remediation: "Replace consecutive member assertions with one `toMatchObject` assertion.",
|
|
11687
|
+
category: "testing",
|
|
11688
|
+
aliases: ["strict-test-assertions"],
|
|
11689
|
+
autofix: "safe",
|
|
11690
|
+
examples: [
|
|
11691
|
+
{ 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 },
|
|
11692
|
+
{ 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" }] }
|
|
11693
|
+
]
|
|
11694
|
+
};
|
|
10385
11695
|
function literalText(node, getText) {
|
|
10386
11696
|
switch (node.type) {
|
|
10387
11697
|
case AST_NODE_TYPES48.Literal:
|
|
@@ -10419,10 +11729,11 @@ function literalIndex(node) {
|
|
|
10419
11729
|
}
|
|
10420
11730
|
var prefer_whole_object_assertion_default = createRule({
|
|
10421
11731
|
name: "prefer-whole-object-assertion",
|
|
11732
|
+
documentation: preferWholeObjectAssertionDocumentation,
|
|
10422
11733
|
meta: {
|
|
10423
11734
|
type: "suggestion",
|
|
10424
11735
|
docs: {
|
|
10425
|
-
description: "Collapse
|
|
11736
|
+
description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
|
|
10426
11737
|
},
|
|
10427
11738
|
fixable: "code",
|
|
10428
11739
|
messages: {
|
|
@@ -10597,6 +11908,16 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
10597
11908
|
|
|
10598
11909
|
// src/rules/prefer-zod-infer.ts
|
|
10599
11910
|
import { AST_NODE_TYPES as AST_NODE_TYPES49 } from "@typescript-eslint/utils";
|
|
11911
|
+
var preferZodInferDocumentation = {
|
|
11912
|
+
summary: "Derive a type from its Zod schema with `z.infer` instead of hand-writing a twin declaration beside it.",
|
|
11913
|
+
rationale: "A derived type stays synchronized when the runtime schema changes.",
|
|
11914
|
+
remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
|
|
11915
|
+
category: "correctness",
|
|
11916
|
+
examples: [
|
|
11917
|
+
{ 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 },
|
|
11918
|
+
{ 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 }
|
|
11919
|
+
]
|
|
11920
|
+
};
|
|
10600
11921
|
var SHAPE_PRESERVING_METHODS = /* @__PURE__ */ new Set([
|
|
10601
11922
|
"describe",
|
|
10602
11923
|
"refine",
|
|
@@ -10722,6 +12043,7 @@ function leafAgrees(leaf, annotation) {
|
|
|
10722
12043
|
}
|
|
10723
12044
|
var prefer_zod_infer_default = createRule({
|
|
10724
12045
|
name: "prefer-zod-infer",
|
|
12046
|
+
documentation: preferZodInferDocumentation,
|
|
10725
12047
|
meta: {
|
|
10726
12048
|
type: "problem",
|
|
10727
12049
|
docs: {
|
|
@@ -11010,6 +12332,16 @@ import {
|
|
|
11010
12332
|
AST_NODE_TYPES as AST_NODE_TYPES50
|
|
11011
12333
|
} from "@typescript-eslint/utils";
|
|
11012
12334
|
import ts2 from "typescript";
|
|
12335
|
+
var requireAssertNeverDocumentation = {
|
|
12336
|
+
summary: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time.",
|
|
12337
|
+
rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
|
|
12338
|
+
remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
|
|
12339
|
+
category: "correctness",
|
|
12340
|
+
examples: [
|
|
12341
|
+
{ 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 },
|
|
12342
|
+
{ 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 }
|
|
12343
|
+
]
|
|
12344
|
+
};
|
|
11013
12345
|
var isRuntimeHandlingStatement = (statement) => {
|
|
11014
12346
|
if (statement.type === AST_NODE_TYPES50.EmptyStatement) return false;
|
|
11015
12347
|
if (statement.type === AST_NODE_TYPES50.TSTypeAliasDeclaration || statement.type === AST_NODE_TYPES50.TSInterfaceDeclaration) {
|
|
@@ -11067,10 +12399,11 @@ function finiteTypeKey(type, checker) {
|
|
|
11067
12399
|
}
|
|
11068
12400
|
var require_assert_never_default = createRule({
|
|
11069
12401
|
name: "require-assert-never",
|
|
12402
|
+
documentation: requireAssertNeverDocumentation,
|
|
11070
12403
|
meta: {
|
|
11071
12404
|
type: "problem",
|
|
11072
12405
|
docs: {
|
|
11073
|
-
description: "Require an
|
|
12406
|
+
description: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time."
|
|
11074
12407
|
},
|
|
11075
12408
|
schema: [],
|
|
11076
12409
|
messages: {
|
|
@@ -11108,6 +12441,16 @@ var require_assert_never_default = createRule({
|
|
|
11108
12441
|
|
|
11109
12442
|
// src/rules/require-fetch-timeout.ts
|
|
11110
12443
|
import { AST_NODE_TYPES as AST_NODE_TYPES51, ASTUtils as ASTUtils10 } from "@typescript-eslint/utils";
|
|
12444
|
+
var requireFetchTimeoutDocumentation = {
|
|
12445
|
+
summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
|
|
12446
|
+
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
12447
|
+
remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
|
|
12448
|
+
category: "correctness",
|
|
12449
|
+
examples: [
|
|
12450
|
+
{ 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 },
|
|
12451
|
+
{ 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 }
|
|
12452
|
+
]
|
|
12453
|
+
};
|
|
11111
12454
|
var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
|
|
11112
12455
|
"globalThis",
|
|
11113
12456
|
"window",
|
|
@@ -11144,6 +12487,7 @@ function isInlineUrl(node, resolvesToGlobal) {
|
|
|
11144
12487
|
}
|
|
11145
12488
|
var require_fetch_timeout_default = createRule({
|
|
11146
12489
|
name: "require-fetch-timeout",
|
|
12490
|
+
documentation: requireFetchTimeoutDocumentation,
|
|
11147
12491
|
meta: {
|
|
11148
12492
|
type: "problem",
|
|
11149
12493
|
docs: {
|
|
@@ -11203,8 +12547,19 @@ var require_fetch_timeout_default = createRule({
|
|
|
11203
12547
|
}
|
|
11204
12548
|
});
|
|
11205
12549
|
|
|
11206
|
-
// src/rules/require-
|
|
12550
|
+
// src/rules/require-port-for-service.ts
|
|
11207
12551
|
import { AST_NODE_TYPES as AST_NODE_TYPES52 } from "@typescript-eslint/utils";
|
|
12552
|
+
var requirePortForServiceDocumentation = {
|
|
12553
|
+
summary: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
|
|
12554
|
+
rationale: "A declared port keeps consumers coupled to the service capability instead of its concrete implementation.",
|
|
12555
|
+
remediation: "Declare and implement an interface covering the service's public methods.",
|
|
12556
|
+
category: "architecture",
|
|
12557
|
+
aliases: ["require-interface-for-injected-service"],
|
|
12558
|
+
examples: [
|
|
12559
|
+
{ 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 },
|
|
12560
|
+
{ 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 }
|
|
12561
|
+
]
|
|
12562
|
+
};
|
|
11208
12563
|
var CONFIGISH_TYPE_RE = /(?:Options|Opts|Config|Configuration|Settings|Params|Props|Args|Env|Environment|Callbacks|Flags)$/;
|
|
11209
12564
|
var CONFIGISH_NAME_RE = /^(?:options|opts|config|configuration|settings|params|props|args|env|environment|callbacks|flags|logger|log|clock)$/i;
|
|
11210
12565
|
var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
|
|
@@ -11643,8 +12998,9 @@ function hasServicePort(node, methods, classes, interfaces) {
|
|
|
11643
12998
|
}
|
|
11644
12999
|
return methods.every((method) => combined.has(method));
|
|
11645
13000
|
}
|
|
11646
|
-
var
|
|
11647
|
-
name: "require-
|
|
13001
|
+
var require_port_for_service_default = createRule({
|
|
13002
|
+
name: "require-port-for-service",
|
|
13003
|
+
documentation: requirePortForServiceDocumentation,
|
|
11648
13004
|
meta: {
|
|
11649
13005
|
type: "suggestion",
|
|
11650
13006
|
docs: {
|
|
@@ -11709,6 +13065,16 @@ var require_interface_for_injected_service_default = createRule({
|
|
|
11709
13065
|
|
|
11710
13066
|
// src/rules/require-static-next-matcher.ts
|
|
11711
13067
|
import { AST_NODE_TYPES as AST_NODE_TYPES53 } from "@typescript-eslint/utils";
|
|
13068
|
+
var requireStaticNextMatcherDocumentation = {
|
|
13069
|
+
summary: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
|
|
13070
|
+
rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
|
|
13071
|
+
remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
|
|
13072
|
+
category: "correctness",
|
|
13073
|
+
examples: [
|
|
13074
|
+
{ 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 },
|
|
13075
|
+
{ 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 }
|
|
13076
|
+
]
|
|
13077
|
+
};
|
|
11712
13078
|
var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
|
|
11713
13079
|
function unwrapExpression3(node) {
|
|
11714
13080
|
if (node.type === AST_NODE_TYPES53.TSAsExpression || node.type === AST_NODE_TYPES53.TSSatisfiesExpression || node.type === AST_NODE_TYPES53.TSNonNullExpression || node.type === AST_NODE_TYPES53.TSTypeAssertion) {
|
|
@@ -11743,6 +13109,7 @@ function propertyName2(property) {
|
|
|
11743
13109
|
}
|
|
11744
13110
|
var require_static_next_matcher_default = createRule({
|
|
11745
13111
|
name: "require-static-next-matcher",
|
|
13112
|
+
documentation: requireStaticNextMatcherDocumentation,
|
|
11746
13113
|
meta: {
|
|
11747
13114
|
type: "problem",
|
|
11748
13115
|
docs: {
|
|
@@ -11787,6 +13154,16 @@ var require_static_next_matcher_default = createRule({
|
|
|
11787
13154
|
|
|
11788
13155
|
// src/rules/require-zod-form-validation.ts
|
|
11789
13156
|
import { AST_NODE_TYPES as AST_NODE_TYPES54 } from "@typescript-eslint/utils";
|
|
13157
|
+
var requireZodFormValidationDocumentation = {
|
|
13158
|
+
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
13159
|
+
rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
|
|
13160
|
+
remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
|
|
13161
|
+
category: "security",
|
|
13162
|
+
examples: [
|
|
13163
|
+
{ 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 },
|
|
13164
|
+
{ 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 }
|
|
13165
|
+
]
|
|
13166
|
+
};
|
|
11790
13167
|
var isZodParseCall = (node) => {
|
|
11791
13168
|
if (node.type !== AST_NODE_TYPES54.CallExpression) return false;
|
|
11792
13169
|
const callee = node.callee;
|
|
@@ -11827,6 +13204,7 @@ var isFormDataMethodCall = (node) => {
|
|
|
11827
13204
|
};
|
|
11828
13205
|
var require_zod_form_validation_default = createRule({
|
|
11829
13206
|
name: "require-zod-form-validation",
|
|
13207
|
+
documentation: requireZodFormValidationDocumentation,
|
|
11830
13208
|
meta: {
|
|
11831
13209
|
type: "problem",
|
|
11832
13210
|
docs: {
|
|
@@ -11915,11 +13293,22 @@ var require_zod_form_validation_default = createRule({
|
|
|
11915
13293
|
|
|
11916
13294
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
11917
13295
|
import "@typescript-eslint/utils";
|
|
13296
|
+
var storeInsertRequiresOnConflictDocumentation = {
|
|
13297
|
+
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.",
|
|
13298
|
+
rationale: "A replayed bare insert can duplicate data or fail on a uniqueness constraint.",
|
|
13299
|
+
remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
|
|
13300
|
+
category: "correctness",
|
|
13301
|
+
examples: [
|
|
13302
|
+
{ 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 },
|
|
13303
|
+
{ 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 }
|
|
13304
|
+
]
|
|
13305
|
+
};
|
|
11918
13306
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
11919
13307
|
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b/i;
|
|
11920
13308
|
var INSERT_GATE = /insert/i;
|
|
11921
13309
|
var store_insert_requires_on_conflict_default = createRule({
|
|
11922
13310
|
name: "store-insert-requires-on-conflict",
|
|
13311
|
+
documentation: storeInsertRequiresOnConflictDocumentation,
|
|
11923
13312
|
meta: {
|
|
11924
13313
|
type: "problem",
|
|
11925
13314
|
docs: {
|
|
@@ -11946,6 +13335,16 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
11946
13335
|
|
|
11947
13336
|
// src/rules/stepdown.ts
|
|
11948
13337
|
import { AST_NODE_TYPES as AST_NODE_TYPES55, ASTUtils as ASTUtils11 } from "@typescript-eslint/utils";
|
|
13338
|
+
var stepdownDocumentation = {
|
|
13339
|
+
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
13340
|
+
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
13341
|
+
remediation: "Move the private helper immediately below its sole caller.",
|
|
13342
|
+
category: "maintainability",
|
|
13343
|
+
examples: [
|
|
13344
|
+
{ 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 },
|
|
13345
|
+
{ 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 }
|
|
13346
|
+
]
|
|
13347
|
+
};
|
|
11949
13348
|
function isFunction(node) {
|
|
11950
13349
|
return node.type === AST_NODE_TYPES55.ArrowFunctionExpression || node.type === AST_NODE_TYPES55.FunctionDeclaration || node.type === AST_NODE_TYPES55.FunctionExpression;
|
|
11951
13350
|
}
|
|
@@ -12300,6 +13699,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
12300
13699
|
}
|
|
12301
13700
|
var stepdown_default = createRule({
|
|
12302
13701
|
name: "stepdown",
|
|
13702
|
+
documentation: stepdownDocumentation,
|
|
12303
13703
|
meta: {
|
|
12304
13704
|
type: "suggestion",
|
|
12305
13705
|
docs: { description: "Place a private helper below its sole direct same-scope caller." },
|
|
@@ -12336,6 +13736,16 @@ import {
|
|
|
12336
13736
|
AST_NODE_TYPES as AST_NODE_TYPES56,
|
|
12337
13737
|
ASTUtils as ASTUtils12
|
|
12338
13738
|
} from "@typescript-eslint/utils";
|
|
13739
|
+
var zodNamingConventionDocumentation = {
|
|
13740
|
+
summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
|
|
13741
|
+
rationale: "A recognizable schema name distinguishes runtime validators from ordinary values at each use site.",
|
|
13742
|
+
remediation: "Rename the schema with a `Z` prefix or `Schema` suffix, according to the configured convention.",
|
|
13743
|
+
category: "style",
|
|
13744
|
+
examples: [
|
|
13745
|
+
{ 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 },
|
|
13746
|
+
{ 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 }
|
|
13747
|
+
]
|
|
13748
|
+
};
|
|
12339
13749
|
var CONVENTIONS = {
|
|
12340
13750
|
prefix: { test: ZOD_PREFIX_RE, messageId: "zPrefix" },
|
|
12341
13751
|
suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
|
|
@@ -12380,6 +13790,7 @@ var calleeChainRoot = (node) => {
|
|
|
12380
13790
|
};
|
|
12381
13791
|
var zod_naming_convention_default = createRule({
|
|
12382
13792
|
name: "zod-naming-convention",
|
|
13793
|
+
documentation: zodNamingConventionDocumentation,
|
|
12383
13794
|
meta: {
|
|
12384
13795
|
type: "suggestion",
|
|
12385
13796
|
docs: {
|
|
@@ -12462,6 +13873,7 @@ var zod_naming_convention_default = createRule({
|
|
|
12462
13873
|
var renamedRules = {
|
|
12463
13874
|
"jsdoc-restates-signature": "no-restated-jsdoc",
|
|
12464
13875
|
"no-async-callback-in-waitfor": "no-async-callback-in-wait-for",
|
|
13876
|
+
"require-interface-for-injected-service": "require-port-for-service",
|
|
12465
13877
|
"strict-test-assertions": "prefer-whole-object-assertion",
|
|
12466
13878
|
"trailing-value-narration": "no-trailing-value-narration"
|
|
12467
13879
|
};
|
|
@@ -12587,7 +13999,7 @@ var rules = {
|
|
|
12587
13999
|
"prefer-zod-infer": prefer_zod_infer_default,
|
|
12588
14000
|
"require-assert-never": require_assert_never_default,
|
|
12589
14001
|
"require-fetch-timeout": require_fetch_timeout_default,
|
|
12590
|
-
"require-
|
|
14002
|
+
"require-port-for-service": require_port_for_service_default,
|
|
12591
14003
|
"require-static-next-matcher": require_static_next_matcher_default,
|
|
12592
14004
|
"require-zod-form-validation": require_zod_form_validation_default,
|
|
12593
14005
|
"store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
|
|
@@ -12596,7 +14008,7 @@ var rules = {
|
|
|
12596
14008
|
};
|
|
12597
14009
|
var meta = {
|
|
12598
14010
|
name: "@sarj/eslint-plugin",
|
|
12599
|
-
version: "
|
|
14011
|
+
version: "13.1.0"
|
|
12600
14012
|
};
|
|
12601
14013
|
var applicationOnlyRules = [
|
|
12602
14014
|
"no-restricted-library-load",
|
|
@@ -12656,7 +14068,7 @@ var recommendedRules = {
|
|
|
12656
14068
|
"@sarj/prefer-zod-infer": "error",
|
|
12657
14069
|
"@sarj/require-assert-never": "error",
|
|
12658
14070
|
"@sarj/require-fetch-timeout": "error",
|
|
12659
|
-
"@sarj/require-
|
|
14071
|
+
"@sarj/require-port-for-service": "error",
|
|
12660
14072
|
"@sarj/require-static-next-matcher": "error",
|
|
12661
14073
|
"@sarj/require-zod-form-validation": "error",
|
|
12662
14074
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
@@ -12720,7 +14132,7 @@ var strictRules = {
|
|
|
12720
14132
|
"@sarj/prefer-zod-infer": "error",
|
|
12721
14133
|
"@sarj/require-assert-never": "error",
|
|
12722
14134
|
"@sarj/require-fetch-timeout": "error",
|
|
12723
|
-
"@sarj/require-
|
|
14135
|
+
"@sarj/require-port-for-service": "error",
|
|
12724
14136
|
"@sarj/require-static-next-matcher": "error",
|
|
12725
14137
|
"@sarj/require-zod-form-validation": "error",
|
|
12726
14138
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
@@ -12750,6 +14162,7 @@ var index_default = plugin;
|
|
|
12750
14162
|
export {
|
|
12751
14163
|
applicationOnlyRules,
|
|
12752
14164
|
index_default as default,
|
|
14165
|
+
publicDocumentation,
|
|
12753
14166
|
recommendedRules,
|
|
12754
14167
|
renamedRules,
|
|
12755
14168
|
retiredRules,
|