eslint-config-agent 3.0.4 → 3.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/CHANGELOG.md +158 -0
- package/README.md +592 -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/length-rule-scope.js +27 -0
- package/configs/overrides.js +3 -14
- package/configs/test-files.js +40 -1
- package/configs/tsx.js +10 -0
- package/configs/typescript.js +44 -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 +14 -0
- package/exports/recommended.js +110 -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 +883 -7
- package/package.json +46 -17
- 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 +16 -2
- package/rules/plugin/n/no-process-env/index.js +4 -4
- package/rules/plugin/react/index.js +150 -2
- package/rules/plugin/typescript-eslint/index.js +406 -0
- package/rules/require-spec-file-tsx/README.md +57 -0
- package/rules/require-spec-file-tsx/helpers.js +93 -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'
|
|
@@ -26,11 +26,30 @@ const sharedRules = {
|
|
|
26
26
|
...allRules.pluginRules,
|
|
27
27
|
'object-curly-newline': 'off',
|
|
28
28
|
'no-shadow': 'off',
|
|
29
|
+
// The core `no-use-before-define` rule is intentionally left `off`: it
|
|
30
|
+
// false-positives on TypeScript type/interface declarations and enum
|
|
31
|
+
// members referenced before their textual position, which are hoisted and
|
|
32
|
+
// safe. `@typescript-eslint/no-use-before-define` below is the documented
|
|
33
|
+
// replacement — see that rule's comment for the full rationale.
|
|
34
|
+
'no-use-before-define': 'off',
|
|
35
|
+
// The core `no-unused-private-class-members` rule is intentionally left
|
|
36
|
+
// `off`: it only understands ECMAScript `#hashPrivate` fields, not
|
|
37
|
+
// TypeScript's `private` keyword, which is the actual dead-code surface in
|
|
38
|
+
// this codebase. `@typescript-eslint/no-unused-private-class-members` is
|
|
39
|
+
// the documented replacement — see that rule's comment for the full
|
|
40
|
+
// rationale.
|
|
41
|
+
'no-unused-private-class-members': 'off',
|
|
29
42
|
'comma-dangle': 'off',
|
|
30
43
|
'function-paren-newline': 'off',
|
|
31
44
|
quotes: 'off',
|
|
32
45
|
'no-unused-vars': 'off',
|
|
33
46
|
'@typescript-eslint/no-unused-vars': 'off',
|
|
47
|
+
// The core/`@typescript-eslint` `no-unused-vars` rules are disabled above, so
|
|
48
|
+
// nothing flagged dead imports. `eslint-plugin-unused-imports` fills that gap
|
|
49
|
+
// and, unlike the base rules, auto-fixes them: an unused `import` is pure dead
|
|
50
|
+
// weight (no runtime effect, just noise and slower builds) and is always safe
|
|
51
|
+
// to delete. Scoped to imports only — unused locals/args are left alone here.
|
|
52
|
+
'unused-imports/no-unused-imports': 'error',
|
|
34
53
|
'max-lines-per-function': allRules.maxFunctionLinesWarning,
|
|
35
54
|
'max-lines': allRules.maxFileLinesWarning,
|
|
36
55
|
semi: 'off',
|
|
@@ -42,6 +61,781 @@ const sharedRules = {
|
|
|
42
61
|
'no-continue': 'off',
|
|
43
62
|
// Additional built-in error handling rules
|
|
44
63
|
'prefer-promise-reject-errors': 'error',
|
|
64
|
+
// Disallow returning a value from a `Promise` executor — the function passed
|
|
65
|
+
// to `new Promise((resolve, reject) => { ... })`. The executor's return value
|
|
66
|
+
// is discarded by the Promise constructor, so `new Promise(() => readfile())`
|
|
67
|
+
// or `new Promise((resolve) => resolve(x))` written with an implicit return
|
|
68
|
+
// body silently throws the value away. When the returned value is itself a
|
|
69
|
+
// promise (an async executor, a `.then(...)` chain) this is a real bug: the
|
|
70
|
+
// work runs unobserved, rejections become unhandled, and the outer promise
|
|
71
|
+
// settles on a different schedule than the author expects. This is a
|
|
72
|
+
// correctness check, not a style preference — the same class of "the callback
|
|
73
|
+
// returns into a void" mistake that `array-callback-return` above guards
|
|
74
|
+
// against, and exactly the kind of quiet, wrong-behavior bug that type
|
|
75
|
+
// checking will not catch and that AI assistants emit when they reach for a
|
|
76
|
+
// brace body inside `new Promise`. It is not in `eslint:recommended`, so it is
|
|
77
|
+
// enabled explicitly here. The rule is not auto-fixable because only the
|
|
78
|
+
// author knows whether the value should be dropped or the surrounding control
|
|
79
|
+
// flow reworked.
|
|
80
|
+
'no-promise-executor-return': 'error',
|
|
81
|
+
// Disallow `await` inside a loop body. An `await` in a `for`/`for-of`/`while`
|
|
82
|
+
// loop pauses the loop on every iteration and only starts the next one after
|
|
83
|
+
// the current promise settles, so N independent async operations that could
|
|
84
|
+
// have run concurrently are instead serialized end-to-end: the loop takes the
|
|
85
|
+
// *sum* of the latencies instead of the *max*. On a list of network/DB calls
|
|
86
|
+
// that turns a sub-second batch into an N-times-slower stall — a quiet
|
|
87
|
+
// performance bug the type checker cannot see (the code is perfectly typed and
|
|
88
|
+
// correct, just slow) and exactly the shape an AI assistant emits when it
|
|
89
|
+
// mechanically drops an `await` into a `for` loop instead of reaching for
|
|
90
|
+
// `Promise.all`/`Promise.allSettled` over a `.map`. This is the throughput
|
|
91
|
+
// side of the async-hygiene family this config already builds with
|
|
92
|
+
// `no-floating-promises`, `promise-function-async` and `return-await`. When
|
|
93
|
+
// the iterations are genuinely dependent (each one needs the previous result,
|
|
94
|
+
// an ordered write, a deliberate rate limit), the serial `await` is correct —
|
|
95
|
+
// the rule has no auto-fix precisely because only the author knows that, so
|
|
96
|
+
// those loops opt out with an inline `// eslint-disable-next-line
|
|
97
|
+
// no-await-in-loop`. It is not in `eslint:recommended`, so it is enabled
|
|
98
|
+
// explicitly here.
|
|
99
|
+
'no-await-in-loop': 'error',
|
|
100
|
+
// Disallow loop conditions that reference a variable that is never modified
|
|
101
|
+
// inside the loop body. A loop whose condition tests a value that nothing
|
|
102
|
+
// inside the body ever updates is almost always a bug: either an unintended
|
|
103
|
+
// infinite loop or a forgotten increment / mutation. TypeScript does not
|
|
104
|
+
// catch this because the code is well-typed — the variable simply keeps its
|
|
105
|
+
// initial value and the condition never changes truth-value. This is exactly
|
|
106
|
+
// the kind of quiet, wrong-behavior bug AI assistants emit when they
|
|
107
|
+
// scaffold a loop body and lose track of what actually advances it. It is
|
|
108
|
+
// not in `eslint:recommended`, so it must be enabled explicitly here. The
|
|
109
|
+
// rule has a very low false-positive rate (genuine cases where an outer scope
|
|
110
|
+
// change is intended are rare and easy to suppress with a comment) and is not
|
|
111
|
+
// auto-fixable because only the author knows which variable was meant to
|
|
112
|
+
// change.
|
|
113
|
+
'no-unmodified-loop-condition': 'error',
|
|
114
|
+
// Disallow an `else`/`else if` block when the preceding `if` already exits
|
|
115
|
+
// the function via `return`. The `else` is dead weight: once the `if` branch
|
|
116
|
+
// returns, the code after it is unreachable from that path, so wrapping the
|
|
117
|
+
// remainder in `else` only adds a level of nesting that hides the real
|
|
118
|
+
// control flow. This is the built-in companion to the `early-return` plugin
|
|
119
|
+
// already enabled here — both push code toward flat, guard-clause style
|
|
120
|
+
// instead of the deeply-nested if/else trees AI assistants tend to emit. The
|
|
121
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
122
|
+
'no-else-return': ['error', { allowElseIf: false }],
|
|
123
|
+
// Disallow an `if` statement as the *only* statement inside an `else` block,
|
|
124
|
+
// requiring `else if` instead. The lone `if`-in-`else` adds an indentation
|
|
125
|
+
// level that hides what is really a flat chain of conditions, the same
|
|
126
|
+
// needless nesting the `no-else-return` rule directly above and the bundled
|
|
127
|
+
// `early-return` plugin already push back on, and a shape AI assistants emit
|
|
128
|
+
// when they mechanically wrap each new branch instead of extending the chain.
|
|
129
|
+
// Collapsing it into an `else if` keeps the control flow flat and legible. The
|
|
130
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
131
|
+
// Note: `unicorn/no-lonely-if` covers a different pattern (lone `if` inside
|
|
132
|
+
// another `if` without `else`), so both rules are active — they are complementary.
|
|
133
|
+
'no-lonely-if': 'error',
|
|
134
|
+
// Disallow a `return;` (or `return undefined;`) that is the last statement of
|
|
135
|
+
// a function and so changes nothing — control already falls off the end and
|
|
136
|
+
// yields `undefined`. A redundant trailing `return` reads as if it guards
|
|
137
|
+
// something or hands back a meaningful value when it does neither; it is dead
|
|
138
|
+
// punctuation that hides the real control flow, exactly what this config's
|
|
139
|
+
// explicit-over-clever stance exists to surface. It is the natural companion
|
|
140
|
+
// to the `no-else-return` rule just above and to the `early-return` plugin
|
|
141
|
+
// already enabled here: that pair flattens branching by removing unneeded
|
|
142
|
+
// `else` blocks, and this removes the now-pointless `return` they often leave
|
|
143
|
+
// behind. The rule is auto-fixable, so consumers can adopt it with
|
|
144
|
+
// `eslint --fix`.
|
|
145
|
+
'no-useless-return': 'error',
|
|
146
|
+
// Require every `return` statement in a function to either always specify a
|
|
147
|
+
// value or never specify one. A function that returns a value on one branch
|
|
148
|
+
// and falls through — or hits a bare `return;` — on another silently yields
|
|
149
|
+
// `undefined` on the unhandled paths, and the caller then receives
|
|
150
|
+
// `undefined` where it expected the value. The bug surfaces far downstream as
|
|
151
|
+
// a "cannot read property of undefined" crash rather than at the offending
|
|
152
|
+
// branch, and the type checker does not reliably catch it: an inferred
|
|
153
|
+
// `T | undefined` return type type-checks cleanly even though the
|
|
154
|
+
// fall-through was accidental. This is exactly the quiet, plausible-but-wrong
|
|
155
|
+
// control-flow mistake an AI assistant emits when it adds an early-exit
|
|
156
|
+
// branch and forgets to carry a value through it. Not in `eslint:recommended`,
|
|
157
|
+
// so it must be enabled explicitly here. The rule is intentionally not
|
|
158
|
+
// auto-fixable — only the author knows whether the missing branch should
|
|
159
|
+
// return a value or the value-returning branch should stop returning one —
|
|
160
|
+
// so adoption surfaces the call sites rather than silently rewriting them.
|
|
161
|
+
'consistent-return': 'error',
|
|
162
|
+
// Disallow a value assigned to a variable that is never read before the
|
|
163
|
+
// variable is overwritten or its scope ends — a "dead store". Writing
|
|
164
|
+
// `let total = compute()` and then unconditionally reassigning `total = 0`
|
|
165
|
+
// (or returning before `total` is ever used) throws the first value away,
|
|
166
|
+
// and that wasted write is almost never deliberate: it usually means the
|
|
167
|
+
// author meant to *use* the value, assigned to the wrong variable, or left a
|
|
168
|
+
// half-finished branch behind. The result is code that reads as if a value
|
|
169
|
+
// flows through when it silently does not — exactly the quiet, wrong-behavior
|
|
170
|
+
// bug type checking will not catch (the types line up; only the data flow is
|
|
171
|
+
// broken) and one AI assistants emit when they stitch branches together or
|
|
172
|
+
// forget to thread a computed value into its use. It is the data-flow sibling
|
|
173
|
+
// of the `no-useless-return` rule just above: both surface statements that
|
|
174
|
+
// look load-bearing but change nothing. The rule relies on ESLint's code-path
|
|
175
|
+
// analysis (added in ESLint 9.14) and is not in `eslint:recommended`, so it
|
|
176
|
+
// is enabled explicitly here. It is not auto-fixable because only the author
|
|
177
|
+
// knows whether the dead store should be deleted or its value actually used.
|
|
178
|
+
'no-useless-assignment': 'error',
|
|
179
|
+
// Disallow assignment inside a `return` statement — `return total = compute()`
|
|
180
|
+
// or `return (found = next)`. An `=` in the spot a reader expects a value (and
|
|
181
|
+
// where `==`/`===` belongs) does two things at once: it mutates a binding and
|
|
182
|
+
// hands back the assigned value, so the side effect hides in the returned
|
|
183
|
+
// expression where almost no one looks for it. It is most often a typo for a
|
|
184
|
+
// comparison (`return found === next`) or a leftover from refactoring a guard,
|
|
185
|
+
// and the code still type-checks because the assigned value has the right type
|
|
186
|
+
// — only the data flow is wrong, exactly the quiet, plausible-but-wrong shape
|
|
187
|
+
// this config exists to surface and one AI assistants emit when they fold an
|
|
188
|
+
// update into a `return`. It sits with the assignment-discipline rules already
|
|
189
|
+
// here — `no-param-reassign`, `no-multi-assign` and the `no-useless-assignment`
|
|
190
|
+
// dead-store check directly above — all of which keep where and how values are
|
|
191
|
+
// written explicit. `'always'` flags the assignment even when wrapped in
|
|
192
|
+
// parentheses, so the deliberate-looking `return (x = y)` form is rejected too;
|
|
193
|
+
// an author who truly means it can split the assignment onto its own line. It
|
|
194
|
+
// is not in `eslint:recommended`, so it is enabled explicitly here, and it is
|
|
195
|
+
// not auto-fixable because only the author knows whether a comparison or a
|
|
196
|
+
// separate statement was intended.
|
|
197
|
+
'no-return-assign': ['error', 'always'],
|
|
198
|
+
// Disallow ternaries whose branches are themselves a boolean literal
|
|
199
|
+
// (`cond ? true : false`, `cond ? false : true`) or that re-test a value
|
|
200
|
+
// they could simply fall through to (`a ? a : b`). These are the flat
|
|
201
|
+
// sibling of the nested ternaries `no-nested-ternary` already bans here:
|
|
202
|
+
// punctuation-heavy expressions that dress up a plain boolean (or the
|
|
203
|
+
// condition itself) as a branch, exactly the "clever but unreadable"
|
|
204
|
+
// shortcut this config exists to prevent and one AI assistants reach for
|
|
205
|
+
// often. The explicit form — the condition itself, a negation, or a real
|
|
206
|
+
// `if` — keeps the intent legible. The rule is auto-fixable, so consumers
|
|
207
|
+
// can adopt it with `eslint --fix`. `defaultAssignment: false` extends the
|
|
208
|
+
// check to the `a ? a : b` default-value idiom as well.
|
|
209
|
+
'no-unneeded-ternary': ['error', { defaultAssignment: false }],
|
|
210
|
+
// Require the `default` clause of a `switch` to come last. A `default` matches
|
|
211
|
+
// only when no `case` does, so its precedence is independent of where it sits
|
|
212
|
+
// — but a `default` written *before* later cases reads as if those cases were
|
|
213
|
+
// unreachable, and a mid-`switch` `default` that omits `break` silently falls
|
|
214
|
+
// through into the cases below it. Both are the "looks one way, behaves
|
|
215
|
+
// another" footgun this config exists to surface, and a shape an AI assistant
|
|
216
|
+
// emitting a `switch` can easily introduce. Pinning `default` to the end keeps
|
|
217
|
+
// its order-independent meaning legible. It sits with the bundled `switch-case`
|
|
218
|
+
// rules already enabled here. It is not in `eslint:recommended`, so it is
|
|
219
|
+
// enabled explicitly, and it is not auto-fixable because moving a clause that
|
|
220
|
+
// omits `break` could change behavior — only the author can reorder safely.
|
|
221
|
+
'default-case-last': 'error',
|
|
222
|
+
// Require strict equality (=== / !==). Loose equality performs implicit type
|
|
223
|
+
// coercion, exactly the kind of "clever" shortcut this config exists to
|
|
224
|
+
// prevent. Enforcing it in the shared config means consumers no longer have
|
|
225
|
+
// to re-add it on top of the package.
|
|
226
|
+
eqeqeq: ['error', 'always'],
|
|
227
|
+
// Require the natural reading order in comparisons: the variable (or
|
|
228
|
+
// expression) on the left, the literal on the right — `count === 0`, never
|
|
229
|
+
// `0 === count`. The reversed "Yoda" form exists only to guard against the
|
|
230
|
+
// assignment typo `if (count = 0)`, but `eqeqeq` (directly above) already
|
|
231
|
+
// forces `===`/`!==` everywhere, and TypeScript flags accidental assignment
|
|
232
|
+
// in a condition as a type error, so the Yoda workaround is pure readability
|
|
233
|
+
// tax. `exceptRange` keeps the clearer range idiom (`0 <= x && x < limit`)
|
|
234
|
+
// which reads as a natural number-line comparison and is harder to express in
|
|
235
|
+
// the canonical order. The rule is auto-fixable, so consumers can adopt it
|
|
236
|
+
// with `eslint --fix`.
|
|
237
|
+
yoda: ['error', 'never', { exceptRange: true }],
|
|
238
|
+
// Disallow reassigning function parameters and mutating their properties.
|
|
239
|
+
// Reassigning a parameter decouples it from the caller's argument and hides the
|
|
240
|
+
// function's real inputs; mutating a parameter's properties causes
|
|
241
|
+
// action-at-a-distance bugs where a callee silently rewrites an object the
|
|
242
|
+
// caller still holds. Both undermine this config's explicit-over-clever,
|
|
243
|
+
// immutability-leaning stance, so treat parameters as read-only here.
|
|
244
|
+
'no-param-reassign': ['error', { props: true }],
|
|
245
|
+
// Require every parameter with a default value to come after all parameters
|
|
246
|
+
// without one. A default can only ever take effect when the argument is
|
|
247
|
+
// `undefined`, so a default placed before a required parameter
|
|
248
|
+
// (`f(a = 1, b)`) is unreachable in practice: the caller cannot skip `a` to
|
|
249
|
+
// reach `b`, they must pass `f(undefined, x)` to use the default — at which
|
|
250
|
+
// point the default documents an API the call sites cannot actually use.
|
|
251
|
+
// It is almost always a mistake for the parameter order, not a deliberate
|
|
252
|
+
// signature, and exactly the kind of plausible-but-wrong shape an AI
|
|
253
|
+
// assistant emits when it bolts a default onto the first convenient
|
|
254
|
+
// parameter. This sits with the parameter-discipline rules already here
|
|
255
|
+
// (`no-param-reassign` directly above): keep a function's inputs honest and
|
|
256
|
+
// its signature meaningful. It is not in `eslint:recommended`, so it is
|
|
257
|
+
// enabled explicitly. The rule is not auto-fixable because reordering
|
|
258
|
+
// parameters would silently break every call site, so only the author can
|
|
259
|
+
// decide whether the default or the order was wrong.
|
|
260
|
+
'default-param-last': 'error',
|
|
261
|
+
// Require `const` for bindings that are never reassigned. This is the
|
|
262
|
+
// local-binding counterpart to `no-param-reassign`: together they extend the
|
|
263
|
+
// config's immutability-leaning stance from parameters to every variable. A
|
|
264
|
+
// `let` that is never reassigned misleads readers into expecting a mutation
|
|
265
|
+
// that never comes; `const` documents fixed intent up front. The rule is
|
|
266
|
+
// auto-fixable (`eslint --fix`), so adoption is cheap. `destructuring: 'all'`
|
|
267
|
+
// only flags a destructuring pattern when every introduced binding could be
|
|
268
|
+
// `const`, leaving mixed const/let destructuring alone.
|
|
269
|
+
'prefer-const': ['error', { destructuring: 'all' }],
|
|
270
|
+
// Disallow shorthand type coercions such as `!!value`, `+str`, `1 * x`,
|
|
271
|
+
// `'' + n` and `~str.indexOf(...)`. These are the sibling of the loose
|
|
272
|
+
// equality `eqeqeq` already bans here: terse tricks that hide an implicit
|
|
273
|
+
// type conversion behind punctuation, exactly the "clever but unreadable"
|
|
274
|
+
// shortcut this config exists to prevent and one AI assistants reach for
|
|
275
|
+
// often. Requiring the explicit form (`Boolean(value)`, `Number(str)`,
|
|
276
|
+
// `String(n)`) keeps the intended conversion legible. The rule is
|
|
277
|
+
// auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
278
|
+
'no-implicit-coercion': ['error', { allow: [] }],
|
|
279
|
+
// Require an explicit radix argument to `parseInt` — `parseInt(str, 10)`,
|
|
280
|
+
// never `parseInt(str)`. With the base omitted, `parseInt` infers it from the
|
|
281
|
+
// string: a leading `0x` is read as hex and, depending on the engine, a
|
|
282
|
+
// leading `0` can be read as octal, so `parseInt('0x10')` is `16` and
|
|
283
|
+
// `parseInt(userInput)` silently parses in a base the author never chose. The
|
|
284
|
+
// wrong-number result type-checks fine (it is still a `number`) and only the
|
|
285
|
+
// data flow is broken — exactly the quiet, plausible-but-wrong shape this
|
|
286
|
+
// config exists to surface, and the same class of *implicit* behavior it
|
|
287
|
+
// already bans via `eqeqeq` and `no-implicit-coercion` directly above.
|
|
288
|
+
// Forcing the base makes the parse deterministic and the intent explicit. The
|
|
289
|
+
// rule is not auto-fixable because only the author knows which base was meant.
|
|
290
|
+
radix: ['error', 'always'],
|
|
291
|
+
// Require template literals instead of string concatenation. Building a
|
|
292
|
+
// string by chaining `+` (`'Hello ' + name + '!'`) scatters the literal
|
|
293
|
+
// text across operators, makes the final shape hard to read, and leans on
|
|
294
|
+
// the same implicit coercion `no-implicit-coercion` already bans whenever a
|
|
295
|
+
// non-string operand sneaks in. A template literal (`` `Hello ${name}!` ``)
|
|
296
|
+
// keeps the literal text and the interpolated values visually aligned, which
|
|
297
|
+
// is exactly the explicit, readable form this config favors and one AI
|
|
298
|
+
// assistants frequently skip. The rule is auto-fixable, so consumers can
|
|
299
|
+
// adopt it with `eslint --fix`.
|
|
300
|
+
'prefer-template': 'error',
|
|
301
|
+
// Disallow concatenating two string literals — `'Hello ' + 'World'`,
|
|
302
|
+
// `'a' + 'b' + 'c'`, or a literal split across lines with `+`. The operands
|
|
303
|
+
// are known at author time, so the `+` does nothing the source could not say
|
|
304
|
+
// directly: it is pure punctuation that hides a single constant string behind
|
|
305
|
+
// an operator and invites the reader to wonder whether a variable was meant.
|
|
306
|
+
// It is the static sibling of the `prefer-template` rule just above — that one
|
|
307
|
+
// pushes runtime interpolation onto template literals, this one removes the
|
|
308
|
+
// join entirely when there is no value to interpolate — and it leans on the
|
|
309
|
+
// same implicit-`+` surface the config already narrows with `prefer-template`,
|
|
310
|
+
// `no-implicit-coercion` and `@typescript-eslint/restrict-plus-operands`.
|
|
311
|
+
// Collapsing the pieces into one literal (or a template literal when the line
|
|
312
|
+
// length is the only reason for the split) is the explicit, readable form this
|
|
313
|
+
// config favors and one AI assistants frequently skip. The rule is not
|
|
314
|
+
// auto-fixable because only the author knows whether the two pieces were meant
|
|
315
|
+
// to be one literal or a refactor left a variable behind.
|
|
316
|
+
'no-useless-concat': 'error',
|
|
317
|
+
// Disallow `.bind()` on a function that never references `this` (and has no
|
|
318
|
+
// bound arguments) — `function () { return 1 }.bind(this)`,
|
|
319
|
+
// `(() => x).bind(obj)`, `handler.bind(this)` where `handler` ignores `this`.
|
|
320
|
+
// The `.bind()` does nothing: it allocates a new wrapper function on every
|
|
321
|
+
// evaluation and returns one that behaves identically to the original, so the
|
|
322
|
+
// call is pure overhead that also misleads the reader into thinking the
|
|
323
|
+
// receiver matters when it does not. It is the same "looks meaningful but is
|
|
324
|
+
// dead" clutter the `no-useless-return`, `no-useless-assignment` and
|
|
325
|
+
// `no-useless-concat` rules already remove here, and exactly the reflexive
|
|
326
|
+
// `.bind(this)` an AI assistant appends to a callback by habit, whether or not
|
|
327
|
+
// the body uses `this`. Dropping the bind leaves the explicit, allocation-free
|
|
328
|
+
// form this config favors. The rule is auto-fixable, so consumers can adopt it
|
|
329
|
+
// with `eslint --fix`; it is not in `eslint:recommended`, so it is enabled
|
|
330
|
+
// explicitly here.
|
|
331
|
+
'no-extra-bind': 'error',
|
|
332
|
+
// Disallow `.call()` and `.apply()` when the first argument (the `this`
|
|
333
|
+
// binding) is `undefined` or `null` and the result is identical to a
|
|
334
|
+
// direct call. `fn.call(null, a, b)` behaves exactly like `fn(a, b)` in
|
|
335
|
+
// non-strict mode and almost always in strict mode: the wrapper only adds
|
|
336
|
+
// noise and an extra property lookup. `fn.apply(undefined, [a, b])` is the
|
|
337
|
+
// same deadweight applied to a spread. The two are the direct-invocation
|
|
338
|
+
// counterpart of the `no-extra-bind` rule just above, which catches
|
|
339
|
+
// `.bind()` that never changes `this` — together they close the "look up
|
|
340
|
+
// the prototype chain to call the function" footgun an AI assistant emits
|
|
341
|
+
// when it wants to forward arguments or detach a method without realizing
|
|
342
|
+
// a direct call suffices. The rule is auto-fixable (`eslint --fix`) and is
|
|
343
|
+
// not in `eslint:recommended`, so it is enabled explicitly here.
|
|
344
|
+
'no-useless-call': 'error',
|
|
345
|
+
// Disallow renaming an import, export, or destructured binding to the exact
|
|
346
|
+
// name it already has — `import { foo as foo } from './foo'`,
|
|
347
|
+
// `export { bar as bar }`, `const { baz: baz } = obj`. The `as`/`:` clause
|
|
348
|
+
// reads as if it transforms the binding, so a reader stops to compare both
|
|
349
|
+
// sides only to discover they are identical: pure punctuation noise that
|
|
350
|
+
// adds nothing. It is the binding-alias sibling of `no-useless-call` just
|
|
351
|
+
// above — both look meaningful and are dead — and exactly the ceremony an
|
|
352
|
+
// AI assistant spells out mechanically for an alias it never needed. The
|
|
353
|
+
// rule is auto-fixable (`eslint --fix`) and is not in `eslint:recommended`,
|
|
354
|
+
// so it is enabled explicitly here.
|
|
355
|
+
'no-useless-rename': 'error',
|
|
356
|
+
// Require `Object.hasOwn(obj, key)` over the legacy ways of asking the same
|
|
357
|
+
// question. The two forms it replaces are each a footgun: calling
|
|
358
|
+
// `obj.hasOwnProperty(key)` directly breaks the moment `obj` has a `null`
|
|
359
|
+
// prototype (`Object.create(null)`, many map-like objects) or shadows the
|
|
360
|
+
// method with an own `hasOwnProperty` field — it throws or returns the wrong
|
|
361
|
+
// answer — which is why `no-prototype-builtins` already bans it; and the safe
|
|
362
|
+
// workaround, `Object.prototype.hasOwnProperty.call(obj, key)`, is a long,
|
|
363
|
+
// punctuation-heavy incantation that hides a one-word intent behind prototype
|
|
364
|
+
// plumbing. `Object.hasOwn` is the single explicit, prototype-safe spelling of
|
|
365
|
+
// "does this object own this key", so it fits this config's
|
|
366
|
+
// explicit-over-clever, correctness-leaning stance and removes the boilerplate
|
|
367
|
+
// adopters would otherwise reach for. It is auto-fixable (`eslint --fix`) and
|
|
368
|
+
// available on every supported runtime (Node >= 20, ES2022), so adoption is
|
|
369
|
+
// free.
|
|
370
|
+
'prefer-object-has-own': 'error',
|
|
371
|
+
// Disallow `var`. `var` declarations are function-scoped and hoisted, so
|
|
372
|
+
// they leak out of the block they appear to belong to and read as
|
|
373
|
+
// initialized `undefined` before their declaration runs — producing
|
|
374
|
+
// order-dependent bugs that block-scoped `let`/`const` make impossible.
|
|
375
|
+
// `var` is exactly the legacy shortcut an AI assistant trained on older
|
|
376
|
+
// code reaches for, which puts it squarely in scope for this config's
|
|
377
|
+
// explicit-over-clever, AI-safety stance. It is also the foundation the
|
|
378
|
+
// `prefer-const` rule builds on. The rule is auto-fixable.
|
|
379
|
+
'no-var': 'error',
|
|
380
|
+
// Disallow explicitly initializing a variable to `undefined` —
|
|
381
|
+
// `let value = undefined` instead of the equivalent, shorter `let value`.
|
|
382
|
+
// Every declared-but-unassigned binding is already `undefined`, so the
|
|
383
|
+
// initializer adds a token that looks like it is doing something (choosing
|
|
384
|
+
// a starting value) while actually doing nothing; a reader has to stop and
|
|
385
|
+
// confirm that `undefined` really is the intended value rather than a
|
|
386
|
+
// half-finished assignment the author forgot to fill in. It is exactly the
|
|
387
|
+
// no-op an AI assistant emits when it scaffolds a variable before deciding
|
|
388
|
+
// what to assign to it. Not in `eslint:recommended`, so it must be enabled
|
|
389
|
+
// explicitly here. The rule is auto-fixable (`eslint --fix` deletes the
|
|
390
|
+
// redundant initializer), so adoption costs nothing.
|
|
391
|
+
'no-undef-init': 'error',
|
|
392
|
+
// Require object literal shorthand for properties and methods, so
|
|
393
|
+
// `{ value: value }` collapses to `{ value }` and `{ run: function () {} }`
|
|
394
|
+
// to `{ run() {} }`. The longhand forms carry no extra meaning — they are
|
|
395
|
+
// pure boilerplate that an AI assistant trained on older code routinely
|
|
396
|
+
// emits, and the duplicated `value: value` name is a common spot for a
|
|
397
|
+
// copy-paste typo that the shorthand removes entirely. Enforcing one
|
|
398
|
+
// canonical object form keeps literals legible and consistent, matching
|
|
399
|
+
// this config's explicit-over-clever, low-noise stance. The rule is
|
|
400
|
+
// auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
401
|
+
'object-shorthand': ['error', 'always'],
|
|
402
|
+
// Require the compound assignment shorthand (`x += 1`, `total *= rate`)
|
|
403
|
+
// wherever a variable is assigned the result of an operation on itself, so
|
|
404
|
+
// `x = x + 1` is rewritten to `x += 1`. The longhand form names the target
|
|
405
|
+
// twice — once on each side of the `=` — and that duplicated operand is a
|
|
406
|
+
// silent typo site: `total = totals + 1` reads as a self-update but quietly
|
|
407
|
+
// assigns from a *different* variable, a bug type checking cannot catch when
|
|
408
|
+
// both names are in scope. Collapsing to `+=` removes the
|
|
409
|
+
// second spelling of the name entirely, so the mismatch becomes impossible to
|
|
410
|
+
// write. This is the assignment sibling of the `object-shorthand` rule just
|
|
411
|
+
// above (which removes the same duplicated-name typo site in `{ value: value }`)
|
|
412
|
+
// and fits this config's explicit-over-clever, low-noise, correctness-leaning
|
|
413
|
+
// stance: one canonical spelling of "update this in place". The longhand is
|
|
414
|
+
// also exactly what an AI assistant trained on older code reaches for. The
|
|
415
|
+
// rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
416
|
+
'operator-assignment': ['error', 'always'],
|
|
417
|
+
// Require the logical-assignment shorthand (`x ||= y`, `x &&= y`, `x ??= y`)
|
|
418
|
+
// wherever a binding is reassigned by a logical expression over itself. A
|
|
419
|
+
// longhand `x = x || defaultValue` names the target twice — once as the left
|
|
420
|
+
// operand of `||` and once as the assignment destination — which is the same
|
|
421
|
+
// duplicated-name typo site the `operator-assignment` rule directly above
|
|
422
|
+
// already catches for arithmetic operators. Writing `x ||= defaultValue`
|
|
423
|
+
// removes the second spelling of the name entirely so the assignment-by-typo
|
|
424
|
+
// footgun disappears, and it states the intent — "set this variable only if it
|
|
425
|
+
// is currently falsy / nullish / truthy" — in fewer characters and with the
|
|
426
|
+
// exact same semantics. The three forms map directly onto the boolean and
|
|
427
|
+
// nullish operators the config already steers code toward (`eqeqeq`,
|
|
428
|
+
// `@typescript-eslint/prefer-nullish-coalescing`): `&&=` for "assign only when
|
|
429
|
+
// truthy", `||=` for "assign only when falsy", `??=` for "assign only when null
|
|
430
|
+
// or undefined". `enforceForIfStatements: true` extends the check to the
|
|
431
|
+
// equivalent `if`-guarded form (`if (!x) x = y`, `if (x == null) x = y`), so
|
|
432
|
+
// both the expression and the statement spelling of the pattern are caught. The
|
|
433
|
+
// longhand `x = x || y` is also exactly what an AI assistant trained on
|
|
434
|
+
// pre-ES2021 code reaches for, which puts it squarely in this config's
|
|
435
|
+
// AI-safety scope alongside `operator-assignment`. The rule is auto-fixable
|
|
436
|
+
// (`eslint --fix`), so adoption is free, and it is not in `eslint:recommended`,
|
|
437
|
+
// so it must be enabled explicitly here.
|
|
438
|
+
'logical-assignment-operators': [
|
|
439
|
+
'error',
|
|
440
|
+
'always',
|
|
441
|
+
{ enforceForIfStatements: true },
|
|
442
|
+
],
|
|
443
|
+
// Disallow chained assignment expressions such as `a = b = c = 0`. Chaining
|
|
444
|
+
// collapses several writes into one expression: the value flows right-to-left
|
|
445
|
+
// through bindings that have nothing to do with each other, so a reader has to
|
|
446
|
+
// unwind the chain to see how many variables changed and to what. It also
|
|
447
|
+
// quietly couples those variables — touching the chain later risks rewriting
|
|
448
|
+
// more than intended — which runs against the immutability-leaning stance the
|
|
449
|
+
// `prefer-const`, `no-param-reassign` and `no-var` rules above already set
|
|
450
|
+
// here. It is exactly the terse, "clever" shortcut this config exists to
|
|
451
|
+
// prevent and one AI assistants emit when packing initialization onto a single
|
|
452
|
+
// line. Writing each assignment on its own statement keeps the data flow
|
|
453
|
+
// explicit.
|
|
454
|
+
'no-multi-assign': 'error',
|
|
455
|
+
// Disallow `${...}` placeholders inside ordinary string literals
|
|
456
|
+
// (`'Hello ${name}'`, `"total: ${count}"`). Template-literal interpolation
|
|
457
|
+
// only works inside backticks; the moment the quotes are single or double,
|
|
458
|
+
// `${name}` is just literal text and the value is silently never inserted —
|
|
459
|
+
// a bug that type-checking cannot catch because the result is still a valid
|
|
460
|
+
// `string`. Mixing up the quote character is exactly the kind of silent
|
|
461
|
+
// mistake an AI assistant emits when stitching messages together, which puts
|
|
462
|
+
// it squarely in scope for this config's correctness-and-clarity stance. It
|
|
463
|
+
// is the natural companion to the explicit-conversion rules
|
|
464
|
+
// (`no-implicit-coercion`, `eqeqeq`) above: it keeps string building honest
|
|
465
|
+
// by forcing a real template literal whenever interpolation is intended.
|
|
466
|
+
'no-template-curly-in-string': 'error',
|
|
467
|
+
// Disallow the `Object` constructor (`new Object()` and `Object()`) in favor
|
|
468
|
+
// of the `{}` object literal. The constructor form is a strictly more verbose,
|
|
469
|
+
// more indirect way to do exactly what the literal does — and a trap: passing
|
|
470
|
+
// a single non-object argument makes `Object(x)` return *that* value (or a
|
|
471
|
+
// wrapper), so the call quietly stops creating a fresh object at all. The
|
|
472
|
+
// literal has no such ambiguity. This is the object-creation sibling of the
|
|
473
|
+
// wrapper-constructor and coercion bans this config already sets (`Number`,
|
|
474
|
+
// `String`, `!!x`): prefer the plain literal/value over a constructor call
|
|
475
|
+
// dressed up as one. `new Object()` is also a legacy idiom an AI assistant
|
|
476
|
+
// trained on older code reaches for, which puts it squarely in this config's
|
|
477
|
+
// explicit-over-clever, AI-safety scope. The rule is auto-fixable, so
|
|
478
|
+
// consumers can adopt it with `eslint --fix`.
|
|
479
|
+
'no-object-constructor': 'error',
|
|
480
|
+
// Disallow the primitive-wrapper constructors `new String()`, `new Number()`,
|
|
481
|
+
// and `new Boolean()`. These do the opposite of what they look like: instead of
|
|
482
|
+
// converting to a primitive they produce a **boxed object**, so
|
|
483
|
+
// `typeof new Number(5)` is `'object'` (not `'number'`),
|
|
484
|
+
// `new Boolean(false)` is always truthy (the object is truthy), and
|
|
485
|
+
// `new String('x') === 'x'` is `false`. The caller typically expects a
|
|
486
|
+
// primitive and the wrong type is a quiet, wrong-result trap that the type
|
|
487
|
+
// checker will not always catch when the boxed type is widened to `object`.
|
|
488
|
+
// For conversion use the unadorned call form (`Number(str)`, `String(n)`,
|
|
489
|
+
// `Boolean(x)`) — that is what `no-implicit-coercion` already pushes code
|
|
490
|
+
// toward, and these two rules are complementary: `no-implicit-coercion` bans
|
|
491
|
+
// the shorthand coercions (`!!x`, `+str`), `no-new-wrappers` bans the
|
|
492
|
+
// constructor path to the same wrong object. The rule is auto-fixable, so
|
|
493
|
+
// consumers can adopt it with `eslint --fix`.
|
|
494
|
+
'no-new-wrappers': 'error',
|
|
495
|
+
// Require object spread (`{ ...a, ...b }`) over `Object.assign({}, a, b)` when
|
|
496
|
+
// the call is building a brand-new object (its first argument is an object
|
|
497
|
+
// literal). The `Object.assign({}, ...)` form is a more indirect, punctuation-
|
|
498
|
+
// heavy spelling of the same intent: a reader has to recognize the empty `{}`
|
|
499
|
+
// accumulator and the mutate-and-return semantics just to conclude "this makes
|
|
500
|
+
// a fresh, merged object", which the spread states directly. It is the
|
|
501
|
+
// object-merging sibling of the `no-object-constructor` ban right above —
|
|
502
|
+
// both reject an indirect construction idiom in favor of the plain literal —
|
|
503
|
+
// and of `object-shorthand`, which already pushes object literals toward their
|
|
504
|
+
// most legible form. `Object.assign` onto an existing target (`Object.assign(
|
|
505
|
+
// target, src)`) mutates and is left alone, so only the new-object case is
|
|
506
|
+
// flagged. The rule is auto-fixable, so consumers can adopt it with
|
|
507
|
+
// `eslint --fix`.
|
|
508
|
+
'prefer-object-spread': 'error',
|
|
509
|
+
// Require a `return` from every array-method callback that is expected to
|
|
510
|
+
// produce one (`map`, `filter`, `reduce`, `every`, `some`, `find`, `sort`,
|
|
511
|
+
// `flatMap`, ...). A callback that falls off the end returns `undefined`, so
|
|
512
|
+
// `arr.map(x => { doSomething(x) })` silently yields an array of `undefined`
|
|
513
|
+
// and `arr.filter(x => { x.active })` keeps every element — the kind of
|
|
514
|
+
// quiet, wrong-result bug that type checking will not catch and that AI
|
|
515
|
+
// assistants emit when they reach for a brace body and forget the `return`.
|
|
516
|
+
// Unlike most rules here this is a correctness check, not a style
|
|
517
|
+
// preference, which is why it is not covered by `eslint:recommended` and is
|
|
518
|
+
// enabled explicitly. `checkForEach: true` flags the inverse mistake —
|
|
519
|
+
// returning a value from `forEach`, where the result is discarded — which
|
|
520
|
+
// usually means `map` was intended. The rule is not auto-fixable because
|
|
521
|
+
// only the author knows what each callback should return.
|
|
522
|
+
'array-callback-return': [
|
|
523
|
+
'error',
|
|
524
|
+
{ allowImplicit: false, checkForEach: true },
|
|
525
|
+
],
|
|
526
|
+
// Disallow a loop whose body always exits on the first iteration — a `for`,
|
|
527
|
+
// `while`, or `do...while` where every control-flow path ends in `return`,
|
|
528
|
+
// `break`, or `throw`. Such a loop can never reach a second pass: it reads
|
|
529
|
+
// as "iterate every element" but actually runs at most once, collapsing what
|
|
530
|
+
// the author probably meant as a search into a single-element check. The
|
|
531
|
+
// classic form is a misplaced `return` inside a `for-of`:
|
|
532
|
+
// `for (const item of list) { if (cond(item)) return item; return null }`
|
|
533
|
+
// The unconditional `return null` exits on the first iteration regardless of
|
|
534
|
+
// the condition, so every element after the first is silently ignored. This
|
|
535
|
+
// is a wrong-result correctness bug that type checking cannot catch (the
|
|
536
|
+
// types are fine; only the data flow is broken) and exactly the shape AI
|
|
537
|
+
// assistants emit when they flatten a "find the first match" pattern into a
|
|
538
|
+
// loop and lose track of which exit belongs where. It is the loop-level
|
|
539
|
+
// sibling of `array-callback-return` directly above: both catch the "loop
|
|
540
|
+
// body was supposed to visit every element, but silently doesn't" class of
|
|
541
|
+
// mistake. The rule is not in `eslint:recommended`, so it is enabled
|
|
542
|
+
// explicitly. It is not auto-fixable because only the author knows whether
|
|
543
|
+
// the loop or the misplaced exit is wrong.
|
|
544
|
+
'no-unreachable-loop': 'error',
|
|
545
|
+
// Only allow throwing real `Error` objects — reject `throw 'boom'`,
|
|
546
|
+
// `throw { code: 500 }`, `throw 42` and any other non-Error value. A thrown
|
|
547
|
+
// string or plain object carries no stack trace, so the failure surfaces with
|
|
548
|
+
// no record of where it came from, and it breaks every `catch (e)` that does
|
|
549
|
+
// `e instanceof Error` or reads `e.message`/`e.stack` — the consumer's error
|
|
550
|
+
// handling silently misfires. Throwing a bare literal is exactly the terse
|
|
551
|
+
// shortcut an AI assistant emits when it wants to bail out fast, which puts it
|
|
552
|
+
// squarely in this config's correctness-over-clever, AI-safety scope, and it
|
|
553
|
+
// pairs with the bundled `error/*` rules that already shape how errors are
|
|
554
|
+
// declared. Unlike most rules here it catches an outright correctness
|
|
555
|
+
// mistake, not a style preference, which is why it is enabled explicitly —
|
|
556
|
+
// `eslint:recommended` does not turn it on. The rule is not auto-fixable
|
|
557
|
+
// because only the author knows which `Error` subclass the literal should
|
|
558
|
+
// become.
|
|
559
|
+
'no-throw-literal': 'error',
|
|
560
|
+
// Disallow comparing a variable to itself (`x === x`, `x !== x`,
|
|
561
|
+
// `i < i`, ...). A self-comparison is either a plain bug — the author meant
|
|
562
|
+
// to compare against a different operand and the result is a constant
|
|
563
|
+
// `true`/`false` that quietly disables a branch — or the deliberate
|
|
564
|
+
// `value !== value` trick for detecting `NaN`. Both belong to the same
|
|
565
|
+
// family the `eqeqeq`, `no-implicit-coercion` and `no-unneeded-ternary`
|
|
566
|
+
// rules above already target: a punctuation-heavy expression whose real
|
|
567
|
+
// intent is hidden, exactly the "clever but unreadable" shortcut this config
|
|
568
|
+
// exists to prevent and one AI assistants emit when stitching together
|
|
569
|
+
// conditions. Unlike most rules here this also catches an outright
|
|
570
|
+
// correctness mistake, which is why it is enabled explicitly —
|
|
571
|
+
// `eslint:recommended` does not turn it on. The explicit form
|
|
572
|
+
// (`Number.isNaN(value)` for the NaN case, the intended operand otherwise)
|
|
573
|
+
// keeps the check legible. The rule is not auto-fixable because only the
|
|
574
|
+
// author knows which operand was meant.
|
|
575
|
+
'no-self-compare': 'error',
|
|
576
|
+
// Disallow a binary or logical expression whose result is provably
|
|
577
|
+
// constant regardless of a variable operand: a `&&`/`||` whose left side
|
|
578
|
+
// is already a statically known boolean (`true || sideEffect()`, `false
|
|
579
|
+
// && sideEffect()` — the right side, and any side effect in it, never
|
|
580
|
+
// runs, yet reads as if it were conditional), a loose-equality check
|
|
581
|
+
// against `null`/`undefined` where the other side can never be nullish
|
|
582
|
+
// (`null == undefined` — always `true`), or a `===`/`!==` comparison
|
|
583
|
+
// between two freshly constructed objects (`new Error() === new
|
|
584
|
+
// Error()` — two distinct object references can never be `===`-equal, so
|
|
585
|
+
// the comparison always evaluates the same way no matter what either
|
|
586
|
+
// constructor does). Each of these type-checks and runs without error, so
|
|
587
|
+
// the constant result hides behind what reads like a real, live
|
|
588
|
+
// condition. This is exactly the family `no-self-compare` and
|
|
589
|
+
// `no-unsafe-optional-chaining` above already guard against: a condition
|
|
590
|
+
// that looks meaningful but is defeated by its own operands, and
|
|
591
|
+
// precisely the copy-paste slip an AI assistant leaves behind when it
|
|
592
|
+
// stubs out a condition with a literal or duplicates a `new` expression on
|
|
593
|
+
// both sides of a comparison. Not auto-fixable: only the author knows
|
|
594
|
+
// which operand was meant.
|
|
595
|
+
'no-constant-binary-expression': 'error',
|
|
596
|
+
// Disallow optional chaining in positions where a short-circuit to
|
|
597
|
+
// `undefined` is immediately used in a way that throws at runtime — member
|
|
598
|
+
// access, a call, arithmetic, spread, `instanceof`, `in`, or destructuring on
|
|
599
|
+
// the result. `(obj?.foo).bar`, `(obj?.fn)()`, `(obj?.count) + 1` and
|
|
600
|
+
// `[...(obj?.list)]` all look guarded, but the `?.` only protects the link it
|
|
601
|
+
// sits on: the moment the chain yields `undefined`, the surrounding `.bar` /
|
|
602
|
+
// `()` / `+` / spread runs against `undefined` and throws a `TypeError`. The
|
|
603
|
+
// author wrote `?.` precisely because the left side can be nullish, so this is
|
|
604
|
+
// an outright correctness bug — the guard is defeated by the very expression
|
|
605
|
+
// wrapped around it — not a style preference. It is exactly the half-applied
|
|
606
|
+
// null-safety an AI assistant emits when it sprinkles `?.` onto one segment of
|
|
607
|
+
// a longer access, which puts it in this config's correctness-over-clever, AI-
|
|
608
|
+
// safety scope alongside `no-throw-literal` and `no-self-compare` above. The
|
|
609
|
+
// fix is to extend the optional chain over the whole access (`obj?.foo?.bar`)
|
|
610
|
+
// or to guard the result before using it; only the author knows which, so the
|
|
611
|
+
// rule is not auto-fixable. `eslint:recommended` does enable this, but this
|
|
612
|
+
// config does not extend that preset, so it is turned on explicitly here. The
|
|
613
|
+
// shared `web` app of the `animals-shop` repo enforced this rule manually; it
|
|
614
|
+
// belongs in the shared config so every consumer is covered.
|
|
615
|
+
'no-unsafe-optional-chaining': [
|
|
616
|
+
'error',
|
|
617
|
+
{ disallowArithmeticOperators: true },
|
|
618
|
+
],
|
|
619
|
+
// Disallow computed keys that wrap a literal that could be written plainly —
|
|
620
|
+
// `{ ['name']: x }`, `{ [42]: y }`, or `class { ['method']() {} }`. The
|
|
621
|
+
// bracket syntax exists for keys that must be computed at runtime; using it
|
|
622
|
+
// for a static string or number adds punctuation and a layer of indirection
|
|
623
|
+
// that buys nothing and only makes the reader pause to confirm the key is
|
|
624
|
+
// constant after all. It is the same "clever but pointless" clutter the
|
|
625
|
+
// `no-unneeded-ternary` and `no-implicit-coercion` rules already ban here,
|
|
626
|
+
// and one AI assistants emit when they template object keys mechanically.
|
|
627
|
+
// The plain form (`{ name: x }`, `{ method() {} }`) states intent directly.
|
|
628
|
+
// The rule is auto-fixable, so consumers can adopt it with `eslint --fix`.
|
|
629
|
+
// `enforceForClassMembers: true` extends the check from object literals to
|
|
630
|
+
// class members so both surfaces stay consistent.
|
|
631
|
+
'no-useless-computed-key': ['error', { enforceForClassMembers: true }],
|
|
632
|
+
// Disallow building functions from strings with the `Function` constructor —
|
|
633
|
+
// `new Function('a', 'b', 'return a + b')` or `Function(...)()`. This is `eval`
|
|
634
|
+
// wearing a different name: the body is an opaque string the parser, the type
|
|
635
|
+
// checker and every reader have to take on faith, it executes in the global
|
|
636
|
+
// scope (so it silently can't see the surrounding closure a reader would
|
|
637
|
+
// expect it to), and feeding it any untrusted input is a direct code-injection
|
|
638
|
+
// hole. There is a plain, safe equivalent for every legitimate use — an actual
|
|
639
|
+
// function literal — so the dynamic form buys nothing but risk. It sits
|
|
640
|
+
// squarely in this config's scope: a footgun an AI assistant reaches for when
|
|
641
|
+
// asked to "build a function dynamically," and one `eslint-plugin-security`
|
|
642
|
+
// does not cover (its eval rule, `detect-eval-with-expression`, flags `eval`
|
|
643
|
+
// and not the `Function` constructor). The rule is not auto-fixable because
|
|
644
|
+
// only the author can restate the string body as real code.
|
|
645
|
+
'no-new-func': 'error',
|
|
646
|
+
// Disallow monkey-patching the prototype of a built-in object —
|
|
647
|
+
// `Array.prototype.last = function () { ... }`,
|
|
648
|
+
// `Object.prototype.keysOf = function () { ... }`, `String.prototype.foo = ...`.
|
|
649
|
+
// The patch mutates global state every module and dependency shares: the
|
|
650
|
+
// effect is invisible at the call site, order-dependent (whoever loads first
|
|
651
|
+
// wins, so two libraries patching the same prototype clash silently), and an
|
|
652
|
+
// enumerable addition to `Object.prototype` / `Array.prototype` leaks into
|
|
653
|
+
// every `for...in` loop and `in` check in the program, quietly breaking
|
|
654
|
+
// unrelated code. A safe, explicit equivalent always exists — a standalone
|
|
655
|
+
// helper (`last(arr)`) or a local wrapper — so the patch buys a little
|
|
656
|
+
// call-site sugar at the cost of a global hazard, and it is exactly the
|
|
657
|
+
// shortcut an AI assistant reaches for when asked to "add a method to
|
|
658
|
+
// arrays." This is the prototype-mutation sibling of the `no-new-func` ban
|
|
659
|
+
// directly above. It is not in `eslint:recommended`, so it must be enabled
|
|
660
|
+
// explicitly. Not auto-fixable: only the author can move the behavior into a
|
|
661
|
+
// standalone function. Extending a user-defined class is unaffected — the
|
|
662
|
+
// rule only flags built-in globals.
|
|
663
|
+
'no-extend-native': 'error',
|
|
664
|
+
// Disallow `eval(...)`. It hands a string to the JavaScript engine to parse
|
|
665
|
+
// and run at runtime: the body is opaque to the parser, the type checker and
|
|
666
|
+
// every reader, it can reach and mutate the surrounding scope, and feeding it
|
|
667
|
+
// any untrusted input is the textbook code-injection hole. There is a plain,
|
|
668
|
+
// safe equivalent for every legitimate use, so the dynamic form buys nothing
|
|
669
|
+
// but risk. This is the direct-`eval` companion to the `no-new-func` ban right
|
|
670
|
+
// above (which catches `eval` wearing the `Function`-constructor disguise) —
|
|
671
|
+
// together they close the runtime-string-execution footgun an AI assistant
|
|
672
|
+
// reaches for when asked to "run this code dynamically." It is not in
|
|
673
|
+
// `eslint:recommended`, so it is enabled explicitly. Not auto-fixable: only
|
|
674
|
+
// the author can restate the string as real code.
|
|
675
|
+
'no-eval': 'error',
|
|
676
|
+
// Disallow the implied-`eval` forms: a string first argument to `setTimeout`,
|
|
677
|
+
// `setInterval`, `setImmediate`, or `execScript`. Passing a string there makes
|
|
678
|
+
// the engine `eval` it on the timer's behalf, so it carries every hazard of
|
|
679
|
+
// `no-eval` above while hiding behind an everyday timer API — `setTimeout('do()',
|
|
680
|
+
// 100)` reads as a scheduled call but is really deferred string execution. The
|
|
681
|
+
// safe form is already the idiomatic one: pass a function, `setTimeout(() =>
|
|
682
|
+
// do(), 100)`. This pairs with `no-eval` to cover the indirect channel the
|
|
683
|
+
// direct ban misses. Not auto-fixable: only the author can turn the string
|
|
684
|
+
// into the intended callback.
|
|
685
|
+
'no-implied-eval': 'error',
|
|
686
|
+
// Disallow `javascript:` URLs — `href = 'javascript:void(0)'`,
|
|
687
|
+
// `window.location = 'javascript:doThing()'`, a `<a href="javascript:...">`.
|
|
688
|
+
// The string after the `javascript:` scheme is handed to the engine and run
|
|
689
|
+
// exactly as `eval` would run it, so it carries every hazard of `no-eval` and
|
|
690
|
+
// `no-implied-eval` above — opaque to the parser, the type checker and every
|
|
691
|
+
// reader, and a code-injection hole the moment any of it comes from untrusted
|
|
692
|
+
// input — while wearing the everyday disguise of a URL. It is the third
|
|
693
|
+
// channel in the string-executes-as-code family this config already bans:
|
|
694
|
+
// `no-eval` (direct), `no-new-func` (the `Function` constructor) and
|
|
695
|
+
// `no-implied-eval` (string-bodied timers); `no-script-url` closes the URL
|
|
696
|
+
// channel the other three miss. There is a plain, safe equivalent for every
|
|
697
|
+
// legitimate use — an `onClick`/event handler, `href="#"` with
|
|
698
|
+
// `preventDefault`, or a real function — so the `javascript:` form buys
|
|
699
|
+
// nothing but risk, and it is exactly the placeholder shortcut an AI assistant
|
|
700
|
+
// emits for a "do-nothing" link. It is not in `eslint:recommended`, so it is
|
|
701
|
+
// enabled explicitly. Not auto-fixable: only the author can replace the URL
|
|
702
|
+
// with the intended handler.
|
|
703
|
+
'no-script-url': 'error',
|
|
704
|
+
// Disallow stray `console` calls, allowing only `console.warn` and
|
|
705
|
+
// `console.error`. A bare `console.log` is almost always leftover debugging:
|
|
706
|
+
// it slips through review, ships to production, leaks internal state into logs
|
|
707
|
+
// and slows hot paths, all without any type error or runtime failure to flag
|
|
708
|
+
// it — exactly the quiet, wrong-to-ship mistake this config exists to catch
|
|
709
|
+
// and one AI assistants emit liberally while "checking what a value is." The
|
|
710
|
+
// `allow: ['warn', 'error']` exception keeps intentional diagnostics — the two
|
|
711
|
+
// `console` channels meant for surfacing problems — available, so the rule
|
|
712
|
+
// targets only the throwaway logging. Consumers that genuinely build a CLI can
|
|
713
|
+
// relax it for their entry points; for everyone else it is a zero-config
|
|
714
|
+
// guard. The rule is not auto-fixable because only the author knows whether a
|
|
715
|
+
// given `console.log` should be deleted or promoted to a real log channel.
|
|
716
|
+
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
717
|
+
// Disallow the `debugger` statement. A `debugger` left in source pauses
|
|
718
|
+
// execution under an attached devtools/inspector and is otherwise a no-op, so
|
|
719
|
+
// it is purely a debugging artifact that should never reach a commit — the
|
|
720
|
+
// statement-shaped sibling of the throwaway `console.log` that `no-console`
|
|
721
|
+
// directly above already bans. It is invisible to the type checker and most
|
|
722
|
+
// code review, ships silently, and is exactly the leftover an AI assistant
|
|
723
|
+
// emits while "stepping through" a problem. `eslint-config-agent` does not
|
|
724
|
+
// extend `eslint:recommended` (where this rule lives), so it must be enabled
|
|
725
|
+
// explicitly. The rule is auto-fixable — `eslint --fix` deletes the
|
|
726
|
+
// statement — so adoption is cheap. Several of my repos (e.g.
|
|
727
|
+
// `tupe12334/animals-shop`) re-add `no-debugger: 'error'` by hand on top of
|
|
728
|
+
// the shared config; porting it here removes that copy-paste.
|
|
729
|
+
'no-debugger': 'error',
|
|
730
|
+
// Disallow `alert()`, `confirm()`, and `prompt()`. These browser dialog APIs
|
|
731
|
+
// are exclusively debugging artifacts: `alert(value)` is the browser-native
|
|
732
|
+
// counterpart of `console.log` — a quick "did I reach here / what is this
|
|
733
|
+
// value?" check — but one that blocks the UI thread and cannot be silenced in
|
|
734
|
+
// production without a runtime patch to `window.alert`. Like `no-console` and
|
|
735
|
+
// `no-debugger` directly above, the rule exists to prevent leftover debugging
|
|
736
|
+
// scaffolding from reaching a commit: the three rules together guard every
|
|
737
|
+
// major "quick check" channel that AI assistants reach for when they want to
|
|
738
|
+
// inspect state or get user input inline without wiring a real UI or log
|
|
739
|
+
// channel. `confirm` and `prompt` are included because they carry the same
|
|
740
|
+
// blocking, production-hostile behavior and are equally likely to appear in
|
|
741
|
+
// AI-generated event handlers ("just pop a confirm dialog for now"). The rule
|
|
742
|
+
// is not in `eslint:recommended`, so it must be enabled explicitly. It is not
|
|
743
|
+
// auto-fixable because only the author knows whether the call should be
|
|
744
|
+
// deleted, replaced with a proper log, or wired to a real modal component.
|
|
745
|
+
'no-alert': 'error',
|
|
746
|
+
// Require a regex literal (`/\d+/`) instead of the `RegExp` constructor with
|
|
747
|
+
// a string argument (`new RegExp('\\d+')`, `RegExp('\\d+')`) when the pattern
|
|
748
|
+
// is a static string. The string form forces every backslash to be escaped
|
|
749
|
+
// twice — once for the string literal and once for the regex engine — so
|
|
750
|
+
// `\d` has to be written `\\d`, `\.` becomes `\\.`, and a single missed
|
|
751
|
+
// backslash silently changes what the pattern matches without any error.
|
|
752
|
+
// The literal needs none of that doubling and is checked for validity at
|
|
753
|
+
// parse time rather than when the constructor runs, so a malformed pattern
|
|
754
|
+
// surfaces immediately instead of throwing on first use. This is the
|
|
755
|
+
// regex-shaped sibling of the constructor bans this config already ships —
|
|
756
|
+
// `no-object-constructor`, `no-new-wrappers`, `no-new-func` — all of which
|
|
757
|
+
// reject a constructor call dressed up as a literal/value; reaching for
|
|
758
|
+
// `new RegExp('...')` on a constant pattern is exactly the legacy,
|
|
759
|
+
// double-escaping-prone idiom an AI assistant emits when asked to "build a
|
|
760
|
+
// regex." It is not in `eslint:recommended`, so it is enabled explicitly.
|
|
761
|
+
// `disallowRedundantWrapping: true` also flags `new RegExp(/foo/)` — wrapping
|
|
762
|
+
// a literal that is already a regex — which is pure redundancy. The dynamic
|
|
763
|
+
// `new RegExp(variable)` case, where the pattern genuinely is not known until
|
|
764
|
+
// runtime, is left untouched. The rule is auto-fixable for the simple cases,
|
|
765
|
+
// so consumers can adopt much of it with `eslint --fix`.
|
|
766
|
+
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
|
|
767
|
+
// Require named capture groups (`(?<year>\d{4})`) instead of positional ones
|
|
768
|
+
// (`(\d{4})`). An unnamed capture group is referenced by a fragile positional
|
|
769
|
+
// index — `match[1]`, `match[2]` — whose meaning evaporates the moment a
|
|
770
|
+
// group is added, removed, or reordered: `match[1]` silently shifts to a
|
|
771
|
+
// different piece of the string with no type error and no runtime warning.
|
|
772
|
+
// Named groups surface the intent in the regex itself (`(?<year>\d{4})`) and
|
|
773
|
+
// are accessed by key (`match.groups.year`), so the code stays readable and
|
|
774
|
+
// correct even as the pattern evolves. This is the regex-shaped sibling of
|
|
775
|
+
// the explicit-over-clever stance this config already enforces in the rest of
|
|
776
|
+
// the rule set: an unnamed group is exactly the "I'll remember what index 2
|
|
777
|
+
// means" shortcut that doesn't age and that AI assistants emit by default
|
|
778
|
+
// whenever they scaffold a regex. Non-capturing groups (`(?:...)`) are
|
|
779
|
+
// unaffected — only groups that capture and expose a match need a name. The
|
|
780
|
+
// rule is not in `eslint:recommended` and is not covered by
|
|
781
|
+
// `unicorn.configs.all`, so it is enabled explicitly here. It is not
|
|
782
|
+
// auto-fixable because only the author knows what name to give each group.
|
|
783
|
+
'prefer-named-capture-group': 'error',
|
|
784
|
+
// Disallow the comma operator — `a = 1, b = 2`, `for (i = 0, j = 0; ...)`,
|
|
785
|
+
// `return a++, b`. The comma operator evaluates both operands left-to-right
|
|
786
|
+
// and returns the *right-hand* value, silently discarding the left. That is
|
|
787
|
+
// almost never intentional: in almost every real occurrence the author either
|
|
788
|
+
// meant a semicolon (two separate statements), an array literal, or a
|
|
789
|
+
// destructuring assignment — the comma operator just lets the mistake compile
|
|
790
|
+
// without error. It is the hidden-mutation sibling of the assignment
|
|
791
|
+
// discipline rules already here (`no-multi-assign`, `no-return-assign`,
|
|
792
|
+
// `no-useless-assignment`): all of them surface values that look meaningful
|
|
793
|
+
// but are silently discarded or overwritten, exactly the "clever but wrong"
|
|
794
|
+
// pattern this config exists to prevent and one AI assistants emit when
|
|
795
|
+
// packing multiple side effects onto a single expression. The one legitimate
|
|
796
|
+
// use — `for` loop initializers and updates with multiple variables — is
|
|
797
|
+
// carved out by the `allowInParentheses: false` default (the option only
|
|
798
|
+
// affects the parenthesized `(a, b)` form used to suppress some parsers).
|
|
799
|
+
// For `for` loop heads that genuinely need two counters, a destructuring
|
|
800
|
+
// `let [i, j] = [0, 0]` states the intent clearly. The rule is not in
|
|
801
|
+
// `eslint:recommended`, so it is enabled explicitly here. It is not
|
|
802
|
+
// auto-fixable because only the author knows whether to split into separate
|
|
803
|
+
// statements or rewrite the expression entirely.
|
|
804
|
+
'no-sequences': 'error',
|
|
805
|
+
// Disallow empty constructors (`class Foo { constructor() {} }`) and
|
|
806
|
+
// constructors whose only statement forwards all arguments to `super`
|
|
807
|
+
// (`class Foo extends Bar { constructor(...args) { super(...args); } }`). In
|
|
808
|
+
// both cases the constructor adds nothing: JavaScript already synthesizes an
|
|
809
|
+
// implicit empty constructor for base classes and an implicit forwarding
|
|
810
|
+
// constructor for subclasses, so writing them out is pure dead weight — visual
|
|
811
|
+
// noise that looks load-bearing but changes nothing. They are boilerplate an
|
|
812
|
+
// AI assistant reflexively emits when scaffolding a class from a template,
|
|
813
|
+
// putting them squarely in this config's explicit-over-clever, "dead code is
|
|
814
|
+
// not free" stance: the class-declaration siblings of `no-useless-return`,
|
|
815
|
+
// `no-useless-assignment`, and `no-extra-bind` already active here. The rule
|
|
816
|
+
// is auto-fixable, so adopters get a one-shot `eslint --fix`. The core rule
|
|
817
|
+
// is enabled here for JavaScript files; TypeScript files get the same
|
|
818
|
+
// behavior via `@typescript-eslint/no-useless-constructor` enabled by the
|
|
819
|
+
// `strictTypeChecked` preset (which extends `recommended`), so the core rule
|
|
820
|
+
// is turned off in `typescriptEslintRules` to avoid double-reporting.
|
|
821
|
+
'no-useless-constructor': 'error',
|
|
822
|
+
// Disallow returning a value from a class constructor (`return someObject`,
|
|
823
|
+
// `return 5`). `new Thing()` has a contract every caller relies on: the
|
|
824
|
+
// expression evaluates to the instance that was just constructed. Returning
|
|
825
|
+
// an object from the constructor silently overrides that — `new Thing()`
|
|
826
|
+
// hands back a *different* object than the class body initialized, so
|
|
827
|
+
// `instanceof Thing` no longer holds for the value the rest of the program
|
|
828
|
+
// sees. Returning a primitive is the opposite trap: the return value is
|
|
829
|
+
// ignored outright by the `new` semantics, so the statement is dead code
|
|
830
|
+
// that reads as if it does something. Both are exactly the kind of silent,
|
|
831
|
+
// contract-breaking footgun this config exists to catch, and the sort of
|
|
832
|
+
// misplaced `return` an AI assistant emits when it treats a constructor
|
|
833
|
+
// like an ordinary function. A bare early `return;` with no value to bail
|
|
834
|
+
// out of the constructor is unaffected — only returning a value is flagged.
|
|
835
|
+
// The rule is not in `eslint:recommended`, so it is enabled explicitly. It
|
|
836
|
+
// is not auto-fixable because only the author knows whether the returned
|
|
837
|
+
// value or the constructor itself is wrong.
|
|
838
|
+
'no-constructor-return': 'error',
|
|
45
839
|
}
|
|
46
840
|
|
|
47
841
|
// Shared no-restricted-syntax rules for both JS and TS
|
|
@@ -76,7 +870,7 @@ const sharedRestrictedSyntax = [
|
|
|
76
870
|
message:
|
|
77
871
|
'Exporting from external libraries is not allowed. Only re-export from relative paths or scoped packages.',
|
|
78
872
|
},
|
|
79
|
-
allRules.
|
|
873
|
+
allRules.noProcessEnvironmentPropertiesConfig,
|
|
80
874
|
allRules.noExportSpecifiersConfig,
|
|
81
875
|
...allRules.noDefaultClassExportRules,
|
|
82
876
|
]
|
|
@@ -97,32 +891,91 @@ const tsOnlyRestrictedSyntax = [
|
|
|
97
891
|
]
|
|
98
892
|
|
|
99
893
|
const config = defineConfig([
|
|
100
|
-
// Global ignores for non-JS/TS files and build outputs
|
|
894
|
+
// Global ignores for non-JS/TS files and build outputs.
|
|
895
|
+
//
|
|
896
|
+
// Build-output globs are prefixed with `**/` so they match nested package
|
|
897
|
+
// directories, not just the repository root. A bare `dist/**` only ignores
|
|
898
|
+
// a top-level `dist/`, which means in a monorepo every `packages/*/dist/**`
|
|
899
|
+
// (and the same for `build`, `coverage`, etc.) is still linted and floods
|
|
900
|
+
// adopters with errors on generated code. The recursive form ignores those
|
|
901
|
+
// outputs wherever they appear, matching the monorepo support this config
|
|
902
|
+
// documents.
|
|
101
903
|
{
|
|
904
|
+
name: 'agent/ignores',
|
|
102
905
|
ignores: [
|
|
103
906
|
'**/*.json',
|
|
104
907
|
'**/*.md',
|
|
105
908
|
'**/*.yaml',
|
|
106
909
|
'**/*.yml',
|
|
107
|
-
'dist/**',
|
|
108
|
-
'
|
|
910
|
+
'**/dist/**',
|
|
911
|
+
'**/build/**',
|
|
912
|
+
'**/coverage/**',
|
|
913
|
+
'**/.next/**',
|
|
914
|
+
'**/out/**',
|
|
915
|
+
// Storybook's static build output. This config ships first-class
|
|
916
|
+
// Storybook support (`eslint-plugin-storybook` + `configs/storybook.js`),
|
|
917
|
+
// so adopters routinely run `storybook build`, which emits compiled,
|
|
918
|
+
// minified bundles into `storybook-static/`. Linting that generated
|
|
919
|
+
// output is never useful and floods the report, so ignore it like the
|
|
920
|
+
// other build dirs above.
|
|
921
|
+
'**/storybook-static/**',
|
|
922
|
+
// Turborepo's local task cache. In the monorepos this config targets,
|
|
923
|
+
// `turbo` writes hashed cache artifacts into `.turbo/`; they are
|
|
924
|
+
// generated, not source, and must not be linted.
|
|
925
|
+
'**/.turbo/**',
|
|
109
926
|
],
|
|
110
927
|
},
|
|
928
|
+
// Flag `eslint-disable` directives that no longer suppress anything. Stale
|
|
929
|
+
// suppressions hide the fact that a rule is now satisfied (or was renamed)
|
|
930
|
+
// and quietly widen the set of unchecked code over time, which runs against
|
|
931
|
+
// this config's explicit-over-clever goal. Reporting them as errors keeps
|
|
932
|
+
// every suppression honest and removable.
|
|
933
|
+
{
|
|
934
|
+
name: 'agent/linter-options',
|
|
935
|
+
linterOptions: {
|
|
936
|
+
reportUnusedDisableDirectives: 'error',
|
|
937
|
+
},
|
|
938
|
+
},
|
|
111
939
|
// Global plugin definitions
|
|
112
940
|
{
|
|
941
|
+
name: 'agent/plugins',
|
|
113
942
|
plugins,
|
|
114
943
|
},
|
|
115
944
|
{
|
|
945
|
+
name: 'agent/react-hooks',
|
|
116
946
|
rules: {
|
|
117
947
|
'react-hooks/rules-of-hooks': 'error',
|
|
118
948
|
'react-hooks/exhaustive-deps': 'warn',
|
|
119
949
|
},
|
|
120
950
|
},
|
|
121
951
|
js.configs.recommended,
|
|
952
|
+
unicorn.configs.all,
|
|
953
|
+
// Disable unicorn rules that are too opinionated or conflict with this
|
|
954
|
+
// codebase's conventions. These are turned off globally after
|
|
955
|
+
// `unicorn.configs.all` enables them.
|
|
956
|
+
{
|
|
957
|
+
name: 'agent/unicorn-overrides',
|
|
958
|
+
rules: {
|
|
959
|
+
// Prevents `class`-prefixed identifiers like `classExportPlugin`,
|
|
960
|
+
// `classDeclaration`, etc. — common in ESLint plugin code where `class`
|
|
961
|
+
// is part of a domain concept, not a keyword accident.
|
|
962
|
+
'unicorn/no-keyword-prefix': 'off',
|
|
963
|
+
// Disallows `null` in favor of `undefined`. TypeScript APIs use `null`
|
|
964
|
+
// intentionally (e.g. `null` vs `undefined` in type narrowing).
|
|
965
|
+
'unicorn/no-null': 'off',
|
|
966
|
+
// Prevents passing a function reference directly to `.map(fn)`. The
|
|
967
|
+
// direct reference form is idiomatic and readable.
|
|
968
|
+
'unicorn/no-array-callback-reference': 'off',
|
|
969
|
+
// Enforces specific import styles for some packages (e.g. named vs
|
|
970
|
+
// default). Too opinionated for this config's consumers.
|
|
971
|
+
'unicorn/import-style': 'off',
|
|
972
|
+
},
|
|
973
|
+
},
|
|
122
974
|
...tseslint.configs.strictTypeChecked,
|
|
123
975
|
...tseslint.configs.stylisticTypeChecked,
|
|
124
976
|
{
|
|
125
|
-
|
|
977
|
+
name: 'agent/typescript-parser-options',
|
|
978
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
126
979
|
languageOptions: {
|
|
127
980
|
parserOptions: {
|
|
128
981
|
projectService: true,
|
|
@@ -132,10 +985,32 @@ const config = defineConfig([
|
|
|
132
985
|
{
|
|
133
986
|
files: ['**/*.{js,jsx,mjs,cjs}'],
|
|
134
987
|
...tseslint.configs.disableTypeChecked,
|
|
988
|
+
name: 'agent/javascript-disable-type-checked',
|
|
135
989
|
},
|
|
136
990
|
earlyReturn.configs.recommended,
|
|
137
991
|
jsdoc.configs['flat/recommended-typescript-error'],
|
|
138
|
-
|
|
992
|
+
// The jsdoc recommended preset only requires JSDoc on FunctionDeclaration by
|
|
993
|
+
// default. Treat class declarations the same as functions so exported
|
|
994
|
+
// classes are also required to be documented.
|
|
995
|
+
{
|
|
996
|
+
name: 'agent/jsdoc-require-class-jsdoc',
|
|
997
|
+
rules: {
|
|
998
|
+
'jsdoc/require-jsdoc': [
|
|
999
|
+
'error',
|
|
1000
|
+
{
|
|
1001
|
+
require: {
|
|
1002
|
+
ClassDeclaration: true,
|
|
1003
|
+
FunctionDeclaration: true,
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
],
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
plugins: { 'switch-case': switchCase },
|
|
1011
|
+
...switchCase.configs.recommended,
|
|
1012
|
+
name: 'agent/switch-case',
|
|
1013
|
+
},
|
|
139
1014
|
|
|
140
1015
|
// Base plugin strict configs (error, default, guard-clauses)
|
|
141
1016
|
...basePluginsConfig,
|
|
@@ -173,6 +1048,7 @@ const config = defineConfig([
|
|
|
173
1048
|
...jsxClassname.configs.strict,
|
|
174
1049
|
files: ['**/*.{tsx,jsx}'],
|
|
175
1050
|
ignores: ['**/*.stories.{js,jsx,ts,tsx}'],
|
|
1051
|
+
name: 'agent/jsx-classname-strict',
|
|
176
1052
|
},
|
|
177
1053
|
|
|
178
1054
|
// Final overrides (index files, switch case, length rules)
|