@sarj/eslint-plugin 11.0.0 → 11.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 +30 -360
- package/dist/index.cjs +327 -68
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +327 -68
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,379 +1,49 @@
|
|
|
1
1
|
# @sarj/eslint-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deterministic ESLint rules and presets for TypeScript, React, and modern Node.js
|
|
4
|
+
applications. The plugin complements ESLint's core and TypeScript ESLint rules;
|
|
5
|
+
it does not duplicate them.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
`assert(...)` calls. A disabled assertion leaves a collected test passing while
|
|
7
|
-
verifying nothing. JSDoc and assertion examples introduced by prose remain
|
|
8
|
-
exempt, as do active assertions.
|
|
7
|
+
## Use
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`middleware.ts` and `proxy.ts` matcher configuration. Next.js reads these values
|
|
14
|
-
during static analysis, so identifiers, calls, concatenation, interpolated
|
|
15
|
-
templates, and spreads can pass type checking but fail the production build.
|
|
16
|
-
|
|
17
|
-
Custom ESLint rules for hypermodern TypeScript / React / Next.js projects.
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
pnpm add -D @sarj/eslint-plugin
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
```js
|
|
24
|
-
// eslint.config.mjs
|
|
25
|
-
import sarj from "@sarj/eslint-plugin";
|
|
26
|
-
export default [sarj.configs.recommended];
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
66 rules. Each source under `src/rules/` states one concise claim and links to
|
|
30
|
-
its paired tests. The definition and named test cases are the complete rule
|
|
31
|
-
specification, and `meta.docs.url` points directly to those executable examples.
|
|
32
|
-
|
|
33
|
-
Presets: `recommended` is a curated subset and `strict` contains every
|
|
34
|
-
general-profile rule. Every active custom rule in both presets is an error;
|
|
35
|
-
rules that cannot meet that bar are narrowed or retired instead of being left
|
|
36
|
-
as permanent warning noise. Application-only rules are exported through
|
|
37
|
-
`applicationOnlyRules` and are also errors when the application profile enables
|
|
38
|
-
them. `style-guide` remains the formatting/naming subset.
|
|
39
|
-
|
|
40
|
-
## New in 9.8.0 — application library policy adapters
|
|
41
|
-
|
|
42
|
-
`no-restricted-library-load` extends a configured dependency policy to runtime
|
|
43
|
-
loading forms that ESLint's `no-restricted-imports` does not cover: literal
|
|
44
|
-
`import()`, unshadowed `require()` / `require.resolve()`, and TypeScript
|
|
45
|
-
`import x = require(...)`. It matches a package and all of its subpaths, but
|
|
46
|
-
deliberately ignores computed names and shadowed local `require` functions.
|
|
47
|
-
|
|
48
|
-
`prefer-native-random-uuid` reports resolved, zero-argument UUID v4 calls from
|
|
49
|
-
the `uuid` package and suggests `globalThis.crypto.randomUUID()`. The suggestion
|
|
50
|
-
changes only the call; import cleanup remains explicit. Other UUID versions,
|
|
51
|
-
custom-randomness arguments, re-exports, and function references are untouched.
|
|
52
|
-
|
|
53
|
-
Neither rule is in the general presets. The application profile configures them
|
|
54
|
-
for its Node 22.13+ runtime contract and supplies catalog entries to the loader
|
|
55
|
-
rule:
|
|
56
|
-
|
|
57
|
-
```js
|
|
58
|
-
rules: {
|
|
59
|
-
"@sarj/no-restricted-library-load": [
|
|
60
|
-
"error",
|
|
61
|
-
{
|
|
62
|
-
libraries: [
|
|
63
|
-
{
|
|
64
|
-
id: "LIB101",
|
|
65
|
-
module: "axios",
|
|
66
|
-
replacement: "Ky",
|
|
67
|
-
note: "The APIs are not drop-in equivalents.",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
"@sarj/prefer-native-random-uuid": "error",
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Renamed in 7.0.0, aliases deleted in 9.0.0 (breaking)
|
|
77
|
-
|
|
78
|
-
| Old name | New name |
|
|
79
|
-
| --- | --- |
|
|
80
|
-
| `@sarj/jsdoc-restates-signature` | `@sarj/no-restated-jsdoc` |
|
|
81
|
-
| `@sarj/no-async-callback-in-waitfor` | `@sarj/no-async-callback-in-wait-for` |
|
|
82
|
-
| `@sarj/strict-test-assertions` | `@sarj/prefer-whole-object-assertion` |
|
|
83
|
-
| `@sarj/trailing-value-narration` | `@sarj/no-trailing-value-narration` |
|
|
84
|
-
|
|
85
|
-
7.0.0 kept each old name registered as a deprecated alias. 9.0.0 deletes them, so
|
|
86
|
-
the new names are the only names — and an old name left in a config, an
|
|
87
|
-
`eslint-disable` comment or a suppressions baseline now makes ESLint exit 2 with
|
|
88
|
-
`Could not find "@sarj/<rule>" in plugin "@sarj"` before it reads a file.
|
|
89
|
-
|
|
90
|
-
Run `sarj-lint-configs doctor` before upgrading: it reads the shipped rule ledger
|
|
91
|
-
and names every file holding an old name, with the replacement. The map is also
|
|
92
|
-
exported for codemods:
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
import { renamedRules } from "@sarj/eslint-plugin";
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The rename map and migration behavior live in
|
|
99
|
-
[`src/rules/_renames.ts`](src/rules/_renames.ts) and its tests.
|
|
100
|
-
|
|
101
|
-
## New in 4.1.0 — `no-hand-rolled-sleep`
|
|
102
|
-
|
|
103
|
-
`new Promise((resolve) => setTimeout(resolve, ms))` is `node:timers/promises`'s
|
|
104
|
-
`setTimeout` rewritten by hand, minus the `AbortSignal` — so it is a capability
|
|
105
|
-
loss, not verbosity. The hand-rolled sleep holds a live timer that nothing can
|
|
106
|
-
clear, and its mirror image, the `Promise.race([work, rejectAfter(ms)])` timeout
|
|
107
|
-
arm, leaks the timer the other way: when `work` wins, nothing clears it and it
|
|
108
|
-
keeps the event loop alive until it fires.
|
|
109
|
-
|
|
110
|
-
Shipped only after confirming nothing already enabled reports this position. The
|
|
111
|
-
enabled set was resolved with `ESLint#calculateConfigForFile` against the shipped
|
|
112
|
-
`eslint.strict.mjs` (204 rules before this one) and a file containing every
|
|
113
|
-
shape was linted through it: no report. `eslint-plugin-unicorn` 72 has no
|
|
114
|
-
promisified-timer rule among its 341; `unicorn/prefer-abort-signal-timeout` covers the
|
|
115
|
-
`AbortController` + `setTimeout` idiom and not the race arm; core
|
|
116
|
-
`no-promise-executor-return` fires on the concise-arrow spelling only and its
|
|
117
|
-
remedy ("add braces") entrenches the hand-rolled sleep. The polling-loop variant
|
|
118
|
-
(`while (!done) await sleep(ms)`) is deliberately absent — core `no-await-in-loop`
|
|
119
|
-
is already enabled and reports that exact position.
|
|
120
|
-
|
|
121
|
-
Measured over 1,471 files containing `setTimeout` across 15 OSS repos (hono,
|
|
122
|
-
tRPC, drizzle-orm, undici, vitest, got, cal.com, documenso, dub, formbricks,
|
|
123
|
-
midday, openstatus, papermark, unkey, zod) and seven internal ones: 85 + 11
|
|
124
|
-
sleeps and 2 + 1 leaky race arms at the default settings, **0 false positives**.
|
|
125
|
-
|
|
126
|
-
`checkClientModules` is the option that matters. A browser or React Native
|
|
127
|
-
bundle cannot import `node:timers/promises` and the web platform ships no
|
|
128
|
-
equivalent, so client modules are skipped by default — 79% of the internal
|
|
129
|
-
corpus's occurrences live in `.tsx` components where the fix cannot be applied.
|
|
130
|
-
Turn it on only in a tree where every file resolves `node:` builtins. The race
|
|
131
|
-
message is reported everywhere regardless: `AbortSignal.timeout` is on the web
|
|
132
|
-
platform too.
|
|
133
|
-
|
|
134
|
-
| Rule | What it catches | Preset |
|
|
135
|
-
|---|---|---|
|
|
136
|
-
| `no-hand-rolled-spinner` | Intrinsic elements styled as Tailwind border-ring loading spinners outside the design system. | error / error |
|
|
137
|
-
| `prefer-input-group-search` | Search icons and shared Input controls composed without the shared InputGroup primitive. | error / error |
|
|
138
|
-
| `prefer-shadcn-primitives` | Deterministically visible raw JSX controls used instead of shared shadcn primitives. Unknown and hidden input types are excluded. | application profile: error |
|
|
139
|
-
| `require-static-next-matcher` | Dynamic values in exported Next.js middleware and proxy matcher configuration. | error / error |
|
|
140
|
-
| `no-hand-rolled-sleep` | `new Promise((r) => setTimeout(r, ms))` in any spelling, and an uncleared `Promise.race`/`Promise.any` timeout arm. Options: `checkClientModules` (default `false`), `allowIn`. | error / error |
|
|
141
|
-
|
|
142
|
-
`prefer-shadcn-primitives` is application-only because generic plugin consumers
|
|
143
|
-
may use another design system or intentionally expose native controls. The
|
|
144
|
-
application profile enforces its measured, deterministic cases as errors while
|
|
145
|
-
keeping it disabled inside `components/ui` and
|
|
146
|
-
`components/design-system`, where shared primitives must wrap native elements.
|
|
147
|
-
|
|
148
|
-
## New in 2.14.0 — `no-tautological-expect`
|
|
149
|
-
|
|
150
|
-
The TS half of SARJ057. An `expect(...)` whose operands are all literals has
|
|
151
|
-
already decided its outcome before the test runs — `expect(true).toBe(true)`
|
|
152
|
-
passes if you delete the module under test.
|
|
153
|
-
|
|
154
|
-
Python has caught the assertion-*free* test since 0.15.0 (`SARJ043
|
|
155
|
-
zero-assertion-test`) and had no TypeScript counterpart, which is exactly how
|
|
156
|
-
`expect(true).toBe(true); // placeholder` survived in an internal suite named for
|
|
157
|
-
the behaviour it was supposed to check: the file *has* an assertion, so nothing
|
|
158
|
-
was looking at it.
|
|
159
|
-
|
|
160
|
-
| Rule | What it catches | Preset |
|
|
161
|
-
|---|---|---|
|
|
162
|
-
| `no-tautological-expect` | `expect(<literal>).toBe/toEqual/toStrictEqual(<textually identical literal>)`, and `expect(<literal>).toBeDefined()/toBeTruthy()/toBeNull()/…` — a zero-argument matcher on a literal receiver. | warn / error |
|
|
163
|
-
|
|
164
|
-
**The narrowness is the rule.** The obvious generalisation — "flag a comparison
|
|
165
|
-
of a thing with itself" — measures ~95% false positives:
|
|
166
|
-
`expect(hash([o])).toEqual(hash([o]))` is a *determinism* test,
|
|
167
|
-
`expect(memo(x)).toBe(memo(x))` a *memoization* test, `expect(a).toEqual(a)` on a
|
|
168
|
-
value with custom equality a *reflexivity* test. All three can genuinely fail. So
|
|
169
|
-
an identifier, member-expression or call operand is never enough: both sides must
|
|
170
|
-
be literals, and textually identical ones. A modified chain (`.not`, `.resolves`,
|
|
171
|
-
`.rejects`), a spread, an interpolated template literal, and two *different*
|
|
172
|
-
literals are all left alone.
|
|
173
|
-
|
|
174
|
-
Measured before shipping: **3 hits across 5,819 `.ts`/`.tsx` files** (1,003 of
|
|
175
|
-
them test files, where the rule is active) — six internal repos plus `got`,
|
|
176
|
-
`hono`, `swr` and `trpc`. 3 true positives, **0 false positives**; every hit is
|
|
177
|
-
an abandoned placeholder.
|
|
178
|
-
|
|
179
|
-
## New in 2.13.0 — the anti-comment-verbosity family
|
|
180
|
-
|
|
181
|
-
From a 37,918-comment, nine-repo measurement study. All three are
|
|
182
|
-
deletion-class, so each was validated against zod / swr / zustand / TanStack
|
|
183
|
-
Query as well as the maintained repos. Each paired test suite records the
|
|
184
|
-
false-positive shapes guarded by the implementation.
|
|
185
|
-
|
|
186
|
-
| Rule | What it catches | Preset |
|
|
187
|
-
|---|---|---|
|
|
188
|
-
| `no-restated-comment` | A single-line comment whose every content word already appears on the statement below it. Defers to `no-comment-cruft` for the verb-led shape, so a comment is never reported twice. | warn / error |
|
|
189
|
-
| `no-restated-jsdoc` | A JSDoc block whose description and `@param`/`@returns` only re-spell the signature. Offers a delete SUGGESTION, never an auto-`--fix`. | warn / error |
|
|
190
|
-
| `no-trailing-value-narration` | `staleTime: 5 * 60 * 1000, // 5 minutes` — the unit belongs in the name, where it cannot drift. | warn / error |
|
|
191
|
-
| `no-declaration-comment-wall` | The same judgement for an ENUM BODY or a CLASS BODY, which the rule below structurally cannot see. | warn / error |
|
|
192
|
-
| `no-union-in-comment` | `kind: string; // 'pending' \| 'approved'` — the comment IS the union; write it as one so the compiler enforces it. | warn / error |
|
|
193
|
-
| `no-type-member-comment-wall` | An object type whose member comments mostly re-spell the members' own names and types — the VOLUME arm of the family, reported once for the type. | warn / error |
|
|
194
|
-
|
|
195
|
-
## New in 2.9.0
|
|
196
|
-
|
|
197
|
-
Both distilled from two years of PR-review comments across ~1,065 PRs.
|
|
198
|
-
|
|
199
|
-
| Rule | What it catches | Preset |
|
|
200
|
-
|---|---|---|
|
|
201
|
-
| `no-zod-native-enum` | `z.nativeEnum(...)` and `z.enum(SomeTsEnum)` — the schema-layer back door around `no-enum`. Autofixes an inline string-literal object to `z.enum([...])`. | warn / error |
|
|
202
|
-
| `prefer-zod-enum` | `z.union([z.literal("a"), z.literal("b")])` — autofixes closed string choices to the shorter, equivalent `z.enum(["a", "b"])`. | warn / error |
|
|
203
|
-
| `prefer-zod-infer` | An `interface`/`type` that restates a Zod schema declared in the same module instead of deriving it with `z.infer`. Options: `ignoreTypeNames`, `requireIdenticalShape` (default `true`). | warn / error |
|
|
204
|
-
| `prefer-module-level-constant` | A literal-only `const` collection (array, object, `Set`, `Map`, `Object.freeze`) or non-global regex declared inside a function body, never mutated and never escaping — hoist it to module scope. Options: `minElements` (default 3), `checkRegex`, `ignoreTestFiles`. | warn / error |
|
|
205
|
-
| `prefer-module-level-schema` | A Zod schema built inside a function body that closes over nothing the function owns — hoist it to module scope instead of rebuilding it per call, per request, per render. Silent when it references a parameter, local, type parameter, local type, or `this` (that is a schema FACTORY), when it is already memoized, and inside `z.lazy`. Options: `factories` (default: the object-like composites), `minProperties` (default 1), `ignoreTestFiles`. | warn / error |
|
|
206
|
-
| `prefer-non-nullable-collection` | A required array property or direct alias explicitly combined with `null`/`undefined`, creating two equivalent empty states. Optional API fields are excluded because omission can be meaningful. | warn / error |
|
|
207
|
-
|
|
208
|
-
## Options
|
|
209
|
-
|
|
210
|
-
### Declare your logger (`loggerNames` / `logFunctions`)
|
|
211
|
-
|
|
212
|
-
Three rules decide whether a call writes to a log sink: `no-log-only-catch`,
|
|
213
|
-
`no-sentinel-return-on-catch`, `no-secret-in-log`. Out of the box they recognise
|
|
214
|
-
a log method on a logger *receiver* (`console.error`, `logger.warn`,
|
|
215
|
-
`this.logger.info`). A structured logger is usually a free *function* taking a
|
|
216
|
-
meta object, which has no receiver — declare it once and all of them see it:
|
|
9
|
+
Sarj repositories should let `sarj-standards setup` install the tested peer set
|
|
10
|
+
and generate the integration. Direct ESLint consumers can install the package
|
|
11
|
+
and select a preset:
|
|
217
12
|
|
|
218
13
|
```js
|
|
219
|
-
const logging = { logFunctions: ["logEvent"], loggerNames: ["obs"] };
|
|
220
|
-
|
|
221
|
-
rules: {
|
|
222
|
-
"@sarj/no-log-only-catch": ["error", logging],
|
|
223
|
-
"@sarj/no-sentinel-return-on-catch": ["error", logging],
|
|
224
|
-
"@sarj/no-secret-in-log": ["error", logging],
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
- `logFunctions` — free functions (or methods) that log: `logEvent("x", { err })`.
|
|
229
|
-
- `loggerNames` — extra logger *receiver* names, added to the built-in set.
|
|
230
|
-
|
|
231
|
-
This suppresses "swallows the error without logging it" on a correctly-logged
|
|
232
|
-
degraded return, and — importantly — makes `no-secret-in-log` inspect those
|
|
233
|
-
calls, which it could not do before: `logEvent("slack.auth", { botToken })` was
|
|
234
|
-
previously never examined.
|
|
235
|
-
|
|
236
|
-
### `zod-naming-convention`: `convention`
|
|
237
|
-
|
|
238
|
-
`"either"` (default) accepts both the `Z`-prefix (`ZUser`) and the `Schema`
|
|
239
|
-
suffix (`userSchema`) — the two conventions `require-zod-form-validation`
|
|
240
|
-
already recognises. Set `"prefix"` or `"suffix"` to pin one:
|
|
241
|
-
|
|
242
|
-
```js
|
|
243
|
-
"@sarj/zod-naming-convention": ["error", { convention: "suffix" }]
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### `prefer-zod-infer`: `requireIdenticalShape` / `ignoreTypeNames`
|
|
247
|
-
|
|
248
|
-
By default the rule only reports a type whose members match its schema's keys
|
|
249
|
-
one for one — same names, same optionality, same nullability, no `.transform()`
|
|
250
|
-
anywhere in the pair. On a 30,759-file, 17-repo sweep that is 5 reports and 5
|
|
251
|
-
true positives. Drop the shape comparison to report on name correlation alone
|
|
252
|
-
(8 reports on the same corpus, 1 of them noise, and it catches twins that have
|
|
253
|
-
already drifted):
|
|
254
|
-
|
|
255
|
-
```js
|
|
256
|
-
"@sarj/prefer-zod-infer": ["error", { requireIdenticalShape: false }]
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
`ignoreTypeNames` takes regex sources matched against the declared type name,
|
|
260
|
-
for the pair that is genuinely meant to be maintained by hand:
|
|
261
|
-
|
|
262
|
-
```js
|
|
263
|
-
"@sarj/prefer-zod-infer": ["error", { ignoreTypeNames: ["^LegacyUser$"] }]
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### `prefer-string-literal-union`: `ignoreFields`
|
|
267
|
-
|
|
268
|
-
Field names whose value set is owned by a vendor and genuinely open (a Slack
|
|
269
|
-
`event.subtype`, a Resend `bounceType`). Narrowing to a union you don't control
|
|
270
|
-
would be wrong, not better:
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
"@sarj/prefer-string-literal-union": ["warn", { ignoreFields: ["subtype", "bounceType"] }]
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### `no-enum`: `ignoreFiles`
|
|
277
|
-
|
|
278
|
-
Glob patterns whose files opt out (generated code already opts out by default).
|
|
279
|
-
|
|
280
|
-
## Configurable rules
|
|
281
|
-
|
|
282
|
-
Most rules take no options. These do, because they encode a codebase's
|
|
283
|
-
architecture rather than a language fact — the defaults describe one convention
|
|
284
|
-
and every repo gets to name its own.
|
|
285
|
-
|
|
286
|
-
| Rule | Option | Default | Effect |
|
|
287
|
-
|---|---|---|---|
|
|
288
|
-
| `no-raw-fetch-outside-clients` | `allow` | client / vendor-wrapper path patterns | Extra files exempt from the "no bare `fetch`" rule (test files are exempt unconditionally) |
|
|
289
|
-
| `no-dynamic-sql` | `methods` | `["prepare", "exec", "query"]` | Statement-taking methods to inspect |
|
|
290
|
-
| `no-storage-in-stateless-modules` | `modules` | `[]` (rule off) | Directories declared stateless |
|
|
291
|
-
| `no-storage-in-stateless-modules` | `methods` | `["prepare", "put", "getWithMetadata"]` | Storage methods to flag |
|
|
292
|
-
| `no-hand-rolled-sleep` | `checkClientModules` | `false` | Also report the sleep form in browser/React Native modules |
|
|
293
|
-
| `no-hand-rolled-sleep` | `allowIn` | `[]` | Glob patterns for a sanctioned sleep wrapper module |
|
|
294
|
-
|
|
295
|
-
The path options on the first three rules are **regular-expression sources
|
|
296
|
-
matched against the absolute filename**, not globs — so they can express both
|
|
297
|
-
path separators. Test files no longer need to appear there: the rule delegates
|
|
298
|
-
to the shared `isTestFile` predicate, so supplying `allow` replaces only the
|
|
299
|
-
production exemptions and cannot accidentally un-exempt a test tree.
|
|
300
|
-
`allowIn` is the exception, on `no-hand-rolled-sleep` as on
|
|
301
|
-
`require-fetch-timeout`: it takes minimatch-ish **globs**, also matched against
|
|
302
|
-
the absolute path, so anchor them with a `**/` prefix. Supplying an option
|
|
303
|
-
**replaces** the default rather than extending it.
|
|
304
|
-
|
|
305
|
-
`no-storage-in-stateless-modules` is a **no-op until `modules` is set**. The
|
|
306
|
-
method names alone (`put`, `prepare`) carry no type information, so the rule is
|
|
307
|
-
only meaningful once it is pointed at the directories a team has actually
|
|
308
|
-
declared stateless.
|
|
309
|
-
|
|
310
|
-
```js
|
|
311
|
-
// eslint.config.mjs
|
|
312
14
|
import sarj from "@sarj/eslint-plugin";
|
|
313
15
|
|
|
314
16
|
export default [
|
|
315
17
|
sarj.configs.strict,
|
|
316
|
-
{
|
|
317
|
-
rules: {
|
|
318
|
-
// This repo keeps its HTTP layer in `lib/api/`, not `clients/`.
|
|
319
|
-
"@sarj/no-raw-fetch-outside-clients": [
|
|
320
|
-
"error",
|
|
321
|
-
{ allow: ["[\\\\/]lib[\\\\/]api[\\\\/]"] },
|
|
322
|
-
],
|
|
323
|
-
// Declare which modules must stay stateless.
|
|
324
|
-
"@sarj/no-storage-in-stateless-modules": [
|
|
325
|
-
"error",
|
|
326
|
-
{ modules: ["[\\\\/]engineer-digest[\\\\/]"] },
|
|
327
|
-
],
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
18
|
];
|
|
331
|
-
```
|
|
332
19
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
anything useful.
|
|
337
|
-
|
|
338
|
-
`no-generic-single-export-module` warns when a `utils`, `helpers`, `common`,
|
|
339
|
-
`constants`, or similarly generic module has one runtime export that already
|
|
340
|
-
provides a precise filename. Ambiguous re-exports, type-only modules,
|
|
341
|
-
conventional framework filenames, tests, generated files, and CommonJS modules
|
|
342
|
-
are excluded. It is warning-first while corpus evidence accumulates.
|
|
20
|
+
// For staged adoption:
|
|
21
|
+
// export default [sarj.configs.recommended];
|
|
22
|
+
```
|
|
343
23
|
|
|
344
|
-
`
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
and disables `perfectionist/sort-classes`, avoiding contradictory owners. The
|
|
348
|
-
rule resolves module helpers, private methods, recursion and cycles, but leaves
|
|
349
|
-
function-valued private fields and mutable receiver aliases alone.
|
|
24
|
+
Available presets are `recommended` for staged adoption and `strict` for the
|
|
25
|
+
full general policy. Application-only rules are exported separately because
|
|
26
|
+
they depend on an explicit runtime and library policy.
|
|
350
27
|
|
|
351
|
-
`
|
|
352
|
-
|
|
353
|
-
hook-style, interface, function-type, declaration and abstract-method returns.
|
|
354
|
-
The runtime alternative is a named object. Private/local implementation helpers
|
|
355
|
-
and sequence-shaped arrays remain outside the public-boundary rule.
|
|
28
|
+
Use `sarj-standards show peers` for the tested ESLint and parser dependency set.
|
|
29
|
+
Do not combine independent `latest` versions and assume they are compatible.
|
|
356
30
|
|
|
357
|
-
##
|
|
31
|
+
## Rules and suppressions
|
|
358
32
|
|
|
359
|
-
|
|
33
|
+
Every rule is registered in `src/index.ts`; its source under `src/rules/` and
|
|
34
|
+
paired test under `tests/rules/` are the authoritative specification. Run
|
|
35
|
+
`sarj-standards show rules` for the current machine-readable catalog.
|
|
360
36
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
| `no-secret-in-log` | SARJ012 | Credentials persisted into log sinks. |
|
|
365
|
-
| `store-insert-requires-on-conflict` | SARJ018 | Duplicate rows — or unique-constraint failures that re-trigger the handler — when a cron re-runs or a queue message is redelivered. |
|
|
366
|
-
| `no-select-star` | SARJ021 | An implicit row contract that changes silently when a column is added or reordered. |
|
|
367
|
-
| `no-offset-pagination` | SARJ025 | O(N)-per-page scans, and rows repeated or skipped when the offset window shifts under concurrent inserts. |
|
|
368
|
-
| `no-repeated-string-literal` | SARJ024 | Copies of a structured literal (SQL, column lists, prompt templates) drifting apart when only one is edited. |
|
|
369
|
-
| `no-positional-tuple-return` | SARJ026 | Public APIs exposing multi-field tuples—even labelled or hook-style tuples—instead of named objects. |
|
|
370
|
-
| `no-sleep-in-test-body` | SARJ031 | Tests that assert on wall-clock time and flake under CI load. |
|
|
371
|
-
| `no-fat-try-blocks` | SARJ007 | Over-broad `catch` handlers swallowing unrelated failures. |
|
|
372
|
-
| `no-cors-wildcard-with-credentials` | SARJ008 | Credentialed cross-origin requests from any origin. |
|
|
373
|
-
| `single-public-export` | SARJ022 | Modules with no single obvious entry point. |
|
|
374
|
-
| `stepdown` | SARJ023 | A private helper placed above its only direct same-scope caller. Indirect references, callbacks, cycles, overloads, and multi-caller helpers are excluded. |
|
|
375
|
-
| `prefer-string-literal-union` | SARJ006 | An open `string` where a closed set is intended. |
|
|
37
|
+
Prefer the smallest line-scoped `eslint-disable-next-line @sarj/rule-name`
|
|
38
|
+
suppression and explain why the exceptional code is intentional. Unknown or
|
|
39
|
+
retired rule names are configuration errors.
|
|
376
40
|
|
|
377
|
-
|
|
41
|
+
New rules must prove that upstream ESLint and TypeScript ESLint cannot express
|
|
42
|
+
the policy and must pass focused tests plus the repository corpus evaluation
|
|
43
|
+
described in the root contribution guide.
|
|
378
44
|
|
|
379
|
-
|
|
45
|
+
Renamed rules fail closed. Replace `jsdoc-restates-signature` with
|
|
46
|
+
`no-restated-jsdoc`, `no-async-callback-in-waitfor` with
|
|
47
|
+
`no-async-callback-in-wait-for`, `strict-test-assertions` with
|
|
48
|
+
`prefer-whole-object-assertion`, and `trailing-value-narration` with
|
|
49
|
+
`no-trailing-value-narration`.
|