@sarj/eslint-plugin 2.7.0 → 2.9.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 +135 -1
- package/dist/index.cjs +2035 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +284 -10
- package/dist/index.d.ts +284 -10
- package/dist/index.js +2041 -223
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,140 @@ import sarj from "@sarj/eslint-plugin";
|
|
|
12
12
|
export default [...sarj.configs.recommended];
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Each rule's source under `
|
|
15
|
+
41 rules. Each rule's source under `src/rules/` carries its own `@fileoverview` rationale plus `meta.docs.description` + `meta.messages` — read the file for the full reasoning, including the false positives it deliberately does not fire on.
|
|
16
16
|
|
|
17
17
|
Presets: `recommended` (warn-first), `strict` (every rule at error), `style-guide` (formatting/naming subset).
|
|
18
|
+
|
|
19
|
+
## New in 2.9.0
|
|
20
|
+
|
|
21
|
+
Both distilled from two years of PR-review comments across ~1,065 PRs.
|
|
22
|
+
|
|
23
|
+
| Rule | What it catches | Preset |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `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 |
|
|
26
|
+
| `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 |
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
### Declare your logger (`loggerNames` / `logFunctions`)
|
|
31
|
+
|
|
32
|
+
Three rules decide whether a call writes to a log sink: `no-log-only-catch`,
|
|
33
|
+
`no-sentinel-return-on-catch`, `no-secret-in-log`. Out of the box they recognise
|
|
34
|
+
a log method on a logger *receiver* (`console.error`, `logger.warn`,
|
|
35
|
+
`this.logger.info`). A structured logger is usually a free *function* taking a
|
|
36
|
+
meta object, which has no receiver — declare it once and all of them see it:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
const logging = { logFunctions: ["logEvent"], loggerNames: ["obs"] };
|
|
40
|
+
|
|
41
|
+
rules: {
|
|
42
|
+
"@sarj/no-log-only-catch": ["error", logging],
|
|
43
|
+
"@sarj/no-sentinel-return-on-catch": ["error", logging],
|
|
44
|
+
"@sarj/no-secret-in-log": ["error", logging],
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `logFunctions` — free functions (or methods) that log: `logEvent("x", { err })`.
|
|
49
|
+
- `loggerNames` — extra logger *receiver* names, added to the built-in set.
|
|
50
|
+
|
|
51
|
+
This suppresses "swallows the error without logging it" on a correctly-logged
|
|
52
|
+
degraded return, and — importantly — makes `no-secret-in-log` inspect those
|
|
53
|
+
calls, which it could not do before: `logEvent("slack.auth", { botToken })` was
|
|
54
|
+
previously never examined.
|
|
55
|
+
|
|
56
|
+
### `zod-naming-convention`: `convention`
|
|
57
|
+
|
|
58
|
+
`"either"` (default) accepts both the `Z`-prefix (`ZUser`) and the `Schema`
|
|
59
|
+
suffix (`userSchema`) — the two conventions `require-zod-form-validation`
|
|
60
|
+
already recognises. Set `"prefix"` or `"suffix"` to pin one:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
"@sarj/zod-naming-convention": ["error", { convention: "suffix" }]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `prefer-string-literal-union`: `ignoreFields`
|
|
67
|
+
|
|
68
|
+
Field names whose value set is owned by a vendor and genuinely open (a Slack
|
|
69
|
+
`event.subtype`, a Resend `bounceType`). Narrowing to a union you don't control
|
|
70
|
+
would be wrong, not better:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
"@sarj/prefer-string-literal-union": ["warn", { ignoreFields: ["subtype", "bounceType"] }]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `no-enum`: `ignoreFiles`
|
|
77
|
+
|
|
78
|
+
Glob patterns whose files opt out (generated code already opts out by default).
|
|
79
|
+
|
|
80
|
+
## Configurable rules
|
|
81
|
+
|
|
82
|
+
Most rules take no options. These three do, because they encode a codebase's
|
|
83
|
+
architecture rather than a language fact — the defaults describe one convention
|
|
84
|
+
and every repo gets to name its own.
|
|
85
|
+
|
|
86
|
+
| Rule | Option | Default | Effect |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `no-raw-fetch-outside-clients` | `allow` | client/test path patterns | Files exempt from the "no bare `fetch`" rule |
|
|
89
|
+
| `no-dynamic-sql` | `methods` | `["prepare", "exec", "query"]` | Statement-taking methods to inspect |
|
|
90
|
+
| `no-storage-in-stateless-modules` | `modules` | `[]` (rule off) | Directories declared stateless |
|
|
91
|
+
| `no-storage-in-stateless-modules` | `methods` | `["prepare", "put", "getWithMetadata"]` | Storage methods to flag |
|
|
92
|
+
|
|
93
|
+
Every option value is a **regular-expression source matched against the absolute
|
|
94
|
+
filename**, not a glob — so it can express both path separators. Supplying an
|
|
95
|
+
option **replaces** the default rather than extending it.
|
|
96
|
+
|
|
97
|
+
`no-storage-in-stateless-modules` is a **no-op until `modules` is set**. The
|
|
98
|
+
method names alone (`put`, `prepare`) carry no type information, so the rule is
|
|
99
|
+
only meaningful once it is pointed at the directories a team has actually
|
|
100
|
+
declared stateless.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// eslint.config.mjs
|
|
104
|
+
import sarj from "@sarj/eslint-plugin";
|
|
105
|
+
|
|
106
|
+
export default [
|
|
107
|
+
...sarj.configs.strict,
|
|
108
|
+
{
|
|
109
|
+
rules: {
|
|
110
|
+
// This repo keeps its HTTP layer in `lib/api/`, not `clients/`.
|
|
111
|
+
"@sarj/no-raw-fetch-outside-clients": [
|
|
112
|
+
"error",
|
|
113
|
+
{ allow: ["[\\\\/]lib[\\\\/]api[\\\\/]", "\\.test\\.", "\\.spec\\."] },
|
|
114
|
+
],
|
|
115
|
+
// Declare which modules must stay stateless.
|
|
116
|
+
"@sarj/no-storage-in-stateless-modules": [
|
|
117
|
+
"error",
|
|
118
|
+
{ modules: ["[\\\\/]engineer-digest[\\\\/]"] },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Tiering: `no-dynamic-sql` is in both presets (an injection guard with a low
|
|
126
|
+
false-positive rate, relevant to any repo touching SQL). The two architectural
|
|
127
|
+
rules are **`strict`-only**, since they need per-repo configuration to say
|
|
128
|
+
anything useful.
|
|
129
|
+
|
|
130
|
+
## Ported from `sarj_python_lint`
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
134
|
+
| TypeScript rule | Python | What it prevents |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| `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. |
|
|
137
|
+
| `no-secret-in-log` | SARJ012 | Credentials persisted into log sinks. |
|
|
138
|
+
| `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. |
|
|
139
|
+
| `no-select-star` | SARJ021 | An implicit row contract that changes silently when a column is added or reordered. |
|
|
140
|
+
| `no-offset-pagination` | SARJ025 | O(N)-per-page scans, and rows repeated or skipped when the offset window shifts under concurrent inserts. |
|
|
141
|
+
| `no-repeated-string-literal` | SARJ024 | Copies of a structured literal (SQL, column lists, prompt templates) drifting apart when only one is edited. |
|
|
142
|
+
| `no-positional-tuple-return` | SARJ026 | Call sites re-inventing — and disagreeing on — the field names of a positional tuple return. |
|
|
143
|
+
| `no-sleep-in-test-body` | SARJ031 | Tests that assert on wall-clock time and flake under CI load. |
|
|
144
|
+
| `no-fat-try-blocks` | SARJ007 | Over-broad `catch` handlers swallowing unrelated failures. |
|
|
145
|
+
| `no-cors-wildcard-with-credentials` | SARJ008 | Credentialed cross-origin requests from any origin. |
|
|
146
|
+
| `single-public-export` | SARJ022 | Modules with no single obvious entry point. |
|
|
147
|
+
| `prefer-string-literal-union` | SARJ006 | An open `string` where a closed set is intended. |
|
|
148
|
+
|
|
149
|
+
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.
|
|
150
|
+
|
|
151
|
+
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), `stepdown` (SARJ023), `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`.
|