@sarj/eslint-plugin 11.0.0 → 11.0.1

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 CHANGED
@@ -1,379 +1,49 @@
1
1
  # @sarj/eslint-plugin
2
2
 
3
- ## New in 9.11.0 disabled test assertions
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
- `no-comment-cruft` now recognizes commented-out `expect(...)` chains and
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
- ## New in 9.9.0 static Next.js matchers
11
-
12
- `require-static-next-matcher` rejects runtime expressions in exported
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
- Tiering: `no-dynamic-sql` is in both presets (an injection guard with a low
334
- false-positive rate, relevant to any repo touching SQL). The two architectural
335
- rules are **`strict`-only**, since they need per-repo configuration to say
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
- `stepdown` is likewise warning-first: measured adoption is intentionally broad,
345
- so it only owns ordering within a private-method band. The strict preset gives
346
- public/protected/private band ordering to `@typescript-eslint/member-ordering`
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
- `no-positional-tuple-return` now rejects every fixed multi-field tuple on an
352
- exported TypeScript boundary—including homogeneous, labelled, tagged, readonly,
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
- ## Ported from `sarj_python_lint`
31
+ ## Rules and suppressions
358
32
 
359
- Several rules are ports of the Python linter's SARJ rules, retuned for TypeScript. The false-positive tuning documented in the Python docstrings is ported with them — that tuning is the valuable part.
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
- | TypeScript rule | Python | What it prevents |
362
- | --- | --- | --- |
363
- | `prefer-constant-time-secret-compare` | SARJ011 | Byte-by-byte secret recovery through the timing of a short-circuiting `===` on a token / signature / HMAC. On Workers the fix is `crypto.subtle.timingSafeEqual` over equal-length digests. |
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
- Shared helpers live in `src/rules/_*.ts` (`_secret-names.ts`, `_sql.ts`, `_logging.ts`, `_paths.ts`, `_tailwind.ts`) so related rules cannot diverge on what counts as a secret, a SQL statement, a logging call, or a test file.
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
- Deliberately **not** ported: `no-unreachable-after-terminal` (SARJ010) is already covered by `allowUnreachableCode: false` in `@sarj/tsconfig` plus ESLint core `no-unreachable`; `no-aggregation-in-store-query` (SARJ020) assumes a Postgres-OLTP / columnar-mirror split that D1 does not have; `no-query-with-many-joins` (SARJ019), `prefer-class-row`, `prefer-struct-over-namedtuple`, `prefer-timedelta-for-durations`, and `no-fstring-in-log` have no TypeScript defect class or target API; `prefer-str-enum` is covered by `prefer-string-literal-union` + `no-enum`.
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`.
package/dist/index.cjs CHANGED
@@ -12382,7 +12382,7 @@ var rules = {
12382
12382
  };
12383
12383
  var meta = {
12384
12384
  name: "@sarj/eslint-plugin",
12385
- version: "11.0.0"
12385
+ version: "11.0.1"
12386
12386
  };
12387
12387
  var applicationOnlyRules = [
12388
12388
  "no-restricted-library-load",
package/dist/index.d.cts CHANGED
@@ -77,7 +77,7 @@ interface RuleOptions {
77
77
  /**
78
78
  * @fileoverview _renames — every rule this plugin has renamed, old name to new; the old names no longer resolve, so this map is what says what to write instead.
79
79
  *
80
- * `sarj-standards repo sync-ledger` turns each entry into the consumer-facing ledger row.
80
+ * `sarj-standards maintain sync-ledger` turns each entry into the consumer-facing ledger row.
81
81
  *
82
82
  */
83
83
  declare const renamedRules: {
@@ -453,7 +453,7 @@ type FlatPreset = {
453
453
  declare const plugin: {
454
454
  readonly meta: {
455
455
  readonly name: "@sarj/eslint-plugin";
456
- readonly version: "11.0.0";
456
+ readonly version: "11.0.1";
457
457
  };
458
458
  readonly rules: {
459
459
  readonly "duplicate-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"duplicateTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
package/dist/index.d.ts CHANGED
@@ -77,7 +77,7 @@ interface RuleOptions {
77
77
  /**
78
78
  * @fileoverview _renames — every rule this plugin has renamed, old name to new; the old names no longer resolve, so this map is what says what to write instead.
79
79
  *
80
- * `sarj-standards repo sync-ledger` turns each entry into the consumer-facing ledger row.
80
+ * `sarj-standards maintain sync-ledger` turns each entry into the consumer-facing ledger row.
81
81
  *
82
82
  */
83
83
  declare const renamedRules: {
@@ -453,7 +453,7 @@ type FlatPreset = {
453
453
  declare const plugin: {
454
454
  readonly meta: {
455
455
  readonly name: "@sarj/eslint-plugin";
456
- readonly version: "11.0.0";
456
+ readonly version: "11.0.1";
457
457
  };
458
458
  readonly rules: {
459
459
  readonly "duplicate-test-body": _typescript_eslint_utils_ts_eslint.RuleModule<"duplicateTestBody", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
package/dist/index.js CHANGED
@@ -12346,7 +12346,7 @@ var rules = {
12346
12346
  };
12347
12347
  var meta = {
12348
12348
  name: "@sarj/eslint-plugin",
12349
- version: "11.0.0"
12349
+ version: "11.0.1"
12350
12350
  };
12351
12351
  var applicationOnlyRules = [
12352
12352
  "no-restricted-library-load",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "11.0.0",
3
+ "version": "11.0.1",
4
4
  "packageManager": "npm@11.19.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "build": "tsup",
37
37
  "dogfood": "npm run build && eslint --config eslint.dogfood.config.mjs src tests eslint.config.mjs eslint.dogfood.config.mjs tsup.config.ts vitest.config.ts --max-warnings 0 && node -e \"import('./dist/index.js').then(({default:p}) => console.log('dogfood: '+Object.keys(p.rules).length+' TypeScript rules, 0 diagnostics'))\"",
38
38
  "lint": "eslint . --max-warnings 0",
39
- "prepack": "npm run build && npm run verify-package",
39
+ "prepack": "npm run verify-package",
40
40
  "test": "vitest run",
41
41
  "typecheck": "tsc --noEmit",
42
42
  "verify-package": "node -e \"const fs=require('node:fs'),p=require('./package.json'),walk=x=>typeof x==='string'?[x]:x&&typeof x==='object'?Object.values(x).flatMap(walk):[];for(const file of new Set([p.main,p.module,p.types,...walk(p.exports)].filter(Boolean).map(x=>x.startsWith('./')?x.slice(2):x))){if(!file.startsWith('dist/'))throw new Error('export must live under dist: '+file);const stat=fs.statSync(file);if(!stat.isFile()||stat.size===0)throw new Error('missing or empty export: '+file)}\""
@@ -96,6 +96,7 @@
96
96
  "overrides": {
97
97
  "brace-expansion@^5.0.0": "5.0.9",
98
98
  "esbuild": "^0.28.1",
99
+ "nanoid@<3.3.17": "3.3.17",
99
100
  "eslint-plugin-react": {
100
101
  "eslint": "$eslint"
101
102
  }