@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.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
applicationOnlyRules: () => applicationOnlyRules,
|
|
34
34
|
default: () => index_default,
|
|
35
|
+
publicDocumentation: () => publicDocumentation,
|
|
35
36
|
recommendedRules: () => recommendedRules,
|
|
36
37
|
renamedRules: () => renamedRules,
|
|
37
38
|
retiredRules: () => retiredRules,
|
|
@@ -49,7 +50,193 @@ var REPO_BLOB = "https://github.com/sarj-ai/standards/blob/main";
|
|
|
49
50
|
var TESTS_DIR = "packages/typescript/tests/rules";
|
|
50
51
|
var examplesPath = (name) => `${TESTS_DIR}/${name}.test.ts`;
|
|
51
52
|
var examplesUrl = (name) => `${REPO_BLOB}/${examplesPath(name)}`;
|
|
52
|
-
var
|
|
53
|
+
var KEBAB_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
54
|
+
var MAX_SUMMARY_LENGTH = 160;
|
|
55
|
+
var eslintCreateRule = import_utils.ESLintUtils.RuleCreator(examplesUrl);
|
|
56
|
+
function createRule(config) {
|
|
57
|
+
const { documentation, ...eslintConfig } = config;
|
|
58
|
+
const rule = eslintCreateRule(eslintConfig);
|
|
59
|
+
if (documentation !== void 0) {
|
|
60
|
+
Object.defineProperty(rule, "documentation", {
|
|
61
|
+
configurable: false,
|
|
62
|
+
enumerable: false,
|
|
63
|
+
value: nativeSpec(eslintConfig, documentation),
|
|
64
|
+
writable: false
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return rule;
|
|
68
|
+
}
|
|
69
|
+
function documentationWarnings(rules2) {
|
|
70
|
+
return Object.entries(rules2).filter(([, rule]) => rule.documentation === void 0).map(([name]) => `${name}: source-owned documentation has not been migrated`).sort();
|
|
71
|
+
}
|
|
72
|
+
function publicDocumentation(rules2) {
|
|
73
|
+
const missing = documentationWarnings(rules2);
|
|
74
|
+
if (missing.length > 0) {
|
|
75
|
+
throw new TypeError(`cannot publish an incomplete rule catalog:
|
|
76
|
+
${missing.join("\n")}`);
|
|
77
|
+
}
|
|
78
|
+
return Object.entries(rules2).sort(([left], [right]) => left.localeCompare(right)).map(([ruleId, rule]) => {
|
|
79
|
+
const spec = rule.documentation;
|
|
80
|
+
if (spec === void 0 || spec.ruleId !== ruleId) {
|
|
81
|
+
throw new TypeError(`${ruleId}: native documentation identity mismatch`);
|
|
82
|
+
}
|
|
83
|
+
return deepFreeze({
|
|
84
|
+
engine: spec.engine,
|
|
85
|
+
ruleId: spec.ruleId,
|
|
86
|
+
code: spec.code,
|
|
87
|
+
summary: spec.summary,
|
|
88
|
+
rationale: spec.rationale,
|
|
89
|
+
remediation: spec.remediation,
|
|
90
|
+
category: spec.category,
|
|
91
|
+
languages: spec.languages,
|
|
92
|
+
autofix: spec.autofix,
|
|
93
|
+
aliases: spec.aliases,
|
|
94
|
+
limitations: spec.limitations,
|
|
95
|
+
filePatterns: spec.filePatterns,
|
|
96
|
+
references: spec.references,
|
|
97
|
+
since: spec.since,
|
|
98
|
+
messageIds: spec.messageIds,
|
|
99
|
+
optionsSchema: spec.optionsSchema,
|
|
100
|
+
examples: spec.publicExamples.map(publicExample)
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function publicExample(example) {
|
|
105
|
+
return {
|
|
106
|
+
id: example.id,
|
|
107
|
+
title: example.title,
|
|
108
|
+
outcome: example.outcome,
|
|
109
|
+
files: example.files.map(publicFile),
|
|
110
|
+
focusPath: example.focusPath,
|
|
111
|
+
expectedCount: example.expectedCount,
|
|
112
|
+
fixedFiles: (example.fixedFiles ?? []).map(publicFile)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function publicFile(file) {
|
|
116
|
+
return { path: file.path, source: file.source };
|
|
117
|
+
}
|
|
118
|
+
function nativeSpec(config, documentation) {
|
|
119
|
+
const { name, meta: meta2 } = config;
|
|
120
|
+
if (!KEBAB_CASE.test(name)) throw new TypeError("rule ID must be lowercase kebab-case");
|
|
121
|
+
for (const [label, value] of [
|
|
122
|
+
["summary", documentation.summary],
|
|
123
|
+
["rationale", documentation.rationale],
|
|
124
|
+
["remediation", documentation.remediation]
|
|
125
|
+
]) {
|
|
126
|
+
if (value.trim().length === 0) throw new TypeError(`rule ${label} must not be empty`);
|
|
127
|
+
}
|
|
128
|
+
if (documentation.summary.includes("\n") || documentation.summary.length > MAX_SUMMARY_LENGTH) {
|
|
129
|
+
throw new TypeError(`rule summary must be one line of at most ${MAX_SUMMARY_LENGTH} characters`);
|
|
130
|
+
}
|
|
131
|
+
if (documentation.summary !== meta2.docs?.description) {
|
|
132
|
+
throw new TypeError(`${name}: ESLint description must equal the authored documentation summary`);
|
|
133
|
+
}
|
|
134
|
+
const aliases = [...documentation.aliases ?? []];
|
|
135
|
+
assertUnique(aliases, "rule aliases");
|
|
136
|
+
if (aliases.some((alias) => !KEBAB_CASE.test(alias) || alias === name)) {
|
|
137
|
+
throw new TypeError("rule aliases must be historical lowercase kebab-case IDs");
|
|
138
|
+
}
|
|
139
|
+
const limitations = [...documentation.limitations ?? []];
|
|
140
|
+
const filePatterns = [...documentation.filePatterns ?? []];
|
|
141
|
+
if ([...limitations, ...filePatterns].some((value) => value.trim().length === 0)) {
|
|
142
|
+
throw new TypeError("rule limitations and file patterns must not be empty");
|
|
143
|
+
}
|
|
144
|
+
const references = [...documentation.references ?? []];
|
|
145
|
+
if (references.some((reference) => !reference.startsWith("https://"))) {
|
|
146
|
+
throw new TypeError("rule references must use https");
|
|
147
|
+
}
|
|
148
|
+
const examples = [...documentation.examples ?? []];
|
|
149
|
+
examples.forEach(validateExample);
|
|
150
|
+
assertUnique(examples.map((example) => example.id), "rule example IDs");
|
|
151
|
+
const publicOutcomes = new Set(examples.filter((example) => example.public === true).map((example) => example.outcome));
|
|
152
|
+
if (publicOutcomes.size > 0 && !(publicOutcomes.has("match") && publicOutcomes.has("no-match"))) {
|
|
153
|
+
throw new TypeError("published rule examples must include matching and non-matching cases");
|
|
154
|
+
}
|
|
155
|
+
const messageIds = Object.keys(meta2.messages).sort();
|
|
156
|
+
const schema = optionsSchema(meta2.schema);
|
|
157
|
+
const spec = {
|
|
158
|
+
engine: "eslint",
|
|
159
|
+
ruleId: name,
|
|
160
|
+
code: null,
|
|
161
|
+
key: `eslint:${name}`,
|
|
162
|
+
summary: documentation.summary,
|
|
163
|
+
rationale: documentation.rationale,
|
|
164
|
+
remediation: documentation.remediation,
|
|
165
|
+
category: documentation.category,
|
|
166
|
+
languages: [...documentation.languages ?? ["typescript"]],
|
|
167
|
+
autofix: documentation.autofix ?? "none",
|
|
168
|
+
aliases,
|
|
169
|
+
limitations,
|
|
170
|
+
filePatterns,
|
|
171
|
+
references,
|
|
172
|
+
since: documentation.since ?? null,
|
|
173
|
+
examples,
|
|
174
|
+
publicExamples: examples.filter((example) => example.public === true),
|
|
175
|
+
messageIds,
|
|
176
|
+
optionsSchema: schema
|
|
177
|
+
};
|
|
178
|
+
return deepFreeze(spec);
|
|
179
|
+
}
|
|
180
|
+
function optionsSchema(value) {
|
|
181
|
+
if (!Array.isArray(value)) return isObject(value) ? value : null;
|
|
182
|
+
const items = value;
|
|
183
|
+
if (items.length === 0) return null;
|
|
184
|
+
const [only] = items;
|
|
185
|
+
return items.length === 1 && isObject(only) ? only : { type: "array", items };
|
|
186
|
+
}
|
|
187
|
+
function isObject(value) {
|
|
188
|
+
return value !== null && typeof value === "object";
|
|
189
|
+
}
|
|
190
|
+
function validateExample(example) {
|
|
191
|
+
if (!KEBAB_CASE.test(example.id)) {
|
|
192
|
+
throw new TypeError("example ID must be lowercase kebab-case");
|
|
193
|
+
}
|
|
194
|
+
if (example.title.trim().length === 0) {
|
|
195
|
+
throw new TypeError("example title must not be empty");
|
|
196
|
+
}
|
|
197
|
+
if (!Number.isSafeInteger(example.expectedCount) || example.expectedCount < 0) {
|
|
198
|
+
throw new TypeError("example expected count must be a non-negative integer");
|
|
199
|
+
}
|
|
200
|
+
if (example.outcome === "match" && example.expectedCount < 1) {
|
|
201
|
+
throw new TypeError("matching examples must expect at least one diagnostic");
|
|
202
|
+
}
|
|
203
|
+
if (example.outcome === "no-match" && example.expectedCount !== 0) {
|
|
204
|
+
throw new TypeError("non-matching examples must expect zero diagnostics");
|
|
205
|
+
}
|
|
206
|
+
if (example.files.length === 0) {
|
|
207
|
+
throw new TypeError("example files must not be empty");
|
|
208
|
+
}
|
|
209
|
+
const paths = example.files.map((file) => file.path);
|
|
210
|
+
assertUnique(paths, "example file paths");
|
|
211
|
+
for (const file of [...example.files, ...example.fixedFiles ?? []]) {
|
|
212
|
+
assertSafeRelativePath(file.path, "example file path");
|
|
213
|
+
if (file.source.length === 0) {
|
|
214
|
+
throw new TypeError("example file source must not be empty");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
assertSafeRelativePath(example.focusPath, "example focus path");
|
|
218
|
+
if (!paths.includes(example.focusPath)) {
|
|
219
|
+
throw new TypeError("example focus path must name one example file");
|
|
220
|
+
}
|
|
221
|
+
assertUnique((example.fixedFiles ?? []).map((file) => file.path), "fixed example file paths");
|
|
222
|
+
}
|
|
223
|
+
function assertSafeRelativePath(path, label) {
|
|
224
|
+
if (path.length === 0 || path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:[\\/]/u.test(path) || path.split(/[\\/]/u).includes("..")) {
|
|
225
|
+
throw new TypeError(`${label} must be a safe relative path`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function assertUnique(values, label) {
|
|
229
|
+
if (new Set(values).size !== values.length) {
|
|
230
|
+
throw new TypeError(`${label} must be unique`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function deepFreeze(value) {
|
|
234
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
235
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
236
|
+
Object.freeze(value);
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
53
240
|
|
|
54
241
|
// src/rules/_paths.ts
|
|
55
242
|
var SCRIPT_FILE_RE = /(?:^|[\\/])scripts[\\/]|\.mjs$/;
|
|
@@ -138,6 +325,35 @@ function isScriptFile(filename) {
|
|
|
138
325
|
}
|
|
139
326
|
|
|
140
327
|
// src/rules/enforce-file-structure.ts
|
|
328
|
+
var enforceFileStructureDocumentation = {
|
|
329
|
+
summary: "Require imports before body statements and require `use server` to be the first statement.",
|
|
330
|
+
rationale: "Interleaved imports obscure module dependencies, while a displaced `use server` string is not an active directive.",
|
|
331
|
+
remediation: "Move `use server` to the first statement when present, then place imports before declarations and executable statements.",
|
|
332
|
+
category: "correctness",
|
|
333
|
+
limitations: [
|
|
334
|
+
"The rule skips tests and generated files, treats re-exports as neutral, and does not order body declarations."
|
|
335
|
+
],
|
|
336
|
+
examples: [
|
|
337
|
+
{
|
|
338
|
+
id: "imports-first",
|
|
339
|
+
title: "Imports precede module declarations",
|
|
340
|
+
outcome: "no-match",
|
|
341
|
+
files: [{ path: "src/component.ts", source: "import { z } from 'zod';\nexport const schema = z.string();" }],
|
|
342
|
+
focusPath: "src/component.ts",
|
|
343
|
+
expectedCount: 0,
|
|
344
|
+
public: true
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "import-after-declaration",
|
|
348
|
+
title: "An import follows a module declaration",
|
|
349
|
+
outcome: "match",
|
|
350
|
+
files: [{ path: "src/component.ts", source: "export const x = 1;\nimport { z } from 'zod';" }],
|
|
351
|
+
focusPath: "src/component.ts",
|
|
352
|
+
expectedCount: 1,
|
|
353
|
+
public: true
|
|
354
|
+
}
|
|
355
|
+
]
|
|
356
|
+
};
|
|
141
357
|
var classifyStatement = (statement) => {
|
|
142
358
|
switch (statement.type) {
|
|
143
359
|
case import_utils2.AST_NODE_TYPES.ImportDeclaration:
|
|
@@ -159,10 +375,11 @@ var isUseServerDirective = (statement) => {
|
|
|
159
375
|
};
|
|
160
376
|
var enforce_file_structure_default = createRule({
|
|
161
377
|
name: "enforce-file-structure",
|
|
378
|
+
documentation: enforceFileStructureDocumentation,
|
|
162
379
|
meta: {
|
|
163
380
|
type: "suggestion",
|
|
164
381
|
docs: {
|
|
165
|
-
description: "Require
|
|
382
|
+
description: "Require imports before body statements and require `use server` to be the first statement."
|
|
166
383
|
},
|
|
167
384
|
schema: [],
|
|
168
385
|
messages: {
|
|
@@ -233,6 +450,41 @@ var OMITTED_AST_KEYS = /* @__PURE__ */ new Set([
|
|
|
233
450
|
var MIN_STATEMENTS = 3;
|
|
234
451
|
var MAX_NORMALIZED_STRING_LENGTH = 64;
|
|
235
452
|
var TEST_MODULES = /* @__PURE__ */ new Set(["@jest/globals", "@playwright/test", "bun:test", "node:test", "vitest"]);
|
|
453
|
+
var duplicateTestBodyDocumentation = {
|
|
454
|
+
summary: "Disallow substantial sibling tests with the same body shape; express their differing inputs as a parameterized case table.",
|
|
455
|
+
rationale: "Copy-pasted test bodies hide the cases that differ and allow equivalent assertions to drift independently.",
|
|
456
|
+
remediation: "Move the varying inputs and expected values into a case table consumed by `test.each(...)` or `it.each(...)`.",
|
|
457
|
+
category: "testing",
|
|
458
|
+
limitations: [
|
|
459
|
+
"The rule compares substantial sibling tests within one suite and skips inline snapshots and materially different comments."
|
|
460
|
+
],
|
|
461
|
+
examples: [
|
|
462
|
+
{
|
|
463
|
+
id: "parameterized-cases",
|
|
464
|
+
title: "A case table shares one test body",
|
|
465
|
+
outcome: "no-match",
|
|
466
|
+
files: [{
|
|
467
|
+
path: "src/user.test.ts",
|
|
468
|
+
source: "test.each(['a', 'b'])('parses %s', (value) => { const x = parse(value); expect(x.ok).toBe(true); expect(x.value).toBe(value); });"
|
|
469
|
+
}],
|
|
470
|
+
focusPath: "src/user.test.ts",
|
|
471
|
+
expectedCount: 0,
|
|
472
|
+
public: true
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: "copied-sibling-tests",
|
|
476
|
+
title: "Sibling tests repeat the same body",
|
|
477
|
+
outcome: "match",
|
|
478
|
+
files: [{
|
|
479
|
+
path: "src/user.test.ts",
|
|
480
|
+
source: "test('accepts a', () => { const result = parse('a'); expect(result.ok).toBe(true); expect(result.value).toBe('a'); });\ntest('accepts b', () => { const result = parse('b'); expect(result.ok).toBe(true); expect(result.value).toBe('b'); });"
|
|
481
|
+
}],
|
|
482
|
+
focusPath: "src/user.test.ts",
|
|
483
|
+
expectedCount: 1,
|
|
484
|
+
public: true
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
};
|
|
236
488
|
function rootIdentifier(callee) {
|
|
237
489
|
if (callee.type === import_utils3.AST_NODE_TYPES.Identifier) return callee;
|
|
238
490
|
if (callee.type === import_utils3.AST_NODE_TYPES.MemberExpression) return rootIdentifier(callee.object);
|
|
@@ -348,6 +600,7 @@ function normalizedLiteral(node) {
|
|
|
348
600
|
}
|
|
349
601
|
var duplicate_test_body_default = createRule({
|
|
350
602
|
name: "duplicate-test-body",
|
|
603
|
+
documentation: duplicateTestBodyDocumentation,
|
|
351
604
|
meta: {
|
|
352
605
|
type: "suggestion",
|
|
353
606
|
docs: {
|
|
@@ -414,12 +667,43 @@ var duplicate_test_body_default = createRule({
|
|
|
414
667
|
|
|
415
668
|
// src/rules/no-async-callback-in-wait-for.ts
|
|
416
669
|
var import_utils4 = require("@typescript-eslint/utils");
|
|
670
|
+
var noAsyncCallbackInWaitForDocumentation = {
|
|
671
|
+
summary: "Disallow async callbacks in `waitFor` to prevent swallowed promise rejections.",
|
|
672
|
+
rationale: "`waitFor` retries synchronous assertions; an async callback changes that contract and can hide a rejected assertion promise.",
|
|
673
|
+
remediation: "Remove `async` and keep the assertions inside `waitFor` synchronous.",
|
|
674
|
+
category: "testing",
|
|
675
|
+
aliases: ["no-async-callback-in-waitfor"],
|
|
676
|
+
limitations: [
|
|
677
|
+
"The rule checks inline first-argument callbacks to bare or non-computed `.waitFor` calls in test files."
|
|
678
|
+
],
|
|
679
|
+
examples: [
|
|
680
|
+
{
|
|
681
|
+
id: "synchronous-wait-for-callback",
|
|
682
|
+
title: "waitFor retries a synchronous assertion",
|
|
683
|
+
outcome: "no-match",
|
|
684
|
+
files: [{ path: "src/component.test.ts", source: "it('works', async () => { await waitFor(() => expect(foo).toBe(true)); });" }],
|
|
685
|
+
focusPath: "src/component.test.ts",
|
|
686
|
+
expectedCount: 0,
|
|
687
|
+
public: true
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
id: "async-wait-for-callback",
|
|
691
|
+
title: "waitFor receives an async callback",
|
|
692
|
+
outcome: "match",
|
|
693
|
+
files: [{ path: "src/component.test.ts", source: "it('fails', async () => { await waitFor(async () => expect(foo).toBe(true)); });" }],
|
|
694
|
+
focusPath: "src/component.test.ts",
|
|
695
|
+
expectedCount: 1,
|
|
696
|
+
public: true
|
|
697
|
+
}
|
|
698
|
+
]
|
|
699
|
+
};
|
|
417
700
|
var isWaitForCallee = (callee) => {
|
|
418
701
|
if (callee.type === import_utils4.AST_NODE_TYPES.Identifier) return callee.name === "waitFor";
|
|
419
702
|
return callee.type === import_utils4.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils4.AST_NODE_TYPES.Identifier && callee.property.name === "waitFor";
|
|
420
703
|
};
|
|
421
704
|
var no_async_callback_in_wait_for_default = createRule({
|
|
422
705
|
name: "no-async-callback-in-wait-for",
|
|
706
|
+
documentation: noAsyncCallbackInWaitForDocumentation,
|
|
423
707
|
meta: {
|
|
424
708
|
type: "problem",
|
|
425
709
|
docs: {
|
|
@@ -452,6 +736,35 @@ var no_async_callback_in_wait_for_default = createRule({
|
|
|
452
736
|
|
|
453
737
|
// src/rules/no-client-side-data-fetching.ts
|
|
454
738
|
var import_utils5 = require("@typescript-eslint/utils");
|
|
739
|
+
var noClientSideDataFetchingDocumentation = {
|
|
740
|
+
summary: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`.",
|
|
741
|
+
rationale: "Effect-driven reads begin after rendering and can create request waterfalls, duplicate fetches, and loading-state layout shifts.",
|
|
742
|
+
remediation: "Fetch in a React Server Component or Server Action, or use a client cache such as SWR or React Query.",
|
|
743
|
+
category: "performance",
|
|
744
|
+
limitations: [
|
|
745
|
+
"The rule recognizes common fetch clients syntactically and exempts analytics endpoints and non-GET `fetch` calls."
|
|
746
|
+
],
|
|
747
|
+
examples: [
|
|
748
|
+
{
|
|
749
|
+
id: "effect-without-fetch",
|
|
750
|
+
title: "An effect performs no data request",
|
|
751
|
+
outcome: "no-match",
|
|
752
|
+
files: [{ path: "src/users.tsx", source: "import { useEffect } from 'react'; useEffect(() => { console.log('mounted'); }, []);" }],
|
|
753
|
+
focusPath: "src/users.tsx",
|
|
754
|
+
expectedCount: 0,
|
|
755
|
+
public: true
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
id: "fetch-inside-effect",
|
|
759
|
+
title: "An effect starts a data request",
|
|
760
|
+
outcome: "match",
|
|
761
|
+
files: [{ path: "src/users.tsx", source: "useEffect(() => { fetch('/api/users'); }, []);" }],
|
|
762
|
+
focusPath: "src/users.tsx",
|
|
763
|
+
expectedCount: 1,
|
|
764
|
+
public: true
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
};
|
|
455
768
|
var FETCH_LIBS = /* @__PURE__ */ new Set(["axios", "ky", "superagent"]);
|
|
456
769
|
var HTTP_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
457
770
|
"get",
|
|
@@ -550,10 +863,11 @@ function extractUrlString(node) {
|
|
|
550
863
|
}
|
|
551
864
|
var no_client_side_data_fetching_default = createRule({
|
|
552
865
|
name: "no-client-side-data-fetching",
|
|
866
|
+
documentation: noClientSideDataFetchingDocumentation,
|
|
553
867
|
meta: {
|
|
554
868
|
type: "problem",
|
|
555
869
|
docs: {
|
|
556
|
-
description: "Disallow data fetching inside `useEffect`
|
|
870
|
+
description: "Disallow direct data fetching inside `useEffect` or `useLayoutEffect`."
|
|
557
871
|
},
|
|
558
872
|
schema: [],
|
|
559
873
|
messages: {
|
|
@@ -774,6 +1088,35 @@ function headTokens(source) {
|
|
|
774
1088
|
}
|
|
775
1089
|
|
|
776
1090
|
// src/rules/no-comment-cruft.ts
|
|
1091
|
+
var noCommentCruftDocumentation = {
|
|
1092
|
+
summary: "Flag commented-out code, section-banner comments, and leading file-header comment preambles.",
|
|
1093
|
+
rationale: "Decorative, narrated, or dead-code comments obscure the constraints and rationale that comments should preserve.",
|
|
1094
|
+
remediation: "Delete dead code and narration; express boundaries with named code and retain only comments that explain constraints or intent.",
|
|
1095
|
+
category: "maintainability",
|
|
1096
|
+
limitations: [
|
|
1097
|
+
"The rule skips generated files and conservatively preserves prose, issue references, licenses, examples, and tool directives."
|
|
1098
|
+
],
|
|
1099
|
+
examples: [
|
|
1100
|
+
{
|
|
1101
|
+
id: "rationale-comment",
|
|
1102
|
+
title: "A comment explains why retry is required",
|
|
1103
|
+
outcome: "no-match",
|
|
1104
|
+
files: [{ path: "src/retry.ts", source: "// retry because the upstream API is flaky\nconst x = retry();" }],
|
|
1105
|
+
focusPath: "src/retry.ts",
|
|
1106
|
+
expectedCount: 0,
|
|
1107
|
+
public: true
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
id: "region-banner",
|
|
1111
|
+
title: "A region comment decorates a code boundary",
|
|
1112
|
+
outcome: "match",
|
|
1113
|
+
files: [{ path: "src/helpers.ts", source: "const x = 1;\n// region helpers\nconst y = 2;" }],
|
|
1114
|
+
focusPath: "src/helpers.ts",
|
|
1115
|
+
expectedCount: 1,
|
|
1116
|
+
public: true
|
|
1117
|
+
}
|
|
1118
|
+
]
|
|
1119
|
+
};
|
|
777
1120
|
var LEADING_PREAMBLE_MIN = 4;
|
|
778
1121
|
var WALL_MIN_STATEMENTS = 4;
|
|
779
1122
|
var WALL_MIN_COMMENTS = 3;
|
|
@@ -799,6 +1142,7 @@ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|a
|
|
|
799
1142
|
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;
|
|
800
1143
|
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;
|
|
801
1144
|
var FOR_NOW_RE = /\bfor now\b/i;
|
|
1145
|
+
var JSDOC_DEBT_RE = /^@?(?:todo|fixme)\b/i;
|
|
802
1146
|
var DEFERRAL_STOPWORDS = /* @__PURE__ */ new Set([
|
|
803
1147
|
"a",
|
|
804
1148
|
"an",
|
|
@@ -927,6 +1271,7 @@ var DIAGRAM_ARROW_RE = /[-=~]{2,}>|<[-=~]{2,}/;
|
|
|
927
1271
|
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\.)/;
|
|
928
1272
|
var CODE_TAIL_RE = /[;{}()]\s*$|=>\s*$|,\s*$/;
|
|
929
1273
|
var ASSIGN_RE = /^[A-Za-z_$][\w.$[\]]*\s*(?:=(?![=>])|\+=|-=|\*=)\s*\S.*[;)}\]]\s*$/;
|
|
1274
|
+
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)/;
|
|
930
1275
|
var CALL_RE = /^[A-Za-z_$][\w.$]*\([^)]*\)\s*;?\s*$/;
|
|
931
1276
|
var ASSERTION_CODE_RE = /^(?:await\s+)?(?:expect(?:TypeOf)?|assert(?:\.\w+)?)\s*\(/;
|
|
932
1277
|
var HTTP_CONTRACT_RE = /\b(?:GET|HEAD|OPTIONS|PATCH|POST|PUT|DELETE)\s+(?:https?:\/\/|\/|\{[A-Za-z_$])/;
|
|
@@ -954,6 +1299,7 @@ function looksLikeCode(text, allowCall = true) {
|
|
|
954
1299
|
if (!t) return false;
|
|
955
1300
|
if (PROSE_ASSIGNMENT_RE.test(t)) return false;
|
|
956
1301
|
if (CODE_KEYWORD_RE.test(t) && CODE_TAIL_RE.test(t)) return true;
|
|
1302
|
+
if (DECLARATION_RE.test(t)) return true;
|
|
957
1303
|
if (ASSIGN_RE.test(t)) return true;
|
|
958
1304
|
if (ASSERTION_CODE_RE.test(t)) return true;
|
|
959
1305
|
return allowCall && CALL_RE.test(t);
|
|
@@ -1142,6 +1488,7 @@ function hasCommentedOutCode(texts, precedingProse, allowCall) {
|
|
|
1142
1488
|
}
|
|
1143
1489
|
var no_comment_cruft_default = createRule({
|
|
1144
1490
|
name: "no-comment-cruft",
|
|
1491
|
+
documentation: noCommentCruftDocumentation,
|
|
1145
1492
|
meta: {
|
|
1146
1493
|
type: "suggestion",
|
|
1147
1494
|
docs: {
|
|
@@ -1282,6 +1629,11 @@ var no_comment_cruft_default = createRule({
|
|
|
1282
1629
|
}
|
|
1283
1630
|
if (wallMembers.has(comment)) continue;
|
|
1284
1631
|
if (isJsDoc(comment)) {
|
|
1632
|
+
const debt = comment.value.split("\n").map(stripCommentMarker).find((line) => JSDOC_DEBT_RE.test(line));
|
|
1633
|
+
if (debt !== void 0 && !runCitesAReference(comments, i)) {
|
|
1634
|
+
context.report({ node: comment, messageId: "untrackedTodo" });
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1285
1637
|
if (isStandalone(comment) && isSectionJsDoc(comment)) {
|
|
1286
1638
|
context.report({ node: comment, messageId: "sectionBanner" });
|
|
1287
1639
|
}
|
|
@@ -1348,6 +1700,35 @@ var no_comment_cruft_default = createRule({
|
|
|
1348
1700
|
|
|
1349
1701
|
// src/rules/no-conditional-in-test.ts
|
|
1350
1702
|
var import_utils8 = require("@typescript-eslint/utils");
|
|
1703
|
+
var noConditionalInTestDocumentation = {
|
|
1704
|
+
summary: "Disallow test conditionals that can skip a runtime assertion or exit the test before one runs.",
|
|
1705
|
+
rationale: "A branch can skip the assertion that gives a test its meaning, allowing unexpected inputs to pass silently.",
|
|
1706
|
+
remediation: "Split each path into a separate test or use a parameterized case table with unconditional assertions.",
|
|
1707
|
+
category: "testing",
|
|
1708
|
+
limitations: [
|
|
1709
|
+
"The rule exempts lifecycle hooks, nested helpers, and narrow guards whose outcome is pinned by a preceding assertion."
|
|
1710
|
+
],
|
|
1711
|
+
examples: [
|
|
1712
|
+
{
|
|
1713
|
+
id: "unconditional-assertion",
|
|
1714
|
+
title: "A test always executes its assertion",
|
|
1715
|
+
outcome: "no-match",
|
|
1716
|
+
files: [{ path: "src/component.test.ts", source: "it('works', () => { expect(1).toBe(1); });" }],
|
|
1717
|
+
focusPath: "src/component.test.ts",
|
|
1718
|
+
expectedCount: 0,
|
|
1719
|
+
public: true
|
|
1720
|
+
},
|
|
1721
|
+
{
|
|
1722
|
+
id: "conditional-assertion",
|
|
1723
|
+
title: "A branch can skip the assertion",
|
|
1724
|
+
outcome: "match",
|
|
1725
|
+
files: [{ path: "src/component.test.ts", source: "it('fails with if', () => { if (ready) { expect(value).toBe(1); } });" }],
|
|
1726
|
+
focusPath: "src/component.test.ts",
|
|
1727
|
+
expectedCount: 1,
|
|
1728
|
+
public: true
|
|
1729
|
+
}
|
|
1730
|
+
]
|
|
1731
|
+
};
|
|
1351
1732
|
var TEST_CALLERS2 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
1352
1733
|
var NON_TEST_MEMBERS = /* @__PURE__ */ new Set([
|
|
1353
1734
|
"afterAll",
|
|
@@ -1628,6 +2009,7 @@ function isShortCircuitedAssertion(node) {
|
|
|
1628
2009
|
}
|
|
1629
2010
|
var no_conditional_in_test_default = createRule({
|
|
1630
2011
|
name: "no-conditional-in-test",
|
|
2012
|
+
documentation: noConditionalInTestDocumentation,
|
|
1631
2013
|
meta: {
|
|
1632
2014
|
type: "problem",
|
|
1633
2015
|
docs: {
|
|
@@ -1675,6 +2057,35 @@ var no_conditional_in_test_default = createRule({
|
|
|
1675
2057
|
|
|
1676
2058
|
// src/rules/no-cors-wildcard-with-credentials.ts
|
|
1677
2059
|
var import_utils9 = require("@typescript-eslint/utils");
|
|
2060
|
+
var noCorsWildcardWithCredentialsDocumentation = {
|
|
2061
|
+
summary: "Disallow wildcard CORS origins when credentials are enabled.",
|
|
2062
|
+
rationale: "Reflecting every origin while allowing credentials can let an untrusted site read authenticated cross-origin responses.",
|
|
2063
|
+
remediation: "Enumerate the trusted origins that may receive credentialed responses.",
|
|
2064
|
+
category: "security",
|
|
2065
|
+
limitations: [
|
|
2066
|
+
"The rule detects literal CORS option and header combinations within the same syntactic scope; it does not resolve runtime configuration."
|
|
2067
|
+
],
|
|
2068
|
+
examples: [
|
|
2069
|
+
{
|
|
2070
|
+
id: "trusted-origin-with-credentials",
|
|
2071
|
+
title: "Credentials are limited to a trusted origin",
|
|
2072
|
+
outcome: "no-match",
|
|
2073
|
+
files: [{ path: "src/server.ts", source: "app.use(cors({ origin: 'https://app.example.com', credentials: true }));" }],
|
|
2074
|
+
focusPath: "src/server.ts",
|
|
2075
|
+
expectedCount: 0,
|
|
2076
|
+
public: true
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
id: "wildcard-origin-with-credentials",
|
|
2080
|
+
title: "Credentials are enabled for every origin",
|
|
2081
|
+
outcome: "match",
|
|
2082
|
+
files: [{ path: "src/server.ts", source: "app.use(cors({ origin: '*', credentials: true }));" }],
|
|
2083
|
+
focusPath: "src/server.ts",
|
|
2084
|
+
expectedCount: 1,
|
|
2085
|
+
public: true
|
|
2086
|
+
}
|
|
2087
|
+
]
|
|
2088
|
+
};
|
|
1678
2089
|
var ACAO_HEADER = "access-control-allow-origin";
|
|
1679
2090
|
var ACAC_HEADER = "access-control-allow-credentials";
|
|
1680
2091
|
var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
|
|
@@ -1818,10 +2229,11 @@ function enclosingScope(node) {
|
|
|
1818
2229
|
}
|
|
1819
2230
|
var no_cors_wildcard_with_credentials_default = createRule({
|
|
1820
2231
|
name: "no-cors-wildcard-with-credentials",
|
|
2232
|
+
documentation: noCorsWildcardWithCredentialsDocumentation,
|
|
1821
2233
|
meta: {
|
|
1822
2234
|
type: "problem",
|
|
1823
2235
|
docs: {
|
|
1824
|
-
description:
|
|
2236
|
+
description: "Disallow wildcard CORS origins when credentials are enabled."
|
|
1825
2237
|
},
|
|
1826
2238
|
schema: [],
|
|
1827
2239
|
messages: {
|
|
@@ -2028,6 +2440,35 @@ function createSqlListener(handler) {
|
|
|
2028
2440
|
}
|
|
2029
2441
|
|
|
2030
2442
|
// src/rules/no-dynamic-sql.ts
|
|
2443
|
+
var noDynamicSqlDocumentation = {
|
|
2444
|
+
summary: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods.",
|
|
2445
|
+
rationale: "Embedding runtime values in SQL bypasses driver parameterization and can introduce injection defects or unstable query plans.",
|
|
2446
|
+
remediation: "Use SQL placeholders and pass runtime values through the driver's binding API.",
|
|
2447
|
+
category: "security",
|
|
2448
|
+
limitations: [
|
|
2449
|
+
"The rule recognizes SQL by syntax and configured method names; static fragments and parameterizing tagged templates are exempt."
|
|
2450
|
+
],
|
|
2451
|
+
examples: [
|
|
2452
|
+
{
|
|
2453
|
+
id: "bound-sql-parameter",
|
|
2454
|
+
title: "A runtime value is bound separately",
|
|
2455
|
+
outcome: "no-match",
|
|
2456
|
+
files: [{ path: "src/users.ts", source: "db.prepare('select * from users where id = ?').bind(userId);" }],
|
|
2457
|
+
focusPath: "src/users.ts",
|
|
2458
|
+
expectedCount: 0,
|
|
2459
|
+
public: true
|
|
2460
|
+
},
|
|
2461
|
+
{
|
|
2462
|
+
id: "interpolated-sql-value",
|
|
2463
|
+
title: "A runtime value is interpolated into SQL",
|
|
2464
|
+
outcome: "match",
|
|
2465
|
+
files: [{ path: "src/users.ts", source: "db.prepare(`select * from users where id = '${userId}'`);" }],
|
|
2466
|
+
focusPath: "src/users.ts",
|
|
2467
|
+
expectedCount: 1,
|
|
2468
|
+
public: true
|
|
2469
|
+
}
|
|
2470
|
+
]
|
|
2471
|
+
};
|
|
2031
2472
|
var DEFAULT_METHODS = ["prepare", "exec", "query"];
|
|
2032
2473
|
var CONSTANT_CASE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
2033
2474
|
function isStaticFragment(expression) {
|
|
@@ -2095,10 +2536,11 @@ function statementMethodName(node, methods) {
|
|
|
2095
2536
|
}
|
|
2096
2537
|
var no_dynamic_sql_default = createRule({
|
|
2097
2538
|
name: "no-dynamic-sql",
|
|
2539
|
+
documentation: noDynamicSqlDocumentation,
|
|
2098
2540
|
meta: {
|
|
2099
2541
|
type: "problem",
|
|
2100
2542
|
docs: {
|
|
2101
|
-
description: "Disallow
|
|
2543
|
+
description: "Disallow runtime interpolation or concatenation in SQL passed to statement-execution methods."
|
|
2102
2544
|
},
|
|
2103
2545
|
schema: [
|
|
2104
2546
|
{
|
|
@@ -2145,6 +2587,32 @@ var no_dynamic_sql_default = createRule({
|
|
|
2145
2587
|
|
|
2146
2588
|
// src/rules/no-enum.ts
|
|
2147
2589
|
var import_utils12 = require("@typescript-eslint/utils");
|
|
2590
|
+
var noEnumDocumentation = {
|
|
2591
|
+
summary: "Disallow TypeScript `enum`; use string-literal unions or `as const` objects instead.",
|
|
2592
|
+
rationale: "TypeScript enums emit runtime objects and numeric enums accept values outside their declared members, adding behavior where a type-only model is sufficient.",
|
|
2593
|
+
remediation: "Replace the enum with a string-literal union or an `as const` object and derive its value type from that object.",
|
|
2594
|
+
category: "maintainability",
|
|
2595
|
+
examples: [
|
|
2596
|
+
{
|
|
2597
|
+
id: "string-literal-union",
|
|
2598
|
+
title: "A string-literal union has no emitted runtime enum",
|
|
2599
|
+
outcome: "no-match",
|
|
2600
|
+
files: [{ path: "src/status.ts", source: 'type Status = "active" | "inactive";' }],
|
|
2601
|
+
focusPath: "src/status.ts",
|
|
2602
|
+
expectedCount: 0,
|
|
2603
|
+
public: true
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
id: "numeric-enum",
|
|
2607
|
+
title: "A numeric enum emits a mutable runtime object",
|
|
2608
|
+
outcome: "match",
|
|
2609
|
+
files: [{ path: "src/status.ts", source: "enum Status { Active, Inactive }" }],
|
|
2610
|
+
focusPath: "src/status.ts",
|
|
2611
|
+
expectedCount: 1,
|
|
2612
|
+
public: true
|
|
2613
|
+
}
|
|
2614
|
+
]
|
|
2615
|
+
};
|
|
2148
2616
|
function matchesAnyPattern(filename, patterns) {
|
|
2149
2617
|
for (const pattern of patterns) {
|
|
2150
2618
|
const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
|
|
@@ -2156,6 +2624,7 @@ function matchesAnyPattern(filename, patterns) {
|
|
|
2156
2624
|
}
|
|
2157
2625
|
var no_enum_default = createRule({
|
|
2158
2626
|
name: "no-enum",
|
|
2627
|
+
documentation: noEnumDocumentation,
|
|
2159
2628
|
meta: {
|
|
2160
2629
|
type: "suggestion",
|
|
2161
2630
|
docs: {
|
|
@@ -2201,6 +2670,41 @@ var no_enum_default = createRule({
|
|
|
2201
2670
|
|
|
2202
2671
|
// src/rules/no-fat-try-blocks.ts
|
|
2203
2672
|
var import_utils13 = require("@typescript-eslint/utils");
|
|
2673
|
+
var noFatTryBlocksDocumentation = {
|
|
2674
|
+
summary: "Disallow `try` blocks containing more than three top-level operations that can throw.",
|
|
2675
|
+
rationale: "A broad `try` block obscures which operation failed and encourages one catch clause to recover from unrelated errors.",
|
|
2676
|
+
remediation: "Keep only the operations that share one recovery policy inside the `try` block and move other work outside it.",
|
|
2677
|
+
category: "correctness",
|
|
2678
|
+
limitations: [
|
|
2679
|
+
"The rule uses syntax to identify throwing operations and exempts generated files, finally blocks, rethrows, and terminal error boundaries."
|
|
2680
|
+
],
|
|
2681
|
+
examples: [
|
|
2682
|
+
{
|
|
2683
|
+
id: "focused-try-block",
|
|
2684
|
+
title: "A try block contains three throwing operations",
|
|
2685
|
+
outcome: "no-match",
|
|
2686
|
+
files: [{
|
|
2687
|
+
path: "src/load.ts",
|
|
2688
|
+
source: "function f() { try { const a = one(); const b = two(); const c = three(); } catch (error) { handle(error); } finish(); }"
|
|
2689
|
+
}],
|
|
2690
|
+
focusPath: "src/load.ts",
|
|
2691
|
+
expectedCount: 0,
|
|
2692
|
+
public: true
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
id: "broad-try-block",
|
|
2696
|
+
title: "A try block contains four throwing operations",
|
|
2697
|
+
outcome: "match",
|
|
2698
|
+
files: [{
|
|
2699
|
+
path: "src/load.ts",
|
|
2700
|
+
source: "function f() { try { const a = one(); const b = two(); const c = three(); const d = four(); } catch (error) { handle(error); } finish(); }"
|
|
2701
|
+
}],
|
|
2702
|
+
focusPath: "src/load.ts",
|
|
2703
|
+
expectedCount: 1,
|
|
2704
|
+
public: true
|
|
2705
|
+
}
|
|
2706
|
+
]
|
|
2707
|
+
};
|
|
2204
2708
|
var MAX_TRY_BODY_STATEMENTS = 3;
|
|
2205
2709
|
var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
2206
2710
|
import_utils13.AST_NODE_TYPES.FunctionDeclaration,
|
|
@@ -2536,10 +3040,11 @@ var handlerReturnsSuccessShaped = (handler) => subtreeMatches2(
|
|
|
2536
3040
|
);
|
|
2537
3041
|
var no_fat_try_blocks_default = createRule({
|
|
2538
3042
|
name: "no-fat-try-blocks",
|
|
3043
|
+
documentation: noFatTryBlocksDocumentation,
|
|
2539
3044
|
meta: {
|
|
2540
3045
|
type: "problem",
|
|
2541
3046
|
docs: {
|
|
2542
|
-
description: "Disallow `try` blocks
|
|
3047
|
+
description: "Disallow `try` blocks containing more than three top-level operations that can throw."
|
|
2543
3048
|
},
|
|
2544
3049
|
schema: [],
|
|
2545
3050
|
messages: {
|
|
@@ -2580,6 +3085,38 @@ var no_fat_try_blocks_default = createRule({
|
|
|
2580
3085
|
|
|
2581
3086
|
// src/rules/no-hand-rolled-sleep.ts
|
|
2582
3087
|
var import_utils14 = require("@typescript-eslint/utils");
|
|
3088
|
+
var noHandRolledSleepDocumentation = {
|
|
3089
|
+
summary: "Disallow uncancellable promisified timers and timeout arms.",
|
|
3090
|
+
rationale: "A timer that outlives an aborted operation or a lost promise race retains work and can keep the process alive until it fires.",
|
|
3091
|
+
remediation: "Use `node:timers/promises` with an abort signal for delays, or pass `AbortSignal.timeout(...)` to the timed operation.",
|
|
3092
|
+
category: "correctness",
|
|
3093
|
+
limitations: [
|
|
3094
|
+
"The rule skips tests, scripts, generated files, and client modules by default, and supports explicit path exemptions."
|
|
3095
|
+
],
|
|
3096
|
+
examples: [
|
|
3097
|
+
{
|
|
3098
|
+
id: "cancellable-node-timer",
|
|
3099
|
+
title: "A standard-library timer accepts an abort signal",
|
|
3100
|
+
outcome: "no-match",
|
|
3101
|
+
files: [{
|
|
3102
|
+
path: "src/lib/queue.ts",
|
|
3103
|
+
source: 'import { setTimeout as sleep } from "node:timers/promises";\nawait sleep(500, undefined, { signal });'
|
|
3104
|
+
}],
|
|
3105
|
+
focusPath: "src/lib/queue.ts",
|
|
3106
|
+
expectedCount: 0,
|
|
3107
|
+
public: true
|
|
3108
|
+
},
|
|
3109
|
+
{
|
|
3110
|
+
id: "uncancellable-sleep",
|
|
3111
|
+
title: "A Promise wraps a timer without cancellation",
|
|
3112
|
+
outcome: "match",
|
|
3113
|
+
files: [{ path: "src/lib/queue.ts", source: "await new Promise((resolve) => setTimeout(resolve, 500));" }],
|
|
3114
|
+
focusPath: "src/lib/queue.ts",
|
|
3115
|
+
expectedCount: 1,
|
|
3116
|
+
public: true
|
|
3117
|
+
}
|
|
3118
|
+
]
|
|
3119
|
+
};
|
|
2583
3120
|
var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
|
|
2584
3121
|
"globalThis",
|
|
2585
3122
|
"window",
|
|
@@ -2659,10 +3196,11 @@ function isRaceArm(node) {
|
|
|
2659
3196
|
}
|
|
2660
3197
|
var no_hand_rolled_sleep_default = createRule({
|
|
2661
3198
|
name: "no-hand-rolled-sleep",
|
|
3199
|
+
documentation: noHandRolledSleepDocumentation,
|
|
2662
3200
|
meta: {
|
|
2663
3201
|
type: "problem",
|
|
2664
3202
|
docs: {
|
|
2665
|
-
description: "Disallow
|
|
3203
|
+
description: "Disallow uncancellable promisified timers and timeout arms."
|
|
2666
3204
|
},
|
|
2667
3205
|
schema: [
|
|
2668
3206
|
{
|
|
@@ -2755,6 +3293,17 @@ var no_hand_rolled_sleep_default = createRule({
|
|
|
2755
3293
|
|
|
2756
3294
|
// src/rules/no-hand-rolled-spinner.ts
|
|
2757
3295
|
var import_utils15 = require("@typescript-eslint/utils");
|
|
3296
|
+
var noHandRolledSpinnerDocumentation = {
|
|
3297
|
+
summary: "Disallow intrinsic elements styled as Tailwind border-ring spinners outside the design-system implementation.",
|
|
3298
|
+
rationale: "One-off loading indicators duplicate a shared primitive and let accessibility and styling diverge.",
|
|
3299
|
+
remediation: "Render the design-system Spinner component instead.",
|
|
3300
|
+
category: "maintainability",
|
|
3301
|
+
limitations: ["Only static className values on div and span elements are inspected."],
|
|
3302
|
+
examples: [
|
|
3303
|
+
{ 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 },
|
|
3304
|
+
{ 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 }
|
|
3305
|
+
]
|
|
3306
|
+
};
|
|
2758
3307
|
var DESIGN_SYSTEM_PATH = /(?:^|[/\\])components[/\\]ui[/\\]/u;
|
|
2759
3308
|
var BORDER_WIDTH = /^border(?:-[0-9]+)?$/u;
|
|
2760
3309
|
var TRANSPARENT_EDGE = /^border-[trbl]-transparent$/u;
|
|
@@ -2770,6 +3319,7 @@ function staticClassName(attribute) {
|
|
|
2770
3319
|
}
|
|
2771
3320
|
var no_hand_rolled_spinner_default = createRule({
|
|
2772
3321
|
name: "no-hand-rolled-spinner",
|
|
3322
|
+
documentation: noHandRolledSpinnerDocumentation,
|
|
2773
3323
|
meta: {
|
|
2774
3324
|
type: "suggestion",
|
|
2775
3325
|
docs: {
|
|
@@ -2807,6 +3357,17 @@ var no_hand_rolled_spinner_default = createRule({
|
|
|
2807
3357
|
|
|
2808
3358
|
// src/rules/no-insecure-random-id.ts
|
|
2809
3359
|
var import_utils16 = require("@typescript-eslint/utils");
|
|
3360
|
+
var noInsecureRandomIdDocumentation = {
|
|
3361
|
+
summary: "Disallow using `Math.random()` to generate identifiers, tokens, or secrets; use `crypto.randomUUID()` or `crypto.getRandomValues(...)` instead.",
|
|
3362
|
+
rationale: "Math.random is predictable and lacks the entropy required for security-sensitive values.",
|
|
3363
|
+
remediation: "Generate the value with crypto.randomUUID or crypto.getRandomValues.",
|
|
3364
|
+
category: "security",
|
|
3365
|
+
limitations: ["Ambiguous identifiers and test files are excluded to avoid flagging sampling and fixture data."],
|
|
3366
|
+
examples: [
|
|
3367
|
+
{ 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 },
|
|
3368
|
+
{ 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 }
|
|
3369
|
+
]
|
|
3370
|
+
};
|
|
2810
3371
|
var STRONG_SECURITY_PATTERN = /token|secret|csrf|password|passwd|apikey|api[-_]?key|nonce|salt|uuid|authid/i;
|
|
2811
3372
|
var NON_SECURITY_ID_PATTERN = /temp|tmp|cache|correlation|request|req|trace|execution|dev|hmr|mock|test|perf|marker/i;
|
|
2812
3373
|
var PATH_OR_DOM_MARKER = /[\\/#]|\.[A-Za-z0-9]/;
|
|
@@ -2949,6 +3510,7 @@ function collectStaticStringParts(node, out) {
|
|
|
2949
3510
|
}
|
|
2950
3511
|
var no_insecure_random_id_default = createRule({
|
|
2951
3512
|
name: "no-insecure-random-id",
|
|
3513
|
+
documentation: noInsecureRandomIdDocumentation,
|
|
2952
3514
|
meta: {
|
|
2953
3515
|
type: "problem",
|
|
2954
3516
|
docs: {
|
|
@@ -2990,6 +3552,17 @@ var no_insecure_random_id_default = createRule({
|
|
|
2990
3552
|
|
|
2991
3553
|
// src/rules/no-json-stringify-error.ts
|
|
2992
3554
|
var import_utils17 = require("@typescript-eslint/utils");
|
|
3555
|
+
var noJsonStringifyErrorDocumentation = {
|
|
3556
|
+
summary: "Disallow `JSON.stringify` on an Error value; it yields `{}` because `message`/`stack` are non-enumerable.",
|
|
3557
|
+
rationale: "Native Error details are non-enumerable, so generic JSON serialization discards diagnostic information.",
|
|
3558
|
+
remediation: "Serialize explicit error fields or use an error-aware serializer.",
|
|
3559
|
+
category: "correctness",
|
|
3560
|
+
limitations: ["The rule uses local syntax and naming evidence rather than type information."],
|
|
3561
|
+
examples: [
|
|
3562
|
+
{ 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 },
|
|
3563
|
+
{ 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 }
|
|
3564
|
+
]
|
|
3565
|
+
};
|
|
2993
3566
|
var ERROR_NAME_PATTERN = /^(e|err|error|ex|exc)$/i;
|
|
2994
3567
|
var ERROR_PROP_PATTERN = /^(cause|lastError|error|err|exception|originalError|innerError)$/i;
|
|
2995
3568
|
var SAFE_STRING_PROPS = /* @__PURE__ */ new Set(["message", "stack", "name"]);
|
|
@@ -3177,6 +3750,7 @@ function nestedExpressionSuggestsError(expression, scope) {
|
|
|
3177
3750
|
}
|
|
3178
3751
|
var no_json_stringify_error_default = createRule({
|
|
3179
3752
|
name: "no-json-stringify-error",
|
|
3753
|
+
documentation: noJsonStringifyErrorDocumentation,
|
|
3180
3754
|
meta: {
|
|
3181
3755
|
type: "problem",
|
|
3182
3756
|
docs: {
|
|
@@ -3227,6 +3801,47 @@ function isZodModule(source) {
|
|
|
3227
3801
|
}
|
|
3228
3802
|
|
|
3229
3803
|
// src/rules/no-impossible-zod-literal-bounds.ts
|
|
3804
|
+
var noImpossibleZodLiteralBoundsDocumentation = {
|
|
3805
|
+
summary: "Disallow same-chain literal Zod bounds whose accepted set is mathematically empty.",
|
|
3806
|
+
rationale: "A schema with contradictory literal bounds rejects every input, turning validation into an unreachable contract that usually reflects a typo.",
|
|
3807
|
+
remediation: "Choose compatible lower and upper bounds, or remove the constraint that does not express the intended domain.",
|
|
3808
|
+
category: "correctness",
|
|
3809
|
+
limitations: [
|
|
3810
|
+
"Only finite numeric literals in a single number, string, or array schema chain are compared.",
|
|
3811
|
+
"Chains with dynamic bounds, non-bound validators, transforms, pipes, or preprocessors are skipped.",
|
|
3812
|
+
"Test and generated files are excluded."
|
|
3813
|
+
],
|
|
3814
|
+
examples: [
|
|
3815
|
+
{
|
|
3816
|
+
id: "compatible-number-bounds",
|
|
3817
|
+
title: "Allow a number admitted by both bounds",
|
|
3818
|
+
outcome: "no-match",
|
|
3819
|
+
files: [
|
|
3820
|
+
{
|
|
3821
|
+
path: "src/schema.ts",
|
|
3822
|
+
source: 'import { z } from "zod"; const S = z.number().gte(3).lte(3);'
|
|
3823
|
+
}
|
|
3824
|
+
],
|
|
3825
|
+
focusPath: "src/schema.ts",
|
|
3826
|
+
expectedCount: 0,
|
|
3827
|
+
public: true
|
|
3828
|
+
},
|
|
3829
|
+
{
|
|
3830
|
+
id: "contradictory-number-bounds",
|
|
3831
|
+
title: "Reject an empty numeric interval",
|
|
3832
|
+
outcome: "match",
|
|
3833
|
+
files: [
|
|
3834
|
+
{
|
|
3835
|
+
path: "src/schema.ts",
|
|
3836
|
+
source: 'import { z } from "zod"; const S = z.number().min(5).max(4);'
|
|
3837
|
+
}
|
|
3838
|
+
],
|
|
3839
|
+
focusPath: "src/schema.ts",
|
|
3840
|
+
expectedCount: 1,
|
|
3841
|
+
public: true
|
|
3842
|
+
}
|
|
3843
|
+
]
|
|
3844
|
+
};
|
|
3230
3845
|
var KINDS = /* @__PURE__ */ new Set(["array", "number", "string"]);
|
|
3231
3846
|
var NUMBER_METHODS = /* @__PURE__ */ new Set([
|
|
3232
3847
|
"gt",
|
|
@@ -3286,6 +3901,7 @@ function isOutermostCall(node) {
|
|
|
3286
3901
|
}
|
|
3287
3902
|
var no_impossible_zod_literal_bounds_default = createRule({
|
|
3288
3903
|
name: "no-impossible-zod-literal-bounds",
|
|
3904
|
+
documentation: noImpossibleZodLiteralBoundsDocumentation,
|
|
3289
3905
|
meta: {
|
|
3290
3906
|
type: "problem",
|
|
3291
3907
|
docs: {
|
|
@@ -3543,6 +4159,17 @@ function createLogMatcher(options = {}) {
|
|
|
3543
4159
|
}
|
|
3544
4160
|
|
|
3545
4161
|
// src/rules/no-log-only-catch.ts
|
|
4162
|
+
var noLogOnlyCatchDocumentation = {
|
|
4163
|
+
summary: "Disallow `catch` clauses that only log (or silently do nothing) and then swallow the error; rethrow or handle it instead.",
|
|
4164
|
+
rationale: "Swallowing an exception after logging lets execution continue as if the operation succeeded.",
|
|
4165
|
+
remediation: "Rethrow the error, return an explicit fallback, or perform concrete recovery.",
|
|
4166
|
+
category: "correctness",
|
|
4167
|
+
limitations: ["Documented intentional ignores, tests, and catches with observable recovery are excluded."],
|
|
4168
|
+
examples: [
|
|
4169
|
+
{ 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 },
|
|
4170
|
+
{ 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 }
|
|
4171
|
+
]
|
|
4172
|
+
};
|
|
3546
4173
|
var BENCHMARK_DIR_RE = /(?:^|[\\/])benchmarks?[\\/]/;
|
|
3547
4174
|
var SINGLE_STATEMENT_HOSTS = /* @__PURE__ */ new Set([
|
|
3548
4175
|
import_utils20.AST_NODE_TYPES.DoWhileStatement,
|
|
@@ -3630,6 +4257,7 @@ function isSeedValue(node) {
|
|
|
3630
4257
|
}
|
|
3631
4258
|
var no_log_only_catch_default = createRule({
|
|
3632
4259
|
name: "no-log-only-catch",
|
|
4260
|
+
documentation: noLogOnlyCatchDocumentation,
|
|
3633
4261
|
meta: {
|
|
3634
4262
|
type: "problem",
|
|
3635
4263
|
docs: {
|
|
@@ -3811,6 +4439,17 @@ function typedFunction(node) {
|
|
|
3811
4439
|
}
|
|
3812
4440
|
|
|
3813
4441
|
// src/rules/no-long-comment.ts
|
|
4442
|
+
var noLongCommentDocumentation = {
|
|
4443
|
+
summary: "Flag unusually large unstructured JSDoc blocks in implementation code.",
|
|
4444
|
+
rationale: "Large narrative comments become stale and obscure the local facts that belong beside the code.",
|
|
4445
|
+
remediation: "Keep only durable local constraints and express the remaining behavior in code.",
|
|
4446
|
+
category: "maintainability",
|
|
4447
|
+
limitations: ["Only JSDoc blocks are inspected; structured API docs, tests, scripts, generated files, and versioned dependencies are excluded."],
|
|
4448
|
+
examples: [
|
|
4449
|
+
{ 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 },
|
|
4450
|
+
{ 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 }
|
|
4451
|
+
]
|
|
4452
|
+
};
|
|
3814
4453
|
var EXCESSIVE_SENTENCE_COUNT = 8;
|
|
3815
4454
|
var EXCESSIVE_WORD_COUNT = 120;
|
|
3816
4455
|
var PROSE_WORD_RE = /[\p{L}\p{N}][\p{L}\p{N}'’-]*/gu;
|
|
@@ -3830,9 +4469,10 @@ function wordUnits(text) {
|
|
|
3830
4469
|
}
|
|
3831
4470
|
var no_long_comment_default = createRule({
|
|
3832
4471
|
name: "no-long-comment",
|
|
4472
|
+
documentation: noLongCommentDocumentation,
|
|
3833
4473
|
meta: {
|
|
3834
4474
|
type: "suggestion",
|
|
3835
|
-
docs: { description: "Flag unusually large unstructured
|
|
4475
|
+
docs: { description: "Flag unusually large unstructured JSDoc blocks in implementation code." },
|
|
3836
4476
|
schema: [],
|
|
3837
4477
|
messages: {
|
|
3838
4478
|
tooLong: "Comment is an unusually large prose block \u2014 keep the local facts and clarify the code itself."
|
|
@@ -3855,6 +4495,17 @@ var no_long_comment_default = createRule({
|
|
|
3855
4495
|
|
|
3856
4496
|
// src/rules/no-generic-single-export-module.ts
|
|
3857
4497
|
var import_utils23 = require("@typescript-eslint/utils");
|
|
4498
|
+
var noGenericSingleExportModuleDocumentation = {
|
|
4499
|
+
summary: "Disallow generic module stems when one runtime export already names the responsibility.",
|
|
4500
|
+
rationale: "A generic filename hides the sole exported responsibility and makes navigation less descriptive.",
|
|
4501
|
+
remediation: "Rename the module after its single runtime export.",
|
|
4502
|
+
category: "maintainability",
|
|
4503
|
+
limitations: ["Only configured generic stems with exactly one public runtime export are reported."],
|
|
4504
|
+
examples: [
|
|
4505
|
+
{ 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 },
|
|
4506
|
+
{ 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 }
|
|
4507
|
+
]
|
|
4508
|
+
};
|
|
3858
4509
|
var GENERIC_STEMS = /* @__PURE__ */ new Set([
|
|
3859
4510
|
"base",
|
|
3860
4511
|
"common",
|
|
@@ -4017,6 +4668,7 @@ function memberPropertyName(node) {
|
|
|
4017
4668
|
}
|
|
4018
4669
|
var no_generic_single_export_module_default = createRule({
|
|
4019
4670
|
name: "no-generic-single-export-module",
|
|
4671
|
+
documentation: noGenericSingleExportModuleDocumentation,
|
|
4020
4672
|
meta: {
|
|
4021
4673
|
type: "suggestion",
|
|
4022
4674
|
docs: { description: "Disallow generic module stems when one runtime export already names the responsibility." },
|
|
@@ -4069,10 +4721,22 @@ var no_generic_single_export_module_default = createRule({
|
|
|
4069
4721
|
|
|
4070
4722
|
// src/rules/no-offset-pagination.ts
|
|
4071
4723
|
var import_utils24 = require("@typescript-eslint/utils");
|
|
4724
|
+
var noOffsetPaginationDocumentation = {
|
|
4725
|
+
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.",
|
|
4726
|
+
rationale: "Offset pagination scans skipped rows and shifts page boundaries under concurrent writes.",
|
|
4727
|
+
remediation: "Page with a stable ordered key and a cursor predicate.",
|
|
4728
|
+
category: "performance",
|
|
4729
|
+
limitations: ["Only embedded SQL is inspected; test files and non-pagination OFFSET syntax are excluded."],
|
|
4730
|
+
examples: [
|
|
4731
|
+
{ 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 },
|
|
4732
|
+
{ 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 }
|
|
4733
|
+
]
|
|
4734
|
+
};
|
|
4072
4735
|
var OFFSET_PAGINATION = /\bOFFSET\s+(?:%s|%\(\w+\)s|\?\d*|:\w+|@\w+|\$\d+|\d+)/i;
|
|
4073
4736
|
var OFFSET_GATE = /offset/i;
|
|
4074
4737
|
var no_offset_pagination_default = createRule({
|
|
4075
4738
|
name: "no-offset-pagination",
|
|
4739
|
+
documentation: noOffsetPaginationDocumentation,
|
|
4076
4740
|
meta: {
|
|
4077
4741
|
type: "problem",
|
|
4078
4742
|
docs: {
|
|
@@ -4099,6 +4763,17 @@ var no_offset_pagination_default = createRule({
|
|
|
4099
4763
|
|
|
4100
4764
|
// src/rules/no-positional-tuple-return.ts
|
|
4101
4765
|
var import_utils25 = require("@typescript-eslint/utils");
|
|
4766
|
+
var noPositionalTupleReturnDocumentation = {
|
|
4767
|
+
summary: "Disallow returning a multi-field tuple from an exported function; return a named object so call sites cannot mismatch slots.",
|
|
4768
|
+
rationale: "Public tuple fields are identified only by position, so reordering can preserve types while changing meaning.",
|
|
4769
|
+
remediation: "Return an object whose property names describe each value.",
|
|
4770
|
+
category: "maintainability",
|
|
4771
|
+
limitations: ["Only declared multi-field tuple returns on public TypeScript surfaces are inspected."],
|
|
4772
|
+
examples: [
|
|
4773
|
+
{ 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 },
|
|
4774
|
+
{ 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 }
|
|
4775
|
+
]
|
|
4776
|
+
};
|
|
4102
4777
|
var MIN_ELEMENTS = 2;
|
|
4103
4778
|
var AWAITABLE_TYPES = /* @__PURE__ */ new Set(["Promise", "PromiseLike", "Awaited", "Readonly"]);
|
|
4104
4779
|
function staticMemberName2(key) {
|
|
@@ -4323,6 +4998,7 @@ function isExported(node, specifierExports) {
|
|
|
4323
4998
|
}
|
|
4324
4999
|
var no_positional_tuple_return_default = createRule({
|
|
4325
5000
|
name: "no-positional-tuple-return",
|
|
5001
|
+
documentation: noPositionalTupleReturnDocumentation,
|
|
4326
5002
|
meta: {
|
|
4327
5003
|
type: "suggestion",
|
|
4328
5004
|
docs: {
|
|
@@ -4419,6 +5095,17 @@ var no_positional_tuple_return_default = createRule({
|
|
|
4419
5095
|
|
|
4420
5096
|
// src/rules/no-raw-env.ts
|
|
4421
5097
|
var import_utils26 = require("@typescript-eslint/utils");
|
|
5098
|
+
var noRawEnvDocumentation = {
|
|
5099
|
+
summary: "Disallow direct `process.env` and `import.meta.env` reads outside validated boundaries.",
|
|
5100
|
+
rationale: "Raw environment reads are untyped and defer invalid configuration failures until use.",
|
|
5101
|
+
remediation: "Validate environment values at startup and import the typed configuration object.",
|
|
5102
|
+
category: "correctness",
|
|
5103
|
+
limitations: ["Host markers, assignment targets, tests, scripts, build config, and validated boundaries are excluded."],
|
|
5104
|
+
examples: [
|
|
5105
|
+
{ 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 },
|
|
5106
|
+
{ 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 }
|
|
5107
|
+
]
|
|
5108
|
+
};
|
|
4422
5109
|
var CONFIG_FILE_RE = /(^|[\\/])[\w.-]+\.config\.[cm]?[jt]sx?$/;
|
|
4423
5110
|
var ENV_BOUNDARY_FILE_RE = /(^|[\\/])(?:env|client-env|server-env|client-settings|server-settings)\.[cm]?[jt]sx?$/;
|
|
4424
5111
|
var ENV_VALIDATION_MARKER_RE = /\bcreateEnv\s*\(|\bz\.object\s*\(|\.(?:safeParse|parse)\s*\(/;
|
|
@@ -4465,6 +5152,7 @@ function isWholeEnvSpread(node) {
|
|
|
4465
5152
|
}
|
|
4466
5153
|
var no_raw_env_default = createRule({
|
|
4467
5154
|
name: "no-raw-env",
|
|
5155
|
+
documentation: noRawEnvDocumentation,
|
|
4468
5156
|
meta: {
|
|
4469
5157
|
type: "problem",
|
|
4470
5158
|
docs: {
|
|
@@ -4496,6 +5184,17 @@ var no_raw_env_default = createRule({
|
|
|
4496
5184
|
|
|
4497
5185
|
// src/rules/no-raw-fetch-outside-clients.ts
|
|
4498
5186
|
var import_utils27 = require("@typescript-eslint/utils");
|
|
5187
|
+
var noRawFetchOutsideClientsDocumentation = {
|
|
5188
|
+
summary: "Disallow calling the global `fetch` outside the client layer; route outbound HTTP through a client module that owns retry, timeout and status handling.",
|
|
5189
|
+
rationale: "Scattered fetch calls bypass shared transport policy and are harder to stub and observe consistently.",
|
|
5190
|
+
remediation: "Move the request into a client module and call that abstraction from application code.",
|
|
5191
|
+
category: "architecture",
|
|
5192
|
+
limitations: ["Tests, client-layer paths, constructed handoffs, and pre-signed URL transfers are excluded."],
|
|
5193
|
+
examples: [
|
|
5194
|
+
{ 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 },
|
|
5195
|
+
{ 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 }
|
|
5196
|
+
]
|
|
5197
|
+
};
|
|
4499
5198
|
var DEFAULT_ALLOW = [
|
|
4500
5199
|
"[\\\\/]clients?[\\\\/]",
|
|
4501
5200
|
"-client\\.[cm]?[jt]sx?$",
|
|
@@ -4559,6 +5258,7 @@ function compile(patterns) {
|
|
|
4559
5258
|
}
|
|
4560
5259
|
var no_raw_fetch_outside_clients_default = createRule({
|
|
4561
5260
|
name: "no-raw-fetch-outside-clients",
|
|
5261
|
+
documentation: noRawFetchOutsideClientsDocumentation,
|
|
4562
5262
|
meta: {
|
|
4563
5263
|
type: "problem",
|
|
4564
5264
|
docs: {
|
|
@@ -4607,6 +5307,17 @@ var no_raw_fetch_outside_clients_default = createRule({
|
|
|
4607
5307
|
|
|
4608
5308
|
// src/rules/no-restricted-library-load.ts
|
|
4609
5309
|
var import_utils28 = require("@typescript-eslint/utils");
|
|
5310
|
+
var noRestrictedLibraryLoadDocumentation = {
|
|
5311
|
+
summary: "Apply a configured library-replacement policy to literal dynamic imports, CommonJS loads, and TypeScript import-equals declarations.",
|
|
5312
|
+
rationale: "Runtime module loads can bypass the replacement policy enforced for static imports.",
|
|
5313
|
+
remediation: "Load the configured replacement library instead of the restricted module.",
|
|
5314
|
+
category: "architecture",
|
|
5315
|
+
limitations: ["Only literal dynamic imports, unshadowed CommonJS loads, and TypeScript import-equals declarations are checked."],
|
|
5316
|
+
examples: [
|
|
5317
|
+
{ 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 },
|
|
5318
|
+
{ 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 }
|
|
5319
|
+
]
|
|
5320
|
+
};
|
|
4610
5321
|
function literalModule(node) {
|
|
4611
5322
|
return node?.type === import_utils28.AST_NODE_TYPES.Literal && typeof node.value === "string" ? node.value : null;
|
|
4612
5323
|
}
|
|
@@ -4615,6 +5326,7 @@ function matchesModule(source, module2) {
|
|
|
4615
5326
|
}
|
|
4616
5327
|
var no_restricted_library_load_default = createRule({
|
|
4617
5328
|
name: "no-restricted-library-load",
|
|
5329
|
+
documentation: noRestrictedLibraryLoadDocumentation,
|
|
4618
5330
|
meta: {
|
|
4619
5331
|
type: "problem",
|
|
4620
5332
|
docs: {
|
|
@@ -4705,13 +5417,25 @@ var MIN_DISTINCT_SCOPES = 2;
|
|
|
4705
5417
|
var PREVIEW_LENGTH = 40;
|
|
4706
5418
|
var SQL_KEYWORD_RE = /\b(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|VALUES|ON CONFLICT|RETURNING|GROUP BY|ORDER BY)\b/;
|
|
4707
5419
|
var IDENTIFIER_RE = /^[a-z_][a-z0-9_.]*$/;
|
|
5420
|
+
var URL_PATH_RE = /^\/(?=[^\s]*[A-Za-z0-9])[A-Za-z0-9._~!$&'()*+,;=:@%/?#{}\u005B\u005D-]+$/;
|
|
4708
5421
|
var FUNCTION_TYPES4 = /* @__PURE__ */ new Set([
|
|
4709
5422
|
import_utils29.AST_NODE_TYPES.FunctionDeclaration,
|
|
4710
5423
|
import_utils29.AST_NODE_TYPES.FunctionExpression,
|
|
4711
5424
|
import_utils29.AST_NODE_TYPES.ArrowFunctionExpression
|
|
4712
5425
|
]);
|
|
5426
|
+
var noRepeatedStringLiteralDocumentation = {
|
|
5427
|
+
summary: "Disallow a long structured string literal repeated across functions; the copies drift when one is edited. Extract a module-level constant.",
|
|
5428
|
+
rationale: "Independent copies of a query, route template, or identifier can diverge and silently change behavior.",
|
|
5429
|
+
remediation: "Extract the repeated value to one module-level constant and reference it from each function.",
|
|
5430
|
+
category: "maintainability",
|
|
5431
|
+
limitations: ["Test files, short strings, prose, substitutions, module sources, JSX attributes, and repetition within one function are excluded."],
|
|
5432
|
+
examples: [
|
|
5433
|
+
{ 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 },
|
|
5434
|
+
{ 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 }
|
|
5435
|
+
]
|
|
5436
|
+
};
|
|
4713
5437
|
function isStructured(value) {
|
|
4714
|
-
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value);
|
|
5438
|
+
return value.includes("\n") || SQL_KEYWORD_RE.test(value) || IDENTIFIER_RE.test(value) || URL_PATH_RE.test(value);
|
|
4715
5439
|
}
|
|
4716
5440
|
function preview(value) {
|
|
4717
5441
|
const oneLine = value.replaceAll("\n", " ").trim();
|
|
@@ -4730,11 +5454,13 @@ function isScaffolding(node) {
|
|
|
4730
5454
|
if (parent === void 0) {
|
|
4731
5455
|
return true;
|
|
4732
5456
|
}
|
|
5457
|
+
const isNonComputedPropertyKey = (parent.type === import_utils29.AST_NODE_TYPES.Property || parent.type === import_utils29.AST_NODE_TYPES.PropertyDefinition || parent.type === import_utils29.AST_NODE_TYPES.MethodDefinition || parent.type === import_utils29.AST_NODE_TYPES.AccessorProperty) && parent.key === node && !parent.computed;
|
|
4733
5458
|
const isRequireSource = parent.type === import_utils29.AST_NODE_TYPES.CallExpression && parent.callee.type === import_utils29.AST_NODE_TYPES.Identifier && parent.callee.name === "require";
|
|
4734
|
-
return parent.type === import_utils29.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ImportExpression || parent.type === import_utils29.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils29.AST_NODE_TYPES.TSImportType || parent.type === import_utils29.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils29.AST_NODE_TYPES.TSLiteralType || isRequireSource;
|
|
5459
|
+
return parent.type === import_utils29.AST_NODE_TYPES.ImportDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ImportExpression || parent.type === import_utils29.AST_NODE_TYPES.ExportNamedDeclaration || parent.type === import_utils29.AST_NODE_TYPES.ExportAllDeclaration || parent.type === import_utils29.AST_NODE_TYPES.TSImportType || parent.type === import_utils29.AST_NODE_TYPES.JSXAttribute || parent.type === import_utils29.AST_NODE_TYPES.TSLiteralType || isNonComputedPropertyKey || isRequireSource;
|
|
4735
5460
|
}
|
|
4736
5461
|
var no_repeated_string_literal_default = createRule({
|
|
4737
5462
|
name: "no-repeated-string-literal",
|
|
5463
|
+
documentation: noRepeatedStringLiteralDocumentation,
|
|
4738
5464
|
meta: {
|
|
4739
5465
|
type: "suggestion",
|
|
4740
5466
|
docs: {
|
|
@@ -4747,7 +5473,7 @@ var no_repeated_string_literal_default = createRule({
|
|
|
4747
5473
|
},
|
|
4748
5474
|
defaultOptions: [],
|
|
4749
5475
|
create(context) {
|
|
4750
|
-
if (isTestFile(context.filename)) {
|
|
5476
|
+
if (isTestFile(context.filename) || isGeneratedFile(context.filename, context.sourceCode.text)) {
|
|
4751
5477
|
return {};
|
|
4752
5478
|
}
|
|
4753
5479
|
const occurrences = /* @__PURE__ */ new Map();
|
|
@@ -4820,6 +5546,17 @@ var NON_ASCII_LETTER_RE = /[^\p{ASCII}\p{N}\p{P}\p{Z}]/u;
|
|
|
4820
5546
|
var WALL_NARRATION_RE2 = /^(?:(?:\d+[.)]|(?:phase|step)\s+\d+\s*:?)\s*)?(?:add|build|call|check|compute|copy|count|create|fetch|filter|find|get|handle|load|map|merge|parse|process|read|remove|return|save|send|set|sort|store|update|validate|write)(?:s|es|d|ed|ing)?\b/i;
|
|
4821
5547
|
var WALL_CLUSTER_MAX_LINE_GAP = 8;
|
|
4822
5548
|
var WALL_CLUSTER_MIN_COMMENTS = 3;
|
|
5549
|
+
var noRestatedCommentDocumentation = {
|
|
5550
|
+
summary: "Flag a single-line comment whose every word already appears on the statement below it.",
|
|
5551
|
+
rationale: "A comment that only repeats code adds no context and can become stale independently.",
|
|
5552
|
+
remediation: "Delete the comment or replace it with the reason, constraint, or consequence absent from the code.",
|
|
5553
|
+
category: "maintainability",
|
|
5554
|
+
limitations: ["Directives, protected references, questions, multi-line prose, comments with novel content, and generated files are excluded."],
|
|
5555
|
+
examples: [
|
|
5556
|
+
{ 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 },
|
|
5557
|
+
{ 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 }
|
|
5558
|
+
]
|
|
5559
|
+
};
|
|
4823
5560
|
function areAdjacentLineComments2(a, b) {
|
|
4824
5561
|
return a !== void 0 && b !== void 0 && a.type === "Line" && b.type === "Line" && b.loc.start.line === a.loc.end.line + 1;
|
|
4825
5562
|
}
|
|
@@ -4834,6 +5571,7 @@ function headsSiblingRun(node) {
|
|
|
4834
5571
|
}
|
|
4835
5572
|
var no_restated_comment_default = createRule({
|
|
4836
5573
|
name: "no-restated-comment",
|
|
5574
|
+
documentation: noRestatedCommentDocumentation,
|
|
4837
5575
|
meta: {
|
|
4838
5576
|
type: "suggestion",
|
|
4839
5577
|
docs: {
|
|
@@ -4919,6 +5657,19 @@ var no_restated_comment_default = createRule({
|
|
|
4919
5657
|
|
|
4920
5658
|
// src/rules/no-restated-jsdoc.ts
|
|
4921
5659
|
var import_utils31 = require("@typescript-eslint/utils");
|
|
5660
|
+
var noRestatedJsdocDocumentation = {
|
|
5661
|
+
summary: "Flag a JSDoc block whose description and tags only re-spell the signature they document.",
|
|
5662
|
+
rationale: "Signature-only JSDoc duplicates type information and drifts without helping callers.",
|
|
5663
|
+
remediation: "Delete the block or document behavior, constraints, failures, or context the signature cannot express.",
|
|
5664
|
+
category: "maintainability",
|
|
5665
|
+
aliases: ["jsdoc-restates-signature"],
|
|
5666
|
+
autofix: "suggestion",
|
|
5667
|
+
limitations: ["Generated files, detached blocks, unknown tags, empty blocks, and JSDoc with information absent from the signature are excluded."],
|
|
5668
|
+
examples: [
|
|
5669
|
+
{ 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 },
|
|
5670
|
+
{ 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 }
|
|
5671
|
+
]
|
|
5672
|
+
};
|
|
4922
5673
|
var MODELLED_TAGS = /* @__PURE__ */ new Set([
|
|
4923
5674
|
"arg",
|
|
4924
5675
|
"argument",
|
|
@@ -5018,6 +5769,7 @@ function tokensOf(names) {
|
|
|
5018
5769
|
}
|
|
5019
5770
|
var no_restated_jsdoc_default = createRule({
|
|
5020
5771
|
name: "no-restated-jsdoc",
|
|
5772
|
+
documentation: noRestatedJsdocDocumentation,
|
|
5021
5773
|
meta: {
|
|
5022
5774
|
type: "suggestion",
|
|
5023
5775
|
hasSuggestions: true,
|
|
@@ -5041,10 +5793,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5041
5793
|
for (const comment of sourceCode.getAllComments()) {
|
|
5042
5794
|
if (comment.type !== "Block" || !comment.value.startsWith("*")) continue;
|
|
5043
5795
|
const { description, tags } = parseJsDoc(comment.value);
|
|
5044
|
-
|
|
5796
|
+
const describedText = [
|
|
5797
|
+
description,
|
|
5798
|
+
...tags.filter((tag) => tag.name === "description").map((tag) => tag.text)
|
|
5799
|
+
].filter((text) => text.length > 0).join("\n");
|
|
5800
|
+
if (DIRECTIVE_RE4.test(describedText)) continue;
|
|
5045
5801
|
const tagNames = new Set(tags.map((tag) => tag.name));
|
|
5046
5802
|
if ([...tagNames].some((name) => !MODELLED_TAGS.has(name))) continue;
|
|
5047
|
-
if (isProtected(
|
|
5803
|
+
if (isProtected(describedText)) continue;
|
|
5048
5804
|
const token = sourceCode.getTokenAfter(comment, { includeComments: false });
|
|
5049
5805
|
if (token === null || token.loc.start.line !== comment.loc.end.line + 1) continue;
|
|
5050
5806
|
let node = sourceCode.getNodeByRangeIndex(token.range[0]);
|
|
@@ -5057,13 +5813,14 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5057
5813
|
if (declaration === null) continue;
|
|
5058
5814
|
const paramTags = tags.filter((tag) => PARAM_TAGS.has(tag.name));
|
|
5059
5815
|
const returnTags = tags.filter((tag) => RETURN_TAGS.has(tag.name));
|
|
5060
|
-
if (
|
|
5816
|
+
if (describedText.length === 0 && paramTags.length === 0 && returnTags.length === 0) {
|
|
5061
5817
|
continue;
|
|
5062
5818
|
}
|
|
5819
|
+
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5063
5820
|
const nameTokens = tokensOf([declaration.name]);
|
|
5064
5821
|
const paramTokens = tokensOf(declaration.params);
|
|
5065
5822
|
const known = /* @__PURE__ */ new Set([...nameTokens, ...paramTokens]);
|
|
5066
|
-
let addsNothing = covered(
|
|
5823
|
+
let addsNothing = covered(describedText, known);
|
|
5067
5824
|
for (const tag of paramTags) {
|
|
5068
5825
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
5069
5826
|
const match = /^\[?([A-Za-z_$][\w.$]*)\]?\s*-?\s*([\s\S]*)$/.exec(text);
|
|
@@ -5071,7 +5828,13 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5071
5828
|
addsNothing = false;
|
|
5072
5829
|
break;
|
|
5073
5830
|
}
|
|
5074
|
-
const
|
|
5831
|
+
const path = match[1] ?? "";
|
|
5832
|
+
const root = path.split(".")[0] ?? "";
|
|
5833
|
+
if (!declaration.params.includes(root)) {
|
|
5834
|
+
addsNothing = false;
|
|
5835
|
+
break;
|
|
5836
|
+
}
|
|
5837
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens]);
|
|
5075
5838
|
if (!covered(match[2] ?? "", own)) {
|
|
5076
5839
|
addsNothing = false;
|
|
5077
5840
|
break;
|
|
@@ -5249,6 +6012,17 @@ function isAuthSecretName(identifier) {
|
|
|
5249
6012
|
}
|
|
5250
6013
|
|
|
5251
6014
|
// src/rules/no-secret-in-log.ts
|
|
6015
|
+
var noSecretInLogDocumentation = {
|
|
6016
|
+
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.",
|
|
6017
|
+
rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
|
|
6018
|
+
remediation: "Omit the value or log an explicitly redacted, truncated, or derived non-sensitive field.",
|
|
6019
|
+
category: "security",
|
|
6020
|
+
limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers."],
|
|
6021
|
+
examples: [
|
|
6022
|
+
{ 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 },
|
|
6023
|
+
{ 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 }
|
|
6024
|
+
]
|
|
6025
|
+
};
|
|
5252
6026
|
var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
|
|
5253
6027
|
...INNOCUOUS_WORDS,
|
|
5254
6028
|
"name",
|
|
@@ -5368,6 +6142,7 @@ function propertyKeyName2(prop) {
|
|
|
5368
6142
|
}
|
|
5369
6143
|
var no_secret_in_log_default = createRule({
|
|
5370
6144
|
name: "no-secret-in-log",
|
|
6145
|
+
documentation: noSecretInLogDocumentation,
|
|
5371
6146
|
meta: {
|
|
5372
6147
|
type: "problem",
|
|
5373
6148
|
docs: {
|
|
@@ -5442,6 +6217,17 @@ var no_secret_in_log_default = createRule({
|
|
|
5442
6217
|
|
|
5443
6218
|
// src/rules/no-select-star.ts
|
|
5444
6219
|
var import_utils33 = require("@typescript-eslint/utils");
|
|
6220
|
+
var noSelectStarDocumentation = {
|
|
6221
|
+
summary: "Disallow SELECT * in embedded SQL; it over-fetches and leaves the row contract implicit, so a schema change breaks row parsing silently.",
|
|
6222
|
+
rationale: "Wildcard projections couple row shape and query cost to unrelated schema changes.",
|
|
6223
|
+
remediation: "List every required column explicitly in the projection.",
|
|
6224
|
+
category: "correctness",
|
|
6225
|
+
limitations: ["Only statically visible embedded SQL is checked; function arguments such as COUNT(*) and stars inside EXISTS are excluded."],
|
|
6226
|
+
examples: [
|
|
6227
|
+
{ 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 },
|
|
6228
|
+
{ 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 }
|
|
6229
|
+
]
|
|
6230
|
+
};
|
|
5445
6231
|
var QUERY_SHAPE = /\bSELECT\b[\s\S]*?\bFROM\b/i;
|
|
5446
6232
|
var SELECT_KEYWORD = /\bSELECT\b/gi;
|
|
5447
6233
|
var FROM_KEYWORD = /^FROM\b/i;
|
|
@@ -5483,6 +6269,7 @@ function isProjectionStar(sql, pos) {
|
|
|
5483
6269
|
}
|
|
5484
6270
|
var no_select_star_default = createRule({
|
|
5485
6271
|
name: "no-select-star",
|
|
6272
|
+
documentation: noSelectStarDocumentation,
|
|
5486
6273
|
meta: {
|
|
5487
6274
|
type: "problem",
|
|
5488
6275
|
docs: {
|
|
@@ -5509,6 +6296,17 @@ var no_select_star_default = createRule({
|
|
|
5509
6296
|
|
|
5510
6297
|
// src/rules/no-sentinel-return-on-catch.ts
|
|
5511
6298
|
var import_utils34 = require("@typescript-eslint/utils");
|
|
6299
|
+
var noSentinelReturnOnCatchDocumentation = {
|
|
6300
|
+
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.",
|
|
6301
|
+
rationale: "An unreported fallback makes operational failure indistinguishable from a legitimate empty result.",
|
|
6302
|
+
remediation: "Rethrow, report the error before returning, or model expected absence with an explicit predicate, safe-parse, or result contract.",
|
|
6303
|
+
category: "correctness",
|
|
6304
|
+
limitations: ["Recognized predicate, safe-parse, normal-path sentinel, deliberate parse, generated-client, and configured logging patterns are excluded."],
|
|
6305
|
+
examples: [
|
|
6306
|
+
{ 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 },
|
|
6307
|
+
{ 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 }
|
|
6308
|
+
]
|
|
6309
|
+
};
|
|
5512
6310
|
function unwrapSentinelExpression(arg) {
|
|
5513
6311
|
let current = arg;
|
|
5514
6312
|
while (current?.type === import_utils34.AST_NODE_TYPES.TSAsExpression || current?.type === import_utils34.AST_NODE_TYPES.TSTypeAssertion || current?.type === import_utils34.AST_NODE_TYPES.TSSatisfiesExpression) {
|
|
@@ -5832,10 +6630,11 @@ function isWithin(node, ancestor) {
|
|
|
5832
6630
|
}
|
|
5833
6631
|
var no_sentinel_return_on_catch_default = createRule({
|
|
5834
6632
|
name: "no-sentinel-return-on-catch",
|
|
6633
|
+
documentation: noSentinelReturnOnCatchDocumentation,
|
|
5835
6634
|
meta: {
|
|
5836
6635
|
type: "problem",
|
|
5837
6636
|
docs: {
|
|
5838
|
-
description: "Disallow swallowing a caught error by returning an empty sentinel
|
|
6637
|
+
description: "Disallow swallowing a caught error by returning an empty sentinel unless the error is handled or the sentinel is part of the function contract."
|
|
5839
6638
|
},
|
|
5840
6639
|
schema: [
|
|
5841
6640
|
{
|
|
@@ -5860,10 +6659,10 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
5860
6659
|
return false;
|
|
5861
6660
|
}
|
|
5862
6661
|
if (matcher.isLoggingCall(current)) {
|
|
5863
|
-
return
|
|
6662
|
+
return caughtName === null || argsIncludeBinding(current.arguments, caughtName);
|
|
5864
6663
|
}
|
|
5865
6664
|
const name = calleeName2(current.callee);
|
|
5866
|
-
return name !== null && REPORT_NAME_RE.test(name) && argsIncludeBinding(current.arguments, caughtName);
|
|
6665
|
+
return name !== null && REPORT_NAME_RE.test(name) && (caughtName === null || argsIncludeBinding(current.arguments, caughtName));
|
|
5867
6666
|
});
|
|
5868
6667
|
}
|
|
5869
6668
|
return {
|
|
@@ -5913,6 +6712,17 @@ var no_sentinel_return_on_catch_default = createRule({
|
|
|
5913
6712
|
|
|
5914
6713
|
// src/rules/no-silent-promise-catch.ts
|
|
5915
6714
|
var import_utils35 = require("@typescript-eslint/utils");
|
|
6715
|
+
var noSilentPromiseCatchDocumentation = {
|
|
6716
|
+
summary: "Disallow `.catch()` and second-argument `.then()` handlers that silently swallow a rejection; log, rethrow, or handle the error.",
|
|
6717
|
+
rationale: "A swallowed rejection hides failures and gives callers an indistinguishable fallback value.",
|
|
6718
|
+
remediation: "Log, rethrow, or explicitly recover from the rejection; explain intentional teardown suppression.",
|
|
6719
|
+
category: "correctness",
|
|
6720
|
+
limitations: ["Test files, teardown calls, explanatory comments, non-function handlers, and handlers that consume or report the error are excluded."],
|
|
6721
|
+
examples: [
|
|
6722
|
+
{ 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 },
|
|
6723
|
+
{ 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 }
|
|
6724
|
+
]
|
|
6725
|
+
};
|
|
5916
6726
|
function isBodyParseCall(node) {
|
|
5917
6727
|
return node.type === import_utils35.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils35.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils35.AST_NODE_TYPES.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
|
|
5918
6728
|
}
|
|
@@ -5967,6 +6777,7 @@ function isSilentExpression(node) {
|
|
|
5967
6777
|
}
|
|
5968
6778
|
var no_silent_promise_catch_default = createRule({
|
|
5969
6779
|
name: "no-silent-promise-catch",
|
|
6780
|
+
documentation: noSilentPromiseCatchDocumentation,
|
|
5970
6781
|
meta: {
|
|
5971
6782
|
type: "problem",
|
|
5972
6783
|
docs: {
|
|
@@ -6036,6 +6847,18 @@ var no_silent_promise_catch_default = createRule({
|
|
|
6036
6847
|
|
|
6037
6848
|
// src/rules/no-sleep-in-test-body.ts
|
|
6038
6849
|
var import_utils36 = require("@typescript-eslint/utils");
|
|
6850
|
+
var noSleepInTestBodyDocumentation = {
|
|
6851
|
+
summary: "Disallow a fixed timed sleep directly in a test body; it flakes under CI load. Synchronize on the signal or use fake timers.",
|
|
6852
|
+
rationale: "Wall-clock delays make test correctness depend on scheduler and machine speed.",
|
|
6853
|
+
remediation: "Await the observable signal or advance deterministic fake timers.",
|
|
6854
|
+
category: "testing",
|
|
6855
|
+
filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
|
|
6856
|
+
limitations: ["Only fixed nonzero sleeps directly inside test and per-test hook callbacks are checked; nested fakes and parameterized delays are excluded."],
|
|
6857
|
+
examples: [
|
|
6858
|
+
{ 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 },
|
|
6859
|
+
{ 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 }
|
|
6860
|
+
]
|
|
6861
|
+
};
|
|
6039
6862
|
var SLEEP_HELPERS = /* @__PURE__ */ new Set(["sleep", "delay", "wait", "pause"]);
|
|
6040
6863
|
var TEST_CALLERS3 = /* @__PURE__ */ new Set([
|
|
6041
6864
|
"it",
|
|
@@ -6111,6 +6934,7 @@ function testCallerName2(callee) {
|
|
|
6111
6934
|
}
|
|
6112
6935
|
var no_sleep_in_test_body_default = createRule({
|
|
6113
6936
|
name: "no-sleep-in-test-body",
|
|
6937
|
+
documentation: noSleepInTestBodyDocumentation,
|
|
6114
6938
|
meta: {
|
|
6115
6939
|
type: "problem",
|
|
6116
6940
|
docs: {
|
|
@@ -6156,6 +6980,17 @@ var DEFAULT_METHODS2 = [
|
|
|
6156
6980
|
"getWithMetadata"
|
|
6157
6981
|
];
|
|
6158
6982
|
var MIN_ARGUMENTS = /* @__PURE__ */ new Map([["put", 2]]);
|
|
6983
|
+
var noStorageInStatelessModulesDocumentation = {
|
|
6984
|
+
summary: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead.",
|
|
6985
|
+
rationale: "Private storage in a stateless workflow creates another source of truth that can silently diverge.",
|
|
6986
|
+
remediation: "Read from the system of record or derive state from an artifact the workflow already produces.",
|
|
6987
|
+
category: "architecture",
|
|
6988
|
+
limitations: ["The rule is disabled until module path patterns are configured and recognizes only configured storage method names."],
|
|
6989
|
+
examples: [
|
|
6990
|
+
{ 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 },
|
|
6991
|
+
{ 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 }
|
|
6992
|
+
]
|
|
6993
|
+
};
|
|
6159
6994
|
function compile2(patterns) {
|
|
6160
6995
|
const compiled = [];
|
|
6161
6996
|
for (const pattern of patterns) {
|
|
@@ -6182,10 +7017,11 @@ function storageMethodName(node, methods) {
|
|
|
6182
7017
|
}
|
|
6183
7018
|
var no_storage_in_stateless_modules_default = createRule({
|
|
6184
7019
|
name: "no-storage-in-stateless-modules",
|
|
7020
|
+
documentation: noStorageInStatelessModulesDocumentation,
|
|
6185
7021
|
meta: {
|
|
6186
7022
|
type: "problem",
|
|
6187
7023
|
docs: {
|
|
6188
|
-
description: "Disallow SQL or key/value access inside
|
|
7024
|
+
description: "Disallow SQL or key/value access inside configured stateless modules; derive state from a system of record instead."
|
|
6189
7025
|
},
|
|
6190
7026
|
schema: [
|
|
6191
7027
|
{
|
|
@@ -6237,6 +7073,35 @@ var no_storage_in_stateless_modules_default = createRule({
|
|
|
6237
7073
|
|
|
6238
7074
|
// src/rules/no-string-concat-in-loop.ts
|
|
6239
7075
|
var import_utils38 = require("@typescript-eslint/utils");
|
|
7076
|
+
var noStringConcatInLoopDocumentation = {
|
|
7077
|
+
summary: "Disallow O(n^2) string building via `+=` on a string variable inside a loop; push parts to an array and `join` instead.",
|
|
7078
|
+
rationale: "Repeatedly rebuilding a growing string can copy all prior content on each iteration, making total work grow quadratically.",
|
|
7079
|
+
remediation: "Collect each fragment in an array, then join the fragments after the loop.",
|
|
7080
|
+
category: "performance",
|
|
7081
|
+
limitations: [
|
|
7082
|
+
"Only local identifiers initialized with a string or template literal and accumulated in a loop body are inspected."
|
|
7083
|
+
],
|
|
7084
|
+
examples: [
|
|
7085
|
+
{
|
|
7086
|
+
id: "join-fragments",
|
|
7087
|
+
title: "Join collected fragments after the loop",
|
|
7088
|
+
outcome: "no-match",
|
|
7089
|
+
files: [{ path: "src/render.ts", source: 'const parts = []; for (const item of items) { parts.push(item); } const output = parts.join("");' }],
|
|
7090
|
+
focusPath: "src/render.ts",
|
|
7091
|
+
expectedCount: 0,
|
|
7092
|
+
public: true
|
|
7093
|
+
},
|
|
7094
|
+
{
|
|
7095
|
+
id: "rebuild-string",
|
|
7096
|
+
title: "Do not rebuild a growing string in a loop",
|
|
7097
|
+
outcome: "match",
|
|
7098
|
+
files: [{ path: "src/render.ts", source: "let output = ''; for (const item of items) { output = `${output}${item}`; }" }],
|
|
7099
|
+
focusPath: "src/render.ts",
|
|
7100
|
+
expectedCount: 1,
|
|
7101
|
+
public: true
|
|
7102
|
+
}
|
|
7103
|
+
]
|
|
7104
|
+
};
|
|
6240
7105
|
var LOOP_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
6241
7106
|
"ForStatement",
|
|
6242
7107
|
"ForOfStatement",
|
|
@@ -6328,6 +7193,7 @@ function enclosingLoop(node) {
|
|
|
6328
7193
|
}
|
|
6329
7194
|
var no_string_concat_in_loop_default = createRule({
|
|
6330
7195
|
name: "no-string-concat-in-loop",
|
|
7196
|
+
documentation: noStringConcatInLoopDocumentation,
|
|
6331
7197
|
meta: {
|
|
6332
7198
|
type: "suggestion",
|
|
6333
7199
|
docs: {
|
|
@@ -6388,6 +7254,35 @@ var no_string_concat_in_loop_default = createRule({
|
|
|
6388
7254
|
|
|
6389
7255
|
// src/rules/no-tautological-expect.ts
|
|
6390
7256
|
var import_utils39 = require("@typescript-eslint/utils");
|
|
7257
|
+
var noTautologicalExpectDocumentation = {
|
|
7258
|
+
summary: "Disallow an assertion whose operands are all literals; its outcome is fixed before the code runs, so it can never fail.",
|
|
7259
|
+
rationale: "An assertion determined entirely by literals does not observe the code under test and can keep passing after that code is removed.",
|
|
7260
|
+
remediation: "Assert on a value produced by the behavior under test, or remove the assertion.",
|
|
7261
|
+
category: "testing",
|
|
7262
|
+
limitations: [
|
|
7263
|
+
"Only direct supported `expect` matcher calls in recognized test files are inspected."
|
|
7264
|
+
],
|
|
7265
|
+
examples: [
|
|
7266
|
+
{
|
|
7267
|
+
id: "produced-value",
|
|
7268
|
+
title: "Assert on a produced value",
|
|
7269
|
+
outcome: "no-match",
|
|
7270
|
+
files: [{ path: "src/add.test.ts", source: "it('adds', () => { expect(add(1, 1)).toBe(2); });" }],
|
|
7271
|
+
focusPath: "src/add.test.ts",
|
|
7272
|
+
expectedCount: 0,
|
|
7273
|
+
public: true
|
|
7274
|
+
},
|
|
7275
|
+
{
|
|
7276
|
+
id: "literal-only-assertion",
|
|
7277
|
+
title: "Do not compare identical literals",
|
|
7278
|
+
outcome: "match",
|
|
7279
|
+
files: [{ path: "src/add.test.ts", source: "it('works', () => { expect(true).toBe(true); });" }],
|
|
7280
|
+
focusPath: "src/add.test.ts",
|
|
7281
|
+
expectedCount: 1,
|
|
7282
|
+
public: true
|
|
7283
|
+
}
|
|
7284
|
+
]
|
|
7285
|
+
};
|
|
6391
7286
|
var EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual"]);
|
|
6392
7287
|
var ZERO_ARG_MATCHERS = /* @__PURE__ */ new Set([
|
|
6393
7288
|
"toBeDefined",
|
|
@@ -6426,6 +7321,7 @@ function expectOperand(callee) {
|
|
|
6426
7321
|
}
|
|
6427
7322
|
var no_tautological_expect_default = createRule({
|
|
6428
7323
|
name: "no-tautological-expect",
|
|
7324
|
+
documentation: noTautologicalExpectDocumentation,
|
|
6429
7325
|
meta: {
|
|
6430
7326
|
type: "problem",
|
|
6431
7327
|
docs: {
|
|
@@ -6486,8 +7382,36 @@ var no_tautological_expect_default = createRule({
|
|
|
6486
7382
|
});
|
|
6487
7383
|
|
|
6488
7384
|
// src/rules/no-typed-doc-sections.ts
|
|
7385
|
+
var noTypedDocSectionsDocumentation = {
|
|
7386
|
+
summary: "Reject typed-signature repetition while preserving behavior that types cannot express.",
|
|
7387
|
+
rationale: "Parameter and return tags repeat typed signatures and can drift without adding runtime behavior or constraints.",
|
|
7388
|
+
remediation: "Remove repeated parameter and return tags; retain documentation for behavior, failures, and external contracts.",
|
|
7389
|
+
category: "maintainability",
|
|
7390
|
+
limitations: ["Parameter and return tags are reported only when the documented function has corresponding explicit TypeScript types."],
|
|
7391
|
+
examples: [
|
|
7392
|
+
{
|
|
7393
|
+
id: "behavioral-documentation",
|
|
7394
|
+
title: "Keep behavior that the signature cannot express",
|
|
7395
|
+
outcome: "no-match",
|
|
7396
|
+
files: [{ path: "src/client.ts", source: "/** Retries when the vendor returns 429. */\nexport function fetchValue(id: string): number { return 1; }" }],
|
|
7397
|
+
focusPath: "src/client.ts",
|
|
7398
|
+
expectedCount: 0,
|
|
7399
|
+
public: true
|
|
7400
|
+
},
|
|
7401
|
+
{
|
|
7402
|
+
id: "repeated-typed-sections",
|
|
7403
|
+
title: "Do not restate typed parameters and returns",
|
|
7404
|
+
outcome: "match",
|
|
7405
|
+
files: [{ path: "src/client.ts", source: "/** @param id external identifier\n * @returns the value\n */\nexport function fetchValue(id: string): number { return 1; }" }],
|
|
7406
|
+
focusPath: "src/client.ts",
|
|
7407
|
+
expectedCount: 1,
|
|
7408
|
+
public: true
|
|
7409
|
+
}
|
|
7410
|
+
]
|
|
7411
|
+
};
|
|
6489
7412
|
var no_typed_doc_sections_default = createRule({
|
|
6490
7413
|
name: "no-typed-doc-sections",
|
|
7414
|
+
documentation: noTypedDocSectionsDocumentation,
|
|
6491
7415
|
meta: {
|
|
6492
7416
|
type: "suggestion",
|
|
6493
7417
|
docs: { description: "Reject typed-signature repetition while preserving behavior that types cannot express." },
|
|
@@ -6512,6 +7436,34 @@ var no_typed_doc_sections_default = createRule({
|
|
|
6512
7436
|
|
|
6513
7437
|
// src/rules/no-trailing-value-narration.ts
|
|
6514
7438
|
var import_utils40 = require("@typescript-eslint/utils");
|
|
7439
|
+
var noTrailingValueNarrationDocumentation = {
|
|
7440
|
+
summary: "Flag a trailing comment that repeats the line's numeric value only to name its unit.",
|
|
7441
|
+
rationale: "A repeated value can disagree with the expression after either the code or comment changes.",
|
|
7442
|
+
remediation: "Put the unit in the identifier and keep comments only when they explain a constraint or non-obvious conversion.",
|
|
7443
|
+
category: "maintainability",
|
|
7444
|
+
aliases: ["trailing-value-narration"],
|
|
7445
|
+
limitations: ["Only trailing comments with numeric values and recognized unit words are inspected."],
|
|
7446
|
+
examples: [
|
|
7447
|
+
{
|
|
7448
|
+
id: "explain-constraint",
|
|
7449
|
+
title: "Explain a domain constraint",
|
|
7450
|
+
outcome: "no-match",
|
|
7451
|
+
files: [{ path: "src/timeouts.ts", source: "const timeout = 5 * 60; // 5 minutes for cold starts" }],
|
|
7452
|
+
focusPath: "src/timeouts.ts",
|
|
7453
|
+
expectedCount: 0,
|
|
7454
|
+
public: true
|
|
7455
|
+
},
|
|
7456
|
+
{
|
|
7457
|
+
id: "repeat-duration",
|
|
7458
|
+
title: "Do not narrate the numeric duration",
|
|
7459
|
+
outcome: "match",
|
|
7460
|
+
files: [{ path: "src/timeouts.ts", source: "const staleTime = 5 * 60 * 1000; // 5 minutes" }],
|
|
7461
|
+
focusPath: "src/timeouts.ts",
|
|
7462
|
+
expectedCount: 1,
|
|
7463
|
+
public: true
|
|
7464
|
+
}
|
|
7465
|
+
]
|
|
7466
|
+
};
|
|
6515
7467
|
var NUMBER_RE = /(?<![\w.])(\d+(?:\.\d+)?)(?![\w.])/g;
|
|
6516
7468
|
var WORD_RE3 = /[A-Za-z]+(?:'[a-z]+)?|\d+(?:\.\d+)?/g;
|
|
6517
7469
|
var UNIT_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -6598,6 +7550,7 @@ function numbersIn(text) {
|
|
|
6598
7550
|
}
|
|
6599
7551
|
var no_trailing_value_narration_default = createRule({
|
|
6600
7552
|
name: "no-trailing-value-narration",
|
|
7553
|
+
documentation: noTrailingValueNarrationDocumentation,
|
|
6601
7554
|
meta: {
|
|
6602
7555
|
type: "suggestion",
|
|
6603
7556
|
docs: {
|
|
@@ -6774,6 +7727,33 @@ function bodyOf(member) {
|
|
|
6774
7727
|
}
|
|
6775
7728
|
|
|
6776
7729
|
// src/rules/no-declaration-comment-wall.ts
|
|
7730
|
+
var noDeclarationCommentWallDocumentation = {
|
|
7731
|
+
summary: "Flag an enum body or class body whose member comments mostly re-spell the members' own names.",
|
|
7732
|
+
rationale: "A dense block of repetitive member comments obscures the few comments that add information and drifts with renamed members.",
|
|
7733
|
+
remediation: "Delete comments that restate member names and retain comments that explain constraints, lifecycle, or behavior.",
|
|
7734
|
+
category: "maintainability",
|
|
7735
|
+
limitations: ["Only enum and class bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
7736
|
+
examples: [
|
|
7737
|
+
{
|
|
7738
|
+
id: "uncommented-members",
|
|
7739
|
+
title: "Let clear member names stand alone",
|
|
7740
|
+
outcome: "no-match",
|
|
7741
|
+
files: [{ path: "src/status.ts", source: "enum Status { Pending = 'pending', Done = 'done', Failed = 'failed' }" }],
|
|
7742
|
+
focusPath: "src/status.ts",
|
|
7743
|
+
expectedCount: 0,
|
|
7744
|
+
public: true
|
|
7745
|
+
},
|
|
7746
|
+
{
|
|
7747
|
+
id: "restated-enum-members",
|
|
7748
|
+
title: "Do not restate every enum member",
|
|
7749
|
+
outcome: "match",
|
|
7750
|
+
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}" }],
|
|
7751
|
+
focusPath: "src/status.ts",
|
|
7752
|
+
expectedCount: 1,
|
|
7753
|
+
public: true
|
|
7754
|
+
}
|
|
7755
|
+
]
|
|
7756
|
+
};
|
|
6777
7757
|
function named(node) {
|
|
6778
7758
|
switch (node.type) {
|
|
6779
7759
|
case import_utils42.AST_NODE_TYPES.TSEnumMember:
|
|
@@ -6789,6 +7769,7 @@ function named(node) {
|
|
|
6789
7769
|
}
|
|
6790
7770
|
var no_declaration_comment_wall_default = createRule({
|
|
6791
7771
|
name: "no-declaration-comment-wall",
|
|
7772
|
+
documentation: noDeclarationCommentWallDocumentation,
|
|
6792
7773
|
meta: {
|
|
6793
7774
|
type: "suggestion",
|
|
6794
7775
|
docs: {
|
|
@@ -6882,6 +7863,33 @@ var no_declaration_comment_wall_default = createRule({
|
|
|
6882
7863
|
|
|
6883
7864
|
// src/rules/no-union-in-comment.ts
|
|
6884
7865
|
var import_utils43 = require("@typescript-eslint/utils");
|
|
7866
|
+
var noUnionInCommentDocumentation = {
|
|
7867
|
+
summary: "Flag a comment that lists a `string` field's allowed values instead of the type listing them.",
|
|
7868
|
+
rationale: "A comment cannot prevent callers from supplying strings outside the listed set, and the list can drift from runtime behavior.",
|
|
7869
|
+
remediation: "Move the allowed values into a string-literal union and remove the redundant comment.",
|
|
7870
|
+
category: "correctness",
|
|
7871
|
+
limitations: ["Only bare quoted-value lists attached to supported string declarations and schema-builder fields are inspected."],
|
|
7872
|
+
examples: [
|
|
7873
|
+
{
|
|
7874
|
+
id: "literal-union",
|
|
7875
|
+
title: "Encode allowed values in the type",
|
|
7876
|
+
outcome: "no-match",
|
|
7877
|
+
files: [{ path: "src/record.ts", source: "interface R { kind: 'aa' | 'bb'; }" }],
|
|
7878
|
+
focusPath: "src/record.ts",
|
|
7879
|
+
expectedCount: 0,
|
|
7880
|
+
public: true
|
|
7881
|
+
},
|
|
7882
|
+
{
|
|
7883
|
+
id: "comment-only-union",
|
|
7884
|
+
title: "Do not leave allowed values in a comment",
|
|
7885
|
+
outcome: "match",
|
|
7886
|
+
files: [{ path: "src/record.ts", source: "interface R {\n kind: string; // 'aa' | 'bb'\n}" }],
|
|
7887
|
+
focusPath: "src/record.ts",
|
|
7888
|
+
expectedCount: 1,
|
|
7889
|
+
public: true
|
|
7890
|
+
}
|
|
7891
|
+
]
|
|
7892
|
+
};
|
|
6885
7893
|
var MAX_LITERAL_LENGTH = 28;
|
|
6886
7894
|
var LITERAL = String.raw`(?:'[^'\n]*'|"[^"\n]*"|\`[^\`\n]*\`)`;
|
|
6887
7895
|
var LEAD_IN_RE2 = /^(?:one of|either|values?|allowed(?: values)?|options?|possible(?: values)?)\s*[:=-]?\s*/i;
|
|
@@ -6970,6 +7978,7 @@ function unionLiterals(body2) {
|
|
|
6970
7978
|
}
|
|
6971
7979
|
var no_union_in_comment_default = createRule({
|
|
6972
7980
|
name: "no-union-in-comment",
|
|
7981
|
+
documentation: noUnionInCommentDocumentation,
|
|
6973
7982
|
meta: {
|
|
6974
7983
|
type: "suggestion",
|
|
6975
7984
|
docs: {
|
|
@@ -7030,11 +8039,39 @@ var no_union_in_comment_default = createRule({
|
|
|
7030
8039
|
|
|
7031
8040
|
// src/rules/no-type-member-comment-wall.ts
|
|
7032
8041
|
var import_utils44 = require("@typescript-eslint/utils");
|
|
8042
|
+
var noTypeMemberCommentWallDocumentation = {
|
|
8043
|
+
summary: "Flag an object type whose member comments mostly re-spell the members' own names and types.",
|
|
8044
|
+
rationale: "Repetitive member comments add scanning cost while hiding the comments that describe facts absent from the type.",
|
|
8045
|
+
remediation: "Delete comments that restate member names or types and keep comments that add constraints or behavior.",
|
|
8046
|
+
category: "maintainability",
|
|
8047
|
+
limitations: ["Only interface and type-literal bodies meeting the configured comment-count and restatement-ratio thresholds are reported."],
|
|
8048
|
+
examples: [
|
|
8049
|
+
{
|
|
8050
|
+
id: "uncommented-members",
|
|
8051
|
+
title: "Let clear member names and types stand alone",
|
|
8052
|
+
outcome: "no-match",
|
|
8053
|
+
files: [{ path: "src/credentials.ts", source: "interface Credentials { host: string; port: number; username: string; }" }],
|
|
8054
|
+
focusPath: "src/credentials.ts",
|
|
8055
|
+
expectedCount: 0,
|
|
8056
|
+
public: true
|
|
8057
|
+
},
|
|
8058
|
+
{
|
|
8059
|
+
id: "restated-type-members",
|
|
8060
|
+
title: "Do not restate member names and types",
|
|
8061
|
+
outcome: "match",
|
|
8062
|
+
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}" }],
|
|
8063
|
+
focusPath: "src/credentials.ts",
|
|
8064
|
+
expectedCount: 1,
|
|
8065
|
+
public: true
|
|
8066
|
+
}
|
|
8067
|
+
]
|
|
8068
|
+
};
|
|
7033
8069
|
function isNamedMember(node) {
|
|
7034
8070
|
return (node.type === import_utils44.AST_NODE_TYPES.TSPropertySignature || node.type === import_utils44.AST_NODE_TYPES.TSMethodSignature) && !node.computed;
|
|
7035
8071
|
}
|
|
7036
8072
|
var no_type_member_comment_wall_default = createRule({
|
|
7037
8073
|
name: "no-type-member-comment-wall",
|
|
8074
|
+
documentation: noTypeMemberCommentWallDocumentation,
|
|
7038
8075
|
meta: {
|
|
7039
8076
|
type: "suggestion",
|
|
7040
8077
|
docs: {
|
|
@@ -7094,7 +8131,7 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
7094
8131
|
claimed.add(comment);
|
|
7095
8132
|
commented += 1;
|
|
7096
8133
|
const body2 = commentBody(comment);
|
|
7097
|
-
if (body2.length === 0 || carriesValue(body2)) continue;
|
|
8134
|
+
if (body2.length === 0 || carriesValue(body2) || isTagsOnly(body2)) continue;
|
|
7098
8135
|
if (novelWords(body2, knownTokens(sourceCode.getText(member))) <= options.maxNovelWords) {
|
|
7099
8136
|
restated += 1;
|
|
7100
8137
|
}
|
|
@@ -7119,6 +8156,33 @@ var no_type_member_comment_wall_default = createRule({
|
|
|
7119
8156
|
|
|
7120
8157
|
// src/rules/no-unnecessary-use-client.ts
|
|
7121
8158
|
var import_utils45 = require("@typescript-eslint/utils");
|
|
8159
|
+
var noUnnecessaryUseClientDocumentation = {
|
|
8160
|
+
summary: "Flag `'use client'` files with no hooks or event handlers \u2014 they could be RSC.",
|
|
8161
|
+
rationale: "An unnecessary client boundary sends the component and its transitive dependencies to the browser without using client-only behavior.",
|
|
8162
|
+
remediation: "Remove the directive, or keep it only when the module uses a supported client-side API or boundary dependency.",
|
|
8163
|
+
category: "performance",
|
|
8164
|
+
limitations: ["Client need is inferred from recognized hooks, handlers, browser globals, exports, classes, and known client-only imports."],
|
|
8165
|
+
examples: [
|
|
8166
|
+
{
|
|
8167
|
+
id: "interactive-component",
|
|
8168
|
+
title: "Keep the directive for interactive components",
|
|
8169
|
+
outcome: "no-match",
|
|
8170
|
+
files: [{ path: "src/counter.tsx", source: "'use client'; import { useState } from 'react'; export default function X() { const [n] = useState(0); return <div>{n}</div>; }" }],
|
|
8171
|
+
focusPath: "src/counter.tsx",
|
|
8172
|
+
expectedCount: 0,
|
|
8173
|
+
public: true
|
|
8174
|
+
},
|
|
8175
|
+
{
|
|
8176
|
+
id: "static-component",
|
|
8177
|
+
title: "Remove the directive from static components",
|
|
8178
|
+
outcome: "match",
|
|
8179
|
+
files: [{ path: "src/banner.tsx", source: "'use client'; export default function X() { return <div>hello</div>; }" }],
|
|
8180
|
+
focusPath: "src/banner.tsx",
|
|
8181
|
+
expectedCount: 1,
|
|
8182
|
+
public: true
|
|
8183
|
+
}
|
|
8184
|
+
]
|
|
8185
|
+
};
|
|
7122
8186
|
var HOOK_REGEX = /^use([A-Z]|$)/;
|
|
7123
8187
|
var EVENT_PROP_REGEX = /^on[A-Z]/;
|
|
7124
8188
|
var ERROR_FILE_REGEX = /\b(?:global-)?error\.[jt]sx?$/;
|
|
@@ -7193,6 +8257,7 @@ var isGlobalReference = (node, context) => {
|
|
|
7193
8257
|
};
|
|
7194
8258
|
var no_unnecessary_use_client_default = createRule({
|
|
7195
8259
|
name: "no-unnecessary-use-client",
|
|
8260
|
+
documentation: noUnnecessaryUseClientDocumentation,
|
|
7196
8261
|
meta: {
|
|
7197
8262
|
type: "suggestion",
|
|
7198
8263
|
docs: {
|
|
@@ -7323,8 +8388,36 @@ var MOCK_MODULES = /* @__PURE__ */ new Set([
|
|
|
7323
8388
|
"jest-mock",
|
|
7324
8389
|
"@jest/globals"
|
|
7325
8390
|
]);
|
|
8391
|
+
var noUnsafeMockCastingDocumentation = {
|
|
8392
|
+
summary: "Disallow casting to mock types like `jest.Mock` or `vi.Mock`. Use `vi.mocked()` or `jest.mocked()` instead.",
|
|
8393
|
+
rationale: "A type assertion can claim an unmocked value is a mock and bypass checking between the original callable and the mock API.",
|
|
8394
|
+
remediation: "Use the test framework's `mocked` helper to obtain the typed mock reference.",
|
|
8395
|
+
category: "testing",
|
|
8396
|
+
limitations: ["Only mock types imported from Vitest or Jest modules are inspected."],
|
|
8397
|
+
examples: [
|
|
8398
|
+
{
|
|
8399
|
+
id: "typed-mock-helper",
|
|
8400
|
+
title: "Use the framework helper",
|
|
8401
|
+
outcome: "no-match",
|
|
8402
|
+
files: [{ path: "src/client.test.ts", source: "const m = vi.mocked(myFn);" }],
|
|
8403
|
+
focusPath: "src/client.test.ts",
|
|
8404
|
+
expectedCount: 0,
|
|
8405
|
+
public: true
|
|
8406
|
+
},
|
|
8407
|
+
{
|
|
8408
|
+
id: "mock-type-assertion",
|
|
8409
|
+
title: "Do not assert that a value is a mock",
|
|
8410
|
+
outcome: "match",
|
|
8411
|
+
files: [{ path: "src/client.test.ts", source: 'import type * as vi from "vitest"; const m = myFn as vi.Mock;' }],
|
|
8412
|
+
focusPath: "src/client.test.ts",
|
|
8413
|
+
expectedCount: 1,
|
|
8414
|
+
public: true
|
|
8415
|
+
}
|
|
8416
|
+
]
|
|
8417
|
+
};
|
|
7326
8418
|
var no_unsafe_mock_casting_default = createRule({
|
|
7327
8419
|
name: "no-unsafe-mock-casting",
|
|
8420
|
+
documentation: noUnsafeMockCastingDocumentation,
|
|
7328
8421
|
meta: {
|
|
7329
8422
|
type: "problem",
|
|
7330
8423
|
docs: {
|
|
@@ -7392,6 +8485,35 @@ var no_unsafe_mock_casting_default = createRule({
|
|
|
7392
8485
|
// src/rules/no-zod-native-enum.ts
|
|
7393
8486
|
var import_utils47 = require("@typescript-eslint/utils");
|
|
7394
8487
|
var ts = __toESM(require("typescript"), 1);
|
|
8488
|
+
var noZodNativeEnumDocumentation = {
|
|
8489
|
+
summary: 'Disallow `z.nativeEnum()` (and `z.enum()` over a TypeScript enum); use `z.enum(["a", "b"])` with a string-literal union instead.',
|
|
8490
|
+
rationale: "Wrapping a TypeScript enum preserves its emitted runtime object and duplicates the schema's value definition across two constructs.",
|
|
8491
|
+
remediation: "Pass string literals directly to `z.enum` and derive the TypeScript type with `z.infer`.",
|
|
8492
|
+
category: "maintainability",
|
|
8493
|
+
autofix: "safe",
|
|
8494
|
+
limitations: ["Automatic fixes are limited to inline object literals whose unique values are all string literals."],
|
|
8495
|
+
examples: [
|
|
8496
|
+
{
|
|
8497
|
+
id: "zod-literal-enum",
|
|
8498
|
+
title: "Declare string values directly in Zod",
|
|
8499
|
+
outcome: "no-match",
|
|
8500
|
+
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }],
|
|
8501
|
+
focusPath: "src/status.ts",
|
|
8502
|
+
expectedCount: 0,
|
|
8503
|
+
public: true
|
|
8504
|
+
},
|
|
8505
|
+
{
|
|
8506
|
+
id: "zod-native-enum",
|
|
8507
|
+
title: "Do not wrap a TypeScript enum",
|
|
8508
|
+
outcome: "match",
|
|
8509
|
+
files: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.nativeEnum({ Active: "active", Inactive: "inactive" });' }],
|
|
8510
|
+
focusPath: "src/status.ts",
|
|
8511
|
+
expectedCount: 1,
|
|
8512
|
+
public: true,
|
|
8513
|
+
fixedFiles: [{ path: "src/status.ts", source: 'import { z } from "zod"; const S = z.enum(["active", "inactive"]);' }]
|
|
8514
|
+
}
|
|
8515
|
+
]
|
|
8516
|
+
};
|
|
7395
8517
|
var IGNORE_PATTERNS = [
|
|
7396
8518
|
/[\\/]generated[\\/]/,
|
|
7397
8519
|
/\.gen\.tsx?$/,
|
|
@@ -7461,6 +8583,7 @@ function resolvesToImportedEnum(node, services) {
|
|
|
7461
8583
|
}
|
|
7462
8584
|
var no_zod_native_enum_default = createRule({
|
|
7463
8585
|
name: "no-zod-native-enum",
|
|
8586
|
+
documentation: noZodNativeEnumDocumentation,
|
|
7464
8587
|
meta: {
|
|
7465
8588
|
type: "suggestion",
|
|
7466
8589
|
fixable: "code",
|
|
@@ -7572,6 +8695,18 @@ var no_zod_native_enum_default = createRule({
|
|
|
7572
8695
|
|
|
7573
8696
|
// src/rules/test-loops-over-literal-cases.ts
|
|
7574
8697
|
var import_utils48 = require("@typescript-eslint/utils");
|
|
8698
|
+
var testLoopsOverLiteralCasesDocumentation = {
|
|
8699
|
+
summary: "Disallow assertions over an inline literal case loop in a test; parameterization reports and names every case independently.",
|
|
8700
|
+
rationale: "A loop is reported as one test, so failures hide the individual case name and may stop later cases from running.",
|
|
8701
|
+
remediation: "Create one named parameterized test or runner-aware subtest for each literal case.",
|
|
8702
|
+
category: "testing",
|
|
8703
|
+
filePatterns: ["**/*.test.*", "**/*.spec.*", "**/tests/**"],
|
|
8704
|
+
limitations: ["Only inline literal for-of cases containing framework assertions are reported."],
|
|
8705
|
+
examples: [
|
|
8706
|
+
{ 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 },
|
|
8707
|
+
{ 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 }
|
|
8708
|
+
]
|
|
8709
|
+
};
|
|
7575
8710
|
var TEST_CALLERS4 = /* @__PURE__ */ new Set(["it", "test"]);
|
|
7576
8711
|
var TEST_MODIFIERS2 = /* @__PURE__ */ new Set(["concurrent", "fails", "only", "sequential", "skip"]);
|
|
7577
8712
|
var ASSERTION_ROOTS2 = /* @__PURE__ */ new Set([
|
|
@@ -7705,6 +8840,7 @@ var LOOP_CARRIED_CONTROL = /* @__PURE__ */ new Set([
|
|
|
7705
8840
|
]);
|
|
7706
8841
|
var test_loops_over_literal_cases_default = createRule({
|
|
7707
8842
|
name: "test-loops-over-literal-cases",
|
|
8843
|
+
documentation: testLoopsOverLiteralCasesDocumentation,
|
|
7708
8844
|
meta: {
|
|
7709
8845
|
type: "suggestion",
|
|
7710
8846
|
docs: {
|
|
@@ -7764,6 +8900,17 @@ function unwrapExpression(node) {
|
|
|
7764
8900
|
|
|
7765
8901
|
// src/rules/prefer-constant-time-secret-compare.ts
|
|
7766
8902
|
var import_utils49 = require("@typescript-eslint/utils");
|
|
8903
|
+
var preferConstantTimeSecretCompareDocumentation = {
|
|
8904
|
+
summary: "Disallow `===`/`!==` on a secret-like value; short-circuiting comparison leaks the secret through timing. Use a constant-time compare.",
|
|
8905
|
+
rationale: "Ordinary equality stops at the first differing byte, allowing repeated measurements to reveal secret material.",
|
|
8906
|
+
remediation: "Compare equal-length cryptographic digests with a constant-time comparison primitive.",
|
|
8907
|
+
category: "security",
|
|
8908
|
+
limitations: ["Secret-like values are identified conservatively from their names; test files and public sentinel comparisons are excluded."],
|
|
8909
|
+
examples: [
|
|
8910
|
+
{ 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 },
|
|
8911
|
+
{ 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 }
|
|
8912
|
+
]
|
|
8913
|
+
};
|
|
7767
8914
|
var EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
|
|
7768
8915
|
var SENTINEL_IDENTIFIERS = /* @__PURE__ */ new Set(["undefined", "NaN"]);
|
|
7769
8916
|
var SENTINEL_WORDS = /(^|_)(SENTINEL|EMPTY|NONE|NULL|UNSET|MISSING|PLACEHOLDER|DUMMY|FAKE|EXAMPLE)(_|$)/;
|
|
@@ -7818,6 +8965,7 @@ function secretNameOf(node) {
|
|
|
7818
8965
|
}
|
|
7819
8966
|
var prefer_constant_time_secret_compare_default = createRule({
|
|
7820
8967
|
name: "prefer-constant-time-secret-compare",
|
|
8968
|
+
documentation: preferConstantTimeSecretCompareDocumentation,
|
|
7821
8969
|
meta: {
|
|
7822
8970
|
type: "problem",
|
|
7823
8971
|
docs: {
|
|
@@ -7859,6 +9007,17 @@ var prefer_constant_time_secret_compare_default = createRule({
|
|
|
7859
9007
|
// src/rules/prefer-discriminated-union.ts
|
|
7860
9008
|
var import_utils50 = require("@typescript-eslint/utils");
|
|
7861
9009
|
var import_utils51 = require("@typescript-eslint/utils");
|
|
9010
|
+
var preferDiscriminatedUnionDocumentation = {
|
|
9011
|
+
summary: "Flag flat result objects with a required positive boolean status and optional success/failure payloads.",
|
|
9012
|
+
rationale: "A boolean status plus optional branch data permits contradictory and incomplete states.",
|
|
9013
|
+
remediation: "Represent each result branch as a discriminated union member with its required payload.",
|
|
9014
|
+
category: "correctness",
|
|
9015
|
+
limitations: ["Only local object shapes with recognized positive status and payload names are inspected."],
|
|
9016
|
+
examples: [
|
|
9017
|
+
{ 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 },
|
|
9018
|
+
{ 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 }
|
|
9019
|
+
]
|
|
9020
|
+
};
|
|
7862
9021
|
var STATUS_MEMBER_NAMES = /* @__PURE__ */ new Set([
|
|
7863
9022
|
"success",
|
|
7864
9023
|
"ok"
|
|
@@ -7940,6 +9099,7 @@ function inlineReturnTypeLiteral(node) {
|
|
|
7940
9099
|
}
|
|
7941
9100
|
var prefer_discriminated_union_default = createRule({
|
|
7942
9101
|
name: "prefer-discriminated-union",
|
|
9102
|
+
documentation: preferDiscriminatedUnionDocumentation,
|
|
7943
9103
|
meta: {
|
|
7944
9104
|
type: "suggestion",
|
|
7945
9105
|
docs: {
|
|
@@ -7988,6 +9148,17 @@ var prefer_discriminated_union_default = createRule({
|
|
|
7988
9148
|
|
|
7989
9149
|
// src/rules/prefer-input-group-search.ts
|
|
7990
9150
|
var import_utils52 = require("@typescript-eslint/utils");
|
|
9151
|
+
var preferInputGroupSearchDocumentation = {
|
|
9152
|
+
summary: "Require search icons and shared Input controls in the same visual wrapper to use InputGroup.",
|
|
9153
|
+
rationale: "The shared compound control provides consistent spacing, focus behavior, and accessible composition.",
|
|
9154
|
+
remediation: "Compose the search icon and field with InputGroup, InputGroupAddon, and InputGroupInput.",
|
|
9155
|
+
category: "style",
|
|
9156
|
+
limitations: ["Only Search and Input bindings imported from the recognized shared modules are paired."],
|
|
9157
|
+
examples: [
|
|
9158
|
+
{ 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 },
|
|
9159
|
+
{ 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 }
|
|
9160
|
+
]
|
|
9161
|
+
};
|
|
7991
9162
|
var INPUT_MODULE = /(?:^|\/)components\/ui\/input$/u;
|
|
7992
9163
|
var INPUT_GROUP_MODULE = /(?:^|\/)components\/ui\/input-group$/u;
|
|
7993
9164
|
var MAX_JSX_DISTANCE = 2;
|
|
@@ -8031,6 +9202,7 @@ function nearestEligibleCommonAncestor(search, input, inputGroupNames) {
|
|
|
8031
9202
|
}
|
|
8032
9203
|
var prefer_input_group_search_default = createRule({
|
|
8033
9204
|
name: "prefer-input-group-search",
|
|
9205
|
+
documentation: preferInputGroupSearchDocumentation,
|
|
8034
9206
|
meta: {
|
|
8035
9207
|
type: "suggestion",
|
|
8036
9208
|
docs: {
|
|
@@ -8101,6 +9273,35 @@ var prefer_input_group_search_default = createRule({
|
|
|
8101
9273
|
|
|
8102
9274
|
// src/rules/prefer-immutable-module-constant.ts
|
|
8103
9275
|
var import_utils53 = require("@typescript-eslint/utils");
|
|
9276
|
+
var preferImmutableModuleConstantDocumentation = {
|
|
9277
|
+
summary: "Require module-level constant collections to expose readonly state.",
|
|
9278
|
+
rationale: "A const binding prevents reassignment but does not stop callers from mutating its array, object, Set, or Map contents.",
|
|
9279
|
+
remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
|
|
9280
|
+
category: "correctness",
|
|
9281
|
+
limitations: [
|
|
9282
|
+
"The rule skips generated files, test files, JavaScript files, and collections that are deliberately mutated in their declaring module."
|
|
9283
|
+
],
|
|
9284
|
+
examples: [
|
|
9285
|
+
{
|
|
9286
|
+
id: "readonly-array-literal",
|
|
9287
|
+
title: "A module constant exposes a readonly literal",
|
|
9288
|
+
outcome: "no-match",
|
|
9289
|
+
files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3] as const;" }],
|
|
9290
|
+
focusPath: "src/constants.ts",
|
|
9291
|
+
expectedCount: 0,
|
|
9292
|
+
public: true
|
|
9293
|
+
},
|
|
9294
|
+
{
|
|
9295
|
+
id: "mutable-array-literal",
|
|
9296
|
+
title: "A module constant exposes a mutable array",
|
|
9297
|
+
outcome: "match",
|
|
9298
|
+
files: [{ path: "src/constants.ts", source: "const VALUES = [1, 2, 3];" }],
|
|
9299
|
+
focusPath: "src/constants.ts",
|
|
9300
|
+
expectedCount: 1,
|
|
9301
|
+
public: true
|
|
9302
|
+
}
|
|
9303
|
+
]
|
|
9304
|
+
};
|
|
8104
9305
|
var CONSTANT_NAME = /^_?[A-Z][A-Z0-9_]*$/;
|
|
8105
9306
|
var JAVASCRIPT_FILE_RE = /\.[cm]?jsx?$/i;
|
|
8106
9307
|
var MUTATING_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -8208,6 +9409,7 @@ function referenceMutates(identifier, isUnshadowedGlobal) {
|
|
|
8208
9409
|
}
|
|
8209
9410
|
var prefer_immutable_module_constant_default = createRule({
|
|
8210
9411
|
name: "prefer-immutable-module-constant",
|
|
9412
|
+
documentation: preferImmutableModuleConstantDocumentation,
|
|
8211
9413
|
meta: {
|
|
8212
9414
|
type: "suggestion",
|
|
8213
9415
|
docs: {
|
|
@@ -8294,6 +9496,17 @@ function unwrapTransparentExport(node) {
|
|
|
8294
9496
|
|
|
8295
9497
|
// src/rules/prefer-shadcn-primitives.ts
|
|
8296
9498
|
var import_utils54 = require("@typescript-eslint/utils");
|
|
9499
|
+
var preferShadcnPrimitivesDocumentation = {
|
|
9500
|
+
summary: "Require visible raw JSX controls to use the corresponding shared shadcn primitive.",
|
|
9501
|
+
rationale: "Shared primitives centralize interaction, accessibility, and visual behavior across the product.",
|
|
9502
|
+
remediation: "Replace the raw visible control with the corresponding shared shadcn component.",
|
|
9503
|
+
category: "style",
|
|
9504
|
+
limitations: ["Hidden and file inputs, unassociated labels, and non-control semantic elements are excluded."],
|
|
9505
|
+
examples: [
|
|
9506
|
+
{ 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 },
|
|
9507
|
+
{ 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 }
|
|
9508
|
+
]
|
|
9509
|
+
};
|
|
8297
9510
|
var SHADCN_PRIMITIVES = {
|
|
8298
9511
|
button: "Button",
|
|
8299
9512
|
dialog: "Dialog or AlertDialog family",
|
|
@@ -8396,6 +9609,7 @@ function replacementFor(node, element) {
|
|
|
8396
9609
|
}
|
|
8397
9610
|
var prefer_shadcn_primitives_default = createRule({
|
|
8398
9611
|
name: "prefer-shadcn-primitives",
|
|
9612
|
+
documentation: preferShadcnPrimitivesDocumentation,
|
|
8399
9613
|
meta: {
|
|
8400
9614
|
type: "suggestion",
|
|
8401
9615
|
docs: {
|
|
@@ -8427,6 +9641,17 @@ var prefer_shadcn_primitives_default = createRule({
|
|
|
8427
9641
|
|
|
8428
9642
|
// src/rules/prefer-module-level-constant.ts
|
|
8429
9643
|
var import_utils55 = require("@typescript-eslint/utils");
|
|
9644
|
+
var preferModuleLevelConstantDocumentation = {
|
|
9645
|
+
summary: "Hoist literal-only constant collections and regexes out of function bodies to module scope so they are allocated once.",
|
|
9646
|
+
rationale: "Recreating immutable lookup data on every call wastes allocations and obscures its constant nature.",
|
|
9647
|
+
remediation: "Declare immutable literal collections and non-stateful regular expressions once at module scope.",
|
|
9648
|
+
category: "performance",
|
|
9649
|
+
limitations: ["Collections that are small, mutated, escape the function, or depend on local values are not reported."],
|
|
9650
|
+
examples: [
|
|
9651
|
+
{ 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 },
|
|
9652
|
+
{ 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 }
|
|
9653
|
+
]
|
|
9654
|
+
};
|
|
8430
9655
|
var DEFAULT_MIN_ELEMENTS = 3;
|
|
8431
9656
|
var MAX_LITERAL_DEPTH = 4;
|
|
8432
9657
|
var IGNORE_PATTERNS2 = [
|
|
@@ -8632,6 +9857,7 @@ function isNonRetainingBuiltinCall(node, argument) {
|
|
|
8632
9857
|
}
|
|
8633
9858
|
var prefer_module_level_constant_default = createRule({
|
|
8634
9859
|
name: "prefer-module-level-constant",
|
|
9860
|
+
documentation: preferModuleLevelConstantDocumentation,
|
|
8635
9861
|
meta: {
|
|
8636
9862
|
type: "suggestion",
|
|
8637
9863
|
docs: {
|
|
@@ -8723,6 +9949,17 @@ var prefer_module_level_constant_default = createRule({
|
|
|
8723
9949
|
|
|
8724
9950
|
// src/rules/prefer-module-level-schema.ts
|
|
8725
9951
|
var import_utils56 = require("@typescript-eslint/utils");
|
|
9952
|
+
var preferModuleLevelSchemaDocumentation = {
|
|
9953
|
+
summary: "Declare a Zod schema at module scope when it closes over nothing in the enclosing function",
|
|
9954
|
+
rationale: "A closed schema created inside a function is rebuilt on every call and cannot be reused or exported for inference.",
|
|
9955
|
+
remediation: "Move the closed schema declaration to module scope and reference it from the function.",
|
|
9956
|
+
category: "performance",
|
|
9957
|
+
limitations: ["Schemas that depend on local state or are wrapped in a recognized memoization helper are excluded."],
|
|
9958
|
+
examples: [
|
|
9959
|
+
{ 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 },
|
|
9960
|
+
{ 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 }
|
|
9961
|
+
]
|
|
9962
|
+
};
|
|
8726
9963
|
var DEFAULT_FACTORIES = [
|
|
8727
9964
|
"discriminatedUnion",
|
|
8728
9965
|
"intersection",
|
|
@@ -8869,6 +10106,7 @@ function collectReferences(scope, out) {
|
|
|
8869
10106
|
}
|
|
8870
10107
|
var prefer_module_level_schema_default = createRule({
|
|
8871
10108
|
name: "prefer-module-level-schema",
|
|
10109
|
+
documentation: preferModuleLevelSchemaDocumentation,
|
|
8872
10110
|
meta: {
|
|
8873
10111
|
type: "problem",
|
|
8874
10112
|
docs: {
|
|
@@ -9066,11 +10304,24 @@ var prefer_module_level_schema_default = createRule({
|
|
|
9066
10304
|
|
|
9067
10305
|
// src/rules/prefer-native-random-uuid.ts
|
|
9068
10306
|
var import_utils57 = require("@typescript-eslint/utils");
|
|
10307
|
+
var preferNativeRandomUuidDocumentation = {
|
|
10308
|
+
summary: "Prefer `globalThis.crypto.randomUUID()` over resolved zero-argument UUID v4 bindings from the `uuid` package.",
|
|
10309
|
+
rationale: "The platform implementation avoids an unnecessary dependency for standard random UUID generation.",
|
|
10310
|
+
remediation: "Call `globalThis.crypto.randomUUID()` and remove the unused `uuid` v4 import when possible.",
|
|
10311
|
+
category: "maintainability",
|
|
10312
|
+
autofix: "suggestion",
|
|
10313
|
+
limitations: ["Only resolved zero-argument UUID v4 calls are reported; customized and other UUID versions are excluded."],
|
|
10314
|
+
examples: [
|
|
10315
|
+
{ 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 },
|
|
10316
|
+
{ 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 }
|
|
10317
|
+
]
|
|
10318
|
+
};
|
|
9069
10319
|
function requireUuid(node) {
|
|
9070
10320
|
return node?.type === import_utils57.AST_NODE_TYPES.CallExpression && node.callee.type === import_utils57.AST_NODE_TYPES.Identifier && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0]?.type === import_utils57.AST_NODE_TYPES.Literal && node.arguments[0].value === "uuid";
|
|
9071
10321
|
}
|
|
9072
10322
|
var prefer_native_random_uuid_default = createRule({
|
|
9073
10323
|
name: "prefer-native-random-uuid",
|
|
10324
|
+
documentation: preferNativeRandomUuidDocumentation,
|
|
9074
10325
|
meta: {
|
|
9075
10326
|
type: "suggestion",
|
|
9076
10327
|
docs: {
|
|
@@ -9152,6 +10403,17 @@ var prefer_native_random_uuid_default = createRule({
|
|
|
9152
10403
|
|
|
9153
10404
|
// src/rules/prefer-non-nullable-collection.ts
|
|
9154
10405
|
var import_utils58 = require("@typescript-eslint/utils");
|
|
10406
|
+
var preferNonNullableCollectionDocumentation = {
|
|
10407
|
+
summary: "Suggest non-null arrays only when local control flow proves the nullish state is equivalent to an empty collection.",
|
|
10408
|
+
rationale: "A redundant nullish collection state spreads defaults and guards through consumers without carrying information.",
|
|
10409
|
+
remediation: "Use a non-null collection type and normalize omitted input to an empty collection at the boundary.",
|
|
10410
|
+
category: "maintainability",
|
|
10411
|
+
limitations: ["The rule requires local evidence that nullish and empty values are treated identically and skips exported wire shapes."],
|
|
10412
|
+
examples: [
|
|
10413
|
+
{ 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 },
|
|
10414
|
+
{ 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 }
|
|
10415
|
+
]
|
|
10416
|
+
};
|
|
9155
10417
|
var ARRAY_TYPE_NAMES = /* @__PURE__ */ new Set(["Array", "ReadonlyArray"]);
|
|
9156
10418
|
function propertyName(node) {
|
|
9157
10419
|
const key = node.key;
|
|
@@ -9289,6 +10551,7 @@ function memberIsOnlyCoalesced(context, object, property, fn) {
|
|
|
9289
10551
|
}
|
|
9290
10552
|
var prefer_non_nullable_collection_default = createRule({
|
|
9291
10553
|
name: "prefer-non-nullable-collection",
|
|
10554
|
+
documentation: preferNonNullableCollectionDocumentation,
|
|
9292
10555
|
meta: {
|
|
9293
10556
|
type: "suggestion",
|
|
9294
10557
|
docs: {
|
|
@@ -9385,6 +10648,17 @@ var prefer_non_nullable_collection_default = createRule({
|
|
|
9385
10648
|
|
|
9386
10649
|
// src/rules/prefer-schema-for-api-payload.ts
|
|
9387
10650
|
var import_utils59 = require("@typescript-eslint/utils");
|
|
10651
|
+
var preferSchemaForApiPayloadDocumentation = {
|
|
10652
|
+
summary: "Require Zod (or similar) schema validation on `response.json()` / `JSON.parse()` results before property access.",
|
|
10653
|
+
rationale: "External JSON is untrusted at runtime even when its expected TypeScript shape is known statically.",
|
|
10654
|
+
remediation: "Parse the payload through a schema or establish a recognized runtime validation guard before reading fields.",
|
|
10655
|
+
category: "correctness",
|
|
10656
|
+
limitations: ["Test fixtures, generated clients, local JSON files, and recognized validation guards are excluded."],
|
|
10657
|
+
examples: [
|
|
10658
|
+
{ 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 },
|
|
10659
|
+
{ 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 }
|
|
10660
|
+
]
|
|
10661
|
+
};
|
|
9388
10662
|
var unwrap4 = (node) => {
|
|
9389
10663
|
let current = node;
|
|
9390
10664
|
while (current !== null && current !== void 0) {
|
|
@@ -9626,6 +10900,7 @@ var unvalidatedVariableRef = (node, scope, tracked) => {
|
|
|
9626
10900
|
};
|
|
9627
10901
|
var prefer_schema_for_api_payload_default = createRule({
|
|
9628
10902
|
name: "prefer-schema-for-api-payload",
|
|
10903
|
+
documentation: preferSchemaForApiPayloadDocumentation,
|
|
9629
10904
|
meta: {
|
|
9630
10905
|
type: "problem",
|
|
9631
10906
|
docs: {
|
|
@@ -9828,6 +11103,17 @@ var tailwindBase = (token) => token.replace(/^(?:[a-z0-9-]+:)+/i, "").replace(/^
|
|
|
9828
11103
|
var classTokens = (value) => value.split(/\s+/).filter(Boolean);
|
|
9829
11104
|
|
|
9830
11105
|
// src/rules/prefer-semantic-colors.ts
|
|
11106
|
+
var preferSemanticColorsDocumentation = {
|
|
11107
|
+
summary: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals.",
|
|
11108
|
+
rationale: "Semantic tokens keep themes and product meaning consistent while raw colors couple components to a palette value.",
|
|
11109
|
+
remediation: "Replace raw palette and literal colors with the closest semantic design-system token or CSS variable.",
|
|
11110
|
+
category: "style",
|
|
11111
|
+
limitations: ["Email, PDF, icon artwork, masks, gradients, stories, and explicitly configured non-token projects have targeted exclusions."],
|
|
11112
|
+
examples: [
|
|
11113
|
+
{ 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 },
|
|
11114
|
+
{ 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 }
|
|
11115
|
+
]
|
|
11116
|
+
};
|
|
9831
11117
|
var COLOR_PREFIXES = "text|bg|border(?:-[trblxyse])?|ring(?:-offset)?|fill|stroke|from|via|to|divide|decoration|placeholder|accent|caret|shadow|outline";
|
|
9832
11118
|
var PALETTE = "red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone";
|
|
9833
11119
|
var COLOR_FN = "rgba?|hsla?|hwb|oklch|oklab|lab|lch|color";
|
|
@@ -10106,10 +11392,11 @@ var staticallyImportsEmailOrPdfRenderer = (program) => program.body.some((statem
|
|
|
10106
11392
|
});
|
|
10107
11393
|
var prefer_semantic_colors_default = createRule({
|
|
10108
11394
|
name: "prefer-semantic-colors",
|
|
11395
|
+
documentation: preferSemanticColorsDocumentation,
|
|
10109
11396
|
meta: {
|
|
10110
11397
|
type: "suggestion",
|
|
10111
11398
|
docs: {
|
|
10112
|
-
description: "Enforce
|
|
11399
|
+
description: "Enforce semantic color tokens over raw Tailwind palette classes, arbitrary color values, and inline color literals."
|
|
10113
11400
|
},
|
|
10114
11401
|
schema: [
|
|
10115
11402
|
{
|
|
@@ -10244,6 +11531,17 @@ var prefer_semantic_colors_default = createRule({
|
|
|
10244
11531
|
|
|
10245
11532
|
// src/rules/prefer-server-actions.ts
|
|
10246
11533
|
var import_utils61 = require("@typescript-eslint/utils");
|
|
11534
|
+
var preferServerActionsDocumentation = {
|
|
11535
|
+
summary: "Prefer Next.js Server Actions over /api/* mutations.",
|
|
11536
|
+
rationale: "Server Actions preserve typed application calls and avoid an internal JSON request-response boundary.",
|
|
11537
|
+
remediation: "Move the mutation into a Server Action and invoke that action from the React client.",
|
|
11538
|
+
category: "architecture",
|
|
11539
|
+
limitations: ["Only statically recognizable /api/ mutations in applicable React modules are reported."],
|
|
11540
|
+
examples: [
|
|
11541
|
+
{ 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 },
|
|
11542
|
+
{ 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 }
|
|
11543
|
+
]
|
|
11544
|
+
};
|
|
10247
11545
|
var MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "DELETE", "PATCH"]);
|
|
10248
11546
|
var AXIOS_MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "delete", "patch"]);
|
|
10249
11547
|
var SKIP_FILE_REGEX = /(?:\.test\.[jt]sx?$|\.spec\.[jt]sx?$|-(?:test|spec)\.[jt]sx?$|\/tests?\/|\/__tests__\/|\/__testfixtures__\/|\/scripts?\/|\/app\/api\/.*\/route\.[jt]sx?$|\/pages\/api\/)/;
|
|
@@ -10341,6 +11639,7 @@ function getPropertyNode(objNode, propName2) {
|
|
|
10341
11639
|
}
|
|
10342
11640
|
var prefer_server_actions_default = createRule({
|
|
10343
11641
|
name: "prefer-server-actions",
|
|
11642
|
+
documentation: preferServerActionsDocumentation,
|
|
10344
11643
|
meta: {
|
|
10345
11644
|
type: "suggestion",
|
|
10346
11645
|
docs: {
|
|
@@ -10416,6 +11715,18 @@ var COLLECTION_PROPERTIES = /* @__PURE__ */ new Set(["length", "size"]);
|
|
|
10416
11715
|
var LITERAL_KEY_HAZARDS = /* @__PURE__ */ new Set(["__proto__"]);
|
|
10417
11716
|
var NUMERIC_SIGNS2 = /* @__PURE__ */ new Set(["-", "+"]);
|
|
10418
11717
|
var MIN_RUN_LENGTH = 2;
|
|
11718
|
+
var preferWholeObjectAssertionDocumentation = {
|
|
11719
|
+
summary: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together.",
|
|
11720
|
+
rationale: "One whole-object assertion presents related expectations together and produces a complete structural diff.",
|
|
11721
|
+
remediation: "Replace consecutive member assertions with one `toMatchObject` assertion.",
|
|
11722
|
+
category: "testing",
|
|
11723
|
+
aliases: ["strict-test-assertions"],
|
|
11724
|
+
autofix: "safe",
|
|
11725
|
+
examples: [
|
|
11726
|
+
{ 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 },
|
|
11727
|
+
{ 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" }] }
|
|
11728
|
+
]
|
|
11729
|
+
};
|
|
10419
11730
|
function literalText(node, getText) {
|
|
10420
11731
|
switch (node.type) {
|
|
10421
11732
|
case import_utils62.AST_NODE_TYPES.Literal:
|
|
@@ -10453,10 +11764,11 @@ function literalIndex(node) {
|
|
|
10453
11764
|
}
|
|
10454
11765
|
var prefer_whole_object_assertion_default = createRule({
|
|
10455
11766
|
name: "prefer-whole-object-assertion",
|
|
11767
|
+
documentation: preferWholeObjectAssertionDocumentation,
|
|
10456
11768
|
meta: {
|
|
10457
11769
|
type: "suggestion",
|
|
10458
11770
|
docs: {
|
|
10459
|
-
description: "Collapse
|
|
11771
|
+
description: "Collapse consecutive assertions on one object into a whole-object assertion so related mismatches are reported together."
|
|
10460
11772
|
},
|
|
10461
11773
|
fixable: "code",
|
|
10462
11774
|
messages: {
|
|
@@ -10631,6 +11943,16 @@ var prefer_whole_object_assertion_default = createRule({
|
|
|
10631
11943
|
|
|
10632
11944
|
// src/rules/prefer-zod-infer.ts
|
|
10633
11945
|
var import_utils63 = require("@typescript-eslint/utils");
|
|
11946
|
+
var preferZodInferDocumentation = {
|
|
11947
|
+
summary: "Derive a type from its Zod schema with `z.infer` instead of hand-writing a twin declaration beside it.",
|
|
11948
|
+
rationale: "A derived type stays synchronized when the runtime schema changes.",
|
|
11949
|
+
remediation: "Replace the hand-written twin with `z.infer<typeof Schema>`.",
|
|
11950
|
+
category: "correctness",
|
|
11951
|
+
examples: [
|
|
11952
|
+
{ 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 },
|
|
11953
|
+
{ 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 }
|
|
11954
|
+
]
|
|
11955
|
+
};
|
|
10634
11956
|
var SHAPE_PRESERVING_METHODS = /* @__PURE__ */ new Set([
|
|
10635
11957
|
"describe",
|
|
10636
11958
|
"refine",
|
|
@@ -10756,6 +12078,7 @@ function leafAgrees(leaf, annotation) {
|
|
|
10756
12078
|
}
|
|
10757
12079
|
var prefer_zod_infer_default = createRule({
|
|
10758
12080
|
name: "prefer-zod-infer",
|
|
12081
|
+
documentation: preferZodInferDocumentation,
|
|
10759
12082
|
meta: {
|
|
10760
12083
|
type: "problem",
|
|
10761
12084
|
docs: {
|
|
@@ -11041,6 +12364,16 @@ var prefer_zod_infer_default = createRule({
|
|
|
11041
12364
|
// src/rules/require-assert-never.ts
|
|
11042
12365
|
var import_utils64 = require("@typescript-eslint/utils");
|
|
11043
12366
|
var import_typescript = __toESM(require("typescript"), 1);
|
|
12367
|
+
var requireAssertNeverDocumentation = {
|
|
12368
|
+
summary: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time.",
|
|
12369
|
+
rationale: "An empty default silently accepts new union members instead of making the compiler identify the missing case.",
|
|
12370
|
+
remediation: "Call `assertNever` with the discriminant in the exhaustive switch default.",
|
|
12371
|
+
category: "correctness",
|
|
12372
|
+
examples: [
|
|
12373
|
+
{ 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 },
|
|
12374
|
+
{ 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 }
|
|
12375
|
+
]
|
|
12376
|
+
};
|
|
11044
12377
|
var isRuntimeHandlingStatement = (statement) => {
|
|
11045
12378
|
if (statement.type === import_utils64.AST_NODE_TYPES.EmptyStatement) return false;
|
|
11046
12379
|
if (statement.type === import_utils64.AST_NODE_TYPES.TSTypeAliasDeclaration || statement.type === import_utils64.AST_NODE_TYPES.TSInterfaceDeclaration) {
|
|
@@ -11098,10 +12431,11 @@ function finiteTypeKey(type, checker) {
|
|
|
11098
12431
|
}
|
|
11099
12432
|
var require_assert_never_default = createRule({
|
|
11100
12433
|
name: "require-assert-never",
|
|
12434
|
+
documentation: requireAssertNeverDocumentation,
|
|
11101
12435
|
meta: {
|
|
11102
12436
|
type: "problem",
|
|
11103
12437
|
docs: {
|
|
11104
|
-
description: "Require an
|
|
12438
|
+
description: "Require an empty switch default to call `assertNever` so discriminated unions remain exhaustive at compile time."
|
|
11105
12439
|
},
|
|
11106
12440
|
schema: [],
|
|
11107
12441
|
messages: {
|
|
@@ -11139,6 +12473,16 @@ var require_assert_never_default = createRule({
|
|
|
11139
12473
|
|
|
11140
12474
|
// src/rules/require-fetch-timeout.ts
|
|
11141
12475
|
var import_utils65 = require("@typescript-eslint/utils");
|
|
12476
|
+
var requireFetchTimeoutDocumentation = {
|
|
12477
|
+
summary: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever.",
|
|
12478
|
+
rationale: "An unbounded request can occupy work indefinitely when an upstream stalls.",
|
|
12479
|
+
remediation: "Pass an abort signal, such as `AbortSignal.timeout(ms)`, in the fetch init.",
|
|
12480
|
+
category: "correctness",
|
|
12481
|
+
examples: [
|
|
12482
|
+
{ 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 },
|
|
12483
|
+
{ 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 }
|
|
12484
|
+
]
|
|
12485
|
+
};
|
|
11142
12486
|
var GLOBAL_OBJECTS2 = /* @__PURE__ */ new Set([
|
|
11143
12487
|
"globalThis",
|
|
11144
12488
|
"window",
|
|
@@ -11175,6 +12519,7 @@ function isInlineUrl(node, resolvesToGlobal) {
|
|
|
11175
12519
|
}
|
|
11176
12520
|
var require_fetch_timeout_default = createRule({
|
|
11177
12521
|
name: "require-fetch-timeout",
|
|
12522
|
+
documentation: requireFetchTimeoutDocumentation,
|
|
11178
12523
|
meta: {
|
|
11179
12524
|
type: "problem",
|
|
11180
12525
|
docs: {
|
|
@@ -11234,8 +12579,19 @@ var require_fetch_timeout_default = createRule({
|
|
|
11234
12579
|
}
|
|
11235
12580
|
});
|
|
11236
12581
|
|
|
11237
|
-
// src/rules/require-
|
|
12582
|
+
// src/rules/require-port-for-service.ts
|
|
11238
12583
|
var import_utils66 = require("@typescript-eslint/utils");
|
|
12584
|
+
var requirePortForServiceDocumentation = {
|
|
12585
|
+
summary: "Advise when an exported service with injected collaborators has public methods not covered by its declared ports.",
|
|
12586
|
+
rationale: "A declared port keeps consumers coupled to the service capability instead of its concrete implementation.",
|
|
12587
|
+
remediation: "Declare and implement an interface covering the service's public methods.",
|
|
12588
|
+
category: "architecture",
|
|
12589
|
+
aliases: ["require-interface-for-injected-service"],
|
|
12590
|
+
examples: [
|
|
12591
|
+
{ 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 },
|
|
12592
|
+
{ 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 }
|
|
12593
|
+
]
|
|
12594
|
+
};
|
|
11239
12595
|
var CONFIGISH_TYPE_RE = /(?:Options|Opts|Config|Configuration|Settings|Params|Props|Args|Env|Environment|Callbacks|Flags)$/;
|
|
11240
12596
|
var CONFIGISH_NAME_RE = /^(?:options|opts|config|configuration|settings|params|props|args|env|environment|callbacks|flags|logger|log|clock)$/i;
|
|
11241
12597
|
var HTTP_TRANSPORT_TYPE_RE = /^(?:KyInstance|AxiosInstance|Session)$/;
|
|
@@ -11674,8 +13030,9 @@ function hasServicePort(node, methods, classes, interfaces) {
|
|
|
11674
13030
|
}
|
|
11675
13031
|
return methods.every((method) => combined.has(method));
|
|
11676
13032
|
}
|
|
11677
|
-
var
|
|
11678
|
-
name: "require-
|
|
13033
|
+
var require_port_for_service_default = createRule({
|
|
13034
|
+
name: "require-port-for-service",
|
|
13035
|
+
documentation: requirePortForServiceDocumentation,
|
|
11679
13036
|
meta: {
|
|
11680
13037
|
type: "suggestion",
|
|
11681
13038
|
docs: {
|
|
@@ -11740,6 +13097,16 @@ var require_interface_for_injected_service_default = createRule({
|
|
|
11740
13097
|
|
|
11741
13098
|
// src/rules/require-static-next-matcher.ts
|
|
11742
13099
|
var import_utils67 = require("@typescript-eslint/utils");
|
|
13100
|
+
var requireStaticNextMatcherDocumentation = {
|
|
13101
|
+
summary: "Require Next.js middleware and proxy matcher configuration to contain only build-time literals.",
|
|
13102
|
+
rationale: "Next.js must statically analyze matcher values at build time; computed values are ignored.",
|
|
13103
|
+
remediation: "Write matcher strings, arrays, and object fields as literals in the exported config.",
|
|
13104
|
+
category: "correctness",
|
|
13105
|
+
examples: [
|
|
13106
|
+
{ 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 },
|
|
13107
|
+
{ 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 }
|
|
13108
|
+
]
|
|
13109
|
+
};
|
|
11743
13110
|
var NEXT_ENTRY_FILE = /(?:^|[/\\])(?:middleware|proxy)\.[cm]?[jt]sx?$/u;
|
|
11744
13111
|
function unwrapExpression3(node) {
|
|
11745
13112
|
if (node.type === import_utils67.AST_NODE_TYPES.TSAsExpression || node.type === import_utils67.AST_NODE_TYPES.TSSatisfiesExpression || node.type === import_utils67.AST_NODE_TYPES.TSNonNullExpression || node.type === import_utils67.AST_NODE_TYPES.TSTypeAssertion) {
|
|
@@ -11774,6 +13141,7 @@ function propertyName2(property) {
|
|
|
11774
13141
|
}
|
|
11775
13142
|
var require_static_next_matcher_default = createRule({
|
|
11776
13143
|
name: "require-static-next-matcher",
|
|
13144
|
+
documentation: requireStaticNextMatcherDocumentation,
|
|
11777
13145
|
meta: {
|
|
11778
13146
|
type: "problem",
|
|
11779
13147
|
docs: {
|
|
@@ -11818,6 +13186,16 @@ var require_static_next_matcher_default = createRule({
|
|
|
11818
13186
|
|
|
11819
13187
|
// src/rules/require-zod-form-validation.ts
|
|
11820
13188
|
var import_utils68 = require("@typescript-eslint/utils");
|
|
13189
|
+
var requireZodFormValidationDocumentation = {
|
|
13190
|
+
summary: "Require Zod validation (`Schema.parse(...)` / `Schema.safeParse(...)`) when reading values out of a `FormData` object.",
|
|
13191
|
+
rationale: "FormData values are untrusted strings or files and need runtime validation before use.",
|
|
13192
|
+
remediation: "Read the value inside a Zod schema's `parse` or `safeParse` input.",
|
|
13193
|
+
category: "security",
|
|
13194
|
+
examples: [
|
|
13195
|
+
{ 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 },
|
|
13196
|
+
{ 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 }
|
|
13197
|
+
]
|
|
13198
|
+
};
|
|
11821
13199
|
var isZodParseCall = (node) => {
|
|
11822
13200
|
if (node.type !== import_utils68.AST_NODE_TYPES.CallExpression) return false;
|
|
11823
13201
|
const callee = node.callee;
|
|
@@ -11858,6 +13236,7 @@ var isFormDataMethodCall = (node) => {
|
|
|
11858
13236
|
};
|
|
11859
13237
|
var require_zod_form_validation_default = createRule({
|
|
11860
13238
|
name: "require-zod-form-validation",
|
|
13239
|
+
documentation: requireZodFormValidationDocumentation,
|
|
11861
13240
|
meta: {
|
|
11862
13241
|
type: "problem",
|
|
11863
13242
|
docs: {
|
|
@@ -11946,11 +13325,22 @@ var require_zod_form_validation_default = createRule({
|
|
|
11946
13325
|
|
|
11947
13326
|
// src/rules/store-insert-requires-on-conflict.ts
|
|
11948
13327
|
var import_utils69 = require("@typescript-eslint/utils");
|
|
13328
|
+
var storeInsertRequiresOnConflictDocumentation = {
|
|
13329
|
+
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.",
|
|
13330
|
+
rationale: "A replayed bare insert can duplicate data or fail on a uniqueness constraint.",
|
|
13331
|
+
remediation: "Add an appropriate `ON CONFLICT` action or supported replay-safe insert form.",
|
|
13332
|
+
category: "correctness",
|
|
13333
|
+
examples: [
|
|
13334
|
+
{ 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 },
|
|
13335
|
+
{ 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 }
|
|
13336
|
+
]
|
|
13337
|
+
};
|
|
11949
13338
|
var INSERT_WRITE = /\bINSERT\s+(?:OR\s+\w+\s+)?INTO\s+[\w."'`?$:@-]+\s*(?:\([^)]*\)\s*)?(?:VALUES|SELECT|DEFAULT\s+VALUES)\b/i;
|
|
11950
13339
|
var CONFLICT_HANDLED = /\bON\s+CONFLICT\b|\bON\s+DUPLICATE\s+KEY\b|\bINSERT\s+OR\s+(?:IGNORE|REPLACE)\b/i;
|
|
11951
13340
|
var INSERT_GATE = /insert/i;
|
|
11952
13341
|
var store_insert_requires_on_conflict_default = createRule({
|
|
11953
13342
|
name: "store-insert-requires-on-conflict",
|
|
13343
|
+
documentation: storeInsertRequiresOnConflictDocumentation,
|
|
11954
13344
|
meta: {
|
|
11955
13345
|
type: "problem",
|
|
11956
13346
|
docs: {
|
|
@@ -11977,6 +13367,16 @@ var store_insert_requires_on_conflict_default = createRule({
|
|
|
11977
13367
|
|
|
11978
13368
|
// src/rules/stepdown.ts
|
|
11979
13369
|
var import_utils70 = require("@typescript-eslint/utils");
|
|
13370
|
+
var stepdownDocumentation = {
|
|
13371
|
+
summary: "Place a private helper below its sole direct same-scope caller.",
|
|
13372
|
+
rationale: "Caller-first ordering lets a reader follow the main flow before descending into implementation details.",
|
|
13373
|
+
remediation: "Move the private helper immediately below its sole caller.",
|
|
13374
|
+
category: "maintainability",
|
|
13375
|
+
examples: [
|
|
13376
|
+
{ 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 },
|
|
13377
|
+
{ 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 }
|
|
13378
|
+
]
|
|
13379
|
+
};
|
|
11980
13380
|
function isFunction(node) {
|
|
11981
13381
|
return node.type === import_utils70.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils70.AST_NODE_TYPES.FunctionDeclaration || node.type === import_utils70.AST_NODE_TYPES.FunctionExpression;
|
|
11982
13382
|
}
|
|
@@ -12331,6 +13731,7 @@ function classScope(context, node, computedReferenceNames) {
|
|
|
12331
13731
|
}
|
|
12332
13732
|
var stepdown_default = createRule({
|
|
12333
13733
|
name: "stepdown",
|
|
13734
|
+
documentation: stepdownDocumentation,
|
|
12334
13735
|
meta: {
|
|
12335
13736
|
type: "suggestion",
|
|
12336
13737
|
docs: { description: "Place a private helper below its sole direct same-scope caller." },
|
|
@@ -12364,6 +13765,16 @@ var stepdown_default = createRule({
|
|
|
12364
13765
|
|
|
12365
13766
|
// src/rules/zod-naming-convention.ts
|
|
12366
13767
|
var import_utils71 = require("@typescript-eslint/utils");
|
|
13768
|
+
var zodNamingConventionDocumentation = {
|
|
13769
|
+
summary: "Enforce a consistent Zod schema naming convention \u2014 a `Z` prefix (`ZUser`) or a `Schema` suffix (`userSchema`); both are accepted by default.",
|
|
13770
|
+
rationale: "A recognizable schema name distinguishes runtime validators from ordinary values at each use site.",
|
|
13771
|
+
remediation: "Rename the schema with a `Z` prefix or `Schema` suffix, according to the configured convention.",
|
|
13772
|
+
category: "style",
|
|
13773
|
+
examples: [
|
|
13774
|
+
{ 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 },
|
|
13775
|
+
{ 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 }
|
|
13776
|
+
]
|
|
13777
|
+
};
|
|
12367
13778
|
var CONVENTIONS = {
|
|
12368
13779
|
prefix: { test: ZOD_PREFIX_RE, messageId: "zPrefix" },
|
|
12369
13780
|
suffix: { test: ZOD_SUFFIX_RE, messageId: "schemaSuffix" },
|
|
@@ -12408,6 +13819,7 @@ var calleeChainRoot = (node) => {
|
|
|
12408
13819
|
};
|
|
12409
13820
|
var zod_naming_convention_default = createRule({
|
|
12410
13821
|
name: "zod-naming-convention",
|
|
13822
|
+
documentation: zodNamingConventionDocumentation,
|
|
12411
13823
|
meta: {
|
|
12412
13824
|
type: "suggestion",
|
|
12413
13825
|
docs: {
|
|
@@ -12490,6 +13902,7 @@ var zod_naming_convention_default = createRule({
|
|
|
12490
13902
|
var renamedRules = {
|
|
12491
13903
|
"jsdoc-restates-signature": "no-restated-jsdoc",
|
|
12492
13904
|
"no-async-callback-in-waitfor": "no-async-callback-in-wait-for",
|
|
13905
|
+
"require-interface-for-injected-service": "require-port-for-service",
|
|
12493
13906
|
"strict-test-assertions": "prefer-whole-object-assertion",
|
|
12494
13907
|
"trailing-value-narration": "no-trailing-value-narration"
|
|
12495
13908
|
};
|
|
@@ -12615,7 +14028,7 @@ var rules = {
|
|
|
12615
14028
|
"prefer-zod-infer": prefer_zod_infer_default,
|
|
12616
14029
|
"require-assert-never": require_assert_never_default,
|
|
12617
14030
|
"require-fetch-timeout": require_fetch_timeout_default,
|
|
12618
|
-
"require-
|
|
14031
|
+
"require-port-for-service": require_port_for_service_default,
|
|
12619
14032
|
"require-static-next-matcher": require_static_next_matcher_default,
|
|
12620
14033
|
"require-zod-form-validation": require_zod_form_validation_default,
|
|
12621
14034
|
"store-insert-requires-on-conflict": store_insert_requires_on_conflict_default,
|
|
@@ -12624,7 +14037,7 @@ var rules = {
|
|
|
12624
14037
|
};
|
|
12625
14038
|
var meta = {
|
|
12626
14039
|
name: "@sarj/eslint-plugin",
|
|
12627
|
-
version: "
|
|
14040
|
+
version: "13.1.0"
|
|
12628
14041
|
};
|
|
12629
14042
|
var applicationOnlyRules = [
|
|
12630
14043
|
"no-restricted-library-load",
|
|
@@ -12684,7 +14097,7 @@ var recommendedRules = {
|
|
|
12684
14097
|
"@sarj/prefer-zod-infer": "error",
|
|
12685
14098
|
"@sarj/require-assert-never": "error",
|
|
12686
14099
|
"@sarj/require-fetch-timeout": "error",
|
|
12687
|
-
"@sarj/require-
|
|
14100
|
+
"@sarj/require-port-for-service": "error",
|
|
12688
14101
|
"@sarj/require-static-next-matcher": "error",
|
|
12689
14102
|
"@sarj/require-zod-form-validation": "error",
|
|
12690
14103
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
@@ -12748,7 +14161,7 @@ var strictRules = {
|
|
|
12748
14161
|
"@sarj/prefer-zod-infer": "error",
|
|
12749
14162
|
"@sarj/require-assert-never": "error",
|
|
12750
14163
|
"@sarj/require-fetch-timeout": "error",
|
|
12751
|
-
"@sarj/require-
|
|
14164
|
+
"@sarj/require-port-for-service": "error",
|
|
12752
14165
|
"@sarj/require-static-next-matcher": "error",
|
|
12753
14166
|
"@sarj/require-zod-form-validation": "error",
|
|
12754
14167
|
"@sarj/store-insert-requires-on-conflict": "error",
|
|
@@ -12778,6 +14191,7 @@ var index_default = plugin;
|
|
|
12778
14191
|
// Annotate the CommonJS export names for ESM import in node:
|
|
12779
14192
|
0 && (module.exports = {
|
|
12780
14193
|
applicationOnlyRules,
|
|
14194
|
+
publicDocumentation,
|
|
12781
14195
|
recommendedRules,
|
|
12782
14196
|
renamedRules,
|
|
12783
14197
|
retiredRules,
|