@himynameisdave/oxlint-config 1.0.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/dist/base.js ADDED
@@ -0,0 +1,1165 @@
1
+ import { defineConfig } from 'oxlint';
2
+ /**
3
+ * Base config: core JS/TS rules with no framework assumptions.
4
+ *
5
+ * Every rule from every enabled plugin is listed explicitly with a severity and
6
+ * a reason. Categories are all `"off"` — nothing is enabled implicitly, so this
7
+ * file is the complete, greppable inventory of what is enforced.
8
+ *
9
+ * Severity policy: `"error"` or `"off"`, never `"warn"` — warnings are noise
10
+ * that scroll by unfixed. Run with `--deny-warnings` anyway.
11
+ *
12
+ * Type-aware rules (requiring `--type-aware` + `oxlint-tsgolint`) live in
13
+ * `type-aware.ts`, not here.
14
+ */
15
+ export default defineConfig({
16
+ // Explicit over implicit: every category off; only the rules below are active.
17
+ categories: {
18
+ correctness: 'off',
19
+ suspicious: 'off',
20
+ pedantic: 'off',
21
+ perf: 'off',
22
+ style: 'off',
23
+ restriction: 'off',
24
+ nursery: 'off'
25
+ },
26
+ plugins: ['typescript', 'unicorn', 'oxc', 'import', 'promise', 'node', 'jsdoc'],
27
+ env: {
28
+ browser: true,
29
+ es2024: true
30
+ },
31
+ rules: {
32
+ /* ================================================================== *
33
+ * eslint — correctness
34
+ * ================================================================== */
35
+ // Missing super() in a derived class throws at construction time.
36
+ 'eslint/constructor-super': 'error',
37
+ // A for-loop counting the wrong direction never terminates.
38
+ 'eslint/for-direction': 'error',
39
+ // A getter with no return silently yields undefined.
40
+ 'eslint/getter-return': 'error',
41
+ // Async executors swallow rejections the Promise constructor can't see.
42
+ 'eslint/no-async-promise-executor': 'error',
43
+ // arguments.caller/callee are deprecated and break in strict mode.
44
+ 'eslint/no-caller': 'error',
45
+ // Reassigning a class binding clobbers the class for all callers.
46
+ 'eslint/no-class-assign': 'error',
47
+ // x === -0 passes for +0 too; Object.is is the real check.
48
+ 'eslint/no-compare-neg-zero': 'error',
49
+ // Assignment in a condition is almost always a typo'd comparison.
50
+ 'eslint/no-cond-assign': 'error',
51
+ // Reassigning a const throws at runtime.
52
+ 'eslint/no-const-assign': 'error',
53
+ // Expressions like `x === y || z` with constant halves are always bugs.
54
+ 'eslint/no-constant-binary-expression': 'error',
55
+ // while(true) is fine only when intentional; constant conditions usually aren't.
56
+ 'eslint/no-constant-condition': 'error',
57
+ // Control characters in regexes are invisible and almost never intended.
58
+ 'eslint/no-control-regex': 'error',
59
+ // debugger statements must not ship.
60
+ 'eslint/no-debugger': 'error',
61
+ // delete on a plain variable is a strict-mode SyntaxError.
62
+ 'eslint/no-delete-var': 'error',
63
+ // Duplicate class members: the last silently wins.
64
+ 'eslint/no-dupe-class-members': 'error',
65
+ // Duplicate else-if conditions make the later branch dead code.
66
+ 'eslint/no-dupe-else-if': 'error',
67
+ // Duplicate object keys: the last silently wins.
68
+ 'eslint/no-dupe-keys': 'error',
69
+ // Duplicate case labels make the later case unreachable.
70
+ 'eslint/no-duplicate-case': 'error',
71
+ // [] in a regex matches nothing — the pattern is broken.
72
+ 'eslint/no-empty-character-class': 'error',
73
+ // Empty destructuring patterns do nothing and hide typos.
74
+ 'eslint/no-empty-pattern': 'error',
75
+ // An empty static block is leftover scaffolding.
76
+ 'eslint/no-empty-static-block': 'error',
77
+ // eval executes arbitrary strings — a security hole and an optimizer killer.
78
+ 'eslint/no-eval': 'error',
79
+ // Reassigning the caught error loses the original failure.
80
+ 'eslint/no-ex-assign': 'error',
81
+ // !!x in a boolean context is redundant noise.
82
+ 'eslint/no-extra-boolean-cast': 'error',
83
+ // Reassigning a function declaration confuses hoisting and readers.
84
+ 'eslint/no-func-assign': 'error',
85
+ // Assigning to window/globalThis members clobbers shared state.
86
+ 'eslint/no-global-assign': 'error',
87
+ // Assigning to an import binding throws in ESM.
88
+ 'eslint/no-import-assign': 'error',
89
+ // Invalid regex literals throw at parse time in RegExp().
90
+ 'eslint/no-invalid-regexp': 'error',
91
+ // Zero-width/irregular whitespace is invisible and breaks tokenizing.
92
+ 'eslint/no-irregular-whitespace': 'error',
93
+ // __iterator__ is dead legacy API.
94
+ 'eslint/no-iterator': 'error',
95
+ // Literals beyond Number precision silently round (9007199254740993 → ...992).
96
+ 'eslint/no-loss-of-precision': 'error',
97
+ // Astral-plane chars split across character classes match garbage.
98
+ 'eslint/no-misleading-character-class': 'error',
99
+ // Symbol/BigInt with `new` throw at runtime.
100
+ 'eslint/no-new-native-nonconstructor': 'error',
101
+ // \8 and \9 escapes are legacy octal traps.
102
+ 'eslint/no-nonoctal-decimal-escape': 'error',
103
+ // Calling Math/JSON/Reflect as functions throws.
104
+ 'eslint/no-obj-calls': 'error',
105
+ // x = x is dead code or a typo.
106
+ 'eslint/no-self-assign': 'error',
107
+ // Returning from a setter silently discards the value.
108
+ 'eslint/no-setter-return': 'error',
109
+ // Shadowing undefined/NaN/Infinity poisons the whole scope.
110
+ 'eslint/no-shadow-restricted-names': 'error',
111
+ // [1, , 3] holes behave inconsistently across array methods.
112
+ 'eslint/no-sparse-arrays': 'error',
113
+ // `this` before super() throws in derived constructors.
114
+ 'eslint/no-this-before-super': 'error',
115
+ // A declared-but-never-assigned variable read as undefined is a latent bug.
116
+ 'eslint/no-unassigned-vars': 'error',
117
+ // Code after return/throw never runs.
118
+ 'eslint/no-unreachable': 'error',
119
+ // return/throw in finally silently overrides the try block's result.
120
+ 'eslint/no-unsafe-finally': 'error',
121
+ // !x in instanceof/in negates the operand, not the expression.
122
+ 'eslint/no-unsafe-negation': 'error',
123
+ // (a?.b).c throws when a is nullish — optional chain gives false safety.
124
+ 'eslint/no-unsafe-optional-chaining': 'error',
125
+ // An expression statement that does nothing is a lost assignment or call.
126
+ 'eslint/no-unused-expressions': 'error',
127
+ // Labels that nothing jumps to are dead weight.
128
+ 'eslint/no-unused-labels': 'error',
129
+ // Unused private members are unreachable dead code.
130
+ 'eslint/no-unused-private-class-members': 'error',
131
+ // Unused variables hide refactoring leftovers and typos.
132
+ 'eslint/no-unused-vars': 'error',
133
+ // Backreferences to groups that can't have matched are always empty.
134
+ 'eslint/no-useless-backreference': 'error',
135
+ // catch { throw e } is a no-op that destroys stack usefulness signals.
136
+ 'eslint/no-useless-catch': 'error',
137
+ // Escaping characters that don't need it obscures the real escapes.
138
+ 'eslint/no-useless-escape': 'error',
139
+ // import { a as a } is noise.
140
+ 'eslint/no-useless-rename': 'error',
141
+ // `with` is banned in strict mode and defeats static analysis.
142
+ 'eslint/no-with': 'error',
143
+ // A generator that never yields should be a function.
144
+ 'eslint/require-yield': 'error',
145
+ // x === NaN is always false; isNaN/Number.isNaN is the real check.
146
+ 'eslint/use-isnan': 'error',
147
+ // typeof x === "strnig" typos silently never match.
148
+ 'eslint/valid-typeof': 'error',
149
+ /* ================================================================== *
150
+ * eslint — suspicious
151
+ * ================================================================== */
152
+ // var hoisted out of its block reads as in-scope when it isn't.
153
+ 'eslint/block-scoped-var': 'error',
154
+ // Mutating builtin prototypes breaks every other consumer of the runtime.
155
+ 'eslint/no-extend-native': 'error',
156
+ // bind() on a function that never uses `this` is a silent no-op.
157
+ 'eslint/no-extra-bind': 'error',
158
+ // setTimeout("code") is eval with extra steps.
159
+ 'eslint/no-implied-eval': 'error',
160
+ // `new Foo()` purely for side effects hides the effect in a constructor.
161
+ 'eslint/no-new': 'error',
162
+ // Shadowed names make the inner scope silently read the wrong variable.
163
+ 'eslint/no-shadow': 'error',
164
+ // Dangling underscores fake privacy JS doesn't have; use #private or naming.
165
+ // Consumers with ORM/bundler conventions (_count, __APP_VERSION__) add their own `allow`.
166
+ 'eslint/no-underscore-dangle': 'error',
167
+ // ASI hazards: a line starting with ( or [ glues onto the previous statement.
168
+ 'eslint/no-unexpected-multiline': 'error',
169
+ // A loop condition never touched inside the loop never exits.
170
+ 'eslint/no-unmodified-loop-condition': 'error',
171
+ // cond ? true : false is just Boolean(cond).
172
+ 'eslint/no-unneeded-ternary': 'error',
173
+ // "a" + "b" between literals should be one literal.
174
+ 'eslint/no-useless-concat': 'error',
175
+ // A constructor that only calls super() is implicit anyway.
176
+ 'eslint/no-useless-constructor': 'error',
177
+ // Rethrowing a new error without `cause` erases the original stack.
178
+ 'eslint/preserve-caught-error': 'error',
179
+ /* ================================================================== *
180
+ * eslint — pedantic
181
+ * ================================================================== */
182
+ // A setter without a getter makes a write-only property — usually a mistake.
183
+ 'eslint/accessor-pairs': 'error',
184
+ // map/filter callbacks that forget to return produce arrays of undefined.
185
+ 'eslint/array-callback-return': 'error',
186
+ // == coerces operands unpredictably; === says what it means.
187
+ 'eslint/eqeqeq': 'error',
188
+ // One class per file keeps modules focused; tests are exempt (override below).
189
+ 'eslint/max-classes-per-file': 'error',
190
+ // Nesting past 4 levels reads as a flowchart; extract functions instead.
191
+ 'eslint/max-depth': 'error',
192
+ // File-length caps punish cohesive modules — splitting to satisfy a number helps nobody.
193
+ 'eslint/max-lines': 'off',
194
+ // Function-length caps do the same; complexity shows up in review, not line counts.
195
+ 'eslint/max-lines-per-function': 'off',
196
+ // Deeply nested callbacks are what promises/async exist to fix.
197
+ 'eslint/max-nested-callbacks': 'error',
198
+ // Array(3) makes holes, Array(3, 4) makes elements — too confusable; use literals.
199
+ 'eslint/no-array-constructor': 'error',
200
+ // let/const in a case without braces leaks into sibling cases.
201
+ 'eslint/no-case-declarations': 'error',
202
+ // Returning a value from a constructor is discarded (or hijacks `new`).
203
+ 'eslint/no-constructor-return': 'error',
204
+ // `return x; else return y` — the else is dead ceremony.
205
+ 'eslint/no-else-return': 'error',
206
+ // Silent case fallthrough is the classic switch bug; comment it if intended.
207
+ 'eslint/no-fallthrough': 'error',
208
+ // Inline comments are fine — position of a comment isn't a defect.
209
+ 'eslint/no-inline-comments': 'off',
210
+ // Function declarations inside blocks hoist differently across engines.
211
+ 'eslint/no-inner-declarations': 'error',
212
+ // else { if } is an else-if written the long way.
213
+ 'eslint/no-lonely-if': 'error',
214
+ // Closures over loop variables capture the final value with var semantics.
215
+ 'eslint/no-loop-func': 'error',
216
+ // if (!x) {a} else {b} reads backwards; flip the branches.
217
+ 'eslint/no-negated-condition': 'error',
218
+ // new String/Number/Boolean create objects that compare by reference.
219
+ 'eslint/no-new-wrappers': 'error',
220
+ // new Object() is {} with extra steps.
221
+ 'eslint/no-object-constructor': 'error',
222
+ // Returning from a promise executor doesn't resolve anything — a silent bug.
223
+ 'eslint/no-promise-executor-return': 'error',
224
+ // obj.hasOwnProperty breaks on null-prototype objects; use Object.hasOwn.
225
+ 'eslint/no-prototype-builtins': 'error',
226
+ // Redeclaring a variable is either a typo or shadowing done wrong.
227
+ 'eslint/no-redeclare': 'error',
228
+ // x === x is only useful as a NaN check, and use-isnan bans that spelling too.
229
+ 'eslint/no-self-compare': 'error',
230
+ // throw "string" produces errors with no stack trace.
231
+ 'eslint/no-throw-literal': 'error',
232
+ // A bare return at the end of a function is dead code.
233
+ 'eslint/no-useless-return': 'error',
234
+ // TODO/FIXME are legitimate workflow markers; banning them just renames them.
235
+ // (Stricter cacographer config had this on via category — deliberate deviation.)
236
+ 'eslint/no-warning-comments': 'off',
237
+ // parseInt without a radix guesses base from the string's shape.
238
+ 'eslint/radix': 'error',
239
+ // The u flag makes regexes treat astral chars correctly and rejects bad escapes.
240
+ 'eslint/require-unicode-regexp': 'error',
241
+ // Same stance as sort-keys: alphabetizing declarations is churn, not clarity.
242
+ 'eslint/sort-vars': 'off',
243
+ // async without await is a sync function wearing a costume — but wrapper
244
+ // functions matching an async interface are common; off per both source configs.
245
+ 'eslint/require-await': 'off',
246
+ // Symbol("what-this-is") makes debugging findable; bare Symbol() doesn't.
247
+ 'eslint/symbol-description': 'error',
248
+ /* ================================================================== *
249
+ * eslint — perf
250
+ * ================================================================== */
251
+ // Sequential awaits in a loop serialize what Promise.all can parallelize;
252
+ // intentionally sequential work gets a disable comment explaining why.
253
+ 'eslint/no-await-in-loop': 'error',
254
+ // fn.call(undefined, x) is fn(x).
255
+ 'eslint/no-useless-call': 'error',
256
+ /* ================================================================== *
257
+ * eslint — style
258
+ * ================================================================== */
259
+ // Arrow bodies with a lone return should be expressions (as-needed default).
260
+ 'eslint/arrow-body-style': 'error',
261
+ // Comment casing isn't worth policing.
262
+ 'eslint/capitalized-comments': 'off',
263
+ // Braces on every block: the goto-fail bug was an unbraced if.
264
+ 'eslint/curly': 'error',
265
+ // default anywhere but last in a switch reads as a case.
266
+ 'eslint/default-case-last': 'error',
267
+ // Optional params before required ones can never be omitted.
268
+ 'eslint/default-param-last': 'error',
269
+ // const handleClick = function handleTap() {} — two names, one lie.
270
+ 'eslint/func-name-matching': 'error',
271
+ // Anonymous function expressions produce anonymous stack traces.
272
+ 'eslint/func-names': 'error',
273
+ // Declaration vs expression is context-dependent; not worth enforcing globally.
274
+ 'eslint/func-style': 'off',
275
+ // get/set for the same property should sit together.
276
+ 'eslint/grouped-accessor-pairs': 'error',
277
+ // for-in walks the prototype chain; guard or use Object.keys.
278
+ 'eslint/guard-for-in': 'error',
279
+ // No-op without a project-specific list; consumers configure it if wanted.
280
+ 'eslint/id-denylist': 'off',
281
+ // Short names are fine where scope is short (i, x, db).
282
+ 'eslint/id-length': 'off',
283
+ // No-op without a project-specific pattern; consumers configure it if wanted.
284
+ 'eslint/id-match': 'off',
285
+ // `let x;` then assign-in-branches is a legitimate pattern.
286
+ 'eslint/init-declarations': 'off',
287
+ // x = x || y has a ??=/||= spelling now.
288
+ 'eslint/logical-assignment-operators': 'error',
289
+ // Beyond 5 params, nobody remembers the order — take an options object.
290
+ 'eslint/max-params': ['error', { max: 5 }],
291
+ // Statement-count caps are line-count caps with extra steps.
292
+ 'eslint/max-statements': 'off',
293
+ // Constructors are capitalized; calling a lowercase name with `new` is a smell.
294
+ 'eslint/new-cap': 'error',
295
+ // continue is a fine guard-clause tool in loops.
296
+ 'eslint/no-continue': 'off',
297
+ // import/no-duplicates covers this with type-import awareness; avoid double reports.
298
+ 'eslint/no-duplicate-imports': 'off',
299
+ // A label on the loop `break` already targets is noise.
300
+ 'eslint/no-extra-label': 'error',
301
+ // !!x and +x golf coercion; Boolean(x) and Number(x) say it.
302
+ 'eslint/no-implicit-coercion': 'error',
303
+ // A label sharing a variable's name makes break/continue ambiguous to readers.
304
+ 'eslint/no-label-var': 'error',
305
+ // Labeled blocks are goto cosplay; restructure instead.
306
+ 'eslint/no-labels': 'error',
307
+ // A block that creates no scope is visual noise.
308
+ 'eslint/no-lone-blocks': 'error',
309
+ // Magic numbers are usually self-evident in context; naming 86400 helps, naming 2 doesn't.
310
+ 'eslint/no-magic-numbers': 'off',
311
+ // a = b = c makes b a global in sloppy contexts and hides one assignment.
312
+ 'eslint/no-multi-assign': 'error',
313
+ // Multiline strings via trailing backslash break when trailing space sneaks in.
314
+ 'eslint/no-multi-str': 'error',
315
+ // unicorn/no-nested-ternary is on instead — it allows one parenthesized level.
316
+ 'eslint/no-nested-ternary': 'off',
317
+ // new Function("code") is eval with extra steps.
318
+ 'eslint/no-new-func': 'error',
319
+ // return (x = y) hides mutation inside a return.
320
+ 'eslint/no-return-assign': 'error',
321
+ // javascript: URLs are eval in href clothing.
322
+ 'eslint/no-script-url': 'error',
323
+ // "Hello ${name}" in a plain string means someone forgot the backticks.
324
+ 'eslint/no-template-curly-in-string': 'error',
325
+ // Ternaries are expressions doing their job.
326
+ 'eslint/no-ternary': 'off',
327
+ // {["a"]: 1} is {a: 1}.
328
+ 'eslint/no-useless-computed-key': 'error',
329
+ // {x: x} has a shorthand for a reason.
330
+ 'eslint/object-shorthand': 'error',
331
+ // x = x + y has a += spelling.
332
+ 'eslint/operator-assignment': 'error',
333
+ // Arrow callbacks keep lexical this; function callbacks invite this-bugs.
334
+ 'eslint/prefer-arrow-callback': 'error',
335
+ // A binding that's never reassigned is a const fact — let claims otherwise.
336
+ 'eslint/prefer-const': 'error',
337
+ // const [first] = xs names the shape; const first = xs[0] hides it.
338
+ 'eslint/prefer-destructuring': 'error',
339
+ // Math.pow(x, 2) predates **.
340
+ 'eslint/prefer-exponentiation-operator': 'error',
341
+ // match[3] means nothing next month; (?<name>) documents the group.
342
+ 'eslint/prefer-named-capture-group': 'error',
343
+ // parseInt("0xFF", 16) has a 0xFF literal spelling.
344
+ 'eslint/prefer-numeric-literals': 'error',
345
+ // Object.hasOwn is the safe hasOwnProperty.
346
+ 'eslint/prefer-object-has-own': 'error',
347
+ // {...a, b} beats Object.assign for immutability and inference.
348
+ 'eslint/prefer-object-spread': 'error',
349
+ // reject("nope") produces rejections with no stack; reject(new Error(...)).
350
+ 'eslint/prefer-promise-reject-errors': 'error',
351
+ // new RegExp("static") should be /static/ — literals get syntax checking at parse.
352
+ 'eslint/prefer-regex-literals': 'error',
353
+ // arguments is array-like legacy; rest params are real arrays.
354
+ 'eslint/prefer-rest-params': 'error',
355
+ // fn.apply(null, args) is fn(...args).
356
+ 'eslint/prefer-spread': 'error',
357
+ // "a" + x + "b" is a template literal written the hard way.
358
+ 'eslint/prefer-template': 'error',
359
+ // Import order carries meaning (side effects, grouping); alphabetizing is churn.
360
+ 'eslint/sort-imports': 'off',
361
+ // Key order carries meaning (importance, schema shape); alphabetizing is churn.
362
+ 'eslint/sort-keys': 'off',
363
+ // Only applies to var, which no-var already bans; kept on to catch stragglers.
364
+ 'eslint/vars-on-top': 'error',
365
+ // if (5 === x) guards against a typo === already catches.
366
+ 'eslint/yoda': 'error',
367
+ /* ================================================================== *
368
+ * eslint — restriction
369
+ * ================================================================== */
370
+ // Forcing static for this-less methods churns APIs mid-refactor.
371
+ 'eslint/class-methods-use-this': 'off',
372
+ // Cyclomatic thresholds are arbitrary; same stance as max-lines.
373
+ 'eslint/complexity': 'off',
374
+ // TS exhaustiveness (switch-exhaustiveness-check, type-aware) beats dead default branches.
375
+ 'eslint/default-case': 'off',
376
+ // alert/confirm block the main thread — leftover debug UI.
377
+ 'eslint/no-alert': 'error',
378
+ // Bitwise ops are legitimate (hashing, flags, graphics); oxc/bad-bitwise-operator catches typos.
379
+ 'eslint/no-bitwise': 'off',
380
+ // console output is debugging residue in app code; tests exempt via override.
381
+ 'eslint/no-console': 'error',
382
+ // Obscure rule about /=/ regexes; a parenthesized regex is not clearer.
383
+ 'eslint/no-div-regex': 'off',
384
+ // An empty block either hides a swallowed error or marks unfinished work.
385
+ 'eslint/no-empty': 'error',
386
+ // () => {} as a deliberate noop/default is idiomatic; no-empty covers accidents.
387
+ 'eslint/no-empty-function': 'off',
388
+ // eqeqeq (set to always, no null exception) already bans == null.
389
+ 'eslint/no-eq-null': 'off',
390
+ // Accidental globals from script-scope declarations; harmless in ESM, fatal outside it.
391
+ 'eslint/no-implicit-globals': 'error',
392
+ // Mutating a parameter mutates the caller's object — spooky action at a distance.
393
+ 'eslint/no-param-reassign': 'error',
394
+ // i++ in a for-header is not a readability problem.
395
+ 'eslint/no-plusplus': 'off',
396
+ // __proto__ is deprecated; Object.getPrototypeOf is the API.
397
+ 'eslint/no-proto': 'error',
398
+ // /a b/ with meaningful double spaces is unreadable; use /a {2}b/.
399
+ 'eslint/no-regex-spaces': 'error',
400
+ // No-op without a project-specific list.
401
+ 'eslint/no-restricted-globals': 'off',
402
+ // No-op without a project-specific list.
403
+ 'eslint/no-restricted-imports': 'off',
404
+ // No-op without a project-specific list.
405
+ 'eslint/no-restricted-properties': 'off',
406
+ // The comma operator hides side effects in expression position.
407
+ 'eslint/no-sequences': 'error',
408
+ // undefined as a value is normal JS; unicorn/no-useless-undefined trims the noise cases.
409
+ 'eslint/no-undefined': 'off',
410
+ // TDZ errors and hoisting confusion; declare before use.
411
+ 'eslint/no-use-before-define': 'error',
412
+ // var is function-scoped legacy; let/const or nothing.
413
+ 'eslint/no-var': 'error',
414
+ // `void promise` is the idiomatic fire-and-forget marker under no-floating-promises.
415
+ 'eslint/no-void': 'off',
416
+ // A BOM breaks shebangs, concatenation, and some parsers.
417
+ 'eslint/unicode-bom': 'error',
418
+ /* ================================================================== *
419
+ * eslint — nursery (all off: not stabilized in oxlint yet)
420
+ * ================================================================== */
421
+ // Nursery + no-op without a project-specific list.
422
+ 'eslint/no-restricted-exports': 'off',
423
+ // TypeScript already errors on unknown identifiers; this duplicates tsc, with false positives.
424
+ 'eslint/no-undef': 'off',
425
+ // Nursery: revisit when stabilized.
426
+ 'eslint/no-unreachable-loop': 'off',
427
+ // Nursery: revisit when stabilized.
428
+ 'eslint/no-useless-assignment': 'off',
429
+ /* ================================================================== *
430
+ * typescript — correctness
431
+ * ================================================================== */
432
+ // Two enum members with one value make reverse lookups ambiguous.
433
+ 'typescript/no-duplicate-enum-values': 'error',
434
+ // x!! is x! — the second assertion asserts nothing.
435
+ 'typescript/no-extra-non-null-assertion': 'error',
436
+ // `new` on an interface method or class-typed `constructor` is always wrong.
437
+ 'typescript/no-misused-new': 'error',
438
+ // foo?.bar! contradicts itself — asserting non-null on an optional chain.
439
+ 'typescript/no-non-null-asserted-optional-chain': 'error',
440
+ // const self = this defeats arrow functions and types alike.
441
+ 'typescript/no-this-alias': 'error',
442
+ // Assigning to a parameter property already assigned by the constructor shorthand.
443
+ 'typescript/no-unnecessary-parameter-property-assignment': 'error',
444
+ // Merging a class with an interface silently adds unimplemented members.
445
+ 'typescript/no-unsafe-declaration-merging': 'error',
446
+ // export {} in a module that already has exports is dead syntax.
447
+ 'typescript/no-useless-empty-export': 'error',
448
+ // String/Number/Boolean types accept boxed objects; use string/number/boolean.
449
+ 'typescript/no-wrapper-object-types': 'error',
450
+ // as const preserves literal types; manual literal annotations drift.
451
+ 'typescript/prefer-as-const': 'error',
452
+ // `module` keyword for namespaces collides with ESM vocabulary.
453
+ 'typescript/prefer-namespace-keyword': 'error',
454
+ // /// <reference> is pre-ESM dependency wiring; imports do this now.
455
+ 'typescript/triple-slash-reference': 'error',
456
+ /* ================================================================== *
457
+ * typescript — suspicious
458
+ * ================================================================== */
459
+ // a! == b reads as (a!) == b to the compiler but not to humans.
460
+ 'typescript/no-confusing-non-null-assertion': 'error',
461
+ // A class of only statics is a namespace; use module-level functions.
462
+ 'typescript/no-extraneous-class': 'error',
463
+ // <T extends unknown> constrains nothing.
464
+ 'typescript/no-unnecessary-type-constraint': 'error',
465
+ /* ================================================================== *
466
+ * typescript — pedantic
467
+ * ================================================================== */
468
+ // A ts-ignore directive hides errors forever; ts-expect-error (with description) self-expires.
469
+ 'typescript/ban-ts-comment': 'error',
470
+ // Deprecated upstream — split into no-wrapper-object-types/no-empty-object-type (both on).
471
+ 'typescript/ban-types': 'off',
472
+ // The Function type accepts any callable with any args; write a signature.
473
+ 'typescript/no-unsafe-function-type': 'error',
474
+ // Implicit enum values renumber when members reorder — breaks serialized data.
475
+ 'typescript/prefer-enum-initializers': 'error',
476
+ // Same reasoning as ban-ts-comment: expect-error errors when the error is fixed.
477
+ 'typescript/prefer-ts-expect-error': 'error',
478
+ /* ================================================================== *
479
+ * typescript — style
480
+ * ================================================================== */
481
+ // Overloads scattered through a class read as duplicates.
482
+ 'typescript/adjacent-overload-signatures': 'error',
483
+ // T[] over Array<T> for simple types — reads left-to-right.
484
+ 'typescript/array-type': 'error',
485
+ // tslint died in 2019; its disable comments are fossils.
486
+ 'typescript/ban-tslint-comment': 'error',
487
+ // readonly field = "x" beats get x() { return "x" } — no call overhead, same guarantee.
488
+ 'typescript/class-literal-property-style': 'error',
489
+ // new Map<string, number>() vs new Map(): pick constructor-side annotations consistently.
490
+ 'typescript/consistent-generic-constructors': 'error',
491
+ // Record<K, V> over {[k: K]: V} — it's the same type with a name.
492
+ 'typescript/consistent-indexed-object-style': 'error',
493
+ // `as T` over <T> casts — angle brackets collide with JSX and read as generics.
494
+ 'typescript/consistent-type-assertions': 'error',
495
+ // type over interface: no declaration merging surprises, works for unions too.
496
+ 'typescript/consistent-type-definitions': ['error', 'type'],
497
+ // Inline `import { type X }` keeps one import per module; pairs with
498
+ // import/consistent-type-specifier-style below.
499
+ 'typescript/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
500
+ // Property signatures (fn: () => void) get strict variance checking; methods don't.
501
+ 'typescript/method-signature-style': 'error',
502
+ // An empty interface is {} — either meaningless or a wrong extends.
503
+ 'typescript/no-empty-interface': 'error',
504
+ // const x: number = 5 — the annotation restates the obvious.
505
+ 'typescript/no-inferrable-types': 'error',
506
+ // Constructor parameter properties hide field declarations in a signature; off per
507
+ // cacographer — declare fields explicitly where you want them visible.
508
+ 'typescript/parameter-properties': 'off',
509
+ // for-of over index loops when the index isn't used.
510
+ 'typescript/prefer-for-of': 'error',
511
+ // An interface with only a call signature is a function type.
512
+ 'typescript/prefer-function-type': 'error',
513
+ // Overloads differing only in one union'd param should be one signature.
514
+ 'typescript/unified-signatures': 'error',
515
+ /* ================================================================== *
516
+ * typescript — restriction
517
+ * ================================================================== */
518
+ // Inference is TypeScript's core value; annotating every return is ceremony.
519
+ 'typescript/explicit-function-return-type': 'off',
520
+ // public-by-default is idiomatic TS; annotating it is noise.
521
+ 'typescript/explicit-member-accessibility': 'off',
522
+ // Same stance as explicit-function-return-type — inference carries module boundaries.
523
+ 'typescript/explicit-module-boundary-types': 'off',
524
+ // delete obj[computed] defeats shape optimization and type tracking.
525
+ 'typescript/no-dynamic-delete': 'error',
526
+ // {} means "anything non-nullish", never "empty object" — a classic trap.
527
+ 'typescript/no-empty-object-type': 'error',
528
+ // any turns the checker off for everything it touches; unknown keeps it on.
529
+ 'typescript/no-explicit-any': 'error',
530
+ // import type with side-effect syntax emits an unexpected runtime import.
531
+ 'typescript/no-import-type-side-effects': 'error',
532
+ // void outside return position (unions, params) behaves surprisingly.
533
+ 'typescript/no-invalid-void-type': 'error',
534
+ // Namespaces predate ES modules; files are the namespace now.
535
+ 'typescript/no-namespace': 'error',
536
+ // x! ?? y: the assertion makes the ?? dead code.
537
+ 'typescript/no-non-null-asserted-nullish-coalescing': 'error',
538
+ // ! is an unchecked cast wearing a convenience syntax; tests exempt via override.
539
+ 'typescript/no-non-null-assertion': 'error',
540
+ // require() in TS is CJS leakage; import.
541
+ 'typescript/no-require-imports': 'error',
542
+ // No-op without a project-specific list.
543
+ 'typescript/no-restricted-types': 'off',
544
+ // const x = require() bypasses the module graph and its types.
545
+ 'typescript/no-var-requires': 'error',
546
+ // Computed enum members from function calls aren't statically analyzable.
547
+ 'typescript/prefer-literal-enum-member': 'error',
548
+ /* ================================================================== *
549
+ * unicorn — correctness
550
+ * ================================================================== */
551
+ // await inside Promise.all's array argument serializes what it parallelizes.
552
+ 'unicorn/no-await-in-promise-methods': 'error',
553
+ // An empty file is a merge artifact or a forgotten stub.
554
+ 'unicorn/no-empty-file': 'error',
555
+ // fetch(url, {body}) without method: "POST" throws or silently GETs.
556
+ 'unicorn/no-invalid-fetch-options': 'error',
557
+ // removeEventListener with an inline arrow can never remove anything.
558
+ 'unicorn/no-invalid-remove-event-listener': 'error',
559
+ // new Array(n) makes holes; Array.from({length: n}) makes elements.
560
+ 'unicorn/no-new-array': 'error',
561
+ // Promise.all([one]) is just await one.
562
+ 'unicorn/no-single-promise-in-promise-methods': 'error',
563
+ // Objects with a `then` method get absorbed by await — a footgun, not a feature.
564
+ 'unicorn/no-thenable': 'error',
565
+ // await on a non-promise is a no-op that implies async where there is none.
566
+ 'unicorn/no-unnecessary-await': 'error',
567
+ // {...(foo || {})} — spread already treats nullish as empty.
568
+ 'unicorn/no-useless-fallback-in-spread': 'error',
569
+ // arr.length > 0 && arr.some(...) — some() already handles empty.
570
+ 'unicorn/no-useless-length-check': 'error',
571
+ // [...arr] passed straight to a function that doesn't mutate is a wasted copy.
572
+ 'unicorn/no-useless-spread': 'error',
573
+ // new Set(x).size === 1 checks uniqueness; .length on a Set is undefined.
574
+ 'unicorn/prefer-set-size': 'error',
575
+ // /^foo/.test(s) is s.startsWith("foo") without the regex overhead.
576
+ 'unicorn/prefer-string-starts-ends-with': 'error',
577
+ /* ================================================================== *
578
+ * unicorn — suspicious
579
+ * ================================================================== */
580
+ // A function that doesn't capture from its enclosing scope belongs outside it.
581
+ 'unicorn/consistent-function-scoping': 'error',
582
+ // A getter reading itself recurses forever.
583
+ 'unicorn/no-accessor-recursion': 'error',
584
+ // arr.fill({}) shares ONE object across every slot.
585
+ 'unicorn/no-array-fill-with-reference-type': 'error',
586
+ // reverse() mutates in place; toReversed() doesn't surprise the other holder.
587
+ 'unicorn/no-array-reverse': 'error',
588
+ // sort() mutates and compares as strings by default; toSorted(comparator).
589
+ 'unicorn/no-array-sort': 'error',
590
+ // new Array().with() on sparse arrays behaves unexpectedly.
591
+ 'unicorn/no-confusing-array-with': 'error',
592
+ // instanceof breaks across realms/iframes for builtins; use type-specific checks.
593
+ 'unicorn/no-instanceof-builtins': 'error',
594
+ // onclick= assignment silently replaces the previous handler; addEventListener stacks.
595
+ 'unicorn/prefer-add-event-listener': 'error',
596
+ // Bare `import {}` braces style — off per smallreads; not worth policing.
597
+ 'unicorn/require-module-specifiers': 'off',
598
+ // postMessage without targetOrigin broadcasts to any origin — a security hole.
599
+ 'unicorn/require-post-message-target-origin': 'error',
600
+ /* ================================================================== *
601
+ * unicorn — pedantic
602
+ * ================================================================== */
603
+ // assert() from node:assert vs console.assert have different failure modes; be consistent.
604
+ 'unicorn/consistent-assert': 'error',
605
+ // [...(cond ? [a] : [])] conditional spread has one idiomatic shape; stick to it.
606
+ 'unicorn/consistent-empty-array-spread': 'error',
607
+ // \xFF vs \xff: one casing for escapes.
608
+ 'unicorn/escape-case': 'error',
609
+ // if (arr.length) hides a number-as-boolean coercion; compare > 0.
610
+ 'unicorn/explicit-length-check': 'error',
611
+ // Map/Set/Date without new work differently or throw; always new for builtins.
612
+ 'unicorn/new-for-builtins': 'error',
613
+ // arr.map(parseInt) passes the index as radix — the canonical extra-args bug.
614
+ 'unicorn/no-array-callback-reference': 'error',
615
+ // \x1B over \u{1b}: hex escapes for bytes.
616
+ 'unicorn/no-hex-escape': 'error',
617
+ // Mutating right after creation ([...x].sort()) has non-mutating spellings (toSorted).
618
+ 'unicorn/no-immediate-mutation': 'error',
619
+ // Array.isArray works across realms; instanceof Array doesn't.
620
+ 'unicorn/no-instanceof-array': 'error',
621
+ // Same as eslint/no-lonely-if but catches unicorn-specific shapes.
622
+ 'unicorn/no-lonely-if': 'error',
623
+ // Negated condition with an else: flip it.
624
+ 'unicorn/no-negated-condition': 'error',
625
+ // !(a === b) is a !== b.
626
+ 'unicorn/no-negation-in-equality-check': 'error',
627
+ // new Buffer() is deprecated and unsafe; Buffer.from/alloc.
628
+ 'unicorn/no-new-buffer': 'error',
629
+ // Object literal defaults ({} = {}) create a new object per call — surprises identity checks.
630
+ 'unicorn/no-object-as-default-parameter': 'error',
631
+ // A class of only statics is a namespace (mirrors typescript/no-extraneous-class).
632
+ 'unicorn/no-static-only-class': 'error',
633
+ // const self = this — arrow functions exist (mirrors typescript/no-this-alias).
634
+ 'unicorn/no-this-assignment': 'error',
635
+ // typeof x === "undefined" for a known binding is x === undefined.
636
+ 'unicorn/no-typeof-undefined': 'error',
637
+ // flat(1) is flat().
638
+ 'unicorn/no-unnecessary-array-flat-depth': 'error',
639
+ // splice(0, arr.length) has clearer spellings.
640
+ 'unicorn/no-unnecessary-array-splice-count': 'error',
641
+ // slice(0, arr.length) — the default end is already the end.
642
+ 'unicorn/no-unnecessary-slice-end': 'error',
643
+ // An IIFE with convoluted wrapping obscures what actually runs.
644
+ 'unicorn/no-unreadable-iife': 'error',
645
+ // return Promise.resolve(x) inside async is return x.
646
+ 'unicorn/no-useless-promise-resolve-reject': 'error',
647
+ // A case that only falls through to default is dead syntax.
648
+ 'unicorn/no-useless-switch-case': 'error',
649
+ // return undefined / foo(undefined) — undefined is the default everywhere.
650
+ 'unicorn/no-useless-undefined': 'error',
651
+ // [].concat(...arrays) flattens; flat() says so.
652
+ 'unicorn/prefer-array-flat': 'error',
653
+ // filter(...).length > 0 scans everything; some() short-circuits.
654
+ 'unicorn/prefer-array-some': 'error',
655
+ // arr[arr.length - 1] is fine; .at(-1) adds no clarity and costs in hot paths
656
+ // (off per smallreads).
657
+ 'unicorn/prefer-at': 'off',
658
+ // FileReader is callback-era; blob.text()/arrayBuffer() are promises.
659
+ 'unicorn/prefer-blob-reading-methods': 'error',
660
+ // charCodeAt splits surrogate pairs; codePointAt handles all of Unicode.
661
+ 'unicorn/prefer-code-point': 'error',
662
+ // new Date().getTime() is Date.now() with an allocation.
663
+ 'unicorn/prefer-date-now': 'error',
664
+ // append() takes strings and multiple args; appendChild doesn't.
665
+ 'unicorn/prefer-dom-node-append': 'error',
666
+ // dataset.foo over getAttribute("data-foo").
667
+ 'unicorn/prefer-dom-node-dataset': 'error',
668
+ // el.remove() over parent.removeChild(el).
669
+ 'unicorn/prefer-dom-node-remove': 'error',
670
+ // EventTarget is the standard; EventEmitter is Node-only.
671
+ 'unicorn/prefer-event-target': 'error',
672
+ // import.meta.dirname/filename over fileURLToPath gymnastics.
673
+ 'unicorn/prefer-import-meta-properties': 'error',
674
+ // Math.min(a, b) over a < b ? a : b.
675
+ 'unicorn/prefer-math-min-max': 'error',
676
+ // Math.trunc over |0 bit-twiddling.
677
+ 'unicorn/prefer-math-trunc': 'error',
678
+ // Number over x => Number(x) wrapper arrows.
679
+ 'unicorn/prefer-native-coercion-functions': 'error',
680
+ // Number(x) over +x (pairs with eslint/no-implicit-coercion).
681
+ 'unicorn/prefer-number-coercion': 'error',
682
+ // Array.prototype.slice.call over [].slice.call — no throwaway array.
683
+ 'unicorn/prefer-prototype-methods': 'error',
684
+ // querySelector is one API for every selector; getElementById et al are shards.
685
+ 'unicorn/prefer-query-selector': 'error',
686
+ // regex.test() for booleans; match() allocates the result you're discarding.
687
+ 'unicorn/prefer-regexp-test': 'error',
688
+ // append(a); append(b) → append(a, b).
689
+ 'unicorn/prefer-single-call': 'error',
690
+ // replaceAll says all; replace(/g/) hides it in a flag.
691
+ 'unicorn/prefer-string-replace-all': 'error',
692
+ // slice over substr (deprecated) and substring (argument-swapping).
693
+ 'unicorn/prefer-string-slice': 'error',
694
+ // Top-level await vs .then() at module top is a per-project loading decision
695
+ // (off per smallreads — TLA blocks the whole module graph).
696
+ 'unicorn/prefer-top-level-await': 'off',
697
+ // Wrong-type arguments deserve TypeError, not Error.
698
+ 'unicorn/prefer-type-error': 'error',
699
+ // toFixed() without digits defaults to 0 — surprising; say toFixed(0).
700
+ 'unicorn/require-number-to-fixed-digits-argument': 'error',
701
+ /* ================================================================== *
702
+ * unicorn — perf
703
+ * ================================================================== */
704
+ // filter(fn)[0] scans everything; find(fn) stops at the first hit.
705
+ 'unicorn/prefer-array-find': 'error',
706
+ // map(fn).flat() makes two arrays; flatMap makes one.
707
+ 'unicorn/prefer-array-flat-map': 'error',
708
+ // arr.includes in a loop is O(n²); Set.has is O(1) (explicit in cacographer).
709
+ 'unicorn/prefer-set-has': 'error',
710
+ /* ================================================================== *
711
+ * unicorn — style
712
+ * ================================================================== */
713
+ // catch (e) — name it error; abbreviations hide what it is.
714
+ 'unicorn/catch-error-name': 'error',
715
+ // new Date(date) clones; new Date(date.getTime()) is the same with extra steps.
716
+ 'unicorn/consistent-date-clone': 'error',
717
+ // indexOf(x) !== -1 vs includes(x): one existence idiom.
718
+ 'unicorn/consistent-existence-index-check': 'error',
719
+ // One escaping style inside template literals.
720
+ 'unicorn/consistent-template-literal-escape': 'error',
721
+ // Custom errors need name set and prototype fixed; enforce the full pattern.
722
+ 'unicorn/custom-error-definition': 'error',
723
+ // Pure whitespace — oxfmt's jurisdiction, but harmless and autofixed.
724
+ 'unicorn/empty-brace-spaces': 'off',
725
+ // new Error() with no message throws away the one chance to say what broke.
726
+ 'unicorn/error-message': 'error',
727
+ // setTimeout(fn) with no delay hides the "run async" intent; write the 0.
728
+ 'unicorn/explicit-timer-delay': 'error',
729
+ // Mixed-case filenames break on case-insensitive filesystems mid-collaboration;
730
+ // kebab/camel/pascal each allowed (per cacographer), just be internally consistent.
731
+ 'unicorn/filename-case': [
732
+ 'error',
733
+ { cases: { kebabCase: true, camelCase: true, pascalCase: true } }
734
+ ],
735
+ // fn(fn(fn(fn(x)))) — extract intermediates.
736
+ 'unicorn/max-nested-calls': 'error',
737
+ // map(fn, thisArg) — the thisArg param is invisible at the callsite; bind or arrow.
738
+ 'unicorn/no-array-method-this-argument': 'error',
739
+ // (await foo()).bar buries the await; name the intermediate.
740
+ 'unicorn/no-await-expression-member': 'error',
741
+ // console.log("a ", x) — the stray space was probably not a choice.
742
+ 'unicorn/no-console-spaces': 'error',
743
+ // Allows ONE parenthesized nesting level — stricter eslint version off above.
744
+ 'unicorn/no-nested-ternary': 'error',
745
+ // null is a legitimate value (JSON, DBs, DOM APIs return it) — off per cacographer.
746
+ 'unicorn/no-null': 'off',
747
+ // const [,, third] = arr — count the commas to find the bug.
748
+ 'unicorn/no-unreadable-array-destructuring': 'error',
749
+ // new Set([...set]) copies a copy.
750
+ 'unicorn/no-useless-collection-argument': 'error',
751
+ // 1.0 is 1; the fraction implies float semantics JS doesn't have.
752
+ 'unicorn/no-zero-fractions': 'error',
753
+ // 0xFF not 0xff, 1e10 not 1E10: one casing for literals.
754
+ 'unicorn/number-literal-case': 'error',
755
+ // 1_000_000 over 1000000 past four digits.
756
+ 'unicorn/numeric-separators-style': 'error',
757
+ // findIndex(x => x === v) is indexOf(v).
758
+ 'unicorn/prefer-array-index-of': 'error',
759
+ // 123n over BigInt(123) for constants.
760
+ 'unicorn/prefer-bigint-literals': 'error',
761
+ // Class fields over constructor-only assignments.
762
+ 'unicorn/prefer-class-fields': 'error',
763
+ // classList.toggle(name, force) over if/add/else/remove.
764
+ 'unicorn/prefer-classlist-toggle': 'error',
765
+ // function f(x = 1) over x = x || 1 in the body.
766
+ 'unicorn/prefer-default-parameters': 'error',
767
+ // textContent over innerText (no reflow, no CSS-awareness surprises).
768
+ 'unicorn/prefer-dom-node-text-content': 'error',
769
+ // export { x } from "./mod" over import-then-export.
770
+ 'unicorn/prefer-export-from': 'error',
771
+ // globalThis is the one spelling that works in every runtime.
772
+ 'unicorn/prefer-global-this': 'error',
773
+ // arr.indexOf(x) !== -1 is arr.includes(x).
774
+ 'unicorn/prefer-includes': 'error',
775
+ // event.key ("Enter") over event.keyCode (13) — keyCode is deprecated.
776
+ 'unicorn/prefer-keyboard-event-key': 'error',
777
+ // a || b over a ? a : b — no double evaluation.
778
+ 'unicorn/prefer-logical-operator-over-ternary': 'error',
779
+ // replaceChildren/before/after over replaceChild/insertBefore contortions.
780
+ 'unicorn/prefer-modern-dom-apis': 'error',
781
+ // slice(-2) over slice(arr.length - 2).
782
+ 'unicorn/prefer-negative-index': 'error',
783
+ // Object.fromEntries over reduce-into-object.
784
+ 'unicorn/prefer-object-from-entries': 'error',
785
+ // catch {} over catch (unused) {}.
786
+ 'unicorn/prefer-optional-catch-binding': 'error',
787
+ // Reflect.apply over Function.prototype.apply.call gymnastics.
788
+ 'unicorn/prefer-reflect-apply': 'error',
789
+ // Response.json(data) over new Response(JSON.stringify(data), headers...).
790
+ 'unicorn/prefer-response-static-json': 'error',
791
+ // [...iterable] over Array.from(iterable) when no map fn.
792
+ 'unicorn/prefer-spread': 'error',
793
+ // String.raw for windows\paths and regex sources — no double-backslash counting.
794
+ 'unicorn/prefer-string-raw': 'error',
795
+ // trimStart/trimEnd over trimLeft/trimRight (RTL-ambiguous aliases).
796
+ 'unicorn/prefer-string-trim-start-end': 'error',
797
+ // structuredClone over JSON.parse(JSON.stringify(x)) — handles Dates, Maps, cycles.
798
+ 'unicorn/prefer-structured-clone': 'error',
799
+ // Simple if/else assignments read fine as ternaries; pairs with no-nested-ternary.
800
+ 'unicorn/prefer-ternary': 'error',
801
+ // ./relative URLs in new URL(): one style.
802
+ 'unicorn/relative-url-style': 'error',
803
+ // join() defaults to commas — say join(",") so the reader needn't remember.
804
+ 'unicorn/require-array-join-separator': 'error',
805
+ // import attrs (with { type: "json" }) required where the runtime needs them.
806
+ 'unicorn/require-module-attributes': 'error',
807
+ // Braces on every case (pairs with eslint/no-case-declarations).
808
+ 'unicorn/switch-case-braces': 'error',
809
+ // break at the end of the case body, not mid-block.
810
+ 'unicorn/switch-case-break-position': 'error',
811
+ // "utf8" not "UTF-8": one spelling for encoding identifiers.
812
+ 'unicorn/text-encoding-identifier-case': 'error',
813
+ // throw new Error() not throw Error() — consistency with every other constructor.
814
+ 'unicorn/throw-new-error': 'error',
815
+ /* ================================================================== *
816
+ * unicorn — restriction
817
+ * ================================================================== */
818
+ // Named vs default vs namespace import preference is per-library; not enforceable globally.
819
+ 'unicorn/import-style': 'off',
820
+ // A disable comment without a rule name disables everything, forever, silently.
821
+ 'unicorn/no-abusive-eslint-disable': 'error',
822
+ // export default () => {} shows up as "default" in every stack trace and dev tool.
823
+ 'unicorn/no-anonymous-default-export': 'error',
824
+ // for-of is faster, breakable, awaitable; forEach is none of those.
825
+ 'unicorn/no-array-for-each': 'error',
826
+ // reduce is fine when the accumulator is named well; banning it forces clunkier loops.
827
+ 'unicorn/no-array-reduce': 'off',
828
+ // document.cookie's string API is a parsing trap; use CookieStore or a helper.
829
+ 'unicorn/no-document-cookie': 'error',
830
+ // slice(1, arr.length) as "to the end" — just omit the argument.
831
+ 'unicorn/no-length-as-slice-end': 'error',
832
+ // flat(2) — why 2? Name the depth or restructure the data.
833
+ 'unicorn/no-magic-array-flat-depth': 'error',
834
+ // process.exit skips flush/cleanup handlers; throw and let the top level decide.
835
+ 'unicorn/no-process-exit': 'error',
836
+ // captureStackTrace on an Error that already has a stack.
837
+ 'unicorn/no-useless-error-capture-stack-trace': 'error',
838
+ // Math.hypot et al over hand-rolled sqrt(x*x + y*y).
839
+ 'unicorn/prefer-modern-math-apis': 'error',
840
+ // ESM only — CJS files don't belong in new code (pairs with import/no-commonjs).
841
+ 'unicorn/prefer-module': 'error',
842
+ // node: prefix makes builtin imports unambiguous and un-shadowable.
843
+ 'unicorn/prefer-node-protocol': 'error',
844
+ // Number.parseInt/isNaN over globals — no coercion surprises, greppable.
845
+ 'unicorn/prefer-number-properties': 'error',
846
+ /* ================================================================== *
847
+ * unicorn — nursery (off: not stabilized)
848
+ * ================================================================== */
849
+ // Nursery: revisit when stabilized.
850
+ 'unicorn/no-useless-iterator-to-array': 'off',
851
+ /* ================================================================== *
852
+ * oxc — correctness
853
+ * ================================================================== */
854
+ // Array methods on `arguments` (not a real array) misbehave.
855
+ 'oxc/bad-array-method-on-arguments': 'error',
856
+ // charAt(n) === "ab" can never be true — charAt returns one char.
857
+ 'oxc/bad-char-at-comparison': 'error',
858
+ // a === b === c compares a boolean to c.
859
+ 'oxc/bad-comparison-sequence': 'error',
860
+ // matchAll with a non-global regex throws at runtime (twin of bad-replace-all-arg).
861
+ 'oxc/bad-match-all-arg': 'error',
862
+ // Math.min(Math.max(x, hi), lo) with swapped bounds clamps to a constant.
863
+ 'oxc/bad-min-max-func': 'error',
864
+ // obj === {} is always false — reference comparison against a fresh literal.
865
+ 'oxc/bad-object-literal-comparison': 'error',
866
+ // replaceAll with a non-global regex throws at runtime.
867
+ 'oxc/bad-replace-all-arg': 'error',
868
+ // x < x and friends are always true/false — a typo'd variable.
869
+ 'oxc/const-comparisons': 'error',
870
+ // (x == y) == z double comparisons don't chain like math.
871
+ 'oxc/double-comparisons': 'error',
872
+ // x & 0 and x * 0 erase the operand — either dead code or a typo.
873
+ 'oxc/erasing-op': 'error',
874
+ // `new Error(...)` without throw constructs an error and drops it.
875
+ 'oxc/missing-throw': 'error',
876
+ // toFixed(101) and friends throw RangeError at runtime.
877
+ 'oxc/number-arg-out-of-range': 'error',
878
+ // A parameter only passed to the recursive call does nothing.
879
+ 'oxc/only-used-in-recursion': 'error',
880
+ // Array-from callback constructed but never invoked.
881
+ 'oxc/uninvoked-array-callback': 'error',
882
+ /* ================================================================== *
883
+ * oxc — suspicious
884
+ * ================================================================== */
885
+ // 3.14 inline where Math.PI was meant loses precision silently.
886
+ 'oxc/approx-constant': 'error',
887
+ // a += a + b was probably a = a + b or a += b.
888
+ 'oxc/misrefactored-assign-op': 'error',
889
+ // Express-4-specific (async handlers need wrappers there); not a general defect.
890
+ 'oxc/no-async-endpoint-handlers': 'off',
891
+ // `this` in an exported plain function is undefined in ESM strict mode.
892
+ 'oxc/no-this-in-exported-function': 'error',
893
+ /* ================================================================== *
894
+ * oxc — pedantic
895
+ * ================================================================== */
896
+ // Identical code in both branches belongs outside the if.
897
+ 'oxc/branches-sharing-code': 'error',
898
+ /* ================================================================== *
899
+ * oxc — perf
900
+ * ================================================================== */
901
+ // [...acc, x] in reduce is O(n²) — push instead.
902
+ 'oxc/no-accumulating-spread': 'error',
903
+ // map(x => ({...x, y})) clones every element; mutate a fresh object or restructure.
904
+ 'oxc/no-map-spread': 'error',
905
+ /* ================================================================== *
906
+ * oxc — restriction
907
+ * ================================================================== */
908
+ // & where && was meant type-checks but computes garbage; this catches the typo
909
+ // shapes (which is why plain no-bitwise stays off).
910
+ 'oxc/bad-bitwise-operator': 'error',
911
+ // Banning async/await outright is for esoteric codebases only.
912
+ 'oxc/no-async-await': 'off',
913
+ // Barrel files are a build-perf tradeoff, not a defect; per-project call.
914
+ 'oxc/no-barrel-file': 'off',
915
+ // const enum breaks isolatedModules and every non-tsc transpiler.
916
+ 'oxc/no-const-enum': 'error',
917
+ // Optional chaining is standard now; banning it is for ES2019 targets only.
918
+ 'oxc/no-optional-chaining': 'off',
919
+ // Rest/spread properties are standard now; same legacy-target reasoning.
920
+ 'oxc/no-rest-spread-properties': 'off',
921
+ /* ================================================================== *
922
+ * import — correctness
923
+ * ================================================================== */
924
+ // Importing a default from a module that has none is undefined at runtime.
925
+ 'import/default': 'error',
926
+ // ns.missing on a namespace import is undefined at runtime.
927
+ 'import/namespace': 'error',
928
+ /* ================================================================== *
929
+ * import — suspicious
930
+ * ================================================================== */
931
+ // Absolute paths are machine-specific; they break on every other machine.
932
+ 'import/no-absolute-path': 'error',
933
+ // import {} from "x" — empty braces import nothing; make it a side-effect import or delete.
934
+ 'import/no-empty-named-blocks': 'error',
935
+ // Importing a named export under the default's name reads as the wrong binding.
936
+ 'import/no-named-as-default': 'error',
937
+ // default.namedExport — the member lives on the module, not the default.
938
+ 'import/no-named-as-default-member': 'error',
939
+ // A module importing itself is always a refactoring accident.
940
+ 'import/no-self-import': 'error',
941
+ // Bare imports hide side effects; CSS is the sanctioned exception (per cacographer).
942
+ 'import/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
943
+ /* ================================================================== *
944
+ * import — pedantic
945
+ * ================================================================== */
946
+ // Past ~24 imports a module is doing too many jobs (threshold per cacographer).
947
+ 'import/max-dependencies': ['error', { max: 24 }],
948
+ /* ================================================================== *
949
+ * import — style
950
+ * ================================================================== */
951
+ // import { type X, y } inline — one import line per module, types marked in place.
952
+ 'import/consistent-type-specifier-style': ['error', 'prefer-inline'],
953
+ // Export placement is layout preference, not correctness (off per cacographer).
954
+ 'import/exports-last': 'off',
955
+ // Imports scattered below code hide the dependency list.
956
+ 'import/first': 'error',
957
+ // Grouping all exports into one statement is ceremony (off per cacographer).
958
+ 'import/group-exports': 'off',
959
+ // Blank-line-after-imports is whitespace — oxfmt's jurisdiction.
960
+ 'import/newline-after-import': 'off',
961
+ // unicorn/no-anonymous-default-export covers this; avoid double reports.
962
+ 'import/no-anonymous-default-export': 'off',
963
+ // Two imports from one module belong on one line (type-aware, unlike eslint's).
964
+ 'import/no-duplicates': 'error',
965
+ // Reassigning an exported binding mutates state for every importer.
966
+ 'import/no-mutable-exports': 'error',
967
+ // import { default as x } is import x.
968
+ 'import/no-named-default': 'error',
969
+ // Named exports are the default stance here (off per cacographer).
970
+ 'import/no-named-export': 'off',
971
+ // import * pulls the whole module and defeats tree-shaking signals; import what you use.
972
+ 'import/no-namespace': 'error',
973
+ // Node builtins are fine — universal-runtime purity is a per-project choice.
974
+ 'import/no-nodejs-modules': 'off',
975
+ // Default exports rename themselves at every import site; prefer named (off per cacographer).
976
+ 'import/prefer-default-export': 'off',
977
+ /* ================================================================== *
978
+ * import — restriction
979
+ * ================================================================== */
980
+ // Extension requirements are bundler/tsconfig-dependent; the resolver enforces reality.
981
+ 'import/extensions': 'off',
982
+ // AMD is a dead module format.
983
+ 'import/no-amd': 'error',
984
+ // CJS in source is legacy (pairs with unicorn/prefer-module).
985
+ 'import/no-commonjs': 'error',
986
+ // Import cycles make module init order undefined — the bug appears at 3am.
987
+ 'import/no-cycle': 'error',
988
+ // Frameworks require default exports (routes, configs, Svelte); can't ban globally.
989
+ 'import/no-default-export': 'off',
990
+ // require(variable) defeats static analysis and bundling.
991
+ 'import/no-dynamic-require': 'error',
992
+ // ../ imports are normal; path aliases are a per-project choice.
993
+ 'import/no-relative-parent-imports': 'off',
994
+ // loader!./file syntax is webpack-specific and non-portable.
995
+ 'import/no-webpack-loader-syntax': 'error',
996
+ // Script-vs-module ambiguity is moot in ESM-only codebases.
997
+ 'import/unambiguous': 'off',
998
+ /* ================================================================== *
999
+ * import — nursery (off: not stabilized; tsc catches missing exports)
1000
+ * ================================================================== */
1001
+ // Nursery: tsc already errors on importing a missing export.
1002
+ 'import/export': 'off',
1003
+ // Nursery: same — TypeScript validates named imports.
1004
+ 'import/named': 'off',
1005
+ /* ================================================================== *
1006
+ * promise — correctness
1007
+ * ================================================================== */
1008
+ // Calling a node-style callback inside a promise chain mixes error channels.
1009
+ 'promise/no-callback-in-promise': 'error',
1010
+ // Promise.all is static-only; new Promise.all() throws.
1011
+ 'promise/no-new-statics': 'error',
1012
+ // Promise.all(notAnArray) rejects at runtime.
1013
+ 'promise/valid-params': 'error',
1014
+ /* ================================================================== *
1015
+ * promise — suspicious
1016
+ * ================================================================== */
1017
+ // A .then that sometimes returns and sometimes doesn't feeds undefined downstream.
1018
+ 'promise/always-return': 'error',
1019
+ // Two code paths resolving the same promise: the second silently loses.
1020
+ 'promise/no-multiple-resolved': 'error',
1021
+ // Promises inside node-style callbacks — pick one async model per boundary.
1022
+ 'promise/no-promise-in-callback': 'error',
1023
+ /* ================================================================== *
1024
+ * promise — style
1025
+ * ================================================================== */
1026
+ // new Promise() is the correct tool for wrapping callback APIs.
1027
+ 'promise/avoid-new': 'off',
1028
+ // Nested .then chains are callback hell with promises; flatten or use await.
1029
+ 'promise/no-nesting': 'error',
1030
+ // resolve(Promise.resolve(x)) double-wraps; return the value.
1031
+ 'promise/no-return-wrap': 'error',
1032
+ // (resolve, reject) — the standard names; anything else makes readers translate.
1033
+ 'promise/param-names': 'error',
1034
+ // Callback APIs deserve promisification at the boundary, not propagation.
1035
+ 'promise/prefer-await-to-callbacks': 'error',
1036
+ // await reads top-to-bottom; .then chains read inside-out.
1037
+ 'promise/prefer-await-to-then': 'error',
1038
+ // .catch(fn) over .then(undefined, fn) — the two-arg form skips same-handler errors.
1039
+ 'promise/prefer-catch': 'error',
1040
+ /* ================================================================== *
1041
+ * promise — restriction
1042
+ * ================================================================== */
1043
+ // Every .then chain must end handled (caught or returned) — the syntax-level
1044
+ // floating-promise guard for non-type-aware runs.
1045
+ 'promise/catch-or-return': 'error',
1046
+ // Flags newly-standardized statics (Promise.try) as nonstandard; too eager.
1047
+ 'promise/spec-only': 'off',
1048
+ /* ================================================================== *
1049
+ * promise — nursery (off: not stabilized)
1050
+ * ================================================================== */
1051
+ // Nursery: revisit when stabilized.
1052
+ 'promise/no-return-in-finally': 'off',
1053
+ /* ================================================================== *
1054
+ * node
1055
+ * ================================================================== */
1056
+ // Callback-era Node style; ESM codebases promisify instead.
1057
+ 'node/callback-return': 'off',
1058
+ // CJS-era rule; import/no-commonjs already bans require wholesale.
1059
+ 'node/global-require': 'off',
1060
+ // Which flavor of CJS export to use is moot — no-commonjs bans them all.
1061
+ 'node/exports-style': 'off',
1062
+ // Assigning to `exports` alone silently breaks — module.exports is the binding.
1063
+ 'node/no-exports-assign': 'error',
1064
+ // CJS-era rule about require grouping; moot under no-commonjs.
1065
+ 'node/no-mixed-requires': 'off',
1066
+ // Sync fs calls are legitimate in CLIs/scripts/startup (off per cacographer).
1067
+ 'node/no-sync': 'off',
1068
+ // Callback-era error handling; promises carry errors now.
1069
+ 'node/handle-callback-err': 'off',
1070
+ // CJS-era (new require()); moot under no-commonjs.
1071
+ 'node/no-new-require': 'off',
1072
+ // __dirname + "/file" breaks on Windows; path.join.
1073
+ 'node/no-path-concat': 'error',
1074
+ // Reading process.env is how configuration works; banning it is impractical.
1075
+ 'node/no-process-env': 'off',
1076
+ // Top-level await is standard ESM; unicorn/prefer-top-level-await is equally off —
1077
+ // no stance either direction.
1078
+ 'node/no-top-level-await': 'off',
1079
+ /* ================================================================== *
1080
+ * jsdoc
1081
+ *
1082
+ * Stance: every EXPORTED symbol should have JSDoc; internal code doesn't
1083
+ * need it. oxlint's jsdoc plugin has no require-jsdoc rule yet (upstream's
1084
+ * has publicOnly — exactly this stance), so existence-on-exports stays a
1085
+ * review expectation for now. What IS enforced: any doc you do write must
1086
+ * be complete and descriptive — half-documented is worse than undocumented.
1087
+ * Types never go in JSDoc; TypeScript owns them.
1088
+ * ================================================================== */
1089
+ // @property names that don't match the object shape are lies.
1090
+ 'jsdoc/check-property-names': 'error',
1091
+ // @returnz and made-up tags render as nothing everywhere.
1092
+ 'jsdoc/check-tag-names': 'error',
1093
+ // @implements on a non-class is meaningless.
1094
+ 'jsdoc/implements-on-classes': 'error',
1095
+ // Defaults documented in JSDoc drift from the code's actual defaults.
1096
+ 'jsdoc/no-defaults': 'error',
1097
+ // Demanding @property blocks on every typedef is ceremony; completeness
1098
+ // rules below govern the ones you write.
1099
+ 'jsdoc/require-property': 'off',
1100
+ // A @property tag with no description tells the reader nothing.
1101
+ 'jsdoc/require-property-description': 'error',
1102
+ // A @property tag with no name documents nothing at all.
1103
+ 'jsdoc/require-property-name': 'error',
1104
+ // Types live in TypeScript, not JSDoc annotations.
1105
+ 'jsdoc/require-property-type': 'off',
1106
+ // Description-only docs on generators are fine; don't force @yields tags.
1107
+ 'jsdoc/require-yields': 'off',
1108
+ // If you document SOME params you must document them all — a partial list
1109
+ // reads as complete and misleads. One-line description-only docs stay legal
1110
+ // (ignoreWhenAllParamsMissing), which is what keeps this off internal code's back.
1111
+ 'jsdoc/require-param': ['error', { ignoreWhenAllParamsMissing: true }],
1112
+ // A @param with no description is a name the signature already shows.
1113
+ 'jsdoc/require-param-description': 'error',
1114
+ // A @param with no name can't be matched to a parameter.
1115
+ 'jsdoc/require-param-name': 'error',
1116
+ // Types live in TypeScript; @param {type} duplicates and drifts.
1117
+ 'jsdoc/require-param-type': 'off',
1118
+ // Description-only docs are fine; don't force a @returns tag onto every function.
1119
+ 'jsdoc/require-returns': 'off',
1120
+ // But a @returns you did write with no description says nothing.
1121
+ 'jsdoc/require-returns-description': 'error',
1122
+ // Types live in TypeScript; @returns {type} duplicates and drifts.
1123
+ 'jsdoc/require-returns-type': 'off',
1124
+ // Types live in TypeScript — and it has no throws clause to sync with anyway.
1125
+ 'jsdoc/require-throws-type': 'off',
1126
+ // Types live in TypeScript.
1127
+ 'jsdoc/require-yields-type': 'off',
1128
+ // A @throws with no description doesn't say when or why it throws.
1129
+ 'jsdoc/require-throws-description': 'error',
1130
+ // A @yields with no description doesn't say what comes out.
1131
+ 'jsdoc/require-yields-description': 'error',
1132
+ // @access/@public/@private validation — on, since wrong access tags mislead.
1133
+ 'jsdoc/check-access': 'error',
1134
+ // @param with no name/description is an empty tag — either fill it or delete it.
1135
+ 'jsdoc/empty-tags': 'error'
1136
+ },
1137
+ overrides: [
1138
+ {
1139
+ // Ambient declaration files augment existing scopes (SvelteKit's app.d.ts
1140
+ // App namespace, module augmentation) — declaration merging only works
1141
+ // with interface, so forcing type there breaks the file's whole purpose.
1142
+ files: ['**/*.d.ts'],
1143
+ rules: {
1144
+ 'typescript/consistent-type-definitions': 'off'
1145
+ }
1146
+ },
1147
+ {
1148
+ // Tests trade some rigor for expressiveness: console output for debugging,
1149
+ // any/! for constructing intentionally-invalid fixtures, multiple tiny
1150
+ // classes for scenario setup.
1151
+ files: ['**/*.test.ts', '**/*.spec.ts', 'tests/**/*.ts', '**/testUtils.ts'],
1152
+ rules: {
1153
+ 'eslint/no-console': 'off',
1154
+ 'typescript/no-explicit-any': 'off',
1155
+ 'typescript/no-non-null-assertion': 'off',
1156
+ 'eslint/max-classes-per-file': 'off',
1157
+ // Type-aware rule: only fires for consumers who also extend type-aware.ts.
1158
+ 'typescript/no-unsafe-type-assertion': 'off'
1159
+ }
1160
+ }
1161
+ ],
1162
+ // Build artifacts only — project-specific ignores (generated code, configs)
1163
+ // belong in the consumer's own ignorePatterns.
1164
+ ignorePatterns: ['node_modules', 'dist', 'build', '.svelte-kit']
1165
+ });