eslint-config-setup 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/configs/0b23ff88.js +138 -24
- package/dist/configs/12c62446.js +155 -9
- package/dist/configs/196d687e.js +281 -47
- package/dist/configs/1c3f743c.js +157 -9
- package/dist/configs/2f6f3a82.js +303 -39
- package/dist/configs/4eb62e57.js +140 -21
- package/dist/configs/52762a42.js +278 -34
- package/dist/configs/532f50a4.js +476 -49
- package/dist/configs/5a302873.js +301 -39
- package/dist/configs/6bc0d588.js +442 -57
- package/dist/configs/91e82988.js +478 -49
- package/dist/configs/c2fecd3d.js +280 -31
- package/dist/configs/cde010b4.js +309 -16
- package/dist/configs/d537b683.js +444 -54
- package/dist/configs/db69ebb6.js +283 -44
- package/dist/configs/e4b137fa.js +307 -16
- package/dist/index.d.ts +13 -13
- package/dist/index.js +65 -67
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +39 -40
- package/dist/modules.js +1136 -1105
- package/dist/modules.js.map +1 -1
- package/dist/oxlint-configs/3047b7d6.json +243 -148
- package/dist/oxlint-configs/58d5e03b.json +242 -148
- package/dist/oxlint-configs/78b40daa.json +274 -158
- package/dist/oxlint-configs/7ff0d87e.json +326 -244
- package/dist/oxlint-configs/85912bf0.json +326 -243
- package/dist/oxlint-configs/9dc42dc3.json +273 -158
- package/dist/oxlint-configs/d46cb9b7.json +351 -246
- package/dist/oxlint-configs/ef643c60.json +352 -246
- package/dist/{types-CAUUIuJR.d.ts → types-CAO1PbsB.d.ts} +7 -7
- package/package.json +32 -31
package/dist/modules.js
CHANGED
|
@@ -1,594 +1,81 @@
|
|
|
1
1
|
// src/configs/ai.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"prefer-named-capture-group": "error",
|
|
79
|
-
// Require Unicode-aware regex (`u` or `v` flag) — correct string handling
|
|
80
|
-
// https://eslint.org/docs/latest/rules/require-unicode-regexp
|
|
81
|
-
"require-unicode-regexp": "error",
|
|
82
|
-
// ── B. Magic numbers & constants — no unexplained code ────────
|
|
83
|
-
// No magic numbers — extract to named constants (allows -1, 0, 1, 2)
|
|
84
|
-
// https://typescript-eslint.io/rules/no-magic-numbers
|
|
85
|
-
"@typescript-eslint/no-magic-numbers": [
|
|
86
|
-
"error",
|
|
87
|
-
{
|
|
88
|
-
ignore: [-1, 0, 1, 2],
|
|
89
|
-
ignoreArrayIndexes: true,
|
|
90
|
-
ignoreDefaultValues: true,
|
|
91
|
-
enforceConst: true,
|
|
92
|
-
ignoreClassFieldInitialValues: true,
|
|
93
|
-
ignoreEnums: true,
|
|
94
|
-
ignoreNumericLiteralTypes: true,
|
|
95
|
-
ignoreReadonlyClassProperties: true,
|
|
96
|
-
ignoreTypeIndexes: true
|
|
97
|
-
}
|
|
98
|
-
],
|
|
99
|
-
// No duplicate strings (threshold 3) — extract to constant
|
|
100
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1192/javascript
|
|
101
|
-
"sonarjs/no-duplicate-string": ["error", { threshold: 3 }],
|
|
102
|
-
// Flag TODO/FIXME/HACK comments — technical debt tracker
|
|
103
|
-
// https://eslint.org/docs/latest/rules/no-warning-comments
|
|
104
|
-
"no-warning-comments": "warn",
|
|
105
|
-
// ── F. Async/Promise hygiene ──────────────────────────────────
|
|
106
|
-
// No await inside loops — use Promise.all() for parallel execution
|
|
107
|
-
// https://eslint.org/docs/latest/rules/no-await-in-loop
|
|
108
|
-
"no-await-in-loop": "error",
|
|
109
|
-
// Disallow returning values from Promise executors — use resolve/reject
|
|
110
|
-
// https://eslint.org/docs/latest/rules/no-promise-executor-return
|
|
111
|
-
"no-promise-executor-return": "error",
|
|
112
|
-
// Every Promise must be awaited, returned, or voided — prevents silent failures
|
|
113
|
-
// https://typescript-eslint.io/rules/no-floating-promises
|
|
114
|
-
"@typescript-eslint/no-floating-promises": [
|
|
115
|
-
"error",
|
|
116
|
-
{ checkThenables: true, ignoreVoid: true }
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: "eslint-config-setup/ai-typescript",
|
|
122
|
-
rules: {
|
|
123
|
-
// ── C. TypeScript strictness — explicit types, safe patterns ──
|
|
124
|
-
// Require explicit return types — self-documenting function signatures
|
|
125
|
-
// https://typescript-eslint.io/rules/explicit-function-return-type
|
|
126
|
-
"@typescript-eslint/explicit-function-return-type": [
|
|
127
|
-
"error",
|
|
128
|
-
{
|
|
129
|
-
allowExpressions: true,
|
|
130
|
-
allowTypedFunctionExpressions: true,
|
|
131
|
-
allowHigherOrderFunctions: true,
|
|
132
|
-
allowIIFEs: true
|
|
133
|
-
}
|
|
134
|
-
],
|
|
135
|
-
// Enforce consistent naming: camelCase for values, PascalCase for types,
|
|
136
|
-
// is/has/can/should/will/did prefix for boolean variables
|
|
137
|
-
// https://typescript-eslint.io/rules/naming-convention
|
|
138
|
-
"@typescript-eslint/naming-convention": [
|
|
139
|
-
"error",
|
|
140
|
-
{
|
|
141
|
-
selector: "variable",
|
|
142
|
-
format: isReact ? ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] : ["strictCamelCase", "UPPER_CASE"],
|
|
143
|
-
leadingUnderscore: "allowSingleOrDouble",
|
|
144
|
-
trailingUnderscore: "allow",
|
|
145
|
-
filter: { regex: "[- ]", match: false }
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
selector: "function",
|
|
149
|
-
format: isReact ? ["strictCamelCase", "StrictPascalCase"] : ["strictCamelCase"]
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
selector: "parameter",
|
|
153
|
-
format: ["strictCamelCase"],
|
|
154
|
-
leadingUnderscore: "allow"
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
selector: "import",
|
|
158
|
-
format: ["strictCamelCase", "StrictPascalCase", "UPPER_CASE"]
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
selector: [
|
|
162
|
-
"classProperty",
|
|
163
|
-
"parameterProperty",
|
|
164
|
-
"classMethod",
|
|
165
|
-
"objectLiteralMethod",
|
|
166
|
-
"typeMethod",
|
|
167
|
-
"accessor"
|
|
168
|
-
],
|
|
169
|
-
format: ["strictCamelCase"],
|
|
170
|
-
leadingUnderscore: "allowSingleOrDouble",
|
|
171
|
-
trailingUnderscore: "allow",
|
|
172
|
-
filter: { regex: "[- ]", match: false }
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
selector: ["objectLiteralProperty", "typeProperty"],
|
|
176
|
-
format: null
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
selector: "typeLike",
|
|
180
|
-
format: ["StrictPascalCase"]
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
// No "I" prefix on interfaces — use descriptive names (XO convention)
|
|
184
|
-
selector: "interface",
|
|
185
|
-
format: ["StrictPascalCase"],
|
|
186
|
-
custom: { regex: "^I[A-Z]", match: false }
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
// Type parameters: single uppercase letter (T) or PascalCase (TResult)
|
|
190
|
-
selector: "typeParameter",
|
|
191
|
-
format: ["PascalCase"],
|
|
192
|
-
custom: { regex: "^(T([A-Z][a-zA-Z]*)?|[A-Z])$", match: true }
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
selector: "variable",
|
|
196
|
-
types: ["boolean"],
|
|
197
|
-
format: ["StrictPascalCase"],
|
|
198
|
-
prefix: ["is", "has", "can", "should", "will", "did"]
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
selector: ["classProperty", "objectLiteralProperty"],
|
|
202
|
-
format: null,
|
|
203
|
-
modifiers: ["requiresQuotes"]
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
// Enforce `import type { T }` — types are erased at compile time
|
|
207
|
-
// https://typescript-eslint.io/rules/consistent-type-imports
|
|
208
|
-
"@typescript-eslint/consistent-type-imports": [
|
|
209
|
-
"error",
|
|
210
|
-
{ fixStyle: "inline-type-imports" }
|
|
211
|
-
],
|
|
212
|
-
// Enforce `export type { T }` — matches import convention
|
|
213
|
-
// https://typescript-eslint.io/rules/consistent-type-exports
|
|
214
|
-
"@typescript-eslint/consistent-type-exports": [
|
|
215
|
-
"error",
|
|
216
|
-
{ fixMixedExportsWithInlineTypeSpecifier: true }
|
|
217
|
-
],
|
|
218
|
-
// Disallow `any` type — auto-fix to `unknown` for type safety
|
|
219
|
-
// https://typescript-eslint.io/rules/no-explicit-any
|
|
220
|
-
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
|
|
221
|
-
// Prefer readonly for unmodified class properties — signals immutability
|
|
222
|
-
// https://typescript-eslint.io/rules/prefer-readonly
|
|
223
|
-
"@typescript-eslint/prefer-readonly": "error",
|
|
224
|
-
// Functions returning promises must be async — consistent async patterns
|
|
225
|
-
// https://typescript-eslint.io/rules/promise-function-async
|
|
226
|
-
"@typescript-eslint/promise-function-async": "error",
|
|
227
|
-
// Exhaustive switch statements — no missing cases, no unnecessary defaults
|
|
228
|
-
// https://typescript-eslint.io/rules/switch-exhaustiveness-check
|
|
229
|
-
"@typescript-eslint/switch-exhaustiveness-check": [
|
|
230
|
-
"error",
|
|
231
|
-
{
|
|
232
|
-
allowDefaultCaseForExhaustiveSwitch: false,
|
|
233
|
-
requireDefaultForNonUnion: true
|
|
234
|
-
}
|
|
235
|
-
],
|
|
236
|
-
// Disallow unsafe type assertions (as Type) — use type guards instead
|
|
237
|
-
// https://typescript-eslint.io/rules/no-unsafe-type-assertion
|
|
238
|
-
"@typescript-eslint/no-unsafe-type-assertion": "error",
|
|
239
|
-
// Require comparator for Array.sort() — prevents locale-dependent string sort
|
|
240
|
-
// https://typescript-eslint.io/rules/require-array-sort-compare
|
|
241
|
-
"@typescript-eslint/require-array-sort-compare": [
|
|
242
|
-
"error",
|
|
243
|
-
{ ignoreStringArrays: true }
|
|
244
|
-
],
|
|
245
|
-
// Require explicit `public`/`private`/`protected` on class members
|
|
246
|
-
// https://typescript-eslint.io/rules/explicit-member-accessibility
|
|
247
|
-
"@typescript-eslint/explicit-member-accessibility": "error",
|
|
248
|
-
// Enforce property style for method signatures — prevents bivariance issues
|
|
249
|
-
// https://typescript-eslint.io/rules/method-signature-style
|
|
250
|
-
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
251
|
-
// Require explicit values for enum members — prevents accidental shifts on reorder
|
|
252
|
-
// https://typescript-eslint.io/rules/prefer-enum-initializers
|
|
253
|
-
"@typescript-eslint/prefer-enum-initializers": "error",
|
|
254
|
-
// Prefer `type` over `interface` — consistent, supports unions/intersections
|
|
255
|
-
// https://typescript-eslint.io/rules/consistent-type-definitions
|
|
256
|
-
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
|
257
|
-
// Enforce consistent member ordering in classes and interfaces
|
|
258
|
-
// Static → fields by visibility → constructors → methods by visibility (XO convention)
|
|
259
|
-
// https://typescript-eslint.io/rules/member-ordering
|
|
260
|
-
"@typescript-eslint/member-ordering": [
|
|
261
|
-
"warn",
|
|
262
|
-
{
|
|
263
|
-
default: [
|
|
264
|
-
// Index signature
|
|
265
|
-
"signature",
|
|
266
|
-
"call-signature",
|
|
267
|
-
// Static
|
|
268
|
-
"public-static-field",
|
|
269
|
-
"protected-static-field",
|
|
270
|
-
"private-static-field",
|
|
271
|
-
"#private-static-field",
|
|
272
|
-
"static-field",
|
|
273
|
-
"public-static-method",
|
|
274
|
-
"protected-static-method",
|
|
275
|
-
"private-static-method",
|
|
276
|
-
"#private-static-method",
|
|
277
|
-
"static-method",
|
|
278
|
-
// Fields
|
|
279
|
-
"public-decorated-field",
|
|
280
|
-
"protected-decorated-field",
|
|
281
|
-
"private-decorated-field",
|
|
282
|
-
"public-instance-field",
|
|
283
|
-
"protected-instance-field",
|
|
284
|
-
"private-instance-field",
|
|
285
|
-
"#private-instance-field",
|
|
286
|
-
"public-abstract-field",
|
|
287
|
-
"protected-abstract-field",
|
|
288
|
-
"field",
|
|
289
|
-
// Constructors
|
|
290
|
-
"public-constructor",
|
|
291
|
-
"protected-constructor",
|
|
292
|
-
"private-constructor",
|
|
293
|
-
"constructor",
|
|
294
|
-
// Getters/Setters
|
|
295
|
-
["public-get", "public-set"],
|
|
296
|
-
["protected-get", "protected-set"],
|
|
297
|
-
["private-get", "private-set"],
|
|
298
|
-
["#private-get", "#private-set"],
|
|
299
|
-
// Methods
|
|
300
|
-
"public-decorated-method",
|
|
301
|
-
"protected-decorated-method",
|
|
302
|
-
"private-decorated-method",
|
|
303
|
-
"public-instance-method",
|
|
304
|
-
"protected-instance-method",
|
|
305
|
-
"private-instance-method",
|
|
306
|
-
"#private-instance-method",
|
|
307
|
-
"public-abstract-method",
|
|
308
|
-
"protected-abstract-method",
|
|
309
|
-
"method"
|
|
310
|
-
]
|
|
311
|
-
}
|
|
312
|
-
]
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
name: "eslint-config-setup/ai-unicorn",
|
|
317
|
-
rules: {
|
|
318
|
-
// ── D. Unicorn — modern, idiomatic patterns ───────────────────
|
|
319
|
-
// Move functions to the smallest scope where they're used
|
|
320
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md
|
|
321
|
-
"unicorn/consistent-function-scoping": "error",
|
|
322
|
-
// Forbid blanket `/* eslint-disable */` — must specify rules
|
|
323
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-abusive-eslint-disable.md
|
|
324
|
-
"unicorn/no-abusive-eslint-disable": "error",
|
|
325
|
-
// No .forEach() — use for-of loop (breakable, async-safe)
|
|
326
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-for-each.md
|
|
327
|
-
"unicorn/no-array-for-each": "error",
|
|
328
|
-
// No .reduce() — explicit loops are more readable
|
|
329
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md
|
|
330
|
-
"unicorn/no-array-reduce": "error",
|
|
331
|
-
// Prefer simple ternary over if/else for single-line assignments
|
|
332
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-ternary.md
|
|
333
|
-
"unicorn/prefer-ternary": ["error", "only-single-line"],
|
|
334
|
-
// Prefer switch for 3+ conditions on same variable — structured
|
|
335
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-switch.md
|
|
336
|
-
"unicorn/prefer-switch": ["error", { minimumCases: 3 }],
|
|
337
|
-
// Enforce camelCase or PascalCase filenames — consistent project structure
|
|
338
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md
|
|
339
|
-
"unicorn/filename-case": [
|
|
340
|
-
"error",
|
|
341
|
-
{ cases: { camelCase: true, pascalCase: true } }
|
|
342
|
-
],
|
|
343
|
-
// Prevent abbreviated variable names (e → error, btn → button) — readable
|
|
344
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prevent-abbreviations.md
|
|
345
|
-
"unicorn/prevent-abbreviations": "error",
|
|
346
|
-
// Detect useless switch cases that fall through to the next case
|
|
347
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-switch-case.md
|
|
348
|
-
"unicorn/no-useless-switch-case": "error",
|
|
349
|
-
// Enforce correct Error subclassing (name, constructor pattern)
|
|
350
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/custom-error-definition.md
|
|
351
|
-
"unicorn/custom-error-definition": "error",
|
|
352
|
-
// Prefer default parameters over manual reassignment
|
|
353
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-default-parameters.md
|
|
354
|
-
"unicorn/prefer-default-parameters": "error",
|
|
355
|
-
// Prefer `a || b` over `a ? a : b` — simpler when equivalent
|
|
356
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-logical-operator-over-ternary.md
|
|
357
|
-
"unicorn/prefer-logical-operator-over-ternary": "error",
|
|
358
|
-
// Prefer Math.min()/Math.max() over ternaries for clamping
|
|
359
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-min-max.md
|
|
360
|
-
"unicorn/prefer-math-min-max": "error",
|
|
361
|
-
// Prefer Set#size over converting to array — direct and correct
|
|
362
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-size.md
|
|
363
|
-
"unicorn/prefer-set-size": "error",
|
|
364
|
-
// Enforce explicit `.length > 0` / `.length === 0` checks
|
|
365
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/explicit-length-check.md
|
|
366
|
-
"unicorn/explicit-length-check": "error",
|
|
367
|
-
// Prefer for-of over C-style for loops — no off-by-one risk
|
|
368
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md
|
|
369
|
-
"unicorn/no-for-loop": "error",
|
|
370
|
-
// Enforce braces in switch cases — prevents scope leakage
|
|
371
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/switch-case-braces.md
|
|
372
|
-
"unicorn/switch-case-braces": "error",
|
|
373
|
-
// Combine multiple .push() calls into one — cleaner
|
|
374
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-push-push.md
|
|
375
|
-
"unicorn/no-array-push-push": "error"
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: "eslint-config-setup/ai-sonarjs",
|
|
380
|
-
rules: {
|
|
381
|
-
// ── E. SonarJS — code quality and duplicates ──────────────────
|
|
382
|
-
// Detect copy-pasted functions — extract to shared helper
|
|
383
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4144/javascript
|
|
384
|
-
"sonarjs/no-identical-functions": "error",
|
|
385
|
-
// Merge nested if-statements that can be combined — reduce nesting
|
|
386
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1066/javascript
|
|
387
|
-
"sonarjs/no-collapsible-if": "error",
|
|
388
|
-
// Simplify redundant boolean expressions
|
|
389
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1125/javascript
|
|
390
|
-
"sonarjs/no-redundant-boolean": "error",
|
|
391
|
-
// Detect collections that are populated but never read — dead code
|
|
392
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4030/javascript
|
|
393
|
-
"sonarjs/no-unused-collection": "error",
|
|
394
|
-
// Return value directly instead of storing in temp variable
|
|
395
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1488/javascript
|
|
396
|
-
"sonarjs/prefer-immediate-return": "error",
|
|
397
|
-
// Simplify boolean return patterns
|
|
398
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1126/javascript
|
|
399
|
-
"sonarjs/prefer-single-boolean-return": "error",
|
|
400
|
-
// Detect identical sub-expressions on both sides of operator
|
|
401
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1764/javascript
|
|
402
|
-
"sonarjs/no-identical-expressions": "error",
|
|
403
|
-
// Simplify negated boolean checks — `!a !== b` → `a === b`
|
|
404
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1940/javascript
|
|
405
|
-
"sonarjs/no-inverted-boolean-check": "error",
|
|
406
|
-
// Disallow nested switch statements — extract to function
|
|
407
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1821/javascript
|
|
408
|
-
"sonarjs/no-nested-switch": "error",
|
|
409
|
-
// Disallow nested template literals — unreadable
|
|
410
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4624/javascript
|
|
411
|
-
"sonarjs/no-nested-template-literals": "error",
|
|
412
|
-
// Limit union type size — too many members signals missing abstraction
|
|
413
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4622/javascript
|
|
414
|
-
"sonarjs/max-union-size": ["error", { threshold: 5 }],
|
|
415
|
-
// Prefer type predicates for type narrowing — safer than assertions
|
|
416
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4322/javascript
|
|
417
|
-
"sonarjs/prefer-type-guard": "error",
|
|
418
|
-
// Public static fields should be readonly — prevents accidental mutation
|
|
419
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1444/javascript
|
|
420
|
-
"sonarjs/public-static-readonly": "error"
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
name: "eslint-config-setup/ai-regexp",
|
|
425
|
-
rules: {
|
|
426
|
-
// Prefer lookarounds over capturing groups used only for context
|
|
427
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-lookaround.html
|
|
428
|
-
"regexp/prefer-lookaround": "error",
|
|
429
|
-
// Prefer named backreferences — \k<quote> over \1
|
|
430
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-named-backreference.html
|
|
431
|
-
"regexp/prefer-named-backreference": "error",
|
|
432
|
-
// Prefer named replacement — $<name> over $1
|
|
433
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-named-replacement.html
|
|
434
|
-
"regexp/prefer-named-replacement": "error",
|
|
435
|
-
// Prefer quantifier shorthand — a{3} over aaa
|
|
436
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-quantifier.html
|
|
437
|
-
"regexp/prefer-quantifier": "error",
|
|
438
|
-
// Prefer match.groups.name over match[1] — consistent named groups usage
|
|
439
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-result-array-groups.html
|
|
440
|
-
"regexp/prefer-result-array-groups": "error",
|
|
441
|
-
// Require v flag (unicode sets) over u flag — stricter ES2024 superset
|
|
442
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/require-unicode-sets-regexp.html
|
|
443
|
-
"regexp/require-unicode-sets-regexp": "error"
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
{
|
|
447
|
-
name: "eslint-config-setup/ai-jsdoc",
|
|
448
|
-
rules: {
|
|
449
|
-
// Prevent comments that just repeat the name — `/** The name */ name: string`
|
|
450
|
-
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
|
|
451
|
-
"jsdoc/informative-docs": "error",
|
|
452
|
-
// AI should document parameters and return values completely
|
|
453
|
-
"jsdoc/require-param": "error",
|
|
454
|
-
"jsdoc/require-returns": "error"
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
name: "eslint-config-setup/ai-node",
|
|
459
|
-
rules: {
|
|
460
|
-
// Warn when using Node.js builtins not available in the target version
|
|
461
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-unsupported-features/node-builtins.md
|
|
462
|
-
"node/no-unsupported-features/node-builtins": "error"
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
name: "eslint-config-setup/ai-react",
|
|
467
|
-
rules: {
|
|
468
|
-
// No click handlers on static elements without role — use semantic HTML
|
|
469
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-static-element-interactions.md
|
|
470
|
-
"jsx-a11y/no-static-element-interactions": "error",
|
|
471
|
-
// No event handlers on non-interactive elements — use button/link instead
|
|
472
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-interactions.md
|
|
473
|
-
"jsx-a11y/no-noninteractive-element-interactions": "error",
|
|
474
|
-
// Interactive elements (role="button") must be focusable
|
|
475
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/interactive-supports-focus.md
|
|
476
|
-
"jsx-a11y/interactive-supports-focus": "error"
|
|
477
|
-
}
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
name: "eslint-config-setup/ai-complexity",
|
|
481
|
-
rules: {
|
|
482
|
-
// Cyclomatic complexity limit — max branches per function
|
|
483
|
-
// https://eslint.org/docs/latest/rules/complexity
|
|
484
|
-
complexity: ["error", 10],
|
|
485
|
-
// Max nesting depth — deep nesting signals need for extraction
|
|
486
|
-
// https://eslint.org/docs/latest/rules/max-depth
|
|
487
|
-
"max-depth": ["error", 3],
|
|
488
|
-
// Max nested callbacks — prevents callback hell
|
|
489
|
-
// https://eslint.org/docs/latest/rules/max-nested-callbacks
|
|
490
|
-
"max-nested-callbacks": ["error", 2],
|
|
491
|
-
// Max function parameters — many params suggest a config object
|
|
492
|
-
// https://eslint.org/docs/latest/rules/max-params
|
|
493
|
-
"max-params": ["error", 3],
|
|
494
|
-
// Max statements per function — keeps functions focused
|
|
495
|
-
// https://eslint.org/docs/latest/rules/max-statements
|
|
496
|
-
"max-statements": ["error", 15],
|
|
497
|
-
// Max lines per function — encourages extraction of helpers
|
|
498
|
-
// https://eslint.org/docs/latest/rules/max-lines-per-function
|
|
499
|
-
"max-lines-per-function": [
|
|
500
|
-
"error",
|
|
501
|
-
{
|
|
502
|
-
max: 50,
|
|
503
|
-
skipBlankLines: true,
|
|
504
|
-
skipComments: true
|
|
505
|
-
}
|
|
506
|
-
],
|
|
507
|
-
// Max lines per file — encourages modular file organization
|
|
508
|
-
// https://eslint.org/docs/latest/rules/max-lines
|
|
509
|
-
"max-lines": [
|
|
510
|
-
"error",
|
|
511
|
-
{
|
|
512
|
-
max: 300,
|
|
513
|
-
skipBlankLines: true,
|
|
514
|
-
skipComments: true
|
|
515
|
-
}
|
|
516
|
-
],
|
|
517
|
-
// Cognitive complexity — measures how hard a function is to understand
|
|
518
|
-
// https://sonarsource.github.io/rspec/#/rspec/S3776/javascript
|
|
519
|
-
"sonarjs/cognitive-complexity": ["error", 10]
|
|
520
|
-
}
|
|
521
|
-
},
|
|
522
|
-
{
|
|
523
|
-
name: "eslint-config-setup/ai-tests-strict",
|
|
524
|
-
files: ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"],
|
|
525
|
-
rules: {
|
|
526
|
-
// Every test must be inside a describe block — organized test suites
|
|
527
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-top-level-describe.md
|
|
528
|
-
"vitest/require-top-level-describe": "error",
|
|
529
|
-
// Hooks (beforeEach, afterEach) must be at the top of describe — predictable setup
|
|
530
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-hooks-on-top.md
|
|
531
|
-
"vitest/prefer-hooks-on-top": "error"
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
|
-
{
|
|
535
|
-
name: "eslint-config-setup/ai-tests-relaxed",
|
|
536
|
-
files: ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"],
|
|
537
|
-
rules: {
|
|
538
|
-
"max-lines": "off",
|
|
539
|
-
"max-lines-per-function": "off",
|
|
540
|
-
"max-statements": "off",
|
|
541
|
-
"max-nested-callbacks": "off",
|
|
542
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
543
|
-
"sonarjs/no-duplicate-string": "off",
|
|
544
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
545
|
-
"@typescript-eslint/naming-convention": "off",
|
|
546
|
-
"unicorn/prevent-abbreviations": "off"
|
|
547
|
-
}
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
name: "eslint-config-setup/ai-e2e-relaxed",
|
|
551
|
-
files: ["**/*.spec.ts"],
|
|
552
|
-
rules: {
|
|
553
|
-
"max-lines": "off",
|
|
554
|
-
"max-lines-per-function": "off",
|
|
555
|
-
"max-statements": "off",
|
|
556
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
557
|
-
"@typescript-eslint/explicit-function-return-type": "off"
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
name: "eslint-config-setup/ai-config-relaxed",
|
|
562
|
-
files: [
|
|
563
|
-
"**/*.config.{ts,mts,cts,js,mjs,cjs}",
|
|
564
|
-
"**/vite.config.*",
|
|
565
|
-
"**/vitest.config.*",
|
|
566
|
-
"**/next.config.*"
|
|
567
|
-
],
|
|
568
|
-
rules: {
|
|
569
|
-
complexity: "off",
|
|
570
|
-
"max-lines": "off",
|
|
571
|
-
"max-lines-per-function": "off",
|
|
572
|
-
"max-statements": "off",
|
|
573
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
574
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
575
|
-
"@typescript-eslint/naming-convention": "off"
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
name: "eslint-config-setup/ai-declarations-relaxed",
|
|
580
|
-
files: ["**/*.d.ts"],
|
|
581
|
-
rules: {
|
|
582
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
583
|
-
"@typescript-eslint/naming-convention": "off",
|
|
584
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
585
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
586
|
-
"unicorn/prevent-abbreviations": "off",
|
|
587
|
-
"unicorn/filename-case": "off"
|
|
588
|
-
}
|
|
589
|
-
}
|
|
2
|
+
var TEST_FILES = ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"];
|
|
3
|
+
var E2E_FILES = ["**/*.spec.ts"];
|
|
4
|
+
var CONFIG_FILES = [
|
|
5
|
+
"**/*.config.{ts,mts,cts,js,mjs,cjs}",
|
|
6
|
+
"**/vite.config.*",
|
|
7
|
+
"**/vitest.config.*",
|
|
8
|
+
"**/next.config.*"
|
|
9
|
+
];
|
|
10
|
+
var AI_TESTS_STRICT_CONFIG = {
|
|
11
|
+
name: "eslint-config-setup/ai-tests-strict",
|
|
12
|
+
files: TEST_FILES,
|
|
13
|
+
rules: {
|
|
14
|
+
// Every test must be inside a describe block — organized test suites
|
|
15
|
+
"vitest/require-top-level-describe": "error",
|
|
16
|
+
// Hooks (beforeEach, afterEach) must be at the top of describe — predictable setup
|
|
17
|
+
"vitest/prefer-hooks-on-top": "error"
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var AI_TESTS_RELAXED_CONFIG = {
|
|
21
|
+
name: "eslint-config-setup/ai-tests-relaxed",
|
|
22
|
+
files: TEST_FILES,
|
|
23
|
+
rules: {
|
|
24
|
+
"max-lines": "off",
|
|
25
|
+
"max-lines-per-function": "off",
|
|
26
|
+
"max-statements": "off",
|
|
27
|
+
"max-nested-callbacks": "off",
|
|
28
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
29
|
+
"sonarjs/no-duplicate-string": "off",
|
|
30
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
31
|
+
"@typescript-eslint/naming-convention": "off",
|
|
32
|
+
"unicorn/prevent-abbreviations": "off"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var AI_E2E_RELAXED_CONFIG = {
|
|
36
|
+
name: "eslint-config-setup/ai-e2e-relaxed",
|
|
37
|
+
files: E2E_FILES,
|
|
38
|
+
rules: {
|
|
39
|
+
"max-lines": "off",
|
|
40
|
+
"max-lines-per-function": "off",
|
|
41
|
+
"max-statements": "off",
|
|
42
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
43
|
+
"@typescript-eslint/explicit-function-return-type": "off"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var AI_CONFIG_RELAXED_CONFIG = {
|
|
47
|
+
name: "eslint-config-setup/ai-config-relaxed",
|
|
48
|
+
files: CONFIG_FILES,
|
|
49
|
+
rules: {
|
|
50
|
+
complexity: "off",
|
|
51
|
+
"max-lines": "off",
|
|
52
|
+
"max-lines-per-function": "off",
|
|
53
|
+
"max-statements": "off",
|
|
54
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
55
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
56
|
+
"@typescript-eslint/naming-convention": "off"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var AI_DECLARATIONS_RELAXED_CONFIG = {
|
|
60
|
+
name: "eslint-config-setup/ai-declarations-relaxed",
|
|
61
|
+
files: ["**/*.d.ts"],
|
|
62
|
+
rules: {
|
|
63
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
64
|
+
"@typescript-eslint/naming-convention": "off",
|
|
65
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
66
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
67
|
+
"unicorn/prevent-abbreviations": "off",
|
|
68
|
+
"unicorn/filename-case": "off"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function aiConfig() {
|
|
72
|
+
return [
|
|
73
|
+
AI_TESTS_STRICT_CONFIG,
|
|
74
|
+
AI_TESTS_RELAXED_CONFIG,
|
|
75
|
+
AI_E2E_RELAXED_CONFIG,
|
|
76
|
+
AI_CONFIG_RELAXED_CONFIG,
|
|
77
|
+
AI_DECLARATIONS_RELAXED_CONFIG
|
|
590
78
|
];
|
|
591
|
-
return configs;
|
|
592
79
|
}
|
|
593
80
|
|
|
594
81
|
// src/configs/base.ts
|
|
@@ -596,7 +83,7 @@ import eslint from "@eslint/js";
|
|
|
596
83
|
|
|
597
84
|
// src/build/config-builder.ts
|
|
598
85
|
function createConfig(options) {
|
|
599
|
-
const
|
|
86
|
+
const presetRules2 = /* @__PURE__ */ new Map();
|
|
600
87
|
const presetPlugins = {};
|
|
601
88
|
if (options.presets) {
|
|
602
89
|
for (const preset of options.presets) {
|
|
@@ -606,7 +93,7 @@ function createConfig(options) {
|
|
|
606
93
|
if (preset.rules) {
|
|
607
94
|
for (const [name, value] of Object.entries(preset.rules)) {
|
|
608
95
|
if (value !== void 0) {
|
|
609
|
-
|
|
96
|
+
presetRules2.set(name, value);
|
|
610
97
|
}
|
|
611
98
|
}
|
|
612
99
|
}
|
|
@@ -619,7 +106,7 @@ function createConfig(options) {
|
|
|
619
106
|
const fileOverrides = [];
|
|
620
107
|
const builder = {
|
|
621
108
|
overrideRule(name, value) {
|
|
622
|
-
if (!
|
|
109
|
+
if (!presetRules2.has(name)) {
|
|
623
110
|
throw new Error(
|
|
624
111
|
`overrideRule("${name}"): rule not found in preset. Cannot override a rule that doesn't exist in the preset.`
|
|
625
112
|
);
|
|
@@ -627,8 +114,34 @@ function createConfig(options) {
|
|
|
627
114
|
overrides.set(name, value);
|
|
628
115
|
return builder;
|
|
629
116
|
},
|
|
117
|
+
overrideSeverity(name, severity) {
|
|
118
|
+
if (!presetRules2.has(name)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`overrideSeverity("${name}"): rule not found in preset. Cannot override severity of a rule that doesn't exist in the preset.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const existing = presetRules2.get(name);
|
|
124
|
+
if (Array.isArray(existing)) {
|
|
125
|
+
const [, ...restOptions] = existing;
|
|
126
|
+
overrides.set(name, [severity, ...restOptions]);
|
|
127
|
+
} else {
|
|
128
|
+
overrides.set(name, severity);
|
|
129
|
+
}
|
|
130
|
+
return builder;
|
|
131
|
+
},
|
|
132
|
+
overrideOptions(name, ...ruleOptions) {
|
|
133
|
+
if (!presetRules2.has(name)) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`overrideOptions("${name}"): rule not found in preset. Cannot override options of a rule that doesn't exist in the preset.`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const existing = presetRules2.get(name);
|
|
139
|
+
const severity = Array.isArray(existing) ? existing[0] : existing;
|
|
140
|
+
overrides.set(name, [severity, ...ruleOptions]);
|
|
141
|
+
return builder;
|
|
142
|
+
},
|
|
630
143
|
addRule(name, value) {
|
|
631
|
-
if (
|
|
144
|
+
if (presetRules2.has(name)) {
|
|
632
145
|
throw new Error(
|
|
633
146
|
`addRule("${name}"): rule already exists in preset. Use overrideRule() to change its value, or removeRule() to drop it.`
|
|
634
147
|
);
|
|
@@ -642,7 +155,7 @@ function createConfig(options) {
|
|
|
642
155
|
return builder;
|
|
643
156
|
},
|
|
644
157
|
disableRule(name) {
|
|
645
|
-
if (!
|
|
158
|
+
if (!presetRules2.has(name) && !additions.has(name)) {
|
|
646
159
|
throw new Error(
|
|
647
160
|
`disableRule("${name}"): rule not found in preset or additions. Cannot disable a rule that doesn't exist.`
|
|
648
161
|
);
|
|
@@ -651,7 +164,7 @@ function createConfig(options) {
|
|
|
651
164
|
return builder;
|
|
652
165
|
},
|
|
653
166
|
removeRule(name) {
|
|
654
|
-
if (!
|
|
167
|
+
if (!presetRules2.has(name) && !additions.has(name)) {
|
|
655
168
|
throw new Error(
|
|
656
169
|
`removeRule("${name}"): rule not found in preset or additions. Cannot remove a rule that doesn't exist.`
|
|
657
170
|
);
|
|
@@ -665,7 +178,7 @@ function createConfig(options) {
|
|
|
665
178
|
},
|
|
666
179
|
build() {
|
|
667
180
|
const rules = {};
|
|
668
|
-
for (const [name, value] of
|
|
181
|
+
for (const [name, value] of presetRules2) {
|
|
669
182
|
if (!removed.has(name)) {
|
|
670
183
|
rules[name] = value;
|
|
671
184
|
}
|
|
@@ -724,14 +237,147 @@ function createConfig(options) {
|
|
|
724
237
|
}
|
|
725
238
|
|
|
726
239
|
// src/configs/base.ts
|
|
727
|
-
|
|
728
|
-
|
|
240
|
+
var ERROR_PREVENTION_RULES = {
|
|
241
|
+
"accessor-pairs": ["error", { enforceForClassMembers: true }],
|
|
242
|
+
"array-callback-return": ["error", { allowImplicit: true }],
|
|
243
|
+
"no-constructor-return": "error",
|
|
244
|
+
"no-promise-executor-return": "error",
|
|
245
|
+
"no-self-compare": "error",
|
|
246
|
+
"no-template-curly-in-string": "error",
|
|
247
|
+
"no-unreachable-loop": "error",
|
|
248
|
+
"require-atomic-updates": "error",
|
|
249
|
+
"no-unmodified-loop-condition": "error",
|
|
250
|
+
"grouped-accessor-pairs": ["error", "getBeforeSet"],
|
|
251
|
+
"no-useless-rename": "error",
|
|
252
|
+
"no-useless-computed-key": ["error", { enforceForClassMembers: true }]
|
|
253
|
+
};
|
|
254
|
+
var DANGEROUS_PATTERN_RULES = {
|
|
255
|
+
"no-eval": "error",
|
|
256
|
+
"no-alert": "error",
|
|
257
|
+
"no-caller": "error",
|
|
258
|
+
"no-extend-native": "error",
|
|
259
|
+
"no-new-func": "error",
|
|
260
|
+
"no-new-wrappers": "error",
|
|
261
|
+
"no-object-constructor": "error",
|
|
262
|
+
"no-proto": "error",
|
|
263
|
+
"no-iterator": "error",
|
|
264
|
+
"no-script-url": "error",
|
|
265
|
+
"no-octal-escape": "error",
|
|
266
|
+
"no-implicit-globals": "error"
|
|
267
|
+
};
|
|
268
|
+
var CODE_QUALITY_RULES = {
|
|
269
|
+
eqeqeq: ["error", "smart"],
|
|
270
|
+
"guard-for-in": "error",
|
|
271
|
+
"default-case-last": "error",
|
|
272
|
+
radix: "error",
|
|
273
|
+
yoda: "error",
|
|
274
|
+
"no-sequences": ["error", { allowInParentheses: false }],
|
|
275
|
+
"no-new": "error",
|
|
276
|
+
"no-labels": "error",
|
|
277
|
+
"no-extra-bind": "error",
|
|
278
|
+
"no-lone-blocks": "error",
|
|
279
|
+
"no-useless-call": "error",
|
|
280
|
+
"no-useless-concat": "error",
|
|
281
|
+
"no-useless-return": "error",
|
|
282
|
+
"no-return-assign": ["error", "always"],
|
|
283
|
+
"no-multi-str": "error",
|
|
284
|
+
"prefer-regex-literals": ["error", { disallowRedundantWrapping: true }]
|
|
285
|
+
};
|
|
286
|
+
var MODERN_STYLE_RULES = {
|
|
287
|
+
"no-var": "error",
|
|
288
|
+
"prefer-const": ["error", { destructuring: "all" }],
|
|
289
|
+
"prefer-object-has-own": "error",
|
|
290
|
+
"prefer-object-spread": "error",
|
|
291
|
+
"prefer-rest-params": "error",
|
|
292
|
+
"prefer-spread": "error",
|
|
293
|
+
"symbol-description": "error",
|
|
294
|
+
"prefer-numeric-literals": "error",
|
|
295
|
+
"object-shorthand": ["error", "always", { avoidExplicitReturnArrows: true, avoidQuotes: true }]
|
|
296
|
+
};
|
|
297
|
+
var AI_STRUCTURAL_RULES = {
|
|
298
|
+
curly: ["error", "all"],
|
|
299
|
+
"no-else-return": ["error", { allowElseIf: false }],
|
|
300
|
+
"no-nested-ternary": "error",
|
|
301
|
+
"no-unneeded-ternary": "error",
|
|
302
|
+
"no-negated-condition": "error",
|
|
303
|
+
"no-lonely-if": "error",
|
|
304
|
+
"no-param-reassign": ["error", { props: true }],
|
|
305
|
+
"no-multi-assign": "error",
|
|
306
|
+
"one-var": ["error", "never"],
|
|
307
|
+
"no-implicit-coercion": "error",
|
|
308
|
+
"arrow-body-style": "error",
|
|
309
|
+
"prefer-arrow-callback": ["error", { allowNamedFunctions: true }],
|
|
310
|
+
"logical-assignment-operators": ["error", "always", { enforceForIfStatements: true }],
|
|
311
|
+
"max-statements-per-line": ["error", { max: 1 }],
|
|
312
|
+
"prefer-exponentiation-operator": "error",
|
|
313
|
+
"prefer-named-capture-group": "error",
|
|
314
|
+
"require-unicode-regexp": "error",
|
|
315
|
+
"no-warning-comments": "warn",
|
|
316
|
+
"no-await-in-loop": "error"
|
|
317
|
+
};
|
|
318
|
+
function addRules(builder, rules) {
|
|
319
|
+
for (const [ruleName, value] of Object.entries(rules)) {
|
|
320
|
+
builder.addRule(ruleName, value);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function addBaseOverrides(builder) {
|
|
324
|
+
builder.overrideRule("use-isnan", ["error", { enforceForIndexOf: true, enforceForSwitchCase: true }]);
|
|
325
|
+
builder.overrideRule("valid-typeof", ["error", { requireStringLiterals: true }]);
|
|
326
|
+
}
|
|
327
|
+
function addComplexityRules(builder, isAi) {
|
|
328
|
+
builder.addRule("complexity", ["error", isAi ? 10 : 20]);
|
|
329
|
+
builder.addRule("max-depth", ["error", isAi ? 3 : 5]);
|
|
330
|
+
builder.addRule("max-nested-callbacks", ["error", isAi ? 2 : 4]);
|
|
331
|
+
builder.addRule("max-params", ["error", isAi ? 3 : 5]);
|
|
332
|
+
builder.addRule("max-statements", ["error", isAi ? 15 : 25]);
|
|
333
|
+
builder.addRule("max-lines-per-function", [
|
|
334
|
+
"error",
|
|
335
|
+
{ max: isAi ? 100 : 200, skipBlankLines: true, skipComments: true }
|
|
336
|
+
]);
|
|
337
|
+
builder.addRule("max-lines", [
|
|
338
|
+
"error",
|
|
339
|
+
{ max: isAi ? 300 : 500, skipBlankLines: true, skipComments: true }
|
|
340
|
+
]);
|
|
341
|
+
builder.addRule("sonarjs/cognitive-complexity", ["error", isAi ? 10 : 20]);
|
|
342
|
+
}
|
|
343
|
+
function addModernStyleRules(builder, isAi) {
|
|
344
|
+
addRules(builder, MODERN_STYLE_RULES);
|
|
345
|
+
builder.addRule("prefer-template", isAi ? "error" : "warn");
|
|
346
|
+
}
|
|
347
|
+
function baseConfig(opts) {
|
|
348
|
+
const isAi = opts?.ai ?? false;
|
|
349
|
+
const builder = createConfig({
|
|
729
350
|
name: "eslint-config-setup/base",
|
|
730
351
|
presets: [eslint.configs.recommended]
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
352
|
+
});
|
|
353
|
+
addRules(builder, ERROR_PREVENTION_RULES);
|
|
354
|
+
addBaseOverrides(builder);
|
|
355
|
+
addRules(builder, DANGEROUS_PATTERN_RULES);
|
|
356
|
+
addRules(builder, CODE_QUALITY_RULES);
|
|
357
|
+
addComplexityRules(builder, isAi);
|
|
358
|
+
addModernStyleRules(builder, isAi);
|
|
359
|
+
if (isAi) {
|
|
360
|
+
addRules(builder, AI_STRUCTURAL_RULES);
|
|
361
|
+
}
|
|
362
|
+
return builder.build();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/configs/compat.ts
|
|
366
|
+
import compatPlugin from "eslint-plugin-compat";
|
|
367
|
+
function compatConfig() {
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
name: "eslint-config-setup/compat",
|
|
371
|
+
plugins: {
|
|
372
|
+
compat: compatPlugin
|
|
373
|
+
},
|
|
374
|
+
rules: {
|
|
375
|
+
// Warn when using browser APIs not supported in browserslist targets
|
|
376
|
+
// https://github.com/amilajack/eslint-plugin-compat#usage
|
|
377
|
+
"compat/compat": "warn"
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
];
|
|
735
381
|
}
|
|
736
382
|
|
|
737
383
|
// src/configs/cspell.ts
|
|
@@ -791,6 +437,7 @@ function importsConfig() {
|
|
|
791
437
|
{
|
|
792
438
|
name: "eslint-config-setup/imports",
|
|
793
439
|
plugins: {
|
|
440
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Plugin type is compatible
|
|
794
441
|
"import": importXPlugin,
|
|
795
442
|
"unused-imports": unusedImportsPlugin
|
|
796
443
|
},
|
|
@@ -798,7 +445,7 @@ function importsConfig() {
|
|
|
798
445
|
// ── Validation (import-x) ────────────────────────────────────
|
|
799
446
|
// Merge duplicate import paths into one statement — reduces noise
|
|
800
447
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md
|
|
801
|
-
"import/no-duplicates":
|
|
448
|
+
"import/no-duplicates": "error",
|
|
802
449
|
// Forbid a module from importing itself — always a bug
|
|
803
450
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/no-self-import.md
|
|
804
451
|
"import/no-self-import": "error",
|
|
@@ -829,6 +476,30 @@ function importsConfig() {
|
|
|
829
476
|
// Forbid absolute file paths in imports — not portable across machines
|
|
830
477
|
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-absolute-path.md
|
|
831
478
|
"import/no-absolute-path": "error",
|
|
479
|
+
// Forbid imports of packages not listed in package.json — catches phantom deps
|
|
480
|
+
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-extraneous-dependencies.md
|
|
481
|
+
"import/no-extraneous-dependencies": ["error", { includeTypes: true }],
|
|
482
|
+
// Forbid side-effect-only imports — make dependencies explicit
|
|
483
|
+
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-unassigned-import.md
|
|
484
|
+
"import/no-unassigned-import": [
|
|
485
|
+
"error",
|
|
486
|
+
{
|
|
487
|
+
allow: [
|
|
488
|
+
"@babel/polyfill",
|
|
489
|
+
"**/register",
|
|
490
|
+
"**/register.*",
|
|
491
|
+
"**/register/**",
|
|
492
|
+
"**/register/**.*",
|
|
493
|
+
"**/*.css",
|
|
494
|
+
"**/*.scss",
|
|
495
|
+
"**/*.sass",
|
|
496
|
+
"**/*.less"
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
],
|
|
500
|
+
// Forbid `import { default as Foo }` — use `import Foo` instead
|
|
501
|
+
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-named-default.md
|
|
502
|
+
"import/no-named-default": "error",
|
|
832
503
|
// ── Disabled: ordering handled by perfectionist ────────────────
|
|
833
504
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/order.md
|
|
834
505
|
"import/order": "off",
|
|
@@ -845,11 +516,18 @@ function importsConfig() {
|
|
|
845
516
|
|
|
846
517
|
// src/configs/jsdoc.ts
|
|
847
518
|
import jsdocPlugin from "eslint-plugin-jsdoc";
|
|
848
|
-
function jsdocConfig() {
|
|
849
|
-
|
|
519
|
+
function jsdocConfig(opts) {
|
|
520
|
+
const isAi = opts?.ai ?? false;
|
|
521
|
+
const builder = createConfig({
|
|
850
522
|
name: "eslint-config-setup/jsdoc",
|
|
851
523
|
presets: [jsdocPlugin.configs["flat/recommended-typescript-error"]]
|
|
852
|
-
}).overrideRule("jsdoc/require-jsdoc", "off").overrideRule("jsdoc/require-param", "off").overrideRule("jsdoc/require-returns", "off").overrideRule("jsdoc/require-yields", "off").overrideRule("jsdoc/require-param-description", "warn").overrideRule("jsdoc/require-returns-description", "warn").overrideRule("jsdoc/check-tag-names", "error").overrideRule("jsdoc/no-undefined-types", "error").overrideRule("jsdoc/tag-lines", "off")
|
|
524
|
+
}).overrideRule("jsdoc/require-jsdoc", "off").overrideRule("jsdoc/require-param", "off").overrideRule("jsdoc/require-returns", "off").overrideRule("jsdoc/require-yields", "off").overrideRule("jsdoc/require-param-description", "warn").overrideRule("jsdoc/require-returns-description", "warn").overrideRule("jsdoc/check-tag-names", "error").overrideRule("jsdoc/no-undefined-types", "error").overrideRule("jsdoc/tag-lines", "off");
|
|
525
|
+
if (isAi) {
|
|
526
|
+
builder.overrideRule("jsdoc/require-param", "error");
|
|
527
|
+
builder.overrideRule("jsdoc/require-returns", "error");
|
|
528
|
+
builder.overrideRule("jsdoc/informative-docs", "error");
|
|
529
|
+
}
|
|
530
|
+
return builder.build();
|
|
853
531
|
}
|
|
854
532
|
|
|
855
533
|
// src/configs/json.ts
|
|
@@ -913,6 +591,8 @@ function jsonConfig() {
|
|
|
913
591
|
// src/configs/markdown.ts
|
|
914
592
|
import * as mdxPlugin from "eslint-plugin-mdx";
|
|
915
593
|
function markdownConfig() {
|
|
594
|
+
const codeBlockLanguageOptions = mdxPlugin.flatCodeBlocks.languageOptions ?? {};
|
|
595
|
+
const codeBlockParserOptions = codeBlockLanguageOptions.parserOptions ?? {};
|
|
916
596
|
return [
|
|
917
597
|
// ── MDX / Markdown parsing ─────────────────────────────────────
|
|
918
598
|
{
|
|
@@ -927,6 +607,13 @@ function markdownConfig() {
|
|
|
927
607
|
// Relaxes rules that don't apply to incomplete code snippets.
|
|
928
608
|
{
|
|
929
609
|
...mdxPlugin.flatCodeBlocks,
|
|
610
|
+
languageOptions: {
|
|
611
|
+
...codeBlockLanguageOptions,
|
|
612
|
+
parserOptions: {
|
|
613
|
+
...codeBlockParserOptions,
|
|
614
|
+
projectService: false
|
|
615
|
+
}
|
|
616
|
+
},
|
|
930
617
|
rules: {
|
|
931
618
|
...mdxPlugin.flatCodeBlocks.rules,
|
|
932
619
|
// Snippets don't need trailing newlines
|
|
@@ -951,69 +638,76 @@ function markdownConfig() {
|
|
|
951
638
|
// src/configs/node.ts
|
|
952
639
|
import nodePlugin from "eslint-plugin-n";
|
|
953
640
|
import globals from "globals";
|
|
954
|
-
function nodeConfig() {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
641
|
+
function nodeConfig(opts) {
|
|
642
|
+
const isAi = opts?.ai ?? false;
|
|
643
|
+
const builder = createConfig({
|
|
644
|
+
name: "eslint-config-setup/node",
|
|
645
|
+
presets: [
|
|
646
|
+
{
|
|
647
|
+
rules: {
|
|
648
|
+
// Detect usage of deprecated Node.js APIs (fs.exists, url.parse, etc.)
|
|
649
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-deprecated-api.md
|
|
650
|
+
"node/no-deprecated-api": "error",
|
|
651
|
+
// Prevent `module.exports = ...` assignment in ES modules
|
|
652
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-exports-assign.md
|
|
653
|
+
"node/no-exports-assign": "error",
|
|
654
|
+
// OFF: TypeScript resolves imports — this rule has false positives
|
|
655
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-import.md
|
|
656
|
+
"node/no-missing-import": "off",
|
|
657
|
+
// OFF: TypeScript resolves requires — this rule has false positives
|
|
658
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-require.md
|
|
659
|
+
"node/no-missing-require": "off",
|
|
660
|
+
// Warn on process.exit() — prefer throwing errors for clean shutdown
|
|
661
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-process-exit.md
|
|
662
|
+
"node/no-process-exit": "warn",
|
|
663
|
+
// OFF: Too many false positives with monorepos and devDependencies
|
|
664
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-unpublished-import.md
|
|
665
|
+
"node/no-unpublished-import": "off",
|
|
666
|
+
// Validate hashbang lines — correct syntax, Unix linebreaks, only in entry files
|
|
667
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/hashbang.md
|
|
668
|
+
"node/hashbang": "error",
|
|
669
|
+
// Detect `__dirname + '/foo'` — use path.join() instead (breaks on Windows)
|
|
670
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-path-concat.md
|
|
671
|
+
"node/no-path-concat": "error",
|
|
672
|
+
// Treat process.exit() as throw — prevents false positives in unreachable code analysis
|
|
673
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/process-exit-as-throw.md
|
|
674
|
+
"node/process-exit-as-throw": "error",
|
|
675
|
+
// Ensure error parameters in callbacks are handled — don't silently swallow errors
|
|
676
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/handle-callback-err.md
|
|
677
|
+
"node/handle-callback-err": "error",
|
|
678
|
+
// ── Prefer global builtins ────────────────────────────────────
|
|
679
|
+
// Use global Buffer instead of require('buffer').Buffer
|
|
680
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/buffer.md
|
|
681
|
+
"node/prefer-global/buffer": ["error", "always"],
|
|
682
|
+
// Use global console — always available in Node.js
|
|
683
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/console.md
|
|
684
|
+
"node/prefer-global/console": ["error", "always"],
|
|
685
|
+
// Use global process — always available in Node.js
|
|
686
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/process.md
|
|
687
|
+
"node/prefer-global/process": ["error", "always"],
|
|
688
|
+
// Use global URL — available since Node.js 10
|
|
689
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url.md
|
|
690
|
+
"node/prefer-global/url": ["error", "always"],
|
|
691
|
+
// Use global URLSearchParams — available since Node.js 10
|
|
692
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url-search-params.md
|
|
693
|
+
"node/prefer-global/url-search-params": ["error", "always"],
|
|
694
|
+
// ── Prefer promise-based APIs ─────────────────────────────────
|
|
695
|
+
// Use dns.promises instead of callback-based dns — modern async
|
|
696
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/dns.md
|
|
697
|
+
"node/prefer-promises/dns": "error",
|
|
698
|
+
// Use fs.promises instead of callback-based fs — modern async
|
|
699
|
+
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/fs.md
|
|
700
|
+
"node/prefer-promises/fs": "error"
|
|
961
701
|
}
|
|
962
|
-
},
|
|
963
|
-
plugins: {
|
|
964
|
-
node: nodePlugin
|
|
965
|
-
},
|
|
966
|
-
rules: {
|
|
967
|
-
// Detect usage of deprecated Node.js APIs (fs.exists, url.parse, etc.)
|
|
968
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-deprecated-api.md
|
|
969
|
-
"node/no-deprecated-api": "error",
|
|
970
|
-
// Prevent `module.exports = ...` assignment in ES modules
|
|
971
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-exports-assign.md
|
|
972
|
-
"node/no-exports-assign": "error",
|
|
973
|
-
// OFF: TypeScript resolves imports — this rule has false positives
|
|
974
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-import.md
|
|
975
|
-
"node/no-missing-import": "off",
|
|
976
|
-
// OFF: TypeScript resolves requires — this rule has false positives
|
|
977
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-require.md
|
|
978
|
-
"node/no-missing-require": "off",
|
|
979
|
-
// Warn on process.exit() — prefer throwing errors for clean shutdown
|
|
980
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-process-exit.md
|
|
981
|
-
"node/no-process-exit": "warn",
|
|
982
|
-
// OFF: Too many false positives with monorepos and devDependencies
|
|
983
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-unpublished-import.md
|
|
984
|
-
"node/no-unpublished-import": "off",
|
|
985
|
-
// Validate hashbang lines — correct syntax, Unix linebreaks, only in entry files
|
|
986
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/hashbang.md
|
|
987
|
-
"node/hashbang": "error",
|
|
988
|
-
// Detect `__dirname + '/foo'` — use path.join() instead (breaks on Windows)
|
|
989
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-path-concat.md
|
|
990
|
-
"node/no-path-concat": "error",
|
|
991
|
-
// ── Prefer global builtins ────────────────────────────────────
|
|
992
|
-
// Use global Buffer instead of require('buffer').Buffer
|
|
993
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/buffer.md
|
|
994
|
-
"node/prefer-global/buffer": ["error", "always"],
|
|
995
|
-
// Use global console — always available in Node.js
|
|
996
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/console.md
|
|
997
|
-
"node/prefer-global/console": ["error", "always"],
|
|
998
|
-
// Use global process — always available in Node.js
|
|
999
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/process.md
|
|
1000
|
-
"node/prefer-global/process": ["error", "always"],
|
|
1001
|
-
// Use global URL — available since Node.js 10
|
|
1002
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url.md
|
|
1003
|
-
"node/prefer-global/url": ["error", "always"],
|
|
1004
|
-
// Use global URLSearchParams — available since Node.js 10
|
|
1005
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url-search-params.md
|
|
1006
|
-
"node/prefer-global/url-search-params": ["error", "always"],
|
|
1007
|
-
// ── Prefer promise-based APIs ─────────────────────────────────
|
|
1008
|
-
// Use dns.promises instead of callback-based dns — modern async
|
|
1009
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/dns.md
|
|
1010
|
-
"node/prefer-promises/dns": "error",
|
|
1011
|
-
// Use fs.promises instead of callback-based fs — modern async
|
|
1012
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/fs.md
|
|
1013
|
-
"node/prefer-promises/fs": "error"
|
|
1014
702
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
703
|
+
],
|
|
704
|
+
plugins: { node: nodePlugin },
|
|
705
|
+
languageOptions: { globals: { ...globals.node } }
|
|
706
|
+
});
|
|
707
|
+
if (isAi) {
|
|
708
|
+
builder.addRule("node/no-unsupported-features/node-builtins", "error");
|
|
709
|
+
}
|
|
710
|
+
return builder.build();
|
|
1017
711
|
}
|
|
1018
712
|
|
|
1019
713
|
// src/configs/package-json.ts
|
|
@@ -1140,235 +834,326 @@ function prettierCompatConfig() {
|
|
|
1140
834
|
}
|
|
1141
835
|
|
|
1142
836
|
// src/configs/react.ts
|
|
1143
|
-
import
|
|
837
|
+
import eslintReactPlugin2 from "@eslint-react/eslint-plugin";
|
|
1144
838
|
import stylisticPlugin from "@stylistic/eslint-plugin";
|
|
1145
839
|
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
|
|
1146
|
-
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
|
1147
840
|
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
|
1148
841
|
import globals2 from "globals";
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
842
|
+
|
|
843
|
+
// src/file-patterns.ts
|
|
844
|
+
var TYPESCRIPT_SOURCE_FILES = [
|
|
845
|
+
"**/*.ts",
|
|
846
|
+
"**/*.tsx",
|
|
847
|
+
"**/*.mts",
|
|
848
|
+
"**/*.cts"
|
|
849
|
+
];
|
|
850
|
+
var MARKDOWN_CODE_BLOCK_FILES = ["**/*.{md,mdx}/**"];
|
|
851
|
+
|
|
852
|
+
// src/plugins/react-compat.ts
|
|
853
|
+
import eslintReactPlugin from "@eslint-react/eslint-plugin";
|
|
854
|
+
var coreRules = eslintReactPlugin.rules;
|
|
855
|
+
function selectPrefixedRules(prefix) {
|
|
856
|
+
const result = {};
|
|
857
|
+
for (const [name, rule] of Object.entries(coreRules)) {
|
|
858
|
+
if (name.startsWith(prefix)) {
|
|
859
|
+
result[name.slice(prefix.length)] = rule;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return result;
|
|
863
|
+
}
|
|
864
|
+
var domRules = selectPrefixedRules("dom-");
|
|
865
|
+
var jsxRules = selectPrefixedRules("jsx-");
|
|
866
|
+
var namingRules = selectPrefixedRules("naming-convention-");
|
|
867
|
+
var rscRules = selectPrefixedRules("rsc-");
|
|
868
|
+
var webApiRules = selectPrefixedRules("web-api-");
|
|
869
|
+
var LEGACY_ALIASES = {
|
|
870
|
+
// ── Core: identical names ──────────────────────────────────────────
|
|
871
|
+
"no-access-state-in-setstate": [coreRules, "no-access-state-in-setstate"],
|
|
872
|
+
"no-array-index-key": [coreRules, "no-array-index-key"],
|
|
873
|
+
"no-children-prop": [jsxRules, "no-children-prop"],
|
|
874
|
+
"no-direct-mutation-state": [coreRules, "no-direct-mutation-state"],
|
|
875
|
+
"no-redundant-should-component-update": [
|
|
876
|
+
coreRules,
|
|
877
|
+
"no-redundant-should-component-update"
|
|
878
|
+
],
|
|
879
|
+
"no-unused-class-component-members": [
|
|
880
|
+
coreRules,
|
|
881
|
+
"no-unused-class-component-members"
|
|
882
|
+
],
|
|
883
|
+
"no-unused-state": [coreRules, "no-unused-state"],
|
|
884
|
+
"jsx-no-comment-textnodes": [jsxRules, "no-comment-textnodes"],
|
|
885
|
+
// ── Core: renamed 1:1 ──────────────────────────────────────────────
|
|
886
|
+
"jsx-key": [coreRules, "no-missing-key"],
|
|
887
|
+
"jsx-key-before-spread": [jsxRules, "no-key-after-spread"],
|
|
888
|
+
"jsx-no-constructed-context-values": [
|
|
889
|
+
coreRules,
|
|
890
|
+
"no-unstable-context-value"
|
|
891
|
+
],
|
|
892
|
+
"jsx-no-leaked-render": [coreRules, "no-leaked-conditional-rendering"],
|
|
893
|
+
"jsx-no-useless-fragment": [jsxRules, "no-useless-fragment"],
|
|
894
|
+
"no-object-type-as-default-prop": [coreRules, "no-unstable-default-props"],
|
|
895
|
+
"no-unstable-nested-components": [
|
|
896
|
+
coreRules,
|
|
897
|
+
"no-nested-component-definitions"
|
|
898
|
+
],
|
|
899
|
+
"display-name": [coreRules, "no-missing-component-display-name"],
|
|
900
|
+
"forward-ref-uses-ref": [coreRules, "no-forward-ref"],
|
|
901
|
+
"destructuring-assignment": [coreRules, "prefer-destructuring-assignment"],
|
|
902
|
+
"no-did-mount-set-state": [
|
|
903
|
+
coreRules,
|
|
904
|
+
"no-set-state-in-component-did-mount"
|
|
905
|
+
],
|
|
906
|
+
"no-did-update-set-state": [
|
|
907
|
+
coreRules,
|
|
908
|
+
"no-set-state-in-component-did-update"
|
|
909
|
+
],
|
|
910
|
+
"no-will-update-set-state": [
|
|
911
|
+
coreRules,
|
|
912
|
+
"no-set-state-in-component-will-update"
|
|
913
|
+
],
|
|
914
|
+
// ── Naming convention → legacy names ───────────────────────────────
|
|
915
|
+
"hook-use-state": [coreRules, "use-state"],
|
|
916
|
+
// ── DOM → legacy react/ names (not react-dom/) ─────────────────────
|
|
917
|
+
"no-danger": [domRules, "no-dangerously-set-innerhtml"],
|
|
918
|
+
"no-danger-with-children": [
|
|
919
|
+
domRules,
|
|
920
|
+
"no-dangerously-set-innerhtml-with-children"
|
|
921
|
+
],
|
|
922
|
+
"no-find-dom-node": [domRules, "no-find-dom-node"],
|
|
923
|
+
"no-namespace": [jsxRules, "no-namespace"],
|
|
924
|
+
"no-render-return-value": [domRules, "no-render-return-value"],
|
|
925
|
+
"jsx-no-script-url": [domRules, "no-script-url"],
|
|
926
|
+
"jsx-no-target-blank": [domRules, "no-unsafe-target-blank"],
|
|
927
|
+
"no-unknown-property": [domRules, "no-unknown-property"],
|
|
928
|
+
"void-dom-elements-no-children": [
|
|
929
|
+
domRules,
|
|
930
|
+
"no-void-elements-with-children"
|
|
931
|
+
],
|
|
932
|
+
"button-has-type": [domRules, "no-missing-button-type"],
|
|
933
|
+
"iframe-missing-sandbox": [domRules, "no-missing-iframe-sandbox"],
|
|
934
|
+
"style-prop-object": [domRules, "no-string-style-prop"]
|
|
935
|
+
};
|
|
936
|
+
var aliasedOriginals = /* @__PURE__ */ new Set();
|
|
937
|
+
for (const [source, originalName] of Object.values(LEGACY_ALIASES)) {
|
|
938
|
+
if (source === coreRules) {
|
|
939
|
+
aliasedOriginals.add(originalName);
|
|
940
|
+
} else if (source === domRules) {
|
|
941
|
+
aliasedOriginals.add(`dom-${originalName}`);
|
|
942
|
+
} else if (source === jsxRules) {
|
|
943
|
+
aliasedOriginals.add(`jsx-${originalName}`);
|
|
944
|
+
} else if (source === namingRules) {
|
|
945
|
+
aliasedOriginals.add(`naming-convention-${originalName}`);
|
|
946
|
+
} else if (source === rscRules) {
|
|
947
|
+
aliasedOriginals.add(`rsc-${originalName}`);
|
|
948
|
+
} else if (source === webApiRules) {
|
|
949
|
+
aliasedOriginals.add(`web-api-${originalName}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
var mergedRules = {};
|
|
953
|
+
for (const [name, rule] of Object.entries(coreRules)) {
|
|
954
|
+
if (!aliasedOriginals.has(name)) {
|
|
955
|
+
mergedRules[name] = rule;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
var subPlugins = [
|
|
959
|
+
[domRules, /* @__PURE__ */ new Set(["prefer-namespace-import"])],
|
|
960
|
+
[jsxRules, /* @__PURE__ */ new Set()],
|
|
961
|
+
[webApiRules, /* @__PURE__ */ new Set()],
|
|
962
|
+
[namingRules, /* @__PURE__ */ new Set()],
|
|
963
|
+
[rscRules, /* @__PURE__ */ new Set()]
|
|
964
|
+
];
|
|
965
|
+
for (const [rules, skip] of subPlugins) {
|
|
966
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
967
|
+
if (skip.has(name)) continue;
|
|
968
|
+
const isAliased = Object.values(LEGACY_ALIASES).some(
|
|
969
|
+
([source, originalName]) => source === rules && originalName === name
|
|
970
|
+
);
|
|
971
|
+
if (!isAliased) {
|
|
972
|
+
mergedRules[name] = rule;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
for (const [legacyName, [source, originalName]] of Object.entries(
|
|
977
|
+
LEGACY_ALIASES
|
|
978
|
+
)) {
|
|
979
|
+
mergedRules[legacyName] = source[originalName];
|
|
980
|
+
}
|
|
981
|
+
var originalToCompat = /* @__PURE__ */ new Map();
|
|
982
|
+
for (const [legacyName, [source, originalName]] of Object.entries(LEGACY_ALIASES)) {
|
|
983
|
+
originalToCompat.set(originalName, legacyName);
|
|
984
|
+
if (source === domRules) {
|
|
985
|
+
originalToCompat.set(`dom-${originalName}`, legacyName);
|
|
986
|
+
} else if (source === jsxRules) {
|
|
987
|
+
originalToCompat.set(`jsx-${originalName}`, legacyName);
|
|
988
|
+
} else if (source === namingRules) {
|
|
989
|
+
originalToCompat.set(`naming-convention-${originalName}`, legacyName);
|
|
990
|
+
} else if (source === rscRules) {
|
|
991
|
+
originalToCompat.set(`rsc-${originalName}`, legacyName);
|
|
992
|
+
} else if (source === webApiRules) {
|
|
993
|
+
originalToCompat.set(`web-api-${originalName}`, legacyName);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function translatePresetRules(presetRules2) {
|
|
997
|
+
const result = {};
|
|
998
|
+
for (const [key, value] of Object.entries(presetRules2)) {
|
|
999
|
+
const shortName = key.replace(
|
|
1000
|
+
/^@eslint-react\/(?:dom|web-api|naming-convention|rsc)\//,
|
|
1001
|
+
""
|
|
1002
|
+
).replace(/^@eslint-react\//, "").replace(/^(?:dom|web-api|naming-convention|rsc)-/, "");
|
|
1003
|
+
const compatName = originalToCompat.get(shortName) ?? shortName;
|
|
1004
|
+
if (compatName in mergedRules) {
|
|
1005
|
+
result[`react/${compatName}`] = value;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
var reactCompatPlugin = {
|
|
1011
|
+
meta: {
|
|
1012
|
+
name: "react-compat",
|
|
1013
|
+
version: "1.0.0"
|
|
1014
|
+
},
|
|
1015
|
+
rules: mergedRules
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// src/configs/react.ts
|
|
1019
|
+
var typedConfigs = eslintReactPlugin2.configs;
|
|
1020
|
+
var recommendedRules = typedConfigs.recommended.rules;
|
|
1021
|
+
var strictRules = typedConfigs.strict.rules;
|
|
1022
|
+
var presetRules = translatePresetRules({ ...recommendedRules, ...strictRules });
|
|
1023
|
+
var EXTRA_REACT_RULES = {
|
|
1024
|
+
"react/jsx-no-leaked-render": "error",
|
|
1025
|
+
"react/no-duplicate-key": "error",
|
|
1026
|
+
"react/no-implicit-key": "error",
|
|
1027
|
+
"react/no-unused-props": "warn",
|
|
1028
|
+
"react/no-unknown-property": "error",
|
|
1029
|
+
"react/style-prop-object": "error"
|
|
1030
|
+
};
|
|
1031
|
+
var JSX_A11Y_ERROR_RULES = {
|
|
1032
|
+
"jsx-a11y/alt-text": "error",
|
|
1033
|
+
"jsx-a11y/anchor-has-content": "error",
|
|
1034
|
+
"jsx-a11y/anchor-is-valid": "error",
|
|
1035
|
+
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
|
1036
|
+
"jsx-a11y/aria-props": "error",
|
|
1037
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
1038
|
+
"jsx-a11y/aria-role": "error",
|
|
1039
|
+
"jsx-a11y/aria-unsupported-elements": "error",
|
|
1040
|
+
"jsx-a11y/click-events-have-key-events": "error",
|
|
1041
|
+
"jsx-a11y/heading-has-content": "error",
|
|
1042
|
+
"jsx-a11y/html-has-lang": "error",
|
|
1043
|
+
"jsx-a11y/img-redundant-alt": "error",
|
|
1044
|
+
"jsx-a11y/label-has-associated-control": "error",
|
|
1045
|
+
"jsx-a11y/mouse-events-have-key-events": "error",
|
|
1046
|
+
"jsx-a11y/no-access-key": "error",
|
|
1047
|
+
"jsx-a11y/no-distracting-elements": "error",
|
|
1048
|
+
"jsx-a11y/no-redundant-roles": "error",
|
|
1049
|
+
"jsx-a11y/role-has-required-aria-props": "error",
|
|
1050
|
+
"jsx-a11y/role-supports-aria-props": "error",
|
|
1051
|
+
"jsx-a11y/scope": "error",
|
|
1052
|
+
"jsx-a11y/tabindex-no-positive": "error",
|
|
1053
|
+
"jsx-a11y/lang": "error",
|
|
1054
|
+
"jsx-a11y/autocomplete-valid": "error"
|
|
1055
|
+
};
|
|
1056
|
+
var AI_PROMOTED_REACT_RULES = [
|
|
1057
|
+
"react/jsx-no-comment-textnodes",
|
|
1058
|
+
"react/jsx-no-useless-fragment",
|
|
1059
|
+
"react/jsx-no-constructed-context-values",
|
|
1060
|
+
"react/no-array-index-key",
|
|
1061
|
+
"react/no-object-type-as-default-prop",
|
|
1062
|
+
"react/no-unused-state",
|
|
1063
|
+
"react/jsx-no-target-blank",
|
|
1064
|
+
"react/button-has-type",
|
|
1065
|
+
"react/iframe-missing-sandbox",
|
|
1066
|
+
"react/forward-ref-uses-ref",
|
|
1067
|
+
"react/no-context-provider",
|
|
1068
|
+
"react/no-use-context",
|
|
1069
|
+
"react/no-leaked-event-listener",
|
|
1070
|
+
"react/no-leaked-interval",
|
|
1071
|
+
"react/no-leaked-timeout",
|
|
1072
|
+
"react/no-leaked-resize-observer"
|
|
1073
|
+
];
|
|
1074
|
+
var AI_A11Y_RULES = [
|
|
1075
|
+
"jsx-a11y/no-static-element-interactions",
|
|
1076
|
+
"jsx-a11y/no-noninteractive-element-interactions",
|
|
1077
|
+
"jsx-a11y/interactive-supports-focus"
|
|
1078
|
+
];
|
|
1079
|
+
function addRules2(builder, rules) {
|
|
1080
|
+
for (const [ruleName, value] of Object.entries(rules)) {
|
|
1081
|
+
builder.addRule(ruleName, value);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function addExtraReactRules(builder) {
|
|
1085
|
+
addRules2(builder, EXTRA_REACT_RULES);
|
|
1086
|
+
}
|
|
1087
|
+
function addStylisticReactRules(builder) {
|
|
1088
|
+
builder.addRule("@stylistic/jsx-self-closing-comp", "error");
|
|
1089
|
+
builder.addRule("@stylistic/jsx-curly-brace-presence", [
|
|
1090
|
+
"error",
|
|
1091
|
+
{ props: "never", children: "never" }
|
|
1092
|
+
]);
|
|
1093
|
+
}
|
|
1094
|
+
function addReactRefreshRules(builder) {
|
|
1095
|
+
builder.addRule("react-refresh/only-export-components", [
|
|
1096
|
+
"warn",
|
|
1097
|
+
{ allowConstantExport: true }
|
|
1098
|
+
]);
|
|
1099
|
+
}
|
|
1100
|
+
function addAccessibilityRules(builder) {
|
|
1101
|
+
addRules2(builder, JSX_A11Y_ERROR_RULES);
|
|
1102
|
+
builder.addRule("jsx-a11y/no-autofocus", ["error", { ignoreNonDOM: true }]);
|
|
1103
|
+
}
|
|
1104
|
+
function applyAiReactRules(builder) {
|
|
1105
|
+
for (const ruleName of AI_PROMOTED_REACT_RULES) {
|
|
1106
|
+
builder.overrideSeverity(ruleName, "error");
|
|
1107
|
+
}
|
|
1108
|
+
for (const ruleName of AI_A11Y_RULES) {
|
|
1109
|
+
builder.addRule(ruleName, "error");
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function addReactFileOverrides(builder) {
|
|
1113
|
+
builder.addFileOverride(
|
|
1114
|
+
"eslint-config-setup/react-typescript",
|
|
1115
|
+
[...TYPESCRIPT_SOURCE_FILES],
|
|
1153
1116
|
{
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1117
|
+
// Allow async event handlers — onClick={async () => {...}} is idiomatic React
|
|
1118
|
+
"@typescript-eslint/no-misused-promises": [
|
|
1119
|
+
"error",
|
|
1120
|
+
{ checksVoidReturn: false }
|
|
1121
|
+
]
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
function reactConfig(opts) {
|
|
1126
|
+
const isAi = opts?.ai ?? false;
|
|
1127
|
+
const builder = createConfig({
|
|
1128
|
+
name: "eslint-config-setup/react",
|
|
1129
|
+
presets: [{ rules: presetRules }],
|
|
1130
|
+
plugins: {
|
|
1131
|
+
react: reactCompatPlugin,
|
|
1132
|
+
"@stylistic": stylisticPlugin,
|
|
1133
|
+
"react-refresh": reactRefreshPlugin,
|
|
1134
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Plugin from untyped module
|
|
1135
|
+
"jsx-a11y": jsxA11yPlugin
|
|
1136
|
+
},
|
|
1137
|
+
languageOptions: {
|
|
1138
|
+
globals: {
|
|
1139
|
+
...globals2.browser
|
|
1177
1140
|
},
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
"react/no-children-prop": "error",
|
|
1183
|
-
// Prevent direct mutation of this.state — use setState instead
|
|
1184
|
-
// https://eslint-react.xyz/docs/rules/no-direct-mutation-state
|
|
1185
|
-
"react/no-direct-mutation-state": "error",
|
|
1186
|
-
// Prevent unstable nested component definitions — causes remounts
|
|
1187
|
-
// https://eslint-react.xyz/docs/rules/no-nested-component-definitions
|
|
1188
|
-
"react/no-nested-component-definitions": "error",
|
|
1189
|
-
// Require key prop in iterators — prevent reconciliation bugs
|
|
1190
|
-
// https://eslint-react.xyz/docs/rules/no-missing-key
|
|
1191
|
-
"react/no-missing-key": "error",
|
|
1192
|
-
// Prevent duplicate key props in iterators
|
|
1193
|
-
// https://eslint-react.xyz/docs/rules/no-duplicate-key
|
|
1194
|
-
"react/no-duplicate-key": "error",
|
|
1195
|
-
// Prevent comments from being inserted as text nodes in JSX
|
|
1196
|
-
// https://eslint-react.xyz/docs/rules/jsx-no-comment-textnodes
|
|
1197
|
-
"react/jsx-no-comment-textnodes": "error",
|
|
1198
|
-
// Remove unnecessary JSX fragments — <>{x}</> → {x}
|
|
1199
|
-
// https://eslint-react.xyz/docs/rules/no-useless-fragment
|
|
1200
|
-
"react/no-useless-fragment": "error",
|
|
1201
|
-
// Prefer <Foo active /> over <Foo active={true} /> — concise
|
|
1202
|
-
// https://eslint-react.xyz/docs/rules/jsx-shorthand-boolean
|
|
1203
|
-
"react/jsx-shorthand-boolean": "error",
|
|
1204
|
-
// Prevent using array index as key — breaks reconciliation on reorder
|
|
1205
|
-
// https://eslint-react.xyz/docs/rules/no-array-index-key
|
|
1206
|
-
"react/no-array-index-key": "error",
|
|
1207
|
-
// Prevent object/array literals as default props — creates new reference every render
|
|
1208
|
-
// https://eslint-react.xyz/docs/rules/no-unstable-default-props
|
|
1209
|
-
"react/no-unstable-default-props": "error",
|
|
1210
|
-
// Prevent `{count && <Foo />}` — renders "0" when count is 0
|
|
1211
|
-
// https://eslint-react.xyz/docs/rules/no-leaked-conditional-rendering
|
|
1212
|
-
"react/no-leaked-conditional-rendering": "error",
|
|
1213
|
-
// Prevent inline object creation in context providers — causes re-renders
|
|
1214
|
-
// https://eslint-react.xyz/docs/rules/no-unstable-context-value
|
|
1215
|
-
"react/no-unstable-context-value": "error",
|
|
1216
|
-
// Prevent `this.setState({ count: this.state.count + 1 })` — race condition
|
|
1217
|
-
// https://eslint-react.xyz/docs/rules/no-access-state-in-setstate
|
|
1218
|
-
"react/no-access-state-in-setstate": "error",
|
|
1219
|
-
// Detect state properties that are set but never read — dead code
|
|
1220
|
-
// https://eslint-react.xyz/docs/rules/no-unused-state
|
|
1221
|
-
"react/no-unused-state": "error",
|
|
1222
|
-
// ── React 19 migration rules ────────────────────────────────────
|
|
1223
|
-
// React 19: Use <Context> instead of <Context.Provider>
|
|
1224
|
-
// https://eslint-react.xyz/docs/rules/no-context-provider
|
|
1225
|
-
"react/no-context-provider": "error",
|
|
1226
|
-
// React 19: Use ref as prop instead of forwardRef
|
|
1227
|
-
// https://eslint-react.xyz/docs/rules/no-forward-ref
|
|
1228
|
-
"react/no-forward-ref": "error",
|
|
1229
|
-
// React 19: Use use() instead of useContext()
|
|
1230
|
-
// https://eslint-react.xyz/docs/rules/no-use-context
|
|
1231
|
-
"react/no-use-context": "error",
|
|
1232
|
-
// ── React DOM (react-dom/) ──────────────────────────────────────
|
|
1233
|
-
// Prevent unsafe target="_blank" links — requires rel="noreferrer"
|
|
1234
|
-
// https://eslint-react.xyz/docs/rules/dom-no-unsafe-target-blank
|
|
1235
|
-
"react-dom/no-unsafe-target-blank": "error",
|
|
1236
|
-
// Prevent unknown DOM properties (e.g., class → className)
|
|
1237
|
-
// https://eslint-react.xyz/docs/rules/dom-no-unknown-property
|
|
1238
|
-
"react-dom/no-unknown-property": "error",
|
|
1239
|
-
// Prevent void DOM elements (br, img, hr) from having children
|
|
1240
|
-
// https://eslint-react.xyz/docs/rules/dom-no-void-elements-with-children
|
|
1241
|
-
"react-dom/no-void-elements-with-children": "error",
|
|
1242
|
-
// Require sandbox attribute on iframes — security best practice
|
|
1243
|
-
// https://eslint-react.xyz/docs/rules/dom-no-missing-iframe-sandbox
|
|
1244
|
-
"react-dom/no-missing-iframe-sandbox": "error",
|
|
1245
|
-
// Prevent `style="color: red"` — must be an object in React
|
|
1246
|
-
// https://eslint-react.xyz/docs/rules/dom-no-string-style-prop
|
|
1247
|
-
"react-dom/no-string-style-prop": "error",
|
|
1248
|
-
// Require explicit type on <button> — prevents unintended form submits
|
|
1249
|
-
// https://eslint-react.xyz/docs/rules/dom-no-missing-button-type
|
|
1250
|
-
"react-dom/no-missing-button-type": "error",
|
|
1251
|
-
// Prevent dangerouslySetInnerHTML + children at the same time — conflict
|
|
1252
|
-
// https://eslint-react.xyz/docs/rules/dom-no-dangerously-set-innerhtml-with-children
|
|
1253
|
-
"react-dom/no-dangerously-set-innerhtml-with-children": "error",
|
|
1254
|
-
// Warn on dangerouslySetInnerHTML — XSS risk, should be reviewed
|
|
1255
|
-
// https://eslint-react.xyz/docs/rules/dom-no-dangerously-set-innerhtml
|
|
1256
|
-
"react-dom/no-dangerously-set-innerhtml": "warn",
|
|
1257
|
-
// ── React Web API (react-web-api/) — cleanup leak detection ─────
|
|
1258
|
-
// Require cleanup for addEventListener in effects
|
|
1259
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-event-listener
|
|
1260
|
-
"react-web-api/no-leaked-event-listener": "error",
|
|
1261
|
-
// Require cleanup for setInterval in effects
|
|
1262
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-interval
|
|
1263
|
-
"react-web-api/no-leaked-interval": "error",
|
|
1264
|
-
// Require cleanup for setTimeout in effects
|
|
1265
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-timeout
|
|
1266
|
-
"react-web-api/no-leaked-timeout": "error",
|
|
1267
|
-
// Require cleanup for ResizeObserver in effects
|
|
1268
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-resize-observer
|
|
1269
|
-
"react-web-api/no-leaked-resize-observer": "error",
|
|
1270
|
-
// ── React Hooks (react-hooks/) — eslint-plugin-react-hooks ──────
|
|
1271
|
-
// Enforce Rules of Hooks — hooks must be called at the top level
|
|
1272
|
-
// https://react.dev/reference/rules/rules-of-hooks
|
|
1273
|
-
"react-hooks/rules-of-hooks": "error",
|
|
1274
|
-
// Verify dependency arrays in useEffect/useMemo/useCallback
|
|
1275
|
-
// https://react.dev/reference/react/useEffect#specifying-reactive-dependencies
|
|
1276
|
-
"react-hooks/exhaustive-deps": "error",
|
|
1277
|
-
// ── @stylistic — JSX formatting rules ───────────────────────────
|
|
1278
|
-
// Enforce self-closing tags for components without children — <Foo />
|
|
1279
|
-
// https://eslint.style/rules/jsx/jsx-self-closing-comp
|
|
1280
|
-
"@stylistic/jsx-self-closing-comp": "error",
|
|
1281
|
-
// Prevent unnecessary string curly braces: title={"foo"} → title="foo"
|
|
1282
|
-
// https://eslint.style/rules/jsx/jsx-curly-brace-presence
|
|
1283
|
-
"@stylistic/jsx-curly-brace-presence": [
|
|
1284
|
-
"error",
|
|
1285
|
-
{ props: "never", children: "never" }
|
|
1286
|
-
],
|
|
1287
|
-
// ── React Refresh (Fast Refresh / HMR) ────────────────────────
|
|
1288
|
-
// Ensure components are exported in a way that supports Fast Refresh.
|
|
1289
|
-
// Mixed exports (component + constants) break HMR state preservation.
|
|
1290
|
-
// allowConstantExport: Vite handles constant exports without breaking HMR.
|
|
1291
|
-
// https://github.com/ArnaudBarre/eslint-plugin-react-refresh
|
|
1292
|
-
"react-refresh/only-export-components": [
|
|
1293
|
-
"warn",
|
|
1294
|
-
{ allowConstantExport: true }
|
|
1295
|
-
],
|
|
1296
|
-
// ── JSX Accessibility (a11y) ──────────────────────────────────
|
|
1297
|
-
// Require alt text on img, area, input[type="image"], object
|
|
1298
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/alt-text.md
|
|
1299
|
-
"jsx-a11y/alt-text": "error",
|
|
1300
|
-
// Require anchor content — empty links are inaccessible
|
|
1301
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-has-content.md
|
|
1302
|
-
"jsx-a11y/anchor-has-content": "error",
|
|
1303
|
-
// Require valid href on anchors — no `#` or `javascript:` void
|
|
1304
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-is-valid.md
|
|
1305
|
-
"jsx-a11y/anchor-is-valid": "error",
|
|
1306
|
-
// Active descendant elements must be focusable (tabindex)
|
|
1307
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-activedescendant-has-tabindex.md
|
|
1308
|
-
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
|
1309
|
-
// Validate aria-* attributes exist — catches typos in ARIA props
|
|
1310
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-props.md
|
|
1311
|
-
"jsx-a11y/aria-props": "error",
|
|
1312
|
-
// Validate aria-* values match their type (boolean, token, etc.)
|
|
1313
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-proptypes.md
|
|
1314
|
-
"jsx-a11y/aria-proptypes": "error",
|
|
1315
|
-
// Validate role attribute values — catches invalid role names
|
|
1316
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md
|
|
1317
|
-
"jsx-a11y/aria-role": "error",
|
|
1318
|
-
// Prevent ARIA on elements that don't support it (meta, script, style)
|
|
1319
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-unsupported-elements.md
|
|
1320
|
-
"jsx-a11y/aria-unsupported-elements": "error",
|
|
1321
|
-
// Click handlers must have keyboard equivalent — keyboard accessibility
|
|
1322
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/click-events-have-key-events.md
|
|
1323
|
-
"jsx-a11y/click-events-have-key-events": "error",
|
|
1324
|
-
// Heading elements must have content — screen readers need it
|
|
1325
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/heading-has-content.md
|
|
1326
|
-
"jsx-a11y/heading-has-content": "error",
|
|
1327
|
-
// Require lang attribute on <html> — language identification
|
|
1328
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/html-has-lang.md
|
|
1329
|
-
"jsx-a11y/html-has-lang": "error",
|
|
1330
|
-
// Prevent redundant alt text like "image of..." — screen readers add this
|
|
1331
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/img-redundant-alt.md
|
|
1332
|
-
"jsx-a11y/img-redundant-alt": "error",
|
|
1333
|
-
// Every form label must be associated with a control
|
|
1334
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-associated-control.md
|
|
1335
|
-
"jsx-a11y/label-has-associated-control": "error",
|
|
1336
|
-
// Mouse event handlers must have keyboard equivalents
|
|
1337
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/mouse-events-have-key-events.md
|
|
1338
|
-
"jsx-a11y/mouse-events-have-key-events": "error",
|
|
1339
|
-
// No accessKey attribute — inconsistent shortcuts confuse users
|
|
1340
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-access-key.md
|
|
1341
|
-
"jsx-a11y/no-access-key": "error",
|
|
1342
|
-
// No autofocus attribute (except non-DOM) — disorienting for screen readers
|
|
1343
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-autofocus.md
|
|
1344
|
-
"jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }],
|
|
1345
|
-
// No <marquee>/<blink> — deprecated distracting elements
|
|
1346
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-distracting-elements.md
|
|
1347
|
-
"jsx-a11y/no-distracting-elements": "error",
|
|
1348
|
-
// No redundant ARIA roles (e.g., <button role="button">)
|
|
1349
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md
|
|
1350
|
-
"jsx-a11y/no-redundant-roles": "error",
|
|
1351
|
-
// Role elements must have all required ARIA props
|
|
1352
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-has-required-aria-props.md
|
|
1353
|
-
"jsx-a11y/role-has-required-aria-props": "error",
|
|
1354
|
-
// Don't use unsupported ARIA attributes for a given role
|
|
1355
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-supports-aria-props.md
|
|
1356
|
-
"jsx-a11y/role-supports-aria-props": "error",
|
|
1357
|
-
// scope attribute only on <th> elements — HTML specification
|
|
1358
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md
|
|
1359
|
-
"jsx-a11y/scope": "error",
|
|
1360
|
-
// No positive tabIndex values — disrupts natural tab order
|
|
1361
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/tabindex-no-positive.md
|
|
1362
|
-
"jsx-a11y/tabindex-no-positive": "error",
|
|
1363
|
-
// Validate lang attribute values — e.g. "de" ok, "deutsch" not
|
|
1364
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/lang.md
|
|
1365
|
-
"jsx-a11y/lang": "error",
|
|
1366
|
-
// Validate autocomplete attribute values on form elements
|
|
1367
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/autocomplete-valid.md
|
|
1368
|
-
"jsx-a11y/autocomplete-valid": "error"
|
|
1141
|
+
parserOptions: {
|
|
1142
|
+
ecmaFeatures: {
|
|
1143
|
+
jsx: true
|
|
1144
|
+
}
|
|
1369
1145
|
}
|
|
1370
1146
|
}
|
|
1371
|
-
|
|
1147
|
+
});
|
|
1148
|
+
addExtraReactRules(builder);
|
|
1149
|
+
addStylisticReactRules(builder);
|
|
1150
|
+
addReactRefreshRules(builder);
|
|
1151
|
+
addAccessibilityRules(builder);
|
|
1152
|
+
if (isAi) {
|
|
1153
|
+
applyAiReactRules(builder);
|
|
1154
|
+
}
|
|
1155
|
+
addReactFileOverrides(builder);
|
|
1156
|
+
return builder.build();
|
|
1372
1157
|
}
|
|
1373
1158
|
|
|
1374
1159
|
// src/configs/react-effect.ts
|
|
@@ -1415,11 +1200,21 @@ function reactEffectConfig() {
|
|
|
1415
1200
|
|
|
1416
1201
|
// src/configs/regexp.ts
|
|
1417
1202
|
import { configs as regexpConfigs } from "eslint-plugin-regexp";
|
|
1418
|
-
function regexpConfig() {
|
|
1419
|
-
|
|
1203
|
+
function regexpConfig(opts) {
|
|
1204
|
+
const isAi = opts?.ai ?? false;
|
|
1205
|
+
const builder = createConfig({
|
|
1420
1206
|
name: "eslint-config-setup/regexp",
|
|
1421
1207
|
presets: [regexpConfigs["flat/recommended"]]
|
|
1422
|
-
}).addRule("regexp/no-control-character", "error").addRule("regexp/no-octal", "error").addRule("regexp/no-standalone-backslash", "error").addRule("regexp/no-super-linear-move", "error").addRule("regexp/prefer-escape-replacement-dollar-char", "error")
|
|
1208
|
+
}).addRule("regexp/no-control-character", "error").addRule("regexp/no-octal", "error").addRule("regexp/no-standalone-backslash", "error").addRule("regexp/no-super-linear-move", "error").addRule("regexp/prefer-escape-replacement-dollar-char", "error");
|
|
1209
|
+
if (isAi) {
|
|
1210
|
+
builder.addRule("regexp/prefer-lookaround", "error");
|
|
1211
|
+
builder.addRule("regexp/prefer-named-backreference", "error");
|
|
1212
|
+
builder.addRule("regexp/prefer-named-replacement", "error");
|
|
1213
|
+
builder.addRule("regexp/prefer-quantifier", "error");
|
|
1214
|
+
builder.addRule("regexp/prefer-result-array-groups", "error");
|
|
1215
|
+
builder.addRule("regexp/require-unicode-sets-regexp", "error");
|
|
1216
|
+
}
|
|
1217
|
+
return builder.build();
|
|
1423
1218
|
}
|
|
1424
1219
|
|
|
1425
1220
|
// src/configs/security.ts
|
|
@@ -1485,13 +1280,12 @@ function securityConfig() {
|
|
|
1485
1280
|
|
|
1486
1281
|
// src/configs/sonarjs.ts
|
|
1487
1282
|
import sonarjsPlugin from "eslint-plugin-sonarjs";
|
|
1488
|
-
function sonarjsConfig() {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
},
|
|
1283
|
+
function sonarjsConfig(opts) {
|
|
1284
|
+
const isAi = opts?.ai === true;
|
|
1285
|
+
const builder = createConfig({
|
|
1286
|
+
name: "eslint-config-setup/sonarjs",
|
|
1287
|
+
presets: [{
|
|
1288
|
+
plugins: { sonarjs: sonarjsPlugin },
|
|
1495
1289
|
rules: {
|
|
1496
1290
|
// Detect copy-pasted functions — extract to shared helper instead
|
|
1497
1291
|
// https://sonarsource.github.io/rspec/#/rspec/S4144/javascript
|
|
@@ -1554,13 +1348,24 @@ function sonarjsConfig() {
|
|
|
1554
1348
|
// https://sonarsource.github.io/rspec/#/rspec/S6418/javascript
|
|
1555
1349
|
"sonarjs/no-hardcoded-secrets": "warn"
|
|
1556
1350
|
}
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1351
|
+
}]
|
|
1352
|
+
});
|
|
1353
|
+
if (isAi) {
|
|
1354
|
+
builder.addRule("sonarjs/no-nested-switch", "error");
|
|
1355
|
+
builder.addRule("sonarjs/no-nested-template-literals", "error");
|
|
1356
|
+
builder.addRule("sonarjs/max-union-size", ["error", { threshold: 5 }]);
|
|
1357
|
+
builder.addRule("sonarjs/prefer-type-guard", "error");
|
|
1358
|
+
builder.addRule("sonarjs/public-static-readonly", "error");
|
|
1359
|
+
builder.addRule("sonarjs/no-duplicate-string", ["error", { threshold: 3 }]);
|
|
1360
|
+
}
|
|
1361
|
+
return builder.build();
|
|
1559
1362
|
}
|
|
1560
1363
|
|
|
1561
1364
|
// src/configs/typescript.ts
|
|
1562
1365
|
import tseslint from "typescript-eslint";
|
|
1563
|
-
function typescriptConfig() {
|
|
1366
|
+
function typescriptConfig(opts) {
|
|
1367
|
+
const isAi = opts?.ai ?? false;
|
|
1368
|
+
const isReact = opts?.react ?? false;
|
|
1564
1369
|
const typeChecked = tseslint.configs.strictTypeChecked;
|
|
1565
1370
|
const stylistic = tseslint.configs.stylisticTypeChecked;
|
|
1566
1371
|
const structuralBlocks = typeChecked.slice(0, 2);
|
|
@@ -1569,6 +1374,8 @@ function typescriptConfig() {
|
|
|
1569
1374
|
name: "eslint-config-setup/typescript",
|
|
1570
1375
|
passthrough: structuralBlocks,
|
|
1571
1376
|
presets: ruleBlocks,
|
|
1377
|
+
files: [...TYPESCRIPT_SOURCE_FILES],
|
|
1378
|
+
ignores: [...MARKDOWN_CODE_BLOCK_FILES],
|
|
1572
1379
|
languageOptions: {
|
|
1573
1380
|
parserOptions: {
|
|
1574
1381
|
// Use project service for automatic tsconfig resolution
|
|
@@ -1597,10 +1404,25 @@ function typescriptConfig() {
|
|
|
1597
1404
|
"error",
|
|
1598
1405
|
"in-try-catch"
|
|
1599
1406
|
]);
|
|
1600
|
-
builder.
|
|
1407
|
+
builder.overrideSeverity("@typescript-eslint/no-deprecated", "warn");
|
|
1408
|
+
builder.overrideRule("@typescript-eslint/no-explicit-any", [
|
|
1409
|
+
"warn",
|
|
1410
|
+
{ fixToUnknown: true }
|
|
1411
|
+
]);
|
|
1412
|
+
builder.overrideRule("@typescript-eslint/consistent-type-definitions", [
|
|
1413
|
+
"error",
|
|
1414
|
+
"type"
|
|
1415
|
+
]);
|
|
1416
|
+
builder.overrideOptions("@typescript-eslint/ban-ts-comment", {
|
|
1417
|
+
"ts-expect-error": "allow-with-description"
|
|
1418
|
+
});
|
|
1419
|
+
builder.overrideOptions("@typescript-eslint/no-floating-promises", {
|
|
1420
|
+
checkThenables: true,
|
|
1421
|
+
ignoreIIFE: true
|
|
1422
|
+
});
|
|
1601
1423
|
builder.addRule("@typescript-eslint/consistent-type-imports", [
|
|
1602
1424
|
"error",
|
|
1603
|
-
{ fixStyle: "inline-type-imports" }
|
|
1425
|
+
{ fixStyle: isAi ? "inline-type-imports" : "separate-type-imports" }
|
|
1604
1426
|
]);
|
|
1605
1427
|
builder.addRule("@typescript-eslint/consistent-type-exports", [
|
|
1606
1428
|
"error",
|
|
@@ -1614,6 +1436,24 @@ function typescriptConfig() {
|
|
|
1614
1436
|
"error"
|
|
1615
1437
|
);
|
|
1616
1438
|
builder.addRule("@typescript-eslint/strict-void-return", "error");
|
|
1439
|
+
builder.addRule("@typescript-eslint/prefer-readonly", isAi ? "error" : "warn");
|
|
1440
|
+
builder.addRule("@typescript-eslint/require-array-sort-compare", [
|
|
1441
|
+
"error",
|
|
1442
|
+
{ ignoreStringArrays: true }
|
|
1443
|
+
]);
|
|
1444
|
+
builder.addRule("@typescript-eslint/no-unsafe-type-assertion", "error");
|
|
1445
|
+
builder.addRule("@typescript-eslint/switch-exhaustiveness-check", [
|
|
1446
|
+
"error",
|
|
1447
|
+
{
|
|
1448
|
+
allowDefaultCaseForExhaustiveSwitch: false,
|
|
1449
|
+
requireDefaultForNonUnion: true
|
|
1450
|
+
}
|
|
1451
|
+
]);
|
|
1452
|
+
builder.addRule("@typescript-eslint/promise-function-async", "error");
|
|
1453
|
+
builder.addRule("@typescript-eslint/method-signature-style", [
|
|
1454
|
+
"error",
|
|
1455
|
+
"property"
|
|
1456
|
+
]);
|
|
1617
1457
|
builder.addRule("no-shadow", "off");
|
|
1618
1458
|
builder.addRule("@typescript-eslint/no-shadow", [
|
|
1619
1459
|
"error",
|
|
@@ -1624,10 +1464,7 @@ function typescriptConfig() {
|
|
|
1624
1464
|
ignoreFunctionTypeParameterNameValueShadow: true
|
|
1625
1465
|
}
|
|
1626
1466
|
]);
|
|
1627
|
-
builder.
|
|
1628
|
-
"error",
|
|
1629
|
-
{ allowNumber: true }
|
|
1630
|
-
]);
|
|
1467
|
+
builder.overrideOptions("@typescript-eslint/restrict-template-expressions", { allowNumber: true });
|
|
1631
1468
|
builder.addRule("@typescript-eslint/strict-boolean-expressions", [
|
|
1632
1469
|
"error",
|
|
1633
1470
|
{ allowNullableBoolean: true, allowNullableObject: true }
|
|
@@ -1637,18 +1474,168 @@ function typescriptConfig() {
|
|
|
1637
1474
|
["**/*.{js,mjs,cjs}"],
|
|
1638
1475
|
tseslint.configs.disableTypeChecked.rules ?? {}
|
|
1639
1476
|
);
|
|
1477
|
+
if (isAi) {
|
|
1478
|
+
builder.overrideSeverity("@typescript-eslint/no-explicit-any", "error");
|
|
1479
|
+
builder.overrideRule("@typescript-eslint/no-floating-promises", [
|
|
1480
|
+
"error",
|
|
1481
|
+
{ checkThenables: true, ignoreVoid: true }
|
|
1482
|
+
]);
|
|
1483
|
+
builder.addRule("@typescript-eslint/no-magic-numbers", [
|
|
1484
|
+
"error",
|
|
1485
|
+
{
|
|
1486
|
+
ignore: [-1, 0, 1, 2],
|
|
1487
|
+
ignoreArrayIndexes: true,
|
|
1488
|
+
ignoreDefaultValues: true,
|
|
1489
|
+
enforceConst: true,
|
|
1490
|
+
ignoreClassFieldInitialValues: true,
|
|
1491
|
+
ignoreEnums: true,
|
|
1492
|
+
ignoreNumericLiteralTypes: true,
|
|
1493
|
+
ignoreReadonlyClassProperties: true,
|
|
1494
|
+
ignoreTypeIndexes: true
|
|
1495
|
+
}
|
|
1496
|
+
]);
|
|
1497
|
+
builder.addRule("@typescript-eslint/explicit-function-return-type", [
|
|
1498
|
+
"error",
|
|
1499
|
+
{
|
|
1500
|
+
allowExpressions: true,
|
|
1501
|
+
allowTypedFunctionExpressions: true,
|
|
1502
|
+
allowHigherOrderFunctions: true,
|
|
1503
|
+
allowIIFEs: true
|
|
1504
|
+
}
|
|
1505
|
+
]);
|
|
1506
|
+
builder.addRule(
|
|
1507
|
+
"@typescript-eslint/explicit-member-accessibility",
|
|
1508
|
+
"error"
|
|
1509
|
+
);
|
|
1510
|
+
builder.addRule(
|
|
1511
|
+
"@typescript-eslint/prefer-enum-initializers",
|
|
1512
|
+
"error"
|
|
1513
|
+
);
|
|
1514
|
+
builder.addRule("@typescript-eslint/naming-convention", [
|
|
1515
|
+
"error",
|
|
1516
|
+
{
|
|
1517
|
+
selector: "variable",
|
|
1518
|
+
format: isReact ? ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] : ["strictCamelCase", "UPPER_CASE"],
|
|
1519
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1520
|
+
trailingUnderscore: "allow",
|
|
1521
|
+
filter: { regex: "[- ]", match: false }
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
selector: "function",
|
|
1525
|
+
format: isReact ? ["strictCamelCase", "StrictPascalCase"] : ["strictCamelCase"]
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
selector: "parameter",
|
|
1529
|
+
format: ["strictCamelCase"],
|
|
1530
|
+
leadingUnderscore: "allow"
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
selector: "import",
|
|
1534
|
+
format: ["strictCamelCase", "StrictPascalCase", "UPPER_CASE"]
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
selector: [
|
|
1538
|
+
"classProperty",
|
|
1539
|
+
"parameterProperty",
|
|
1540
|
+
"classMethod",
|
|
1541
|
+
"objectLiteralMethod",
|
|
1542
|
+
"typeMethod",
|
|
1543
|
+
"accessor"
|
|
1544
|
+
],
|
|
1545
|
+
format: ["strictCamelCase"],
|
|
1546
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1547
|
+
trailingUnderscore: "allow",
|
|
1548
|
+
filter: { regex: "[- ]", match: false }
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
selector: ["objectLiteralProperty", "typeProperty"],
|
|
1552
|
+
format: null
|
|
1553
|
+
},
|
|
1554
|
+
{ selector: "typeLike", format: ["StrictPascalCase"] },
|
|
1555
|
+
{
|
|
1556
|
+
selector: "interface",
|
|
1557
|
+
format: ["StrictPascalCase"],
|
|
1558
|
+
custom: { regex: "^I[A-Z]", match: false }
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
selector: "typeParameter",
|
|
1562
|
+
format: ["PascalCase"],
|
|
1563
|
+
custom: {
|
|
1564
|
+
regex: "^(T([A-Z][a-zA-Z]*)?|[A-Z])$",
|
|
1565
|
+
match: true
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
selector: "variable",
|
|
1570
|
+
types: ["boolean"],
|
|
1571
|
+
format: ["StrictPascalCase"],
|
|
1572
|
+
prefix: ["is", "has", "can", "should", "will", "did"]
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
selector: ["classProperty", "objectLiteralProperty"],
|
|
1576
|
+
format: null,
|
|
1577
|
+
modifiers: ["requiresQuotes"]
|
|
1578
|
+
}
|
|
1579
|
+
]);
|
|
1580
|
+
builder.addRule("@typescript-eslint/member-ordering", [
|
|
1581
|
+
"warn",
|
|
1582
|
+
{
|
|
1583
|
+
default: [
|
|
1584
|
+
"signature",
|
|
1585
|
+
"call-signature",
|
|
1586
|
+
"public-static-field",
|
|
1587
|
+
"protected-static-field",
|
|
1588
|
+
"private-static-field",
|
|
1589
|
+
"#private-static-field",
|
|
1590
|
+
"static-field",
|
|
1591
|
+
"public-static-method",
|
|
1592
|
+
"protected-static-method",
|
|
1593
|
+
"private-static-method",
|
|
1594
|
+
"#private-static-method",
|
|
1595
|
+
"static-method",
|
|
1596
|
+
"public-decorated-field",
|
|
1597
|
+
"protected-decorated-field",
|
|
1598
|
+
"private-decorated-field",
|
|
1599
|
+
"public-instance-field",
|
|
1600
|
+
"protected-instance-field",
|
|
1601
|
+
"private-instance-field",
|
|
1602
|
+
"#private-instance-field",
|
|
1603
|
+
"public-abstract-field",
|
|
1604
|
+
"protected-abstract-field",
|
|
1605
|
+
"field",
|
|
1606
|
+
"public-constructor",
|
|
1607
|
+
"protected-constructor",
|
|
1608
|
+
"private-constructor",
|
|
1609
|
+
"constructor",
|
|
1610
|
+
["public-get", "public-set"],
|
|
1611
|
+
["protected-get", "protected-set"],
|
|
1612
|
+
["private-get", "private-set"],
|
|
1613
|
+
["#private-get", "#private-set"],
|
|
1614
|
+
"public-decorated-method",
|
|
1615
|
+
"protected-decorated-method",
|
|
1616
|
+
"private-decorated-method",
|
|
1617
|
+
"public-instance-method",
|
|
1618
|
+
"protected-instance-method",
|
|
1619
|
+
"private-instance-method",
|
|
1620
|
+
"#private-instance-method",
|
|
1621
|
+
"public-abstract-method",
|
|
1622
|
+
"protected-abstract-method",
|
|
1623
|
+
"method"
|
|
1624
|
+
]
|
|
1625
|
+
}
|
|
1626
|
+
]);
|
|
1627
|
+
}
|
|
1640
1628
|
return builder.build();
|
|
1641
1629
|
}
|
|
1642
1630
|
|
|
1643
1631
|
// src/configs/unicorn.ts
|
|
1644
1632
|
import unicornPlugin from "eslint-plugin-unicorn";
|
|
1645
|
-
function unicornConfig() {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
},
|
|
1633
|
+
function unicornConfig(opts) {
|
|
1634
|
+
const isAi = opts?.ai === true;
|
|
1635
|
+
const builder = createConfig({
|
|
1636
|
+
name: "eslint-config-setup/unicorn",
|
|
1637
|
+
presets: [{
|
|
1638
|
+
plugins: { unicorn: unicornPlugin },
|
|
1652
1639
|
rules: {
|
|
1653
1640
|
// ── Error prevention ──────────────────────────────────────────
|
|
1654
1641
|
// Forbid `/* eslint-disable */` without specific rule — too broad
|
|
@@ -1678,9 +1665,11 @@ function unicornConfig() {
|
|
|
1678
1665
|
// Remove useless Promise.resolve/reject wrappers — simplify async code
|
|
1679
1666
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md
|
|
1680
1667
|
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
1681
|
-
// Disallow
|
|
1668
|
+
// Disallow redundant undefined where omission preserves semantics.
|
|
1669
|
+
// Keep call arguments unchecked: TypeScript often needs explicit
|
|
1670
|
+
// `undefined` for required parameters typed as `T | undefined`.
|
|
1682
1671
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-undefined.md
|
|
1683
|
-
"unicorn/no-useless-undefined": "
|
|
1672
|
+
"unicorn/no-useless-undefined": ["warn", { checkArguments: false }],
|
|
1684
1673
|
// Disallow `await` in Promise.all/race arguments — already a promise
|
|
1685
1674
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-await-in-promise-methods.md
|
|
1686
1675
|
"unicorn/no-await-in-promise-methods": "error",
|
|
@@ -1736,6 +1725,28 @@ function unicornConfig() {
|
|
|
1736
1725
|
// Enforce consistent style for indexOf/findIndex existence checks
|
|
1737
1726
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-existence-index-check.md
|
|
1738
1727
|
"unicorn/consistent-existence-index-check": "error",
|
|
1728
|
+
// Move functions to the smallest possible scope — avoids re-creation on every call,
|
|
1729
|
+
// improves testability, and makes dependencies explicit
|
|
1730
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md
|
|
1731
|
+
"unicorn/consistent-function-scoping": "warn",
|
|
1732
|
+
// Enforce consistent filename casing — camelCase, PascalCase, or kebab-case (no snake_case)
|
|
1733
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md
|
|
1734
|
+
"unicorn/filename-case": [
|
|
1735
|
+
"error",
|
|
1736
|
+
{
|
|
1737
|
+
cases: {
|
|
1738
|
+
camelCase: true,
|
|
1739
|
+
pascalCase: true,
|
|
1740
|
+
kebabCase: true
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
],
|
|
1744
|
+
// Discourage Array.reduce() — hard to read for many developers, prefer for...of or other methods
|
|
1745
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md
|
|
1746
|
+
"unicorn/no-array-reduce": "warn",
|
|
1747
|
+
// Prefer for...of over C-style for loops — more readable when index isn't needed
|
|
1748
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md
|
|
1749
|
+
"unicorn/no-for-loop": "warn",
|
|
1739
1750
|
// ── Modern API preferences ────────────────────────────────────
|
|
1740
1751
|
// Prefer .flatMap() over .map().flat() — single pass, more readable
|
|
1741
1752
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat-map.md
|
|
@@ -1761,6 +1772,9 @@ function unicornConfig() {
|
|
|
1761
1772
|
// Prefer .before()/.after()/.replaceWith() over parent.insertBefore()
|
|
1762
1773
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md
|
|
1763
1774
|
"unicorn/prefer-modern-dom-apis": "error",
|
|
1775
|
+
// Prefer element.dataset.foo over setAttribute('data-foo') — modern, readable
|
|
1776
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-dataset.md
|
|
1777
|
+
"unicorn/prefer-dom-node-dataset": "error",
|
|
1764
1778
|
// Prefer Math.log10/Math.hypot over manual math — accurate and readable
|
|
1765
1779
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-math-apis.md
|
|
1766
1780
|
"unicorn/prefer-modern-math-apis": "error",
|
|
@@ -1866,8 +1880,27 @@ function unicornConfig() {
|
|
|
1866
1880
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-call.md
|
|
1867
1881
|
"unicorn/prefer-single-call": "off"
|
|
1868
1882
|
}
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1883
|
+
}]
|
|
1884
|
+
});
|
|
1885
|
+
if (isAi) {
|
|
1886
|
+
builder.overrideSeverity("unicorn/consistent-function-scoping", "error");
|
|
1887
|
+
builder.overrideSeverity("unicorn/no-array-reduce", "error");
|
|
1888
|
+
builder.overrideSeverity("unicorn/no-for-loop", "error");
|
|
1889
|
+
builder.addRule("unicorn/no-array-for-each", "error");
|
|
1890
|
+
builder.addRule("unicorn/prefer-ternary", ["error", "only-single-line"]);
|
|
1891
|
+
builder.addRule("unicorn/prefer-switch", ["error", { minimumCases: 3 }]);
|
|
1892
|
+
builder.addRule("unicorn/prevent-abbreviations", "error");
|
|
1893
|
+
builder.addRule("unicorn/no-useless-switch-case", "error");
|
|
1894
|
+
builder.addRule("unicorn/custom-error-definition", "error");
|
|
1895
|
+
builder.addRule("unicorn/prefer-default-parameters", "error");
|
|
1896
|
+
builder.addRule("unicorn/prefer-logical-operator-over-ternary", "error");
|
|
1897
|
+
builder.addRule("unicorn/prefer-math-min-max", "error");
|
|
1898
|
+
builder.addRule("unicorn/prefer-set-size", "error");
|
|
1899
|
+
builder.addRule("unicorn/explicit-length-check", "error");
|
|
1900
|
+
builder.addRule("unicorn/switch-case-braces", "error");
|
|
1901
|
+
builder.addRule("unicorn/no-array-push-push", "error");
|
|
1902
|
+
}
|
|
1903
|
+
return builder.build();
|
|
1871
1904
|
}
|
|
1872
1905
|
|
|
1873
1906
|
// src/overrides/config-files.ts
|
|
@@ -2100,136 +2133,165 @@ function storiesOverride() {
|
|
|
2100
2133
|
// src/overrides/tests.ts
|
|
2101
2134
|
import vitestPlugin from "@vitest/eslint-plugin";
|
|
2102
2135
|
import testingLibraryPlugin from "eslint-plugin-testing-library";
|
|
2136
|
+
var TEST_FILES2 = ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"];
|
|
2137
|
+
var VITEST_RULES = {
|
|
2138
|
+
// ── Vitest rules ──────────────────────────────────────────────
|
|
2139
|
+
// Every test must contain at least one assertion
|
|
2140
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md
|
|
2141
|
+
"vitest/expect-expect": "error",
|
|
2142
|
+
// Prevent duplicate test titles within a describe block
|
|
2143
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-identical-title.md
|
|
2144
|
+
"vitest/no-identical-title": "error",
|
|
2145
|
+
// No it.only / describe.only — prevents accidentally skipping tests in CI
|
|
2146
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-focused-tests.md
|
|
2147
|
+
"vitest/no-focused-tests": "error",
|
|
2148
|
+
// Warn on it.skip / describe.skip — track disabled tests
|
|
2149
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-disabled-tests.md
|
|
2150
|
+
"vitest/no-disabled-tests": "warn",
|
|
2151
|
+
// No duplicate beforeEach/afterEach hooks — merge them
|
|
2152
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-duplicate-hooks.md
|
|
2153
|
+
"vitest/no-duplicate-hooks": "error",
|
|
2154
|
+
// Prefer .toBe() over .toEqual() for primitives — clearer intent
|
|
2155
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-be.md
|
|
2156
|
+
"vitest/prefer-to-be": "error",
|
|
2157
|
+
// Prefer .toHaveLength() over .toBe(arr.length) — better errors
|
|
2158
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-have-length.md
|
|
2159
|
+
"vitest/prefer-to-have-length": "error",
|
|
2160
|
+
// Validate expect() usage — no dangling expect without assertion
|
|
2161
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md
|
|
2162
|
+
"vitest/valid-expect": "error",
|
|
2163
|
+
// Validate test/describe title format — no empty or invalid titles
|
|
2164
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-title.md
|
|
2165
|
+
"vitest/valid-title": "error",
|
|
2166
|
+
// No conditional logic (if/else) inside tests — split into separate tests
|
|
2167
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md
|
|
2168
|
+
"vitest/no-conditional-in-test": "error",
|
|
2169
|
+
// No expect() inside conditional blocks — always assert unconditionally
|
|
2170
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md
|
|
2171
|
+
"vitest/no-conditional-expect": "error",
|
|
2172
|
+
// No standalone expect() outside test blocks — always wrap in it/test
|
|
2173
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md
|
|
2174
|
+
"vitest/no-standalone-expect": "error",
|
|
2175
|
+
// Prefer .toStrictEqual() over .toEqual() — catches undefined vs missing
|
|
2176
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-strict-equal.md
|
|
2177
|
+
"vitest/prefer-strict-equal": "error",
|
|
2178
|
+
// Prefer vi.spyOn() over vi.fn() for method mocks — preserves original
|
|
2179
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-spy-on.md
|
|
2180
|
+
"vitest/prefer-spy-on": "error",
|
|
2181
|
+
// Require message argument in toThrow/toThrowError — verify correct error
|
|
2182
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-to-throw-message.md
|
|
2183
|
+
"vitest/require-to-throw-message": "error",
|
|
2184
|
+
// ── Relaxed rules for tests ───────────────────────────────────
|
|
2185
|
+
// Tests are naturally verbose and use patterns banned in prod code
|
|
2186
|
+
// Tests can be long (setup + many assertions)
|
|
2187
|
+
"max-lines": "off",
|
|
2188
|
+
"max-lines-per-function": "off",
|
|
2189
|
+
"max-statements": "off",
|
|
2190
|
+
// Tests often need `any` for mocking and type assertions
|
|
2191
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
2192
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
2193
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
2194
|
+
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
2195
|
+
};
|
|
2196
|
+
var TESTING_LIBRARY_RULES = {
|
|
2197
|
+
// ── Testing Library rules ───────────────────────────────────
|
|
2198
|
+
// Await async events (userEvent.click, etc.) — prevents race conditions
|
|
2199
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-events.md
|
|
2200
|
+
"testing-library/await-async-events": "error",
|
|
2201
|
+
// Await async queries (findBy*) — they return promises
|
|
2202
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-queries.md
|
|
2203
|
+
"testing-library/await-async-queries": "error",
|
|
2204
|
+
// Await async utilities (waitFor, waitForElementToBeRemoved)
|
|
2205
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md
|
|
2206
|
+
"testing-library/await-async-utils": "error",
|
|
2207
|
+
// Don't await synchronous events — misleading
|
|
2208
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-events.md
|
|
2209
|
+
"testing-library/no-await-sync-events": "error",
|
|
2210
|
+
// Don't await synchronous queries (getBy*, queryBy*) — not promises
|
|
2211
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-queries.md
|
|
2212
|
+
"testing-library/no-await-sync-queries": "error",
|
|
2213
|
+
// Don't use container.querySelector — use queries instead
|
|
2214
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-container.md
|
|
2215
|
+
"testing-library/no-container": "error",
|
|
2216
|
+
// Warn on debug()/prettyDOM() left in tests — debugging artifacts
|
|
2217
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-debugging-utils.md
|
|
2218
|
+
"testing-library/no-debugging-utils": "warn",
|
|
2219
|
+
// Don't access DOM nodes directly — use queries
|
|
2220
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-node-access.md
|
|
2221
|
+
"testing-library/no-node-access": "error",
|
|
2222
|
+
// Don't call render in beforeEach — call in each test
|
|
2223
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-render-in-lifecycle.md
|
|
2224
|
+
"testing-library/no-render-in-lifecycle": "error",
|
|
2225
|
+
// No unnecessary act() wrappers — TL handles this internally
|
|
2226
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-unnecessary-act.md
|
|
2227
|
+
"testing-library/no-unnecessary-act": "error",
|
|
2228
|
+
// One assertion per waitFor — multiple can mask failures
|
|
2229
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-multiple-assertions.md
|
|
2230
|
+
"testing-library/no-wait-for-multiple-assertions": "error",
|
|
2231
|
+
// No side effects in waitFor — only assertions
|
|
2232
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md
|
|
2233
|
+
"testing-library/no-wait-for-side-effects": "error",
|
|
2234
|
+
// Prefer findBy* over waitFor + getBy* — built-in combination
|
|
2235
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-find-by.md
|
|
2236
|
+
"testing-library/prefer-find-by": "error",
|
|
2237
|
+
// Use getBy* (throws) for present elements, queryBy* for absent
|
|
2238
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-presence-queries.md
|
|
2239
|
+
"testing-library/prefer-presence-queries": "error",
|
|
2240
|
+
// Use queryBy* for disappearance checks — returns null when gone
|
|
2241
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-query-by-disappearance.md
|
|
2242
|
+
"testing-library/prefer-query-by-disappearance": "error",
|
|
2243
|
+
// Use screen.getBy* over destructured render result — consistent
|
|
2244
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-screen-queries.md
|
|
2245
|
+
"testing-library/prefer-screen-queries": "error",
|
|
2246
|
+
// Name render result consistently (e.g., `const { getByText } = render(...)`)
|
|
2247
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/render-result-naming-convention.md
|
|
2248
|
+
"testing-library/render-result-naming-convention": "error"
|
|
2249
|
+
};
|
|
2103
2250
|
function testsOverride() {
|
|
2104
|
-
|
|
2251
|
+
return [
|
|
2105
2252
|
{
|
|
2106
2253
|
name: "eslint-config-setup/tests",
|
|
2107
|
-
files:
|
|
2254
|
+
files: TEST_FILES2,
|
|
2108
2255
|
plugins: {
|
|
2109
2256
|
vitest: vitestPlugin
|
|
2110
2257
|
},
|
|
2111
|
-
rules:
|
|
2112
|
-
// ── Vitest rules ──────────────────────────────────────────────
|
|
2113
|
-
// Every test must contain at least one assertion
|
|
2114
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md
|
|
2115
|
-
"vitest/expect-expect": "error",
|
|
2116
|
-
// Prevent duplicate test titles within a describe block
|
|
2117
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-identical-title.md
|
|
2118
|
-
"vitest/no-identical-title": "error",
|
|
2119
|
-
// No it.only / describe.only — prevents accidentally skipping tests in CI
|
|
2120
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-focused-tests.md
|
|
2121
|
-
"vitest/no-focused-tests": "error",
|
|
2122
|
-
// Warn on it.skip / describe.skip — track disabled tests
|
|
2123
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-disabled-tests.md
|
|
2124
|
-
"vitest/no-disabled-tests": "warn",
|
|
2125
|
-
// No duplicate beforeEach/afterEach hooks — merge them
|
|
2126
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-duplicate-hooks.md
|
|
2127
|
-
"vitest/no-duplicate-hooks": "error",
|
|
2128
|
-
// Prefer .toBe() over .toEqual() for primitives — clearer intent
|
|
2129
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-be.md
|
|
2130
|
-
"vitest/prefer-to-be": "error",
|
|
2131
|
-
// Prefer .toHaveLength() over .toBe(arr.length) — better errors
|
|
2132
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-have-length.md
|
|
2133
|
-
"vitest/prefer-to-have-length": "error",
|
|
2134
|
-
// Validate expect() usage — no dangling expect without assertion
|
|
2135
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md
|
|
2136
|
-
"vitest/valid-expect": "error",
|
|
2137
|
-
// Validate test/describe title format — no empty or invalid titles
|
|
2138
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-title.md
|
|
2139
|
-
"vitest/valid-title": "error",
|
|
2140
|
-
// No conditional logic (if/else) inside tests — split into separate tests
|
|
2141
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md
|
|
2142
|
-
"vitest/no-conditional-in-test": "error",
|
|
2143
|
-
// No expect() inside conditional blocks — always assert unconditionally
|
|
2144
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md
|
|
2145
|
-
"vitest/no-conditional-expect": "error",
|
|
2146
|
-
// No standalone expect() outside test blocks — always wrap in it/test
|
|
2147
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md
|
|
2148
|
-
"vitest/no-standalone-expect": "error",
|
|
2149
|
-
// Prefer .toStrictEqual() over .toEqual() — catches undefined vs missing
|
|
2150
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-strict-equal.md
|
|
2151
|
-
"vitest/prefer-strict-equal": "error",
|
|
2152
|
-
// Prefer vi.spyOn() over vi.fn() for method mocks — preserves original
|
|
2153
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-spy-on.md
|
|
2154
|
-
"vitest/prefer-spy-on": "error",
|
|
2155
|
-
// Require message argument in toThrow/toThrowError — verify correct error
|
|
2156
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-to-throw-message.md
|
|
2157
|
-
"vitest/require-to-throw-message": "error",
|
|
2158
|
-
// ── Relaxed rules for tests ───────────────────────────────────
|
|
2159
|
-
// Tests are naturally verbose and use patterns banned in prod code
|
|
2160
|
-
// Tests can be long (setup + many assertions)
|
|
2161
|
-
"max-lines": "off",
|
|
2162
|
-
"max-lines-per-function": "off",
|
|
2163
|
-
"max-statements": "off",
|
|
2164
|
-
// Tests often need `any` for mocking and type assertions
|
|
2165
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
2166
|
-
"@typescript-eslint/no-non-null-assertion": "off",
|
|
2167
|
-
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
2168
|
-
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
2169
|
-
}
|
|
2258
|
+
rules: VITEST_RULES
|
|
2170
2259
|
},
|
|
2171
2260
|
{
|
|
2172
2261
|
name: "eslint-config-setup/tests-testing-library",
|
|
2173
|
-
files:
|
|
2262
|
+
files: TEST_FILES2,
|
|
2174
2263
|
plugins: {
|
|
2175
2264
|
"testing-library": testingLibraryPlugin
|
|
2176
2265
|
},
|
|
2177
|
-
rules:
|
|
2178
|
-
// ── Testing Library rules ───────────────────────────────────
|
|
2179
|
-
// Await async events (userEvent.click, etc.) — prevents race conditions
|
|
2180
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-events.md
|
|
2181
|
-
"testing-library/await-async-events": "error",
|
|
2182
|
-
// Await async queries (findBy*) — they return promises
|
|
2183
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-queries.md
|
|
2184
|
-
"testing-library/await-async-queries": "error",
|
|
2185
|
-
// Await async utilities (waitFor, waitForElementToBeRemoved)
|
|
2186
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md
|
|
2187
|
-
"testing-library/await-async-utils": "error",
|
|
2188
|
-
// Don't await synchronous events — misleading
|
|
2189
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-events.md
|
|
2190
|
-
"testing-library/no-await-sync-events": "error",
|
|
2191
|
-
// Don't await synchronous queries (getBy*, queryBy*) — not promises
|
|
2192
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-queries.md
|
|
2193
|
-
"testing-library/no-await-sync-queries": "error",
|
|
2194
|
-
// Don't use container.querySelector — use queries instead
|
|
2195
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-container.md
|
|
2196
|
-
"testing-library/no-container": "error",
|
|
2197
|
-
// Warn on debug()/prettyDOM() left in tests — debugging artifacts
|
|
2198
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-debugging-utils.md
|
|
2199
|
-
"testing-library/no-debugging-utils": "warn",
|
|
2200
|
-
// Don't access DOM nodes directly — use queries
|
|
2201
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-node-access.md
|
|
2202
|
-
"testing-library/no-node-access": "error",
|
|
2203
|
-
// Don't call render in beforeEach — call in each test
|
|
2204
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-render-in-lifecycle.md
|
|
2205
|
-
"testing-library/no-render-in-lifecycle": "error",
|
|
2206
|
-
// No unnecessary act() wrappers — TL handles this internally
|
|
2207
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-unnecessary-act.md
|
|
2208
|
-
"testing-library/no-unnecessary-act": "error",
|
|
2209
|
-
// One assertion per waitFor — multiple can mask failures
|
|
2210
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-multiple-assertions.md
|
|
2211
|
-
"testing-library/no-wait-for-multiple-assertions": "error",
|
|
2212
|
-
// No side effects in waitFor — only assertions
|
|
2213
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md
|
|
2214
|
-
"testing-library/no-wait-for-side-effects": "error",
|
|
2215
|
-
// Prefer findBy* over waitFor + getBy* — built-in combination
|
|
2216
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-find-by.md
|
|
2217
|
-
"testing-library/prefer-find-by": "error",
|
|
2218
|
-
// Use getBy* (throws) for present elements, queryBy* for absent
|
|
2219
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-presence-queries.md
|
|
2220
|
-
"testing-library/prefer-presence-queries": "error",
|
|
2221
|
-
// Use queryBy* for disappearance checks — returns null when gone
|
|
2222
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-query-by-disappearance.md
|
|
2223
|
-
"testing-library/prefer-query-by-disappearance": "error",
|
|
2224
|
-
// Use screen.getBy* over destructured render result — consistent
|
|
2225
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-screen-queries.md
|
|
2226
|
-
"testing-library/prefer-screen-queries": "error",
|
|
2227
|
-
// Name render result consistently (e.g., `const { getByText } = render(...)`)
|
|
2228
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/render-result-naming-convention.md
|
|
2229
|
-
"testing-library/render-result-naming-convention": "error"
|
|
2230
|
-
}
|
|
2266
|
+
rules: TESTING_LIBRARY_RULES
|
|
2231
2267
|
}
|
|
2232
2268
|
];
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/oxlint/integration.ts
|
|
2272
|
+
import oxlintPlugin from "eslint-plugin-oxlint";
|
|
2273
|
+
function oxlintIntegration(opts) {
|
|
2274
|
+
const typedPlugin = oxlintPlugin;
|
|
2275
|
+
const configs = [];
|
|
2276
|
+
const add = (configName, blockName) => {
|
|
2277
|
+
const raw = typedPlugin.configs[configName];
|
|
2278
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
2279
|
+
for (const item of items) {
|
|
2280
|
+
configs.push({ name: `eslint-config-setup/${blockName}`, ...item });
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
add("flat/recommended", "oxlint");
|
|
2284
|
+
if (opts.react) {
|
|
2285
|
+
add("flat/react", "oxlint-react");
|
|
2286
|
+
add("flat/jsx-a11y", "oxlint-jsx-a11y");
|
|
2287
|
+
}
|
|
2288
|
+
if (opts.node) {
|
|
2289
|
+
add("flat/node", "oxlint-node");
|
|
2290
|
+
}
|
|
2291
|
+
add("flat/typescript", "oxlint-typescript");
|
|
2292
|
+
add("flat/unicorn", "oxlint-unicorn");
|
|
2293
|
+
add("flat/import", "oxlint-import");
|
|
2294
|
+
add("flat/jsdoc", "oxlint-jsdoc");
|
|
2233
2295
|
return configs;
|
|
2234
2296
|
}
|
|
2235
2297
|
|
|
@@ -2284,65 +2346,24 @@ function standardComplexity() {
|
|
|
2284
2346
|
];
|
|
2285
2347
|
}
|
|
2286
2348
|
|
|
2287
|
-
// src/oxlint/integration.ts
|
|
2288
|
-
import oxlintPlugin from "eslint-plugin-oxlint";
|
|
2289
|
-
function oxlintIntegration(opts) {
|
|
2290
|
-
const typedPlugin = oxlintPlugin;
|
|
2291
|
-
const configs = [];
|
|
2292
|
-
const add = (configName, blockName) => {
|
|
2293
|
-
const raw = typedPlugin.configs[configName];
|
|
2294
|
-
const items = Array.isArray(raw) ? raw : [raw];
|
|
2295
|
-
for (const item of items) {
|
|
2296
|
-
configs.push({ name: `eslint-config-setup/${blockName}`, ...item });
|
|
2297
|
-
}
|
|
2298
|
-
};
|
|
2299
|
-
add("flat/recommended", "oxlint");
|
|
2300
|
-
if (opts.react) {
|
|
2301
|
-
add("flat/react", "oxlint-react");
|
|
2302
|
-
add("flat/jsx-a11y", "oxlint-jsx-a11y");
|
|
2303
|
-
}
|
|
2304
|
-
if (opts.node) {
|
|
2305
|
-
add("flat/node", "oxlint-node");
|
|
2306
|
-
}
|
|
2307
|
-
add("flat/typescript", "oxlint-typescript");
|
|
2308
|
-
add("flat/unicorn", "oxlint-unicorn");
|
|
2309
|
-
add("flat/import", "oxlint-import");
|
|
2310
|
-
add("flat/jsdoc", "oxlint-jsdoc");
|
|
2311
|
-
return configs;
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
// src/configs/compat.ts
|
|
2315
|
-
import compatPlugin from "eslint-plugin-compat";
|
|
2316
|
-
function compatConfig() {
|
|
2317
|
-
return [
|
|
2318
|
-
{
|
|
2319
|
-
name: "eslint-config-setup/compat",
|
|
2320
|
-
plugins: {
|
|
2321
|
-
compat: compatPlugin
|
|
2322
|
-
},
|
|
2323
|
-
rules: {
|
|
2324
|
-
// Warn when using browser APIs not supported in browserslist targets
|
|
2325
|
-
// https://github.com/amilajack/eslint-plugin-compat#usage
|
|
2326
|
-
"compat/compat": "warn"
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
];
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
2349
|
// src/build/compose.ts
|
|
2333
|
-
function
|
|
2334
|
-
const
|
|
2335
|
-
config.push(
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2350
|
+
function addCoreConfigs(config, opts) {
|
|
2351
|
+
const ai = opts.ai;
|
|
2352
|
+
config.push(
|
|
2353
|
+
...baseConfig({ ai }),
|
|
2354
|
+
...typescriptConfig({ ai, react: opts.react }),
|
|
2355
|
+
...importsConfig(),
|
|
2356
|
+
...perfectionistConfig(),
|
|
2357
|
+
...unicornConfig({ ai }),
|
|
2358
|
+
...regexpConfig({ ai }),
|
|
2359
|
+
...jsdocConfig({ ai }),
|
|
2360
|
+
...cspellConfig(),
|
|
2361
|
+
...sonarjsConfig({ ai }),
|
|
2362
|
+
...securityConfig(),
|
|
2363
|
+
...deMorganConfig()
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
function addConditionalConfigs(config, opts) {
|
|
2346
2367
|
if (!opts.node) {
|
|
2347
2368
|
config.push(...compatConfig());
|
|
2348
2369
|
}
|
|
@@ -2350,30 +2371,40 @@ function composeConfig(opts) {
|
|
|
2350
2371
|
config.push(...standardComplexity());
|
|
2351
2372
|
}
|
|
2352
2373
|
if (opts.node) {
|
|
2353
|
-
config.push(...nodeConfig());
|
|
2374
|
+
config.push(...nodeConfig({ ai: opts.ai }));
|
|
2354
2375
|
}
|
|
2355
2376
|
if (opts.react) {
|
|
2356
|
-
config.push(...reactConfig());
|
|
2377
|
+
config.push(...reactConfig({ ai: opts.ai }));
|
|
2357
2378
|
config.push(...reactEffectConfig());
|
|
2358
2379
|
}
|
|
2359
2380
|
if (opts.ai) {
|
|
2360
|
-
config.push(...aiConfig(
|
|
2381
|
+
config.push(...aiConfig());
|
|
2361
2382
|
config.push(...perfectionistAiConfig());
|
|
2362
2383
|
config.push(...packageJsonAiConfig());
|
|
2363
2384
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
config.push(
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2385
|
+
}
|
|
2386
|
+
function addFileTypeOverrides(config) {
|
|
2387
|
+
config.push(
|
|
2388
|
+
...testsOverride(),
|
|
2389
|
+
...e2eOverride(),
|
|
2390
|
+
...storiesOverride(),
|
|
2391
|
+
...configFilesOverride(),
|
|
2392
|
+
...declarationsOverride(),
|
|
2393
|
+
...scriptsOverride()
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
function addFinalConfigs(config, opts) {
|
|
2397
|
+
config.push(...jsonConfig(), ...packageJsonConfig(), ...markdownConfig(), ...prettierCompatConfig());
|
|
2374
2398
|
if (opts.oxlint) {
|
|
2375
2399
|
config.push(...oxlintIntegration(opts));
|
|
2376
2400
|
}
|
|
2401
|
+
}
|
|
2402
|
+
function composeConfig(opts) {
|
|
2403
|
+
const config = [];
|
|
2404
|
+
addCoreConfigs(config, opts);
|
|
2405
|
+
addConditionalConfigs(config, opts);
|
|
2406
|
+
addFileTypeOverrides(config);
|
|
2407
|
+
addFinalConfigs(config, opts);
|
|
2377
2408
|
return config;
|
|
2378
2409
|
}
|
|
2379
2410
|
export {
|