eslint-config-agent 3.0.3 → 3.0.5
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/CHANGELOG.md +119 -0
- package/README.md +577 -26
- package/configs/base-plugins.js +32 -0
- package/configs/config-files.js +19 -3
- package/configs/examples.js +12 -1
- package/configs/javascript.js +12 -4
- package/configs/overrides.js +1 -1
- package/configs/test-files.js +40 -1
- package/configs/tsx.js +10 -0
- package/configs/typescript.js +21 -2
- package/exports/ddd.d.ts +10 -0
- package/exports/ddd.js +1 -3
- package/exports/incremental.d.ts +11 -0
- package/exports/incremental.js +39 -0
- package/exports/recommended-incremental.d.ts +11 -0
- package/exports/recommended-incremental.js +46 -0
- package/exports/recommended.d.ts +11 -0
- package/exports/recommended.js +81 -0
- package/exports/to-warnings.d.ts +20 -0
- package/exports/to-warnings.js +41 -0
- package/index.d.ts +18 -0
- package/index.js +594 -7
- package/package.json +62 -29
- package/plugins/index.js +6 -0
- package/rules/index.js +20 -22
- package/rules/no-inline-union-types/index.js +8 -6
- package/rules/no-process-env-properties/index.js +4 -4
- package/rules/no-record-literal-types/index.js +3 -3
- package/rules/plugin/import/index.js +41 -0
- package/rules/plugin/index.js +0 -2
- package/rules/plugin/n/index.js +2 -2
- package/rules/plugin/n/no-process-env/index.js +4 -4
- package/rules/plugin/react/index.js +69 -0
- package/rules/plugin/typescript-eslint/index.js +204 -0
- package/rules/require-spec-file-tsx/README.md +57 -0
- package/rules/require-spec-file-tsx/index.js +109 -0
- package/rules/plugin/jsx-a11y/index.js +0 -3
package/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import js from '@eslint/js'
|
|
2
|
+
import unicorn from 'eslint-plugin-unicorn'
|
|
2
3
|
import earlyReturn from 'eslint-plugin-early-return'
|
|
3
4
|
import switchCase from 'eslint-plugin-switch-case'
|
|
4
5
|
import jsxClassname from 'eslint-plugin-jsx-classname'
|
|
5
6
|
import jsdoc from 'eslint-plugin-jsdoc'
|
|
6
|
-
import reactHooks from 'eslint-plugin-react-hooks'
|
|
7
7
|
import { defineConfig } from 'eslint/config'
|
|
8
8
|
import tseslint from 'typescript-eslint'
|
|
9
9
|
import { basePluginsConfig } from './configs/base-plugins.js'
|
|
@@ -31,6 +31,12 @@ const sharedRules = {
|
|
|
31
31
|
quotes: 'off',
|
|
32
32
|
'no-unused-vars': 'off',
|
|
33
33
|
'@typescript-eslint/no-unused-vars': 'off',
|
|
34
|
+
// The core/`@typescript-eslint` `no-unused-vars` rules are disabled above, so
|
|
35
|
+
// nothing flagged dead imports. `eslint-plugin-unused-imports` fills that gap
|
|
36
|
+
// and, unlike the base rules, auto-fixes them: an unused `import` is pure dead
|
|
37
|
+
// weight (no runtime effect, just noise and slower builds) and is always safe
|
|
38
|
+
// to delete. Scoped to imports only — unused locals/args are left alone here.
|
|
39
|
+
'unused-imports/no-unused-imports': 'error',
|
|
34
40
|
'max-lines-per-function': allRules.maxFunctionLinesWarning,
|
|
35
41
|
'max-lines': allRules.maxFileLinesWarning,
|
|
36
42
|
semi: 'off',
|
|
@@ -42,6 +48,505 @@ const sharedRules = {
|
|
|
42
48
|
'no-continue': 'off',
|
|
43
49
|
// Additional built-in error handling rules
|
|
44
50
|
'prefer-promise-reject-errors': 'error',
|
|
51
|
+
// Disallow returning a value from a `Promise` executor — the function passed
|
|
52
|
+
// to `new Promise((resolve, reject) => { ... })`. The executor's return value
|
|
53
|
+
// is discarded by the Promise constructor, so `new Promise(() => readfile())`
|
|
54
|
+
// or `new Promise((resolve) => resolve(x))` written with an implicit return
|
|
55
|
+
// body silently throws the value away. When the returned value is itself a
|
|
56
|
+
// promise (an async executor, a `.then(...)` chain) this is a real bug: the
|
|
57
|
+
// work runs unobserved, rejections become unhandled, and the outer promise
|
|
58
|
+
// settles on a different schedule than the author expects. This is a
|
|
59
|
+
// correctness check, not a style preference — the same class of "the callback
|
|
60
|
+
// returns into a void" mistake that `array-callback-return` above guards
|
|
61
|
+
// against, and exactly the kind of quiet, wrong-behavior bug that type
|
|
62
|
+
// checking will not catch and that AI assistants emit when they reach for a
|
|
63
|
+
// brace body inside `new Promise`. It is not in `eslint:recommended`, so it is
|
|
64
|
+
// enabled explicitly here. The rule is not auto-fixable because only the
|
|
65
|
+
// author knows whether the value should be dropped or the surrounding control
|
|
66
|
+
// flow reworked.
|
|
67
|
+
'no-promise-executor-return': 'error',
|
|
68
|
+
// Disallow `await` inside a loop body. An `await` in a `for`/`for-of`/`while`
|
|
69
|
+
// loop pauses the loop on every iteration and only starts the next one after
|
|
70
|
+
// the current promise settles, so N independent async operations that could
|
|
71
|
+
// have run concurrently are instead serialized end-to-end: the loop takes the
|
|
72
|
+
// *sum* of the latencies instead of the *max*. On a list of network/DB calls
|
|
73
|
+
// that turns a sub-second batch into an N-times-slower stall — a quiet
|
|
74
|
+
// performance bug the type checker cannot see (the code is perfectly typed and
|
|
75
|
+
// correct, just slow) and exactly the shape an AI assistant emits when it
|
|
76
|
+
// mechanically drops an `await` into a `for` loop instead of reaching for
|
|
77
|
+
// `Promise.all`/`Promise.allSettled` over a `.map`. This is the throughput
|
|
78
|
+
// side of the async-hygiene family this config already builds with
|
|
79
|
+
// `no-floating-promises`, `promise-function-async` and `return-await`. When
|
|
80
|
+
// the iterations are genuinely dependent (each one needs the previous result,
|
|
81
|
+
// an ordered write, a deliberate rate limit), the serial `await` is correct —
|
|
82
|
+
// the rule has no auto-fix precisely because only the author knows that, so
|
|
83
|
+
// those loops opt out with an inline `// eslint-disable-next-line
|
|
84
|
+
// no-await-in-loop`. It is not in `eslint:recommended`, so it is enabled
|
|
85
|
+
// explicitly here.
|
|
86
|
+
'no-await-in-loop': 'error',
|
|
87
|
+
// Disallow an `else`/`else if` block when the preceding `if` already exits
|
|
88
|
+
// the function via `return`. The `else` is dead weight: once the `if` branch
|
|
89
|
+
// returns, the code after it is unreachable from that path, so wrapping the
|
|
90
|
+
// remainder in `else` only adds a level of nesting that hides the real
|
|
91
|
+
// control flow. This is the built-in companion to the `early-return` plugin
|
|
92
|
+
// already enabled here — both push code toward flat, guard-clause style
|
|
93
|
+
// instead of the deeply-nested if/else trees AI assistants tend to emit. The
|
|
94
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
95
|
+
'no-else-return': ['error', { allowElseIf: false }],
|
|
96
|
+
// Disallow an `if` statement as the *only* statement inside an `else` block,
|
|
97
|
+
// requiring `else if` instead. The lone `if`-in-`else` adds an indentation
|
|
98
|
+
// level that hides what is really a flat chain of conditions, the same
|
|
99
|
+
// needless nesting the `no-else-return` rule directly above and the bundled
|
|
100
|
+
// `early-return` plugin already push back on, and a shape AI assistants emit
|
|
101
|
+
// when they mechanically wrap each new branch instead of extending the chain.
|
|
102
|
+
// Collapsing it into an `else if` keeps the control flow flat and legible. The
|
|
103
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
104
|
+
// Note: `unicorn/no-lonely-if` covers a different pattern (lone `if` inside
|
|
105
|
+
// another `if` without `else`), so both rules are active — they are complementary.
|
|
106
|
+
'no-lonely-if': 'error',
|
|
107
|
+
// Disallow a `return;` (or `return undefined;`) that is the last statement of
|
|
108
|
+
// a function and so changes nothing — control already falls off the end and
|
|
109
|
+
// yields `undefined`. A redundant trailing `return` reads as if it guards
|
|
110
|
+
// something or hands back a meaningful value when it does neither; it is dead
|
|
111
|
+
// punctuation that hides the real control flow, exactly what this config's
|
|
112
|
+
// explicit-over-clever stance exists to surface. It is the natural companion
|
|
113
|
+
// to the `no-else-return` rule just above and to the `early-return` plugin
|
|
114
|
+
// already enabled here: that pair flattens branching by removing unneeded
|
|
115
|
+
// `else` blocks, and this removes the now-pointless `return` they often leave
|
|
116
|
+
// behind. The rule is auto-fixable, so consumers can adopt it with
|
|
117
|
+
// `eslint --fix`.
|
|
118
|
+
'no-useless-return': 'error',
|
|
119
|
+
// Disallow a value assigned to a variable that is never read before the
|
|
120
|
+
// variable is overwritten or its scope ends — a "dead store". Writing
|
|
121
|
+
// `let total = compute()` and then unconditionally reassigning `total = 0`
|
|
122
|
+
// (or returning before `total` is ever used) throws the first value away,
|
|
123
|
+
// and that wasted write is almost never deliberate: it usually means the
|
|
124
|
+
// author meant to *use* the value, assigned to the wrong variable, or left a
|
|
125
|
+
// half-finished branch behind. The result is code that reads as if a value
|
|
126
|
+
// flows through when it silently does not — exactly the quiet, wrong-behavior
|
|
127
|
+
// bug type checking will not catch (the types line up; only the data flow is
|
|
128
|
+
// broken) and one AI assistants emit when they stitch branches together or
|
|
129
|
+
// forget to thread a computed value into its use. It is the data-flow sibling
|
|
130
|
+
// of the `no-useless-return` rule just above: both surface statements that
|
|
131
|
+
// look load-bearing but change nothing. The rule relies on ESLint's code-path
|
|
132
|
+
// analysis (added in ESLint 9.14) and is not in `eslint:recommended`, so it
|
|
133
|
+
// is enabled explicitly here. It is not auto-fixable because only the author
|
|
134
|
+
// knows whether the dead store should be deleted or its value actually used.
|
|
135
|
+
'no-useless-assignment': 'error',
|
|
136
|
+
// Disallow assignment inside a `return` statement — `return total = compute()`
|
|
137
|
+
// or `return (found = next)`. An `=` in the spot a reader expects a value (and
|
|
138
|
+
// where `==`/`===` belongs) does two things at once: it mutates a binding and
|
|
139
|
+
// hands back the assigned value, so the side effect hides in the returned
|
|
140
|
+
// expression where almost no one looks for it. It is most often a typo for a
|
|
141
|
+
// comparison (`return found === next`) or a leftover from refactoring a guard,
|
|
142
|
+
// and the code still type-checks because the assigned value has the right type
|
|
143
|
+
// — only the data flow is wrong, exactly the quiet, plausible-but-wrong shape
|
|
144
|
+
// this config exists to surface and one AI assistants emit when they fold an
|
|
145
|
+
// update into a `return`. It sits with the assignment-discipline rules already
|
|
146
|
+
// here — `no-param-reassign`, `no-multi-assign` and the `no-useless-assignment`
|
|
147
|
+
// dead-store check directly above — all of which keep where and how values are
|
|
148
|
+
// written explicit. `'always'` flags the assignment even when wrapped in
|
|
149
|
+
// parentheses, so the deliberate-looking `return (x = y)` form is rejected too;
|
|
150
|
+
// an author who truly means it can split the assignment onto its own line. It
|
|
151
|
+
// is not in `eslint:recommended`, so it is enabled explicitly here, and it is
|
|
152
|
+
// not auto-fixable because only the author knows whether a comparison or a
|
|
153
|
+
// separate statement was intended.
|
|
154
|
+
'no-return-assign': ['error', 'always'],
|
|
155
|
+
// Disallow ternaries whose branches are themselves a boolean literal
|
|
156
|
+
// (`cond ? true : false`, `cond ? false : true`) or that re-test a value
|
|
157
|
+
// they could simply fall through to (`a ? a : b`). These are the flat
|
|
158
|
+
// sibling of the nested ternaries `no-nested-ternary` already bans here:
|
|
159
|
+
// punctuation-heavy expressions that dress up a plain boolean (or the
|
|
160
|
+
// condition itself) as a branch, exactly the "clever but unreadable"
|
|
161
|
+
// shortcut this config exists to prevent and one AI assistants reach for
|
|
162
|
+
// often. The explicit form — the condition itself, a negation, or a real
|
|
163
|
+
// `if` — keeps the intent legible. The rule is auto-fixable, so consumers
|
|
164
|
+
// can adopt it with `eslint --fix`. `defaultAssignment: false` extends the
|
|
165
|
+
// check to the `a ? a : b` default-value idiom as well.
|
|
166
|
+
'no-unneeded-ternary': ['error', { defaultAssignment: false }],
|
|
167
|
+
// Require the `default` clause of a `switch` to come last. A `default` matches
|
|
168
|
+
// only when no `case` does, so its precedence is independent of where it sits
|
|
169
|
+
// — but a `default` written *before* later cases reads as if those cases were
|
|
170
|
+
// unreachable, and a mid-`switch` `default` that omits `break` silently falls
|
|
171
|
+
// through into the cases below it. Both are the "looks one way, behaves
|
|
172
|
+
// another" footgun this config exists to surface, and a shape an AI assistant
|
|
173
|
+
// emitting a `switch` can easily introduce. Pinning `default` to the end keeps
|
|
174
|
+
// its order-independent meaning legible. It sits with the bundled `switch-case`
|
|
175
|
+
// rules already enabled here. It is not in `eslint:recommended`, so it is
|
|
176
|
+
// enabled explicitly, and it is not auto-fixable because moving a clause that
|
|
177
|
+
// omits `break` could change behavior — only the author can reorder safely.
|
|
178
|
+
'default-case-last': 'error',
|
|
179
|
+
// Require strict equality (=== / !==). Loose equality performs implicit type
|
|
180
|
+
// coercion, exactly the kind of "clever" shortcut this config exists to
|
|
181
|
+
// prevent. Enforcing it in the shared config means consumers no longer have
|
|
182
|
+
// to re-add it on top of the package.
|
|
183
|
+
eqeqeq: ['error', 'always'],
|
|
184
|
+
// Disallow reassigning function parameters and mutating their properties.
|
|
185
|
+
// Reassigning a parameter decouples it from the caller's argument and hides the
|
|
186
|
+
// function's real inputs; mutating a parameter's properties causes
|
|
187
|
+
// action-at-a-distance bugs where a callee silently rewrites an object the
|
|
188
|
+
// caller still holds. Both undermine this config's explicit-over-clever,
|
|
189
|
+
// immutability-leaning stance, so treat parameters as read-only here.
|
|
190
|
+
'no-param-reassign': ['error', { props: true }],
|
|
191
|
+
// Require every parameter with a default value to come after all parameters
|
|
192
|
+
// without one. A default can only ever take effect when the argument is
|
|
193
|
+
// `undefined`, so a default placed before a required parameter
|
|
194
|
+
// (`f(a = 1, b)`) is unreachable in practice: the caller cannot skip `a` to
|
|
195
|
+
// reach `b`, they must pass `f(undefined, x)` to use the default — at which
|
|
196
|
+
// point the default documents an API the call sites cannot actually use.
|
|
197
|
+
// It is almost always a mistake for the parameter order, not a deliberate
|
|
198
|
+
// signature, and exactly the kind of plausible-but-wrong shape an AI
|
|
199
|
+
// assistant emits when it bolts a default onto the first convenient
|
|
200
|
+
// parameter. This sits with the parameter-discipline rules already here
|
|
201
|
+
// (`no-param-reassign` directly above): keep a function's inputs honest and
|
|
202
|
+
// its signature meaningful. It is not in `eslint:recommended`, so it is
|
|
203
|
+
// enabled explicitly. The rule is not auto-fixable because reordering
|
|
204
|
+
// parameters would silently break every call site, so only the author can
|
|
205
|
+
// decide whether the default or the order was wrong.
|
|
206
|
+
'default-param-last': 'error',
|
|
207
|
+
// Require `const` for bindings that are never reassigned. This is the
|
|
208
|
+
// local-binding counterpart to `no-param-reassign`: together they extend the
|
|
209
|
+
// config's immutability-leaning stance from parameters to every variable. A
|
|
210
|
+
// `let` that is never reassigned misleads readers into expecting a mutation
|
|
211
|
+
// that never comes; `const` documents fixed intent up front. The rule is
|
|
212
|
+
// auto-fixable (`eslint --fix`), so adoption is cheap. `destructuring: 'all'`
|
|
213
|
+
// only flags a destructuring pattern when every introduced binding could be
|
|
214
|
+
// `const`, leaving mixed const/let destructuring alone.
|
|
215
|
+
'prefer-const': ['error', { destructuring: 'all' }],
|
|
216
|
+
// Disallow shorthand type coercions such as `!!value`, `+str`, `1 * x`,
|
|
217
|
+
// `'' + n` and `~str.indexOf(...)`. These are the sibling of the loose
|
|
218
|
+
// equality `eqeqeq` already bans here: terse tricks that hide an implicit
|
|
219
|
+
// type conversion behind punctuation, exactly the "clever but unreadable"
|
|
220
|
+
// shortcut this config exists to prevent and one AI assistants reach for
|
|
221
|
+
// often. Requiring the explicit form (`Boolean(value)`, `Number(str)`,
|
|
222
|
+
// `String(n)`) keeps the intended conversion legible. The rule is
|
|
223
|
+
// auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
224
|
+
'no-implicit-coercion': ['error', { allow: [] }],
|
|
225
|
+
// Require an explicit radix argument to `parseInt` — `parseInt(str, 10)`,
|
|
226
|
+
// never `parseInt(str)`. With the base omitted, `parseInt` infers it from the
|
|
227
|
+
// string: a leading `0x` is read as hex and, depending on the engine, a
|
|
228
|
+
// leading `0` can be read as octal, so `parseInt('0x10')` is `16` and
|
|
229
|
+
// `parseInt(userInput)` silently parses in a base the author never chose. The
|
|
230
|
+
// wrong-number result type-checks fine (it is still a `number`) and only the
|
|
231
|
+
// data flow is broken — exactly the quiet, plausible-but-wrong shape this
|
|
232
|
+
// config exists to surface, and the same class of *implicit* behavior it
|
|
233
|
+
// already bans via `eqeqeq` and `no-implicit-coercion` directly above.
|
|
234
|
+
// Forcing the base makes the parse deterministic and the intent explicit. The
|
|
235
|
+
// rule is not auto-fixable because only the author knows which base was meant.
|
|
236
|
+
radix: ['error', 'always'],
|
|
237
|
+
// Require template literals instead of string concatenation. Building a
|
|
238
|
+
// string by chaining `+` (`'Hello ' + name + '!'`) scatters the literal
|
|
239
|
+
// text across operators, makes the final shape hard to read, and leans on
|
|
240
|
+
// the same implicit coercion `no-implicit-coercion` already bans whenever a
|
|
241
|
+
// non-string operand sneaks in. A template literal (`` `Hello ${name}!` ``)
|
|
242
|
+
// keeps the literal text and the interpolated values visually aligned, which
|
|
243
|
+
// is exactly the explicit, readable form this config favors and one AI
|
|
244
|
+
// assistants frequently skip. The rule is auto-fixable, so consumers can
|
|
245
|
+
// adopt it with `eslint --fix`.
|
|
246
|
+
'prefer-template': 'error',
|
|
247
|
+
// Disallow concatenating two string literals — `'Hello ' + 'World'`,
|
|
248
|
+
// `'a' + 'b' + 'c'`, or a literal split across lines with `+`. The operands
|
|
249
|
+
// are known at author time, so the `+` does nothing the source could not say
|
|
250
|
+
// directly: it is pure punctuation that hides a single constant string behind
|
|
251
|
+
// an operator and invites the reader to wonder whether a variable was meant.
|
|
252
|
+
// It is the static sibling of the `prefer-template` rule just above — that one
|
|
253
|
+
// pushes runtime interpolation onto template literals, this one removes the
|
|
254
|
+
// join entirely when there is no value to interpolate — and it leans on the
|
|
255
|
+
// same implicit-`+` surface the config already narrows with `prefer-template`,
|
|
256
|
+
// `no-implicit-coercion` and `@typescript-eslint/restrict-plus-operands`.
|
|
257
|
+
// Collapsing the pieces into one literal (or a template literal when the line
|
|
258
|
+
// length is the only reason for the split) is the explicit, readable form this
|
|
259
|
+
// config favors and one AI assistants frequently skip. The rule is not
|
|
260
|
+
// auto-fixable because only the author knows whether the two pieces were meant
|
|
261
|
+
// to be one literal or a refactor left a variable behind.
|
|
262
|
+
'no-useless-concat': 'error',
|
|
263
|
+
// Disallow `.bind()` on a function that never references `this` (and has no
|
|
264
|
+
// bound arguments) — `function () { return 1 }.bind(this)`,
|
|
265
|
+
// `(() => x).bind(obj)`, `handler.bind(this)` where `handler` ignores `this`.
|
|
266
|
+
// The `.bind()` does nothing: it allocates a new wrapper function on every
|
|
267
|
+
// evaluation and returns one that behaves identically to the original, so the
|
|
268
|
+
// call is pure overhead that also misleads the reader into thinking the
|
|
269
|
+
// receiver matters when it does not. It is the same "looks meaningful but is
|
|
270
|
+
// dead" clutter the `no-useless-return`, `no-useless-assignment` and
|
|
271
|
+
// `no-useless-concat` rules already remove here, and exactly the reflexive
|
|
272
|
+
// `.bind(this)` an AI assistant appends to a callback by habit, whether or not
|
|
273
|
+
// the body uses `this`. Dropping the bind leaves the explicit, allocation-free
|
|
274
|
+
// form this config favors. The rule is auto-fixable, so consumers can adopt it
|
|
275
|
+
// with `eslint --fix`; it is not in `eslint:recommended`, so it is enabled
|
|
276
|
+
// explicitly here.
|
|
277
|
+
'no-extra-bind': 'error',
|
|
278
|
+
// Require `Object.hasOwn(obj, key)` over the legacy ways of asking the same
|
|
279
|
+
// question. The two forms it replaces are each a footgun: calling
|
|
280
|
+
// `obj.hasOwnProperty(key)` directly breaks the moment `obj` has a `null`
|
|
281
|
+
// prototype (`Object.create(null)`, many map-like objects) or shadows the
|
|
282
|
+
// method with an own `hasOwnProperty` field — it throws or returns the wrong
|
|
283
|
+
// answer — which is why `no-prototype-builtins` already bans it; and the safe
|
|
284
|
+
// workaround, `Object.prototype.hasOwnProperty.call(obj, key)`, is a long,
|
|
285
|
+
// punctuation-heavy incantation that hides a one-word intent behind prototype
|
|
286
|
+
// plumbing. `Object.hasOwn` is the single explicit, prototype-safe spelling of
|
|
287
|
+
// "does this object own this key", so it fits this config's
|
|
288
|
+
// explicit-over-clever, correctness-leaning stance and removes the boilerplate
|
|
289
|
+
// adopters would otherwise reach for. It is auto-fixable (`eslint --fix`) and
|
|
290
|
+
// available on every supported runtime (Node >= 20, ES2022), so adoption is
|
|
291
|
+
// free.
|
|
292
|
+
'prefer-object-has-own': 'error',
|
|
293
|
+
// Disallow `var`. `var` declarations are function-scoped and hoisted, so
|
|
294
|
+
// they leak out of the block they appear to belong to and read as
|
|
295
|
+
// initialized `undefined` before their declaration runs — producing
|
|
296
|
+
// order-dependent bugs that block-scoped `let`/`const` make impossible.
|
|
297
|
+
// `var` is exactly the legacy shortcut an AI assistant trained on older
|
|
298
|
+
// code reaches for, which puts it squarely in scope for this config's
|
|
299
|
+
// explicit-over-clever, AI-safety stance. It is also the foundation the
|
|
300
|
+
// `prefer-const` rule builds on. The rule is auto-fixable.
|
|
301
|
+
'no-var': 'error',
|
|
302
|
+
// Require object literal shorthand for properties and methods, so
|
|
303
|
+
// `{ value: value }` collapses to `{ value }` and `{ run: function () {} }`
|
|
304
|
+
// to `{ run() {} }`. The longhand forms carry no extra meaning — they are
|
|
305
|
+
// pure boilerplate that an AI assistant trained on older code routinely
|
|
306
|
+
// emits, and the duplicated `value: value` name is a common spot for a
|
|
307
|
+
// copy-paste typo that the shorthand removes entirely. Enforcing one
|
|
308
|
+
// canonical object form keeps literals legible and consistent, matching
|
|
309
|
+
// this config's explicit-over-clever, low-noise stance. The rule is
|
|
310
|
+
// auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
311
|
+
'object-shorthand': ['error', 'always'],
|
|
312
|
+
// Require the compound assignment shorthand (`x += 1`, `total *= rate`)
|
|
313
|
+
// wherever a variable is assigned the result of an operation on itself, so
|
|
314
|
+
// `x = x + 1` is rewritten to `x += 1`. The longhand form names the target
|
|
315
|
+
// twice — once on each side of the `=` — and that duplicated operand is a
|
|
316
|
+
// silent typo site: `total = totals + 1` reads as a self-update but quietly
|
|
317
|
+
// assigns from a *different* variable, a bug type checking cannot catch when
|
|
318
|
+
// both names are in scope. Collapsing to `+=` removes the
|
|
319
|
+
// second spelling of the name entirely, so the mismatch becomes impossible to
|
|
320
|
+
// write. This is the assignment sibling of the `object-shorthand` rule just
|
|
321
|
+
// above (which removes the same duplicated-name typo site in `{ value: value }`)
|
|
322
|
+
// and fits this config's explicit-over-clever, low-noise, correctness-leaning
|
|
323
|
+
// stance: one canonical spelling of "update this in place". The longhand is
|
|
324
|
+
// also exactly what an AI assistant trained on older code reaches for. The
|
|
325
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
326
|
+
'operator-assignment': ['error', 'always'],
|
|
327
|
+
// Disallow chained assignment expressions such as `a = b = c = 0`. Chaining
|
|
328
|
+
// collapses several writes into one expression: the value flows right-to-left
|
|
329
|
+
// through bindings that have nothing to do with each other, so a reader has to
|
|
330
|
+
// unwind the chain to see how many variables changed and to what. It also
|
|
331
|
+
// quietly couples those variables — touching the chain later risks rewriting
|
|
332
|
+
// more than intended — which runs against the immutability-leaning stance the
|
|
333
|
+
// `prefer-const`, `no-param-reassign` and `no-var` rules above already set
|
|
334
|
+
// here. It is exactly the terse, "clever" shortcut this config exists to
|
|
335
|
+
// prevent and one AI assistants emit when packing initialization onto a single
|
|
336
|
+
// line. Writing each assignment on its own statement keeps the data flow
|
|
337
|
+
// explicit.
|
|
338
|
+
'no-multi-assign': 'error',
|
|
339
|
+
// Disallow `${...}` placeholders inside ordinary string literals
|
|
340
|
+
// (`'Hello ${name}'`, `"total: ${count}"`). Template-literal interpolation
|
|
341
|
+
// only works inside backticks; the moment the quotes are single or double,
|
|
342
|
+
// `${name}` is just literal text and the value is silently never inserted —
|
|
343
|
+
// a bug that type-checking cannot catch because the result is still a valid
|
|
344
|
+
// `string`. Mixing up the quote character is exactly the kind of silent
|
|
345
|
+
// mistake an AI assistant emits when stitching messages together, which puts
|
|
346
|
+
// it squarely in scope for this config's correctness-and-clarity stance. It
|
|
347
|
+
// is the natural companion to the explicit-conversion rules
|
|
348
|
+
// (`no-implicit-coercion`, `eqeqeq`) above: it keeps string building honest
|
|
349
|
+
// by forcing a real template literal whenever interpolation is intended.
|
|
350
|
+
'no-template-curly-in-string': 'error',
|
|
351
|
+
// Disallow the `Object` constructor (`new Object()` and `Object()`) in favor
|
|
352
|
+
// of the `{}` object literal. The constructor form is a strictly more verbose,
|
|
353
|
+
// more indirect way to do exactly what the literal does — and a trap: passing
|
|
354
|
+
// a single non-object argument makes `Object(x)` return *that* value (or a
|
|
355
|
+
// wrapper), so the call quietly stops creating a fresh object at all. The
|
|
356
|
+
// literal has no such ambiguity. This is the object-creation sibling of the
|
|
357
|
+
// wrapper-constructor and coercion bans this config already sets (`Number`,
|
|
358
|
+
// `String`, `!!x`): prefer the plain literal/value over a constructor call
|
|
359
|
+
// dressed up as one. `new Object()` is also a legacy idiom an AI assistant
|
|
360
|
+
// trained on older code reaches for, which puts it squarely in this config's
|
|
361
|
+
// explicit-over-clever, AI-safety scope. The rule is auto-fixable, so
|
|
362
|
+
// consumers can adopt it with `eslint --fix`.
|
|
363
|
+
'no-object-constructor': 'error',
|
|
364
|
+
// Require object spread (`{ ...a, ...b }`) over `Object.assign({}, a, b)` when
|
|
365
|
+
// the call is building a brand-new object (its first argument is an object
|
|
366
|
+
// literal). The `Object.assign({}, ...)` form is a more indirect, punctuation-
|
|
367
|
+
// heavy spelling of the same intent: a reader has to recognize the empty `{}`
|
|
368
|
+
// accumulator and the mutate-and-return semantics just to conclude "this makes
|
|
369
|
+
// a fresh, merged object", which the spread states directly. It is the
|
|
370
|
+
// object-merging sibling of the `no-object-constructor` ban right above —
|
|
371
|
+
// both reject an indirect construction idiom in favor of the plain literal —
|
|
372
|
+
// and of `object-shorthand`, which already pushes object literals toward their
|
|
373
|
+
// most legible form. `Object.assign` onto an existing target (`Object.assign(
|
|
374
|
+
// target, src)`) mutates and is left alone, so only the new-object case is
|
|
375
|
+
// flagged. The rule is auto-fixable, so consumers can adopt it with
|
|
376
|
+
// `eslint --fix`.
|
|
377
|
+
'prefer-object-spread': 'error',
|
|
378
|
+
// Require a `return` from every array-method callback that is expected to
|
|
379
|
+
// produce one (`map`, `filter`, `reduce`, `every`, `some`, `find`, `sort`,
|
|
380
|
+
// `flatMap`, ...). A callback that falls off the end returns `undefined`, so
|
|
381
|
+
// `arr.map(x => { doSomething(x) })` silently yields an array of `undefined`
|
|
382
|
+
// and `arr.filter(x => { x.active })` keeps every element — the kind of
|
|
383
|
+
// quiet, wrong-result bug that type checking will not catch and that AI
|
|
384
|
+
// assistants emit when they reach for a brace body and forget the `return`.
|
|
385
|
+
// Unlike most rules here this is a correctness check, not a style
|
|
386
|
+
// preference, which is why it is not covered by `eslint:recommended` and is
|
|
387
|
+
// enabled explicitly. `checkForEach: true` flags the inverse mistake —
|
|
388
|
+
// returning a value from `forEach`, where the result is discarded — which
|
|
389
|
+
// usually means `map` was intended. The rule is not auto-fixable because
|
|
390
|
+
// only the author knows what each callback should return.
|
|
391
|
+
'array-callback-return': [
|
|
392
|
+
'error',
|
|
393
|
+
{ allowImplicit: false, checkForEach: true },
|
|
394
|
+
],
|
|
395
|
+
// Only allow throwing real `Error` objects — reject `throw 'boom'`,
|
|
396
|
+
// `throw { code: 500 }`, `throw 42` and any other non-Error value. A thrown
|
|
397
|
+
// string or plain object carries no stack trace, so the failure surfaces with
|
|
398
|
+
// no record of where it came from, and it breaks every `catch (e)` that does
|
|
399
|
+
// `e instanceof Error` or reads `e.message`/`e.stack` — the consumer's error
|
|
400
|
+
// handling silently misfires. Throwing a bare literal is exactly the terse
|
|
401
|
+
// shortcut an AI assistant emits when it wants to bail out fast, which puts it
|
|
402
|
+
// squarely in this config's correctness-over-clever, AI-safety scope, and it
|
|
403
|
+
// pairs with the bundled `error/*` rules that already shape how errors are
|
|
404
|
+
// declared. Unlike most rules here it catches an outright correctness
|
|
405
|
+
// mistake, not a style preference, which is why it is enabled explicitly —
|
|
406
|
+
// `eslint:recommended` does not turn it on. The rule is not auto-fixable
|
|
407
|
+
// because only the author knows which `Error` subclass the literal should
|
|
408
|
+
// become.
|
|
409
|
+
'no-throw-literal': 'error',
|
|
410
|
+
// Disallow comparing a variable to itself (`x === x`, `x !== x`,
|
|
411
|
+
// `i < i`, ...). A self-comparison is either a plain bug — the author meant
|
|
412
|
+
// to compare against a different operand and the result is a constant
|
|
413
|
+
// `true`/`false` that quietly disables a branch — or the deliberate
|
|
414
|
+
// `value !== value` trick for detecting `NaN`. Both belong to the same
|
|
415
|
+
// family the `eqeqeq`, `no-implicit-coercion` and `no-unneeded-ternary`
|
|
416
|
+
// rules above already target: a punctuation-heavy expression whose real
|
|
417
|
+
// intent is hidden, exactly the "clever but unreadable" shortcut this config
|
|
418
|
+
// exists to prevent and one AI assistants emit when stitching together
|
|
419
|
+
// conditions. Unlike most rules here this also catches an outright
|
|
420
|
+
// correctness mistake, which is why it is enabled explicitly —
|
|
421
|
+
// `eslint:recommended` does not turn it on. The explicit form
|
|
422
|
+
// (`Number.isNaN(value)` for the NaN case, the intended operand otherwise)
|
|
423
|
+
// keeps the check legible. The rule is not auto-fixable because only the
|
|
424
|
+
// author knows which operand was meant.
|
|
425
|
+
'no-self-compare': 'error',
|
|
426
|
+
// Disallow optional chaining in positions where a short-circuit to
|
|
427
|
+
// `undefined` is immediately used in a way that throws at runtime — member
|
|
428
|
+
// access, a call, arithmetic, spread, `instanceof`, `in`, or destructuring on
|
|
429
|
+
// the result. `(obj?.foo).bar`, `(obj?.fn)()`, `(obj?.count) + 1` and
|
|
430
|
+
// `[...(obj?.list)]` all look guarded, but the `?.` only protects the link it
|
|
431
|
+
// sits on: the moment the chain yields `undefined`, the surrounding `.bar` /
|
|
432
|
+
// `()` / `+` / spread runs against `undefined` and throws a `TypeError`. The
|
|
433
|
+
// author wrote `?.` precisely because the left side can be nullish, so this is
|
|
434
|
+
// an outright correctness bug — the guard is defeated by the very expression
|
|
435
|
+
// wrapped around it — not a style preference. It is exactly the half-applied
|
|
436
|
+
// null-safety an AI assistant emits when it sprinkles `?.` onto one segment of
|
|
437
|
+
// a longer access, which puts it in this config's correctness-over-clever, AI-
|
|
438
|
+
// safety scope alongside `no-throw-literal` and `no-self-compare` above. The
|
|
439
|
+
// fix is to extend the optional chain over the whole access (`obj?.foo?.bar`)
|
|
440
|
+
// or to guard the result before using it; only the author knows which, so the
|
|
441
|
+
// rule is not auto-fixable. `eslint:recommended` does enable this, but this
|
|
442
|
+
// config does not extend that preset, so it is turned on explicitly here. The
|
|
443
|
+
// shared `web` app of the `animals-shop` repo enforced this rule manually; it
|
|
444
|
+
// belongs in the shared config so every consumer is covered.
|
|
445
|
+
'no-unsafe-optional-chaining': [
|
|
446
|
+
'error',
|
|
447
|
+
{ disallowArithmeticOperators: true },
|
|
448
|
+
],
|
|
449
|
+
// Disallow computed keys that wrap a literal that could be written plainly —
|
|
450
|
+
// `{ ['name']: x }`, `{ [42]: y }`, or `class { ['method']() {} }`. The
|
|
451
|
+
// bracket syntax exists for keys that must be computed at runtime; using it
|
|
452
|
+
// for a static string or number adds punctuation and a layer of indirection
|
|
453
|
+
// that buys nothing and only makes the reader pause to confirm the key is
|
|
454
|
+
// constant after all. It is the same "clever but pointless" clutter the
|
|
455
|
+
// `no-unneeded-ternary` and `no-implicit-coercion` rules already ban here,
|
|
456
|
+
// and one AI assistants emit when they template object keys mechanically.
|
|
457
|
+
// The plain form (`{ name: x }`, `{ method() {} }`) states intent directly.
|
|
458
|
+
// The rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
459
|
+
// `enforceForClassMembers: true` extends the check from object literals to
|
|
460
|
+
// class members so both surfaces stay consistent.
|
|
461
|
+
'no-useless-computed-key': ['error', { enforceForClassMembers: true }],
|
|
462
|
+
// Disallow building functions from strings with the `Function` constructor —
|
|
463
|
+
// `new Function('a', 'b', 'return a + b')` or `Function(...)()`. This is `eval`
|
|
464
|
+
// wearing a different name: the body is an opaque string the parser, the type
|
|
465
|
+
// checker and every reader have to take on faith, it executes in the global
|
|
466
|
+
// scope (so it silently can't see the surrounding closure a reader would
|
|
467
|
+
// expect it to), and feeding it any untrusted input is a direct code-injection
|
|
468
|
+
// hole. There is a plain, safe equivalent for every legitimate use — an actual
|
|
469
|
+
// function literal — so the dynamic form buys nothing but risk. It sits
|
|
470
|
+
// squarely in this config's scope: a footgun an AI assistant reaches for when
|
|
471
|
+
// asked to "build a function dynamically," and one `eslint-plugin-security`
|
|
472
|
+
// does not cover (its eval rule, `detect-eval-with-expression`, flags `eval`
|
|
473
|
+
// and not the `Function` constructor). The rule is not auto-fixable because
|
|
474
|
+
// only the author can restate the string body as real code.
|
|
475
|
+
'no-new-func': 'error',
|
|
476
|
+
// Disallow `eval(...)`. It hands a string to the JavaScript engine to parse
|
|
477
|
+
// and run at runtime: the body is opaque to the parser, the type checker and
|
|
478
|
+
// every reader, it can reach and mutate the surrounding scope, and feeding it
|
|
479
|
+
// any untrusted input is the textbook code-injection hole. There is a plain,
|
|
480
|
+
// safe equivalent for every legitimate use, so the dynamic form buys nothing
|
|
481
|
+
// but risk. This is the direct-`eval` companion to the `no-new-func` ban right
|
|
482
|
+
// above (which catches `eval` wearing the `Function`-constructor disguise) —
|
|
483
|
+
// together they close the runtime-string-execution footgun an AI assistant
|
|
484
|
+
// reaches for when asked to "run this code dynamically." It is not in
|
|
485
|
+
// `eslint:recommended`, so it is enabled explicitly. Not auto-fixable: only
|
|
486
|
+
// the author can restate the string as real code.
|
|
487
|
+
'no-eval': 'error',
|
|
488
|
+
// Disallow the implied-`eval` forms: a string first argument to `setTimeout`,
|
|
489
|
+
// `setInterval`, `setImmediate`, or `execScript`. Passing a string there makes
|
|
490
|
+
// the engine `eval` it on the timer's behalf, so it carries every hazard of
|
|
491
|
+
// `no-eval` above while hiding behind an everyday timer API — `setTimeout('do()',
|
|
492
|
+
// 100)` reads as a scheduled call but is really deferred string execution. The
|
|
493
|
+
// safe form is already the idiomatic one: pass a function, `setTimeout(() =>
|
|
494
|
+
// do(), 100)`. This pairs with `no-eval` to cover the indirect channel the
|
|
495
|
+
// direct ban misses. Not auto-fixable: only the author can turn the string
|
|
496
|
+
// into the intended callback.
|
|
497
|
+
'no-implied-eval': 'error',
|
|
498
|
+
// Disallow `javascript:` URLs — `href = 'javascript:void(0)'`,
|
|
499
|
+
// `window.location = 'javascript:doThing()'`, a `<a href="javascript:...">`.
|
|
500
|
+
// The string after the `javascript:` scheme is handed to the engine and run
|
|
501
|
+
// exactly as `eval` would run it, so it carries every hazard of `no-eval` and
|
|
502
|
+
// `no-implied-eval` above — opaque to the parser, the type checker and every
|
|
503
|
+
// reader, and a code-injection hole the moment any of it comes from untrusted
|
|
504
|
+
// input — while wearing the everyday disguise of a URL. It is the third
|
|
505
|
+
// channel in the string-executes-as-code family this config already bans:
|
|
506
|
+
// `no-eval` (direct), `no-new-func` (the `Function` constructor) and
|
|
507
|
+
// `no-implied-eval` (string-bodied timers); `no-script-url` closes the URL
|
|
508
|
+
// channel the other three miss. There is a plain, safe equivalent for every
|
|
509
|
+
// legitimate use — an `onClick`/event handler, `href="#"` with
|
|
510
|
+
// `preventDefault`, or a real function — so the `javascript:` form buys
|
|
511
|
+
// nothing but risk, and it is exactly the placeholder shortcut an AI assistant
|
|
512
|
+
// emits for a "do-nothing" link. It is not in `eslint:recommended`, so it is
|
|
513
|
+
// enabled explicitly. Not auto-fixable: only the author can replace the URL
|
|
514
|
+
// with the intended handler.
|
|
515
|
+
'no-script-url': 'error',
|
|
516
|
+
// Disallow stray `console` calls, allowing only `console.warn` and
|
|
517
|
+
// `console.error`. A bare `console.log` is almost always leftover debugging:
|
|
518
|
+
// it slips through review, ships to production, leaks internal state into logs
|
|
519
|
+
// and slows hot paths, all without any type error or runtime failure to flag
|
|
520
|
+
// it — exactly the quiet, wrong-to-ship mistake this config exists to catch
|
|
521
|
+
// and one AI assistants emit liberally while "checking what a value is." The
|
|
522
|
+
// `allow: ['warn', 'error']` exception keeps intentional diagnostics — the two
|
|
523
|
+
// `console` channels meant for surfacing problems — available, so the rule
|
|
524
|
+
// targets only the throwaway logging. Consumers that genuinely build a CLI can
|
|
525
|
+
// relax it for their entry points; for everyone else it is a zero-config
|
|
526
|
+
// guard. The rule is not auto-fixable because only the author knows whether a
|
|
527
|
+
// given `console.log` should be deleted or promoted to a real log channel.
|
|
528
|
+
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
529
|
+
// Require a regex literal (`/\d+/`) instead of the `RegExp` constructor with
|
|
530
|
+
// a string argument (`new RegExp('\\d+')`, `RegExp('\\d+')`) when the pattern
|
|
531
|
+
// is a static string. The string form forces every backslash to be escaped
|
|
532
|
+
// twice — once for the string literal and once for the regex engine — so
|
|
533
|
+
// `\d` has to be written `\\d`, `\.` becomes `\\.`, and a single missed
|
|
534
|
+
// backslash silently changes what the pattern matches without any error.
|
|
535
|
+
// The literal needs none of that doubling and is checked for validity at
|
|
536
|
+
// parse time rather than when the constructor runs, so a malformed pattern
|
|
537
|
+
// surfaces immediately instead of throwing on first use. This is the
|
|
538
|
+
// regex-shaped sibling of the constructor bans this config already ships —
|
|
539
|
+
// `no-object-constructor`, `no-new-wrappers`, `no-new-func` — all of which
|
|
540
|
+
// reject a constructor call dressed up as a literal/value; reaching for
|
|
541
|
+
// `new RegExp('...')` on a constant pattern is exactly the legacy,
|
|
542
|
+
// double-escaping-prone idiom an AI assistant emits when asked to "build a
|
|
543
|
+
// regex." It is not in `eslint:recommended`, so it is enabled explicitly.
|
|
544
|
+
// `disallowRedundantWrapping: true` also flags `new RegExp(/foo/)` — wrapping
|
|
545
|
+
// a literal that is already a regex — which is pure redundancy. The dynamic
|
|
546
|
+
// `new RegExp(variable)` case, where the pattern genuinely is not known until
|
|
547
|
+
// runtime, is left untouched. The rule is auto-fixable for the simple cases,
|
|
548
|
+
// so consumers can adopt much of it with `eslint --fix`.
|
|
549
|
+
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
|
|
45
550
|
}
|
|
46
551
|
|
|
47
552
|
// Shared no-restricted-syntax rules for both JS and TS
|
|
@@ -76,7 +581,7 @@ const sharedRestrictedSyntax = [
|
|
|
76
581
|
message:
|
|
77
582
|
'Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.',
|
|
78
583
|
},
|
|
79
|
-
allRules.
|
|
584
|
+
allRules.noProcessEnvironmentPropertiesConfig,
|
|
80
585
|
allRules.noExportSpecifiersConfig,
|
|
81
586
|
...allRules.noDefaultClassExportRules,
|
|
82
587
|
]
|
|
@@ -97,32 +602,91 @@ const tsOnlyRestrictedSyntax = [
|
|
|
97
602
|
]
|
|
98
603
|
|
|
99
604
|
const config = defineConfig([
|
|
100
|
-
// Global ignores for non-JS/TS files and build outputs
|
|
605
|
+
// Global ignores for non-JS/TS files and build outputs.
|
|
606
|
+
//
|
|
607
|
+
// Build-output globs are prefixed with `**/` so they match nested package
|
|
608
|
+
// directories, not just the repository root. A bare `dist/**` only ignores
|
|
609
|
+
// a top-level `dist/`, which means in a monorepo every `packages/*/dist/**`
|
|
610
|
+
// (and the same for `build`, `coverage`, etc.) is still linted and floods
|
|
611
|
+
// adopters with errors on generated code. The recursive form ignores those
|
|
612
|
+
// outputs wherever they appear, matching the monorepo support this config
|
|
613
|
+
// documents.
|
|
101
614
|
{
|
|
615
|
+
name: 'agent/ignores',
|
|
102
616
|
ignores: [
|
|
103
617
|
'**/*.json',
|
|
104
618
|
'**/*.md',
|
|
105
619
|
'**/*.yaml',
|
|
106
620
|
'**/*.yml',
|
|
107
|
-
'dist/**',
|
|
108
|
-
'
|
|
621
|
+
'**/dist/**',
|
|
622
|
+
'**/build/**',
|
|
623
|
+
'**/coverage/**',
|
|
624
|
+
'**/.next/**',
|
|
625
|
+
'**/out/**',
|
|
626
|
+
// Storybook's static build output. This config ships first-class
|
|
627
|
+
// Storybook support (`eslint-plugin-storybook` + `configs/storybook.js`),
|
|
628
|
+
// so adopters routinely run `storybook build`, which emits compiled,
|
|
629
|
+
// minified bundles into `storybook-static/`. Linting that generated
|
|
630
|
+
// output is never useful and floods the report, so ignore it like the
|
|
631
|
+
// other build dirs above.
|
|
632
|
+
'**/storybook-static/**',
|
|
633
|
+
// Turborepo's local task cache. In the monorepos this config targets,
|
|
634
|
+
// `turbo` writes hashed cache artifacts into `.turbo/`; they are
|
|
635
|
+
// generated, not source, and must not be linted.
|
|
636
|
+
'**/.turbo/**',
|
|
109
637
|
],
|
|
110
638
|
},
|
|
639
|
+
// Flag `eslint-disable` directives that no longer suppress anything. Stale
|
|
640
|
+
// suppressions hide the fact that a rule is now satisfied (or was renamed)
|
|
641
|
+
// and quietly widen the set of unchecked code over time, which runs against
|
|
642
|
+
// this config's explicit-over-clever goal. Reporting them as errors keeps
|
|
643
|
+
// every suppression honest and removable.
|
|
644
|
+
{
|
|
645
|
+
name: 'agent/linter-options',
|
|
646
|
+
linterOptions: {
|
|
647
|
+
reportUnusedDisableDirectives: 'error',
|
|
648
|
+
},
|
|
649
|
+
},
|
|
111
650
|
// Global plugin definitions
|
|
112
651
|
{
|
|
652
|
+
name: 'agent/plugins',
|
|
113
653
|
plugins,
|
|
114
654
|
},
|
|
115
655
|
{
|
|
656
|
+
name: 'agent/react-hooks',
|
|
116
657
|
rules: {
|
|
117
658
|
'react-hooks/rules-of-hooks': 'error',
|
|
118
659
|
'react-hooks/exhaustive-deps': 'warn',
|
|
119
660
|
},
|
|
120
661
|
},
|
|
121
662
|
js.configs.recommended,
|
|
663
|
+
unicorn.configs.all,
|
|
664
|
+
// Disable unicorn rules that are too opinionated or conflict with this
|
|
665
|
+
// codebase's conventions. These are turned off globally after
|
|
666
|
+
// `unicorn.configs.all` enables them.
|
|
667
|
+
{
|
|
668
|
+
name: 'agent/unicorn-overrides',
|
|
669
|
+
rules: {
|
|
670
|
+
// Prevents `class`-prefixed identifiers like `classExportPlugin`,
|
|
671
|
+
// `classDeclaration`, etc. — common in ESLint plugin code where `class`
|
|
672
|
+
// is part of a domain concept, not a keyword accident.
|
|
673
|
+
'unicorn/no-keyword-prefix': 'off',
|
|
674
|
+
// Disallows `null` in favor of `undefined`. TypeScript APIs use `null`
|
|
675
|
+
// intentionally (e.g. `null` vs `undefined` in type narrowing).
|
|
676
|
+
'unicorn/no-null': 'off',
|
|
677
|
+
// Prevents passing a function reference directly to `.map(fn)`. The
|
|
678
|
+
// direct reference form is idiomatic and readable.
|
|
679
|
+
'unicorn/no-array-callback-reference': 'off',
|
|
680
|
+
// Enforces specific import styles for some packages (e.g. named vs
|
|
681
|
+
// default). Too opinionated for this config's consumers.
|
|
682
|
+
'unicorn/import-style': 'off',
|
|
683
|
+
},
|
|
684
|
+
},
|
|
122
685
|
...tseslint.configs.strictTypeChecked,
|
|
123
686
|
...tseslint.configs.stylisticTypeChecked,
|
|
124
687
|
{
|
|
125
|
-
|
|
688
|
+
name: 'agent/typescript-parser-options',
|
|
689
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
126
690
|
languageOptions: {
|
|
127
691
|
parserOptions: {
|
|
128
692
|
projectService: true,
|
|
@@ -132,10 +696,32 @@ const config = defineConfig([
|
|
|
132
696
|
{
|
|
133
697
|
files: ['**/*.{js,jsx,mjs,cjs}'],
|
|
134
698
|
...tseslint.configs.disableTypeChecked,
|
|
699
|
+
name: 'agent/javascript-disable-type-checked',
|
|
135
700
|
},
|
|
136
701
|
earlyReturn.configs.recommended,
|
|
137
702
|
jsdoc.configs['flat/recommended-typescript-error'],
|
|
138
|
-
|
|
703
|
+
// The jsdoc recommended preset only requires JSDoc on FunctionDeclaration by
|
|
704
|
+
// default. Treat class declarations the same as functions so exported
|
|
705
|
+
// classes are also required to be documented.
|
|
706
|
+
{
|
|
707
|
+
name: 'agent/jsdoc-require-class-jsdoc',
|
|
708
|
+
rules: {
|
|
709
|
+
'jsdoc/require-jsdoc': [
|
|
710
|
+
'error',
|
|
711
|
+
{
|
|
712
|
+
require: {
|
|
713
|
+
ClassDeclaration: true,
|
|
714
|
+
FunctionDeclaration: true,
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
plugins: { 'switch-case': switchCase },
|
|
722
|
+
...switchCase.configs.recommended,
|
|
723
|
+
name: 'agent/switch-case',
|
|
724
|
+
},
|
|
139
725
|
|
|
140
726
|
// Base plugin strict configs (error, default, guard-clauses)
|
|
141
727
|
...basePluginsConfig,
|
|
@@ -173,6 +759,7 @@ const config = defineConfig([
|
|
|
173
759
|
...jsxClassname.configs.strict,
|
|
174
760
|
files: ['**/*.{tsx,jsx}'],
|
|
175
761
|
ignores: ['**/*.stories.{js,jsx,ts,tsx}'],
|
|
762
|
+
name: 'agent/jsx-classname-strict',
|
|
176
763
|
},
|
|
177
764
|
|
|
178
765
|
// Final overrides (index files, switch case, length rules)
|