eslint-config-setup 0.3.3 → 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 +134 -23
- package/dist/configs/12c62446.js +155 -9
- package/dist/configs/196d687e.js +277 -46
- package/dist/configs/1c3f743c.js +157 -9
- package/dist/configs/2f6f3a82.js +303 -39
- package/dist/configs/4eb62e57.js +136 -20
- package/dist/configs/52762a42.js +274 -33
- package/dist/configs/532f50a4.js +476 -49
- package/dist/configs/5a302873.js +301 -39
- package/dist/configs/6bc0d588.js +438 -56
- package/dist/configs/91e82988.js +478 -49
- package/dist/configs/c2fecd3d.js +276 -30
- package/dist/configs/cde010b4.js +309 -16
- package/dist/configs/d537b683.js +440 -53
- package/dist/configs/db69ebb6.js +279 -43
- 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 -1102
- 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,591 +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
|
-
"require-unicode-regexp": "error",
|
|
79
|
-
// ── B. Magic numbers & constants — no unexplained code ────────
|
|
80
|
-
// No magic numbers — extract to named constants (allows -1, 0, 1, 2)
|
|
81
|
-
// https://typescript-eslint.io/rules/no-magic-numbers
|
|
82
|
-
"@typescript-eslint/no-magic-numbers": [
|
|
83
|
-
"error",
|
|
84
|
-
{
|
|
85
|
-
ignore: [-1, 0, 1, 2],
|
|
86
|
-
ignoreArrayIndexes: true,
|
|
87
|
-
ignoreDefaultValues: true,
|
|
88
|
-
enforceConst: true,
|
|
89
|
-
ignoreClassFieldInitialValues: true,
|
|
90
|
-
ignoreEnums: true,
|
|
91
|
-
ignoreNumericLiteralTypes: true,
|
|
92
|
-
ignoreReadonlyClassProperties: true,
|
|
93
|
-
ignoreTypeIndexes: true
|
|
94
|
-
}
|
|
95
|
-
],
|
|
96
|
-
// No duplicate strings (threshold 3) — extract to constant
|
|
97
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1192/javascript
|
|
98
|
-
"sonarjs/no-duplicate-string": ["error", { threshold: 3 }],
|
|
99
|
-
// Flag TODO/FIXME/HACK comments — technical debt tracker
|
|
100
|
-
// https://eslint.org/docs/latest/rules/no-warning-comments
|
|
101
|
-
"no-warning-comments": "warn",
|
|
102
|
-
// ── F. Async/Promise hygiene ──────────────────────────────────
|
|
103
|
-
// No await inside loops — use Promise.all() for parallel execution
|
|
104
|
-
// https://eslint.org/docs/latest/rules/no-await-in-loop
|
|
105
|
-
"no-await-in-loop": "error",
|
|
106
|
-
// Disallow returning values from Promise executors — use resolve/reject
|
|
107
|
-
// https://eslint.org/docs/latest/rules/no-promise-executor-return
|
|
108
|
-
"no-promise-executor-return": "error",
|
|
109
|
-
// Every Promise must be awaited, returned, or voided — prevents silent failures
|
|
110
|
-
// https://typescript-eslint.io/rules/no-floating-promises
|
|
111
|
-
"@typescript-eslint/no-floating-promises": [
|
|
112
|
-
"error",
|
|
113
|
-
{ checkThenables: true, ignoreVoid: true }
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "eslint-config-setup/ai-typescript",
|
|
119
|
-
rules: {
|
|
120
|
-
// ── C. TypeScript strictness — explicit types, safe patterns ──
|
|
121
|
-
// Require explicit return types — self-documenting function signatures
|
|
122
|
-
// https://typescript-eslint.io/rules/explicit-function-return-type
|
|
123
|
-
"@typescript-eslint/explicit-function-return-type": [
|
|
124
|
-
"error",
|
|
125
|
-
{
|
|
126
|
-
allowExpressions: true,
|
|
127
|
-
allowTypedFunctionExpressions: true,
|
|
128
|
-
allowHigherOrderFunctions: true,
|
|
129
|
-
allowIIFEs: true
|
|
130
|
-
}
|
|
131
|
-
],
|
|
132
|
-
// Enforce consistent naming: camelCase for values, PascalCase for types,
|
|
133
|
-
// is/has/can/should/will/did prefix for boolean variables
|
|
134
|
-
// https://typescript-eslint.io/rules/naming-convention
|
|
135
|
-
"@typescript-eslint/naming-convention": [
|
|
136
|
-
"error",
|
|
137
|
-
{
|
|
138
|
-
selector: "variable",
|
|
139
|
-
format: isReact ? ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] : ["strictCamelCase", "UPPER_CASE"],
|
|
140
|
-
leadingUnderscore: "allowSingleOrDouble",
|
|
141
|
-
trailingUnderscore: "allow",
|
|
142
|
-
filter: { regex: "[- ]", match: false }
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
selector: "function",
|
|
146
|
-
format: isReact ? ["strictCamelCase", "StrictPascalCase"] : ["strictCamelCase"]
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
selector: "parameter",
|
|
150
|
-
format: ["strictCamelCase"],
|
|
151
|
-
leadingUnderscore: "allow"
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
selector: "import",
|
|
155
|
-
format: ["strictCamelCase", "StrictPascalCase", "UPPER_CASE"]
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
selector: [
|
|
159
|
-
"classProperty",
|
|
160
|
-
"parameterProperty",
|
|
161
|
-
"classMethod",
|
|
162
|
-
"objectLiteralMethod",
|
|
163
|
-
"typeMethod",
|
|
164
|
-
"accessor"
|
|
165
|
-
],
|
|
166
|
-
format: ["strictCamelCase"],
|
|
167
|
-
leadingUnderscore: "allowSingleOrDouble",
|
|
168
|
-
trailingUnderscore: "allow",
|
|
169
|
-
filter: { regex: "[- ]", match: false }
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
selector: ["objectLiteralProperty", "typeProperty"],
|
|
173
|
-
format: null
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
selector: "typeLike",
|
|
177
|
-
format: ["StrictPascalCase"]
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
// No "I" prefix on interfaces — use descriptive names (XO convention)
|
|
181
|
-
selector: "interface",
|
|
182
|
-
format: ["StrictPascalCase"],
|
|
183
|
-
custom: { regex: "^I[A-Z]", match: false }
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
// Type parameters: single uppercase letter (T) or PascalCase (TResult)
|
|
187
|
-
selector: "typeParameter",
|
|
188
|
-
format: ["PascalCase"],
|
|
189
|
-
custom: { regex: "^(T([A-Z][a-zA-Z]*)?|[A-Z])$", match: true }
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
selector: "variable",
|
|
193
|
-
types: ["boolean"],
|
|
194
|
-
format: ["StrictPascalCase"],
|
|
195
|
-
prefix: ["is", "has", "can", "should", "will", "did"]
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
selector: ["classProperty", "objectLiteralProperty"],
|
|
199
|
-
format: null,
|
|
200
|
-
modifiers: ["requiresQuotes"]
|
|
201
|
-
}
|
|
202
|
-
],
|
|
203
|
-
// Enforce `import type { T }` — types are erased at compile time
|
|
204
|
-
// https://typescript-eslint.io/rules/consistent-type-imports
|
|
205
|
-
"@typescript-eslint/consistent-type-imports": [
|
|
206
|
-
"error",
|
|
207
|
-
{ fixStyle: "inline-type-imports" }
|
|
208
|
-
],
|
|
209
|
-
// Enforce `export type { T }` — matches import convention
|
|
210
|
-
// https://typescript-eslint.io/rules/consistent-type-exports
|
|
211
|
-
"@typescript-eslint/consistent-type-exports": [
|
|
212
|
-
"error",
|
|
213
|
-
{ fixMixedExportsWithInlineTypeSpecifier: true }
|
|
214
|
-
],
|
|
215
|
-
// Disallow `any` type — auto-fix to `unknown` for type safety
|
|
216
|
-
// https://typescript-eslint.io/rules/no-explicit-any
|
|
217
|
-
"@typescript-eslint/no-explicit-any": ["error", { fixToUnknown: true }],
|
|
218
|
-
// Prefer readonly for unmodified class properties — signals immutability
|
|
219
|
-
// https://typescript-eslint.io/rules/prefer-readonly
|
|
220
|
-
"@typescript-eslint/prefer-readonly": "error",
|
|
221
|
-
// Functions returning promises must be async — consistent async patterns
|
|
222
|
-
// https://typescript-eslint.io/rules/promise-function-async
|
|
223
|
-
"@typescript-eslint/promise-function-async": "error",
|
|
224
|
-
// Exhaustive switch statements — no missing cases, no unnecessary defaults
|
|
225
|
-
// https://typescript-eslint.io/rules/switch-exhaustiveness-check
|
|
226
|
-
"@typescript-eslint/switch-exhaustiveness-check": [
|
|
227
|
-
"error",
|
|
228
|
-
{
|
|
229
|
-
allowDefaultCaseForExhaustiveSwitch: false,
|
|
230
|
-
requireDefaultForNonUnion: true
|
|
231
|
-
}
|
|
232
|
-
],
|
|
233
|
-
// Disallow unsafe type assertions (as Type) — use type guards instead
|
|
234
|
-
// https://typescript-eslint.io/rules/no-unsafe-type-assertion
|
|
235
|
-
"@typescript-eslint/no-unsafe-type-assertion": "error",
|
|
236
|
-
// Require comparator for Array.sort() — prevents locale-dependent string sort
|
|
237
|
-
// https://typescript-eslint.io/rules/require-array-sort-compare
|
|
238
|
-
"@typescript-eslint/require-array-sort-compare": [
|
|
239
|
-
"error",
|
|
240
|
-
{ ignoreStringArrays: true }
|
|
241
|
-
],
|
|
242
|
-
// Require explicit `public`/`private`/`protected` on class members
|
|
243
|
-
// https://typescript-eslint.io/rules/explicit-member-accessibility
|
|
244
|
-
"@typescript-eslint/explicit-member-accessibility": "error",
|
|
245
|
-
// Enforce property style for method signatures — prevents bivariance issues
|
|
246
|
-
// https://typescript-eslint.io/rules/method-signature-style
|
|
247
|
-
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
248
|
-
// Require explicit values for enum members — prevents accidental shifts on reorder
|
|
249
|
-
// https://typescript-eslint.io/rules/prefer-enum-initializers
|
|
250
|
-
"@typescript-eslint/prefer-enum-initializers": "error",
|
|
251
|
-
// Prefer `type` over `interface` — consistent, supports unions/intersections
|
|
252
|
-
// https://typescript-eslint.io/rules/consistent-type-definitions
|
|
253
|
-
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
|
254
|
-
// Enforce consistent member ordering in classes and interfaces
|
|
255
|
-
// Static → fields by visibility → constructors → methods by visibility (XO convention)
|
|
256
|
-
// https://typescript-eslint.io/rules/member-ordering
|
|
257
|
-
"@typescript-eslint/member-ordering": [
|
|
258
|
-
"warn",
|
|
259
|
-
{
|
|
260
|
-
default: [
|
|
261
|
-
// Index signature
|
|
262
|
-
"signature",
|
|
263
|
-
"call-signature",
|
|
264
|
-
// Static
|
|
265
|
-
"public-static-field",
|
|
266
|
-
"protected-static-field",
|
|
267
|
-
"private-static-field",
|
|
268
|
-
"#private-static-field",
|
|
269
|
-
"static-field",
|
|
270
|
-
"public-static-method",
|
|
271
|
-
"protected-static-method",
|
|
272
|
-
"private-static-method",
|
|
273
|
-
"#private-static-method",
|
|
274
|
-
"static-method",
|
|
275
|
-
// Fields
|
|
276
|
-
"public-decorated-field",
|
|
277
|
-
"protected-decorated-field",
|
|
278
|
-
"private-decorated-field",
|
|
279
|
-
"public-instance-field",
|
|
280
|
-
"protected-instance-field",
|
|
281
|
-
"private-instance-field",
|
|
282
|
-
"#private-instance-field",
|
|
283
|
-
"public-abstract-field",
|
|
284
|
-
"protected-abstract-field",
|
|
285
|
-
"field",
|
|
286
|
-
// Constructors
|
|
287
|
-
"public-constructor",
|
|
288
|
-
"protected-constructor",
|
|
289
|
-
"private-constructor",
|
|
290
|
-
"constructor",
|
|
291
|
-
// Getters/Setters
|
|
292
|
-
["public-get", "public-set"],
|
|
293
|
-
["protected-get", "protected-set"],
|
|
294
|
-
["private-get", "private-set"],
|
|
295
|
-
["#private-get", "#private-set"],
|
|
296
|
-
// Methods
|
|
297
|
-
"public-decorated-method",
|
|
298
|
-
"protected-decorated-method",
|
|
299
|
-
"private-decorated-method",
|
|
300
|
-
"public-instance-method",
|
|
301
|
-
"protected-instance-method",
|
|
302
|
-
"private-instance-method",
|
|
303
|
-
"#private-instance-method",
|
|
304
|
-
"public-abstract-method",
|
|
305
|
-
"protected-abstract-method",
|
|
306
|
-
"method"
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: "eslint-config-setup/ai-unicorn",
|
|
314
|
-
rules: {
|
|
315
|
-
// ── D. Unicorn — modern, idiomatic patterns ───────────────────
|
|
316
|
-
// Move functions to the smallest scope where they're used
|
|
317
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md
|
|
318
|
-
"unicorn/consistent-function-scoping": "error",
|
|
319
|
-
// Forbid blanket `/* eslint-disable */` — must specify rules
|
|
320
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-abusive-eslint-disable.md
|
|
321
|
-
"unicorn/no-abusive-eslint-disable": "error",
|
|
322
|
-
// No .forEach() — use for-of loop (breakable, async-safe)
|
|
323
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-for-each.md
|
|
324
|
-
"unicorn/no-array-for-each": "error",
|
|
325
|
-
// No .reduce() — explicit loops are more readable
|
|
326
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md
|
|
327
|
-
"unicorn/no-array-reduce": "error",
|
|
328
|
-
// Prefer simple ternary over if/else for single-line assignments
|
|
329
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-ternary.md
|
|
330
|
-
"unicorn/prefer-ternary": ["error", "only-single-line"],
|
|
331
|
-
// Prefer switch for 3+ conditions on same variable — structured
|
|
332
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-switch.md
|
|
333
|
-
"unicorn/prefer-switch": ["error", { minimumCases: 3 }],
|
|
334
|
-
// Enforce camelCase or PascalCase filenames — consistent project structure
|
|
335
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md
|
|
336
|
-
"unicorn/filename-case": [
|
|
337
|
-
"error",
|
|
338
|
-
{ cases: { camelCase: true, pascalCase: true } }
|
|
339
|
-
],
|
|
340
|
-
// Prevent abbreviated variable names (e → error, btn → button) — readable
|
|
341
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prevent-abbreviations.md
|
|
342
|
-
"unicorn/prevent-abbreviations": "error",
|
|
343
|
-
// Detect useless switch cases that fall through to the next case
|
|
344
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-switch-case.md
|
|
345
|
-
"unicorn/no-useless-switch-case": "error",
|
|
346
|
-
// Enforce correct Error subclassing (name, constructor pattern)
|
|
347
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/custom-error-definition.md
|
|
348
|
-
"unicorn/custom-error-definition": "error",
|
|
349
|
-
// Prefer default parameters over manual reassignment
|
|
350
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-default-parameters.md
|
|
351
|
-
"unicorn/prefer-default-parameters": "error",
|
|
352
|
-
// Prefer `a || b` over `a ? a : b` — simpler when equivalent
|
|
353
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-logical-operator-over-ternary.md
|
|
354
|
-
"unicorn/prefer-logical-operator-over-ternary": "error",
|
|
355
|
-
// Prefer Math.min()/Math.max() over ternaries for clamping
|
|
356
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-math-min-max.md
|
|
357
|
-
"unicorn/prefer-math-min-max": "error",
|
|
358
|
-
// Prefer Set#size over converting to array — direct and correct
|
|
359
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-size.md
|
|
360
|
-
"unicorn/prefer-set-size": "error",
|
|
361
|
-
// Enforce explicit `.length > 0` / `.length === 0` checks
|
|
362
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/explicit-length-check.md
|
|
363
|
-
"unicorn/explicit-length-check": "error",
|
|
364
|
-
// Prefer for-of over C-style for loops — no off-by-one risk
|
|
365
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md
|
|
366
|
-
"unicorn/no-for-loop": "error",
|
|
367
|
-
// Enforce braces in switch cases — prevents scope leakage
|
|
368
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/switch-case-braces.md
|
|
369
|
-
"unicorn/switch-case-braces": "error",
|
|
370
|
-
// Combine multiple .push() calls into one — cleaner
|
|
371
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-push-push.md
|
|
372
|
-
"unicorn/no-array-push-push": "error"
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
name: "eslint-config-setup/ai-sonarjs",
|
|
377
|
-
rules: {
|
|
378
|
-
// ── E. SonarJS — code quality and duplicates ──────────────────
|
|
379
|
-
// Detect copy-pasted functions — extract to shared helper
|
|
380
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4144/javascript
|
|
381
|
-
"sonarjs/no-identical-functions": "error",
|
|
382
|
-
// Merge nested if-statements that can be combined — reduce nesting
|
|
383
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1066/javascript
|
|
384
|
-
"sonarjs/no-collapsible-if": "error",
|
|
385
|
-
// Simplify redundant boolean expressions
|
|
386
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1125/javascript
|
|
387
|
-
"sonarjs/no-redundant-boolean": "error",
|
|
388
|
-
// Detect collections that are populated but never read — dead code
|
|
389
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4030/javascript
|
|
390
|
-
"sonarjs/no-unused-collection": "error",
|
|
391
|
-
// Return value directly instead of storing in temp variable
|
|
392
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1488/javascript
|
|
393
|
-
"sonarjs/prefer-immediate-return": "error",
|
|
394
|
-
// Simplify boolean return patterns
|
|
395
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1126/javascript
|
|
396
|
-
"sonarjs/prefer-single-boolean-return": "error",
|
|
397
|
-
// Detect identical sub-expressions on both sides of operator
|
|
398
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1764/javascript
|
|
399
|
-
"sonarjs/no-identical-expressions": "error",
|
|
400
|
-
// Simplify negated boolean checks — `!a !== b` → `a === b`
|
|
401
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1940/javascript
|
|
402
|
-
"sonarjs/no-inverted-boolean-check": "error",
|
|
403
|
-
// Disallow nested switch statements — extract to function
|
|
404
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1821/javascript
|
|
405
|
-
"sonarjs/no-nested-switch": "error",
|
|
406
|
-
// Disallow nested template literals — unreadable
|
|
407
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4624/javascript
|
|
408
|
-
"sonarjs/no-nested-template-literals": "error",
|
|
409
|
-
// Limit union type size — too many members signals missing abstraction
|
|
410
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4622/javascript
|
|
411
|
-
"sonarjs/max-union-size": ["error", { threshold: 5 }],
|
|
412
|
-
// Prefer type predicates for type narrowing — safer than assertions
|
|
413
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4322/javascript
|
|
414
|
-
"sonarjs/prefer-type-guard": "error",
|
|
415
|
-
// Public static fields should be readonly — prevents accidental mutation
|
|
416
|
-
// https://sonarsource.github.io/rspec/#/rspec/S1444/javascript
|
|
417
|
-
"sonarjs/public-static-readonly": "error"
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
name: "eslint-config-setup/ai-regexp",
|
|
422
|
-
rules: {
|
|
423
|
-
// Prefer lookarounds over capturing groups used only for context
|
|
424
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-lookaround.html
|
|
425
|
-
"regexp/prefer-lookaround": "error",
|
|
426
|
-
// Prefer named backreferences — \k<quote> over \1
|
|
427
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-named-backreference.html
|
|
428
|
-
"regexp/prefer-named-backreference": "error",
|
|
429
|
-
// Prefer named replacement — $<name> over $1
|
|
430
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-named-replacement.html
|
|
431
|
-
"regexp/prefer-named-replacement": "error",
|
|
432
|
-
// Prefer quantifier shorthand — a{3} over aaa
|
|
433
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-quantifier.html
|
|
434
|
-
"regexp/prefer-quantifier": "error",
|
|
435
|
-
// Prefer match.groups.name over match[1] — consistent named groups usage
|
|
436
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-result-array-groups.html
|
|
437
|
-
"regexp/prefer-result-array-groups": "error",
|
|
438
|
-
// Require v flag (unicode sets) over u flag — stricter ES2024 superset
|
|
439
|
-
// https://ota-meshi.github.io/eslint-plugin-regexp/rules/require-unicode-sets-regexp.html
|
|
440
|
-
"regexp/require-unicode-sets-regexp": "error"
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
name: "eslint-config-setup/ai-jsdoc",
|
|
445
|
-
rules: {
|
|
446
|
-
// Prevent comments that just repeat the name — `/** The name */ name: string`
|
|
447
|
-
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
|
|
448
|
-
"jsdoc/informative-docs": "error",
|
|
449
|
-
// AI should document parameters and return values completely
|
|
450
|
-
"jsdoc/require-param": "error",
|
|
451
|
-
"jsdoc/require-returns": "error"
|
|
452
|
-
}
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
name: "eslint-config-setup/ai-node",
|
|
456
|
-
rules: {
|
|
457
|
-
// Warn when using Node.js builtins not available in the target version
|
|
458
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-unsupported-features/node-builtins.md
|
|
459
|
-
"node/no-unsupported-features/node-builtins": "error"
|
|
460
|
-
}
|
|
461
|
-
},
|
|
462
|
-
{
|
|
463
|
-
name: "eslint-config-setup/ai-react",
|
|
464
|
-
rules: {
|
|
465
|
-
// No click handlers on static elements without role — use semantic HTML
|
|
466
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-static-element-interactions.md
|
|
467
|
-
"jsx-a11y/no-static-element-interactions": "error",
|
|
468
|
-
// No event handlers on non-interactive elements — use button/link instead
|
|
469
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-interactions.md
|
|
470
|
-
"jsx-a11y/no-noninteractive-element-interactions": "error",
|
|
471
|
-
// Interactive elements (role="button") must be focusable
|
|
472
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/interactive-supports-focus.md
|
|
473
|
-
"jsx-a11y/interactive-supports-focus": "error"
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
name: "eslint-config-setup/ai-complexity",
|
|
478
|
-
rules: {
|
|
479
|
-
// Cyclomatic complexity limit — max branches per function
|
|
480
|
-
// https://eslint.org/docs/latest/rules/complexity
|
|
481
|
-
complexity: ["error", 10],
|
|
482
|
-
// Max nesting depth — deep nesting signals need for extraction
|
|
483
|
-
// https://eslint.org/docs/latest/rules/max-depth
|
|
484
|
-
"max-depth": ["error", 3],
|
|
485
|
-
// Max nested callbacks — prevents callback hell
|
|
486
|
-
// https://eslint.org/docs/latest/rules/max-nested-callbacks
|
|
487
|
-
"max-nested-callbacks": ["error", 2],
|
|
488
|
-
// Max function parameters — many params suggest a config object
|
|
489
|
-
// https://eslint.org/docs/latest/rules/max-params
|
|
490
|
-
"max-params": ["error", 3],
|
|
491
|
-
// Max statements per function — keeps functions focused
|
|
492
|
-
// https://eslint.org/docs/latest/rules/max-statements
|
|
493
|
-
"max-statements": ["error", 15],
|
|
494
|
-
// Max lines per function — encourages extraction of helpers
|
|
495
|
-
// https://eslint.org/docs/latest/rules/max-lines-per-function
|
|
496
|
-
"max-lines-per-function": [
|
|
497
|
-
"error",
|
|
498
|
-
{
|
|
499
|
-
max: 50,
|
|
500
|
-
skipBlankLines: true,
|
|
501
|
-
skipComments: true
|
|
502
|
-
}
|
|
503
|
-
],
|
|
504
|
-
// Max lines per file — encourages modular file organization
|
|
505
|
-
// https://eslint.org/docs/latest/rules/max-lines
|
|
506
|
-
"max-lines": [
|
|
507
|
-
"error",
|
|
508
|
-
{
|
|
509
|
-
max: 300,
|
|
510
|
-
skipBlankLines: true,
|
|
511
|
-
skipComments: true
|
|
512
|
-
}
|
|
513
|
-
],
|
|
514
|
-
// Cognitive complexity — measures how hard a function is to understand
|
|
515
|
-
// https://sonarsource.github.io/rspec/#/rspec/S3776/javascript
|
|
516
|
-
"sonarjs/cognitive-complexity": ["error", 10]
|
|
517
|
-
}
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: "eslint-config-setup/ai-tests-strict",
|
|
521
|
-
files: ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"],
|
|
522
|
-
rules: {
|
|
523
|
-
// Every test must be inside a describe block — organized test suites
|
|
524
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-top-level-describe.md
|
|
525
|
-
"vitest/require-top-level-describe": "error",
|
|
526
|
-
// Hooks (beforeEach, afterEach) must be at the top of describe — predictable setup
|
|
527
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-hooks-on-top.md
|
|
528
|
-
"vitest/prefer-hooks-on-top": "error"
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
name: "eslint-config-setup/ai-tests-relaxed",
|
|
533
|
-
files: ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"],
|
|
534
|
-
rules: {
|
|
535
|
-
"max-lines": "off",
|
|
536
|
-
"max-lines-per-function": "off",
|
|
537
|
-
"max-statements": "off",
|
|
538
|
-
"max-nested-callbacks": "off",
|
|
539
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
540
|
-
"sonarjs/no-duplicate-string": "off",
|
|
541
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
542
|
-
"@typescript-eslint/naming-convention": "off",
|
|
543
|
-
"unicorn/prevent-abbreviations": "off"
|
|
544
|
-
}
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: "eslint-config-setup/ai-e2e-relaxed",
|
|
548
|
-
files: ["**/*.spec.ts"],
|
|
549
|
-
rules: {
|
|
550
|
-
"max-lines": "off",
|
|
551
|
-
"max-lines-per-function": "off",
|
|
552
|
-
"max-statements": "off",
|
|
553
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
554
|
-
"@typescript-eslint/explicit-function-return-type": "off"
|
|
555
|
-
}
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
name: "eslint-config-setup/ai-config-relaxed",
|
|
559
|
-
files: [
|
|
560
|
-
"**/*.config.{ts,mts,cts,js,mjs,cjs}",
|
|
561
|
-
"**/vite.config.*",
|
|
562
|
-
"**/vitest.config.*",
|
|
563
|
-
"**/next.config.*"
|
|
564
|
-
],
|
|
565
|
-
rules: {
|
|
566
|
-
complexity: "off",
|
|
567
|
-
"max-lines": "off",
|
|
568
|
-
"max-lines-per-function": "off",
|
|
569
|
-
"max-statements": "off",
|
|
570
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
571
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
572
|
-
"@typescript-eslint/naming-convention": "off"
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
{
|
|
576
|
-
name: "eslint-config-setup/ai-declarations-relaxed",
|
|
577
|
-
files: ["**/*.d.ts"],
|
|
578
|
-
rules: {
|
|
579
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
580
|
-
"@typescript-eslint/naming-convention": "off",
|
|
581
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
582
|
-
"@typescript-eslint/no-magic-numbers": "off",
|
|
583
|
-
"unicorn/prevent-abbreviations": "off",
|
|
584
|
-
"unicorn/filename-case": "off"
|
|
585
|
-
}
|
|
586
|
-
}
|
|
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
|
|
587
78
|
];
|
|
588
|
-
return configs;
|
|
589
79
|
}
|
|
590
80
|
|
|
591
81
|
// src/configs/base.ts
|
|
@@ -593,7 +83,7 @@ import eslint from "@eslint/js";
|
|
|
593
83
|
|
|
594
84
|
// src/build/config-builder.ts
|
|
595
85
|
function createConfig(options) {
|
|
596
|
-
const
|
|
86
|
+
const presetRules2 = /* @__PURE__ */ new Map();
|
|
597
87
|
const presetPlugins = {};
|
|
598
88
|
if (options.presets) {
|
|
599
89
|
for (const preset of options.presets) {
|
|
@@ -603,7 +93,7 @@ function createConfig(options) {
|
|
|
603
93
|
if (preset.rules) {
|
|
604
94
|
for (const [name, value] of Object.entries(preset.rules)) {
|
|
605
95
|
if (value !== void 0) {
|
|
606
|
-
|
|
96
|
+
presetRules2.set(name, value);
|
|
607
97
|
}
|
|
608
98
|
}
|
|
609
99
|
}
|
|
@@ -616,7 +106,7 @@ function createConfig(options) {
|
|
|
616
106
|
const fileOverrides = [];
|
|
617
107
|
const builder = {
|
|
618
108
|
overrideRule(name, value) {
|
|
619
|
-
if (!
|
|
109
|
+
if (!presetRules2.has(name)) {
|
|
620
110
|
throw new Error(
|
|
621
111
|
`overrideRule("${name}"): rule not found in preset. Cannot override a rule that doesn't exist in the preset.`
|
|
622
112
|
);
|
|
@@ -624,8 +114,34 @@ function createConfig(options) {
|
|
|
624
114
|
overrides.set(name, value);
|
|
625
115
|
return builder;
|
|
626
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
|
+
},
|
|
627
143
|
addRule(name, value) {
|
|
628
|
-
if (
|
|
144
|
+
if (presetRules2.has(name)) {
|
|
629
145
|
throw new Error(
|
|
630
146
|
`addRule("${name}"): rule already exists in preset. Use overrideRule() to change its value, or removeRule() to drop it.`
|
|
631
147
|
);
|
|
@@ -639,7 +155,7 @@ function createConfig(options) {
|
|
|
639
155
|
return builder;
|
|
640
156
|
},
|
|
641
157
|
disableRule(name) {
|
|
642
|
-
if (!
|
|
158
|
+
if (!presetRules2.has(name) && !additions.has(name)) {
|
|
643
159
|
throw new Error(
|
|
644
160
|
`disableRule("${name}"): rule not found in preset or additions. Cannot disable a rule that doesn't exist.`
|
|
645
161
|
);
|
|
@@ -648,7 +164,7 @@ function createConfig(options) {
|
|
|
648
164
|
return builder;
|
|
649
165
|
},
|
|
650
166
|
removeRule(name) {
|
|
651
|
-
if (!
|
|
167
|
+
if (!presetRules2.has(name) && !additions.has(name)) {
|
|
652
168
|
throw new Error(
|
|
653
169
|
`removeRule("${name}"): rule not found in preset or additions. Cannot remove a rule that doesn't exist.`
|
|
654
170
|
);
|
|
@@ -662,7 +178,7 @@ function createConfig(options) {
|
|
|
662
178
|
},
|
|
663
179
|
build() {
|
|
664
180
|
const rules = {};
|
|
665
|
-
for (const [name, value] of
|
|
181
|
+
for (const [name, value] of presetRules2) {
|
|
666
182
|
if (!removed.has(name)) {
|
|
667
183
|
rules[name] = value;
|
|
668
184
|
}
|
|
@@ -721,14 +237,147 @@ function createConfig(options) {
|
|
|
721
237
|
}
|
|
722
238
|
|
|
723
239
|
// src/configs/base.ts
|
|
724
|
-
|
|
725
|
-
|
|
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({
|
|
726
350
|
name: "eslint-config-setup/base",
|
|
727
351
|
presets: [eslint.configs.recommended]
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
+
];
|
|
732
381
|
}
|
|
733
382
|
|
|
734
383
|
// src/configs/cspell.ts
|
|
@@ -788,6 +437,7 @@ function importsConfig() {
|
|
|
788
437
|
{
|
|
789
438
|
name: "eslint-config-setup/imports",
|
|
790
439
|
plugins: {
|
|
440
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Plugin type is compatible
|
|
791
441
|
"import": importXPlugin,
|
|
792
442
|
"unused-imports": unusedImportsPlugin
|
|
793
443
|
},
|
|
@@ -795,7 +445,7 @@ function importsConfig() {
|
|
|
795
445
|
// ── Validation (import-x) ────────────────────────────────────
|
|
796
446
|
// Merge duplicate import paths into one statement — reduces noise
|
|
797
447
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md
|
|
798
|
-
"import/no-duplicates":
|
|
448
|
+
"import/no-duplicates": "error",
|
|
799
449
|
// Forbid a module from importing itself — always a bug
|
|
800
450
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/no-self-import.md
|
|
801
451
|
"import/no-self-import": "error",
|
|
@@ -826,6 +476,30 @@ function importsConfig() {
|
|
|
826
476
|
// Forbid absolute file paths in imports — not portable across machines
|
|
827
477
|
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-absolute-path.md
|
|
828
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",
|
|
829
503
|
// ── Disabled: ordering handled by perfectionist ────────────────
|
|
830
504
|
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/order.md
|
|
831
505
|
"import/order": "off",
|
|
@@ -842,11 +516,18 @@ function importsConfig() {
|
|
|
842
516
|
|
|
843
517
|
// src/configs/jsdoc.ts
|
|
844
518
|
import jsdocPlugin from "eslint-plugin-jsdoc";
|
|
845
|
-
function jsdocConfig() {
|
|
846
|
-
|
|
519
|
+
function jsdocConfig(opts) {
|
|
520
|
+
const isAi = opts?.ai ?? false;
|
|
521
|
+
const builder = createConfig({
|
|
847
522
|
name: "eslint-config-setup/jsdoc",
|
|
848
523
|
presets: [jsdocPlugin.configs["flat/recommended-typescript-error"]]
|
|
849
|
-
}).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();
|
|
850
531
|
}
|
|
851
532
|
|
|
852
533
|
// src/configs/json.ts
|
|
@@ -910,6 +591,8 @@ function jsonConfig() {
|
|
|
910
591
|
// src/configs/markdown.ts
|
|
911
592
|
import * as mdxPlugin from "eslint-plugin-mdx";
|
|
912
593
|
function markdownConfig() {
|
|
594
|
+
const codeBlockLanguageOptions = mdxPlugin.flatCodeBlocks.languageOptions ?? {};
|
|
595
|
+
const codeBlockParserOptions = codeBlockLanguageOptions.parserOptions ?? {};
|
|
913
596
|
return [
|
|
914
597
|
// ── MDX / Markdown parsing ─────────────────────────────────────
|
|
915
598
|
{
|
|
@@ -924,6 +607,13 @@ function markdownConfig() {
|
|
|
924
607
|
// Relaxes rules that don't apply to incomplete code snippets.
|
|
925
608
|
{
|
|
926
609
|
...mdxPlugin.flatCodeBlocks,
|
|
610
|
+
languageOptions: {
|
|
611
|
+
...codeBlockLanguageOptions,
|
|
612
|
+
parserOptions: {
|
|
613
|
+
...codeBlockParserOptions,
|
|
614
|
+
projectService: false
|
|
615
|
+
}
|
|
616
|
+
},
|
|
927
617
|
rules: {
|
|
928
618
|
...mdxPlugin.flatCodeBlocks.rules,
|
|
929
619
|
// Snippets don't need trailing newlines
|
|
@@ -948,69 +638,76 @@ function markdownConfig() {
|
|
|
948
638
|
// src/configs/node.ts
|
|
949
639
|
import nodePlugin from "eslint-plugin-n";
|
|
950
640
|
import globals from "globals";
|
|
951
|
-
function nodeConfig() {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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"
|
|
958
701
|
}
|
|
959
|
-
},
|
|
960
|
-
plugins: {
|
|
961
|
-
node: nodePlugin
|
|
962
|
-
},
|
|
963
|
-
rules: {
|
|
964
|
-
// Detect usage of deprecated Node.js APIs (fs.exists, url.parse, etc.)
|
|
965
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-deprecated-api.md
|
|
966
|
-
"node/no-deprecated-api": "error",
|
|
967
|
-
// Prevent `module.exports = ...` assignment in ES modules
|
|
968
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-exports-assign.md
|
|
969
|
-
"node/no-exports-assign": "error",
|
|
970
|
-
// OFF: TypeScript resolves imports — this rule has false positives
|
|
971
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-import.md
|
|
972
|
-
"node/no-missing-import": "off",
|
|
973
|
-
// OFF: TypeScript resolves requires — this rule has false positives
|
|
974
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-require.md
|
|
975
|
-
"node/no-missing-require": "off",
|
|
976
|
-
// Warn on process.exit() — prefer throwing errors for clean shutdown
|
|
977
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-process-exit.md
|
|
978
|
-
"node/no-process-exit": "warn",
|
|
979
|
-
// OFF: Too many false positives with monorepos and devDependencies
|
|
980
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-unpublished-import.md
|
|
981
|
-
"node/no-unpublished-import": "off",
|
|
982
|
-
// Validate hashbang lines — correct syntax, Unix linebreaks, only in entry files
|
|
983
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/hashbang.md
|
|
984
|
-
"node/hashbang": "error",
|
|
985
|
-
// Detect `__dirname + '/foo'` — use path.join() instead (breaks on Windows)
|
|
986
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-path-concat.md
|
|
987
|
-
"node/no-path-concat": "error",
|
|
988
|
-
// ── Prefer global builtins ────────────────────────────────────
|
|
989
|
-
// Use global Buffer instead of require('buffer').Buffer
|
|
990
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/buffer.md
|
|
991
|
-
"node/prefer-global/buffer": ["error", "always"],
|
|
992
|
-
// Use global console — always available in Node.js
|
|
993
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/console.md
|
|
994
|
-
"node/prefer-global/console": ["error", "always"],
|
|
995
|
-
// Use global process — always available in Node.js
|
|
996
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/process.md
|
|
997
|
-
"node/prefer-global/process": ["error", "always"],
|
|
998
|
-
// Use global URL — available since Node.js 10
|
|
999
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url.md
|
|
1000
|
-
"node/prefer-global/url": ["error", "always"],
|
|
1001
|
-
// Use global URLSearchParams — available since Node.js 10
|
|
1002
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-global/url-search-params.md
|
|
1003
|
-
"node/prefer-global/url-search-params": ["error", "always"],
|
|
1004
|
-
// ── Prefer promise-based APIs ─────────────────────────────────
|
|
1005
|
-
// Use dns.promises instead of callback-based dns — modern async
|
|
1006
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/dns.md
|
|
1007
|
-
"node/prefer-promises/dns": "error",
|
|
1008
|
-
// Use fs.promises instead of callback-based fs — modern async
|
|
1009
|
-
// https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/prefer-promises/fs.md
|
|
1010
|
-
"node/prefer-promises/fs": "error"
|
|
1011
702
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
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();
|
|
1014
711
|
}
|
|
1015
712
|
|
|
1016
713
|
// src/configs/package-json.ts
|
|
@@ -1137,235 +834,326 @@ function prettierCompatConfig() {
|
|
|
1137
834
|
}
|
|
1138
835
|
|
|
1139
836
|
// src/configs/react.ts
|
|
1140
|
-
import
|
|
837
|
+
import eslintReactPlugin2 from "@eslint-react/eslint-plugin";
|
|
1141
838
|
import stylisticPlugin from "@stylistic/eslint-plugin";
|
|
1142
839
|
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
|
|
1143
|
-
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
|
1144
840
|
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
|
1145
841
|
import globals2 from "globals";
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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],
|
|
1150
1116
|
{
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
|
1174
1140
|
},
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
"react/no-children-prop": "error",
|
|
1180
|
-
// Prevent direct mutation of this.state — use setState instead
|
|
1181
|
-
// https://eslint-react.xyz/docs/rules/no-direct-mutation-state
|
|
1182
|
-
"react/no-direct-mutation-state": "error",
|
|
1183
|
-
// Prevent unstable nested component definitions — causes remounts
|
|
1184
|
-
// https://eslint-react.xyz/docs/rules/no-nested-component-definitions
|
|
1185
|
-
"react/no-nested-component-definitions": "error",
|
|
1186
|
-
// Require key prop in iterators — prevent reconciliation bugs
|
|
1187
|
-
// https://eslint-react.xyz/docs/rules/no-missing-key
|
|
1188
|
-
"react/no-missing-key": "error",
|
|
1189
|
-
// Prevent duplicate key props in iterators
|
|
1190
|
-
// https://eslint-react.xyz/docs/rules/no-duplicate-key
|
|
1191
|
-
"react/no-duplicate-key": "error",
|
|
1192
|
-
// Prevent comments from being inserted as text nodes in JSX
|
|
1193
|
-
// https://eslint-react.xyz/docs/rules/jsx-no-comment-textnodes
|
|
1194
|
-
"react/jsx-no-comment-textnodes": "error",
|
|
1195
|
-
// Remove unnecessary JSX fragments — <>{x}</> → {x}
|
|
1196
|
-
// https://eslint-react.xyz/docs/rules/no-useless-fragment
|
|
1197
|
-
"react/no-useless-fragment": "error",
|
|
1198
|
-
// Prefer <Foo active /> over <Foo active={true} /> — concise
|
|
1199
|
-
// https://eslint-react.xyz/docs/rules/jsx-shorthand-boolean
|
|
1200
|
-
"react/jsx-shorthand-boolean": "error",
|
|
1201
|
-
// Prevent using array index as key — breaks reconciliation on reorder
|
|
1202
|
-
// https://eslint-react.xyz/docs/rules/no-array-index-key
|
|
1203
|
-
"react/no-array-index-key": "error",
|
|
1204
|
-
// Prevent object/array literals as default props — creates new reference every render
|
|
1205
|
-
// https://eslint-react.xyz/docs/rules/no-unstable-default-props
|
|
1206
|
-
"react/no-unstable-default-props": "error",
|
|
1207
|
-
// Prevent `{count && <Foo />}` — renders "0" when count is 0
|
|
1208
|
-
// https://eslint-react.xyz/docs/rules/no-leaked-conditional-rendering
|
|
1209
|
-
"react/no-leaked-conditional-rendering": "error",
|
|
1210
|
-
// Prevent inline object creation in context providers — causes re-renders
|
|
1211
|
-
// https://eslint-react.xyz/docs/rules/no-unstable-context-value
|
|
1212
|
-
"react/no-unstable-context-value": "error",
|
|
1213
|
-
// Prevent `this.setState({ count: this.state.count + 1 })` — race condition
|
|
1214
|
-
// https://eslint-react.xyz/docs/rules/no-access-state-in-setstate
|
|
1215
|
-
"react/no-access-state-in-setstate": "error",
|
|
1216
|
-
// Detect state properties that are set but never read — dead code
|
|
1217
|
-
// https://eslint-react.xyz/docs/rules/no-unused-state
|
|
1218
|
-
"react/no-unused-state": "error",
|
|
1219
|
-
// ── React 19 migration rules ────────────────────────────────────
|
|
1220
|
-
// React 19: Use <Context> instead of <Context.Provider>
|
|
1221
|
-
// https://eslint-react.xyz/docs/rules/no-context-provider
|
|
1222
|
-
"react/no-context-provider": "error",
|
|
1223
|
-
// React 19: Use ref as prop instead of forwardRef
|
|
1224
|
-
// https://eslint-react.xyz/docs/rules/no-forward-ref
|
|
1225
|
-
"react/no-forward-ref": "error",
|
|
1226
|
-
// React 19: Use use() instead of useContext()
|
|
1227
|
-
// https://eslint-react.xyz/docs/rules/no-use-context
|
|
1228
|
-
"react/no-use-context": "error",
|
|
1229
|
-
// ── React DOM (react-dom/) ──────────────────────────────────────
|
|
1230
|
-
// Prevent unsafe target="_blank" links — requires rel="noreferrer"
|
|
1231
|
-
// https://eslint-react.xyz/docs/rules/dom-no-unsafe-target-blank
|
|
1232
|
-
"react-dom/no-unsafe-target-blank": "error",
|
|
1233
|
-
// Prevent unknown DOM properties (e.g., class → className)
|
|
1234
|
-
// https://eslint-react.xyz/docs/rules/dom-no-unknown-property
|
|
1235
|
-
"react-dom/no-unknown-property": "error",
|
|
1236
|
-
// Prevent void DOM elements (br, img, hr) from having children
|
|
1237
|
-
// https://eslint-react.xyz/docs/rules/dom-no-void-elements-with-children
|
|
1238
|
-
"react-dom/no-void-elements-with-children": "error",
|
|
1239
|
-
// Require sandbox attribute on iframes — security best practice
|
|
1240
|
-
// https://eslint-react.xyz/docs/rules/dom-no-missing-iframe-sandbox
|
|
1241
|
-
"react-dom/no-missing-iframe-sandbox": "error",
|
|
1242
|
-
// Prevent `style="color: red"` — must be an object in React
|
|
1243
|
-
// https://eslint-react.xyz/docs/rules/dom-no-string-style-prop
|
|
1244
|
-
"react-dom/no-string-style-prop": "error",
|
|
1245
|
-
// Require explicit type on <button> — prevents unintended form submits
|
|
1246
|
-
// https://eslint-react.xyz/docs/rules/dom-no-missing-button-type
|
|
1247
|
-
"react-dom/no-missing-button-type": "error",
|
|
1248
|
-
// Prevent dangerouslySetInnerHTML + children at the same time — conflict
|
|
1249
|
-
// https://eslint-react.xyz/docs/rules/dom-no-dangerously-set-innerhtml-with-children
|
|
1250
|
-
"react-dom/no-dangerously-set-innerhtml-with-children": "error",
|
|
1251
|
-
// Warn on dangerouslySetInnerHTML — XSS risk, should be reviewed
|
|
1252
|
-
// https://eslint-react.xyz/docs/rules/dom-no-dangerously-set-innerhtml
|
|
1253
|
-
"react-dom/no-dangerously-set-innerhtml": "warn",
|
|
1254
|
-
// ── React Web API (react-web-api/) — cleanup leak detection ─────
|
|
1255
|
-
// Require cleanup for addEventListener in effects
|
|
1256
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-event-listener
|
|
1257
|
-
"react-web-api/no-leaked-event-listener": "error",
|
|
1258
|
-
// Require cleanup for setInterval in effects
|
|
1259
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-interval
|
|
1260
|
-
"react-web-api/no-leaked-interval": "error",
|
|
1261
|
-
// Require cleanup for setTimeout in effects
|
|
1262
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-timeout
|
|
1263
|
-
"react-web-api/no-leaked-timeout": "error",
|
|
1264
|
-
// Require cleanup for ResizeObserver in effects
|
|
1265
|
-
// https://eslint-react.xyz/docs/rules/web-api-no-leaked-resize-observer
|
|
1266
|
-
"react-web-api/no-leaked-resize-observer": "error",
|
|
1267
|
-
// ── React Hooks (react-hooks/) — eslint-plugin-react-hooks ──────
|
|
1268
|
-
// Enforce Rules of Hooks — hooks must be called at the top level
|
|
1269
|
-
// https://react.dev/reference/rules/rules-of-hooks
|
|
1270
|
-
"react-hooks/rules-of-hooks": "error",
|
|
1271
|
-
// Verify dependency arrays in useEffect/useMemo/useCallback
|
|
1272
|
-
// https://react.dev/reference/react/useEffect#specifying-reactive-dependencies
|
|
1273
|
-
"react-hooks/exhaustive-deps": "error",
|
|
1274
|
-
// ── @stylistic — JSX formatting rules ───────────────────────────
|
|
1275
|
-
// Enforce self-closing tags for components without children — <Foo />
|
|
1276
|
-
// https://eslint.style/rules/jsx/jsx-self-closing-comp
|
|
1277
|
-
"@stylistic/jsx-self-closing-comp": "error",
|
|
1278
|
-
// Prevent unnecessary string curly braces: title={"foo"} → title="foo"
|
|
1279
|
-
// https://eslint.style/rules/jsx/jsx-curly-brace-presence
|
|
1280
|
-
"@stylistic/jsx-curly-brace-presence": [
|
|
1281
|
-
"error",
|
|
1282
|
-
{ props: "never", children: "never" }
|
|
1283
|
-
],
|
|
1284
|
-
// ── React Refresh (Fast Refresh / HMR) ────────────────────────
|
|
1285
|
-
// Ensure components are exported in a way that supports Fast Refresh.
|
|
1286
|
-
// Mixed exports (component + constants) break HMR state preservation.
|
|
1287
|
-
// allowConstantExport: Vite handles constant exports without breaking HMR.
|
|
1288
|
-
// https://github.com/ArnaudBarre/eslint-plugin-react-refresh
|
|
1289
|
-
"react-refresh/only-export-components": [
|
|
1290
|
-
"warn",
|
|
1291
|
-
{ allowConstantExport: true }
|
|
1292
|
-
],
|
|
1293
|
-
// ── JSX Accessibility (a11y) ──────────────────────────────────
|
|
1294
|
-
// Require alt text on img, area, input[type="image"], object
|
|
1295
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/alt-text.md
|
|
1296
|
-
"jsx-a11y/alt-text": "error",
|
|
1297
|
-
// Require anchor content — empty links are inaccessible
|
|
1298
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-has-content.md
|
|
1299
|
-
"jsx-a11y/anchor-has-content": "error",
|
|
1300
|
-
// Require valid href on anchors — no `#` or `javascript:` void
|
|
1301
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-is-valid.md
|
|
1302
|
-
"jsx-a11y/anchor-is-valid": "error",
|
|
1303
|
-
// Active descendant elements must be focusable (tabindex)
|
|
1304
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-activedescendant-has-tabindex.md
|
|
1305
|
-
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
|
1306
|
-
// Validate aria-* attributes exist — catches typos in ARIA props
|
|
1307
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-props.md
|
|
1308
|
-
"jsx-a11y/aria-props": "error",
|
|
1309
|
-
// Validate aria-* values match their type (boolean, token, etc.)
|
|
1310
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-proptypes.md
|
|
1311
|
-
"jsx-a11y/aria-proptypes": "error",
|
|
1312
|
-
// Validate role attribute values — catches invalid role names
|
|
1313
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md
|
|
1314
|
-
"jsx-a11y/aria-role": "error",
|
|
1315
|
-
// Prevent ARIA on elements that don't support it (meta, script, style)
|
|
1316
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-unsupported-elements.md
|
|
1317
|
-
"jsx-a11y/aria-unsupported-elements": "error",
|
|
1318
|
-
// Click handlers must have keyboard equivalent — keyboard accessibility
|
|
1319
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/click-events-have-key-events.md
|
|
1320
|
-
"jsx-a11y/click-events-have-key-events": "error",
|
|
1321
|
-
// Heading elements must have content — screen readers need it
|
|
1322
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/heading-has-content.md
|
|
1323
|
-
"jsx-a11y/heading-has-content": "error",
|
|
1324
|
-
// Require lang attribute on <html> — language identification
|
|
1325
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/html-has-lang.md
|
|
1326
|
-
"jsx-a11y/html-has-lang": "error",
|
|
1327
|
-
// Prevent redundant alt text like "image of..." — screen readers add this
|
|
1328
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/img-redundant-alt.md
|
|
1329
|
-
"jsx-a11y/img-redundant-alt": "error",
|
|
1330
|
-
// Every form label must be associated with a control
|
|
1331
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-associated-control.md
|
|
1332
|
-
"jsx-a11y/label-has-associated-control": "error",
|
|
1333
|
-
// Mouse event handlers must have keyboard equivalents
|
|
1334
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/mouse-events-have-key-events.md
|
|
1335
|
-
"jsx-a11y/mouse-events-have-key-events": "error",
|
|
1336
|
-
// No accessKey attribute — inconsistent shortcuts confuse users
|
|
1337
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-access-key.md
|
|
1338
|
-
"jsx-a11y/no-access-key": "error",
|
|
1339
|
-
// No autofocus attribute (except non-DOM) — disorienting for screen readers
|
|
1340
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-autofocus.md
|
|
1341
|
-
"jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }],
|
|
1342
|
-
// No <marquee>/<blink> — deprecated distracting elements
|
|
1343
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-distracting-elements.md
|
|
1344
|
-
"jsx-a11y/no-distracting-elements": "error",
|
|
1345
|
-
// No redundant ARIA roles (e.g., <button role="button">)
|
|
1346
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md
|
|
1347
|
-
"jsx-a11y/no-redundant-roles": "error",
|
|
1348
|
-
// Role elements must have all required ARIA props
|
|
1349
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-has-required-aria-props.md
|
|
1350
|
-
"jsx-a11y/role-has-required-aria-props": "error",
|
|
1351
|
-
// Don't use unsupported ARIA attributes for a given role
|
|
1352
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-supports-aria-props.md
|
|
1353
|
-
"jsx-a11y/role-supports-aria-props": "error",
|
|
1354
|
-
// scope attribute only on <th> elements — HTML specification
|
|
1355
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md
|
|
1356
|
-
"jsx-a11y/scope": "error",
|
|
1357
|
-
// No positive tabIndex values — disrupts natural tab order
|
|
1358
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/tabindex-no-positive.md
|
|
1359
|
-
"jsx-a11y/tabindex-no-positive": "error",
|
|
1360
|
-
// Validate lang attribute values — e.g. "de" ok, "deutsch" not
|
|
1361
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/lang.md
|
|
1362
|
-
"jsx-a11y/lang": "error",
|
|
1363
|
-
// Validate autocomplete attribute values on form elements
|
|
1364
|
-
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/autocomplete-valid.md
|
|
1365
|
-
"jsx-a11y/autocomplete-valid": "error"
|
|
1141
|
+
parserOptions: {
|
|
1142
|
+
ecmaFeatures: {
|
|
1143
|
+
jsx: true
|
|
1144
|
+
}
|
|
1366
1145
|
}
|
|
1367
1146
|
}
|
|
1368
|
-
|
|
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();
|
|
1369
1157
|
}
|
|
1370
1158
|
|
|
1371
1159
|
// src/configs/react-effect.ts
|
|
@@ -1412,11 +1200,21 @@ function reactEffectConfig() {
|
|
|
1412
1200
|
|
|
1413
1201
|
// src/configs/regexp.ts
|
|
1414
1202
|
import { configs as regexpConfigs } from "eslint-plugin-regexp";
|
|
1415
|
-
function regexpConfig() {
|
|
1416
|
-
|
|
1203
|
+
function regexpConfig(opts) {
|
|
1204
|
+
const isAi = opts?.ai ?? false;
|
|
1205
|
+
const builder = createConfig({
|
|
1417
1206
|
name: "eslint-config-setup/regexp",
|
|
1418
1207
|
presets: [regexpConfigs["flat/recommended"]]
|
|
1419
|
-
}).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();
|
|
1420
1218
|
}
|
|
1421
1219
|
|
|
1422
1220
|
// src/configs/security.ts
|
|
@@ -1482,13 +1280,12 @@ function securityConfig() {
|
|
|
1482
1280
|
|
|
1483
1281
|
// src/configs/sonarjs.ts
|
|
1484
1282
|
import sonarjsPlugin from "eslint-plugin-sonarjs";
|
|
1485
|
-
function sonarjsConfig() {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
},
|
|
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 },
|
|
1492
1289
|
rules: {
|
|
1493
1290
|
// Detect copy-pasted functions — extract to shared helper instead
|
|
1494
1291
|
// https://sonarsource.github.io/rspec/#/rspec/S4144/javascript
|
|
@@ -1551,13 +1348,24 @@ function sonarjsConfig() {
|
|
|
1551
1348
|
// https://sonarsource.github.io/rspec/#/rspec/S6418/javascript
|
|
1552
1349
|
"sonarjs/no-hardcoded-secrets": "warn"
|
|
1553
1350
|
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
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();
|
|
1556
1362
|
}
|
|
1557
1363
|
|
|
1558
1364
|
// src/configs/typescript.ts
|
|
1559
1365
|
import tseslint from "typescript-eslint";
|
|
1560
|
-
function typescriptConfig() {
|
|
1366
|
+
function typescriptConfig(opts) {
|
|
1367
|
+
const isAi = opts?.ai ?? false;
|
|
1368
|
+
const isReact = opts?.react ?? false;
|
|
1561
1369
|
const typeChecked = tseslint.configs.strictTypeChecked;
|
|
1562
1370
|
const stylistic = tseslint.configs.stylisticTypeChecked;
|
|
1563
1371
|
const structuralBlocks = typeChecked.slice(0, 2);
|
|
@@ -1566,6 +1374,8 @@ function typescriptConfig() {
|
|
|
1566
1374
|
name: "eslint-config-setup/typescript",
|
|
1567
1375
|
passthrough: structuralBlocks,
|
|
1568
1376
|
presets: ruleBlocks,
|
|
1377
|
+
files: [...TYPESCRIPT_SOURCE_FILES],
|
|
1378
|
+
ignores: [...MARKDOWN_CODE_BLOCK_FILES],
|
|
1569
1379
|
languageOptions: {
|
|
1570
1380
|
parserOptions: {
|
|
1571
1381
|
// Use project service for automatic tsconfig resolution
|
|
@@ -1594,10 +1404,25 @@ function typescriptConfig() {
|
|
|
1594
1404
|
"error",
|
|
1595
1405
|
"in-try-catch"
|
|
1596
1406
|
]);
|
|
1597
|
-
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
|
+
});
|
|
1598
1423
|
builder.addRule("@typescript-eslint/consistent-type-imports", [
|
|
1599
1424
|
"error",
|
|
1600
|
-
{ fixStyle: "inline-type-imports" }
|
|
1425
|
+
{ fixStyle: isAi ? "inline-type-imports" : "separate-type-imports" }
|
|
1601
1426
|
]);
|
|
1602
1427
|
builder.addRule("@typescript-eslint/consistent-type-exports", [
|
|
1603
1428
|
"error",
|
|
@@ -1611,6 +1436,24 @@ function typescriptConfig() {
|
|
|
1611
1436
|
"error"
|
|
1612
1437
|
);
|
|
1613
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
|
+
]);
|
|
1614
1457
|
builder.addRule("no-shadow", "off");
|
|
1615
1458
|
builder.addRule("@typescript-eslint/no-shadow", [
|
|
1616
1459
|
"error",
|
|
@@ -1621,10 +1464,7 @@ function typescriptConfig() {
|
|
|
1621
1464
|
ignoreFunctionTypeParameterNameValueShadow: true
|
|
1622
1465
|
}
|
|
1623
1466
|
]);
|
|
1624
|
-
builder.
|
|
1625
|
-
"error",
|
|
1626
|
-
{ allowNumber: true }
|
|
1627
|
-
]);
|
|
1467
|
+
builder.overrideOptions("@typescript-eslint/restrict-template-expressions", { allowNumber: true });
|
|
1628
1468
|
builder.addRule("@typescript-eslint/strict-boolean-expressions", [
|
|
1629
1469
|
"error",
|
|
1630
1470
|
{ allowNullableBoolean: true, allowNullableObject: true }
|
|
@@ -1634,18 +1474,168 @@ function typescriptConfig() {
|
|
|
1634
1474
|
["**/*.{js,mjs,cjs}"],
|
|
1635
1475
|
tseslint.configs.disableTypeChecked.rules ?? {}
|
|
1636
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
|
+
}
|
|
1637
1628
|
return builder.build();
|
|
1638
1629
|
}
|
|
1639
1630
|
|
|
1640
1631
|
// src/configs/unicorn.ts
|
|
1641
1632
|
import unicornPlugin from "eslint-plugin-unicorn";
|
|
1642
|
-
function unicornConfig() {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
},
|
|
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 },
|
|
1649
1639
|
rules: {
|
|
1650
1640
|
// ── Error prevention ──────────────────────────────────────────
|
|
1651
1641
|
// Forbid `/* eslint-disable */` without specific rule — too broad
|
|
@@ -1675,9 +1665,11 @@ function unicornConfig() {
|
|
|
1675
1665
|
// Remove useless Promise.resolve/reject wrappers — simplify async code
|
|
1676
1666
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md
|
|
1677
1667
|
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
1678
|
-
// 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`.
|
|
1679
1671
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-undefined.md
|
|
1680
|
-
"unicorn/no-useless-undefined": "
|
|
1672
|
+
"unicorn/no-useless-undefined": ["warn", { checkArguments: false }],
|
|
1681
1673
|
// Disallow `await` in Promise.all/race arguments — already a promise
|
|
1682
1674
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-await-in-promise-methods.md
|
|
1683
1675
|
"unicorn/no-await-in-promise-methods": "error",
|
|
@@ -1733,6 +1725,28 @@ function unicornConfig() {
|
|
|
1733
1725
|
// Enforce consistent style for indexOf/findIndex existence checks
|
|
1734
1726
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-existence-index-check.md
|
|
1735
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",
|
|
1736
1750
|
// ── Modern API preferences ────────────────────────────────────
|
|
1737
1751
|
// Prefer .flatMap() over .map().flat() — single pass, more readable
|
|
1738
1752
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat-map.md
|
|
@@ -1758,6 +1772,9 @@ function unicornConfig() {
|
|
|
1758
1772
|
// Prefer .before()/.after()/.replaceWith() over parent.insertBefore()
|
|
1759
1773
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md
|
|
1760
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",
|
|
1761
1778
|
// Prefer Math.log10/Math.hypot over manual math — accurate and readable
|
|
1762
1779
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-math-apis.md
|
|
1763
1780
|
"unicorn/prefer-modern-math-apis": "error",
|
|
@@ -1863,8 +1880,27 @@ function unicornConfig() {
|
|
|
1863
1880
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-call.md
|
|
1864
1881
|
"unicorn/prefer-single-call": "off"
|
|
1865
1882
|
}
|
|
1866
|
-
}
|
|
1867
|
-
|
|
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();
|
|
1868
1904
|
}
|
|
1869
1905
|
|
|
1870
1906
|
// src/overrides/config-files.ts
|
|
@@ -2097,136 +2133,165 @@ function storiesOverride() {
|
|
|
2097
2133
|
// src/overrides/tests.ts
|
|
2098
2134
|
import vitestPlugin from "@vitest/eslint-plugin";
|
|
2099
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
|
+
};
|
|
2100
2250
|
function testsOverride() {
|
|
2101
|
-
|
|
2251
|
+
return [
|
|
2102
2252
|
{
|
|
2103
2253
|
name: "eslint-config-setup/tests",
|
|
2104
|
-
files:
|
|
2254
|
+
files: TEST_FILES2,
|
|
2105
2255
|
plugins: {
|
|
2106
2256
|
vitest: vitestPlugin
|
|
2107
2257
|
},
|
|
2108
|
-
rules:
|
|
2109
|
-
// ── Vitest rules ──────────────────────────────────────────────
|
|
2110
|
-
// Every test must contain at least one assertion
|
|
2111
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md
|
|
2112
|
-
"vitest/expect-expect": "error",
|
|
2113
|
-
// Prevent duplicate test titles within a describe block
|
|
2114
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-identical-title.md
|
|
2115
|
-
"vitest/no-identical-title": "error",
|
|
2116
|
-
// No it.only / describe.only — prevents accidentally skipping tests in CI
|
|
2117
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-focused-tests.md
|
|
2118
|
-
"vitest/no-focused-tests": "error",
|
|
2119
|
-
// Warn on it.skip / describe.skip — track disabled tests
|
|
2120
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-disabled-tests.md
|
|
2121
|
-
"vitest/no-disabled-tests": "warn",
|
|
2122
|
-
// No duplicate beforeEach/afterEach hooks — merge them
|
|
2123
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-duplicate-hooks.md
|
|
2124
|
-
"vitest/no-duplicate-hooks": "error",
|
|
2125
|
-
// Prefer .toBe() over .toEqual() for primitives — clearer intent
|
|
2126
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-be.md
|
|
2127
|
-
"vitest/prefer-to-be": "error",
|
|
2128
|
-
// Prefer .toHaveLength() over .toBe(arr.length) — better errors
|
|
2129
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-have-length.md
|
|
2130
|
-
"vitest/prefer-to-have-length": "error",
|
|
2131
|
-
// Validate expect() usage — no dangling expect without assertion
|
|
2132
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md
|
|
2133
|
-
"vitest/valid-expect": "error",
|
|
2134
|
-
// Validate test/describe title format — no empty or invalid titles
|
|
2135
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-title.md
|
|
2136
|
-
"vitest/valid-title": "error",
|
|
2137
|
-
// No conditional logic (if/else) inside tests — split into separate tests
|
|
2138
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md
|
|
2139
|
-
"vitest/no-conditional-in-test": "error",
|
|
2140
|
-
// No expect() inside conditional blocks — always assert unconditionally
|
|
2141
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md
|
|
2142
|
-
"vitest/no-conditional-expect": "error",
|
|
2143
|
-
// No standalone expect() outside test blocks — always wrap in it/test
|
|
2144
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md
|
|
2145
|
-
"vitest/no-standalone-expect": "error",
|
|
2146
|
-
// Prefer .toStrictEqual() over .toEqual() — catches undefined vs missing
|
|
2147
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-strict-equal.md
|
|
2148
|
-
"vitest/prefer-strict-equal": "error",
|
|
2149
|
-
// Prefer vi.spyOn() over vi.fn() for method mocks — preserves original
|
|
2150
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-spy-on.md
|
|
2151
|
-
"vitest/prefer-spy-on": "error",
|
|
2152
|
-
// Require message argument in toThrow/toThrowError — verify correct error
|
|
2153
|
-
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-to-throw-message.md
|
|
2154
|
-
"vitest/require-to-throw-message": "error",
|
|
2155
|
-
// ── Relaxed rules for tests ───────────────────────────────────
|
|
2156
|
-
// Tests are naturally verbose and use patterns banned in prod code
|
|
2157
|
-
// Tests can be long (setup + many assertions)
|
|
2158
|
-
"max-lines": "off",
|
|
2159
|
-
"max-lines-per-function": "off",
|
|
2160
|
-
"max-statements": "off",
|
|
2161
|
-
// Tests often need `any` for mocking and type assertions
|
|
2162
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
2163
|
-
"@typescript-eslint/no-non-null-assertion": "off",
|
|
2164
|
-
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
2165
|
-
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
2166
|
-
}
|
|
2258
|
+
rules: VITEST_RULES
|
|
2167
2259
|
},
|
|
2168
2260
|
{
|
|
2169
2261
|
name: "eslint-config-setup/tests-testing-library",
|
|
2170
|
-
files:
|
|
2262
|
+
files: TEST_FILES2,
|
|
2171
2263
|
plugins: {
|
|
2172
2264
|
"testing-library": testingLibraryPlugin
|
|
2173
2265
|
},
|
|
2174
|
-
rules:
|
|
2175
|
-
// ── Testing Library rules ───────────────────────────────────
|
|
2176
|
-
// Await async events (userEvent.click, etc.) — prevents race conditions
|
|
2177
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-events.md
|
|
2178
|
-
"testing-library/await-async-events": "error",
|
|
2179
|
-
// Await async queries (findBy*) — they return promises
|
|
2180
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-queries.md
|
|
2181
|
-
"testing-library/await-async-queries": "error",
|
|
2182
|
-
// Await async utilities (waitFor, waitForElementToBeRemoved)
|
|
2183
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md
|
|
2184
|
-
"testing-library/await-async-utils": "error",
|
|
2185
|
-
// Don't await synchronous events — misleading
|
|
2186
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-events.md
|
|
2187
|
-
"testing-library/no-await-sync-events": "error",
|
|
2188
|
-
// Don't await synchronous queries (getBy*, queryBy*) — not promises
|
|
2189
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-queries.md
|
|
2190
|
-
"testing-library/no-await-sync-queries": "error",
|
|
2191
|
-
// Don't use container.querySelector — use queries instead
|
|
2192
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-container.md
|
|
2193
|
-
"testing-library/no-container": "error",
|
|
2194
|
-
// Warn on debug()/prettyDOM() left in tests — debugging artifacts
|
|
2195
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-debugging-utils.md
|
|
2196
|
-
"testing-library/no-debugging-utils": "warn",
|
|
2197
|
-
// Don't access DOM nodes directly — use queries
|
|
2198
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-node-access.md
|
|
2199
|
-
"testing-library/no-node-access": "error",
|
|
2200
|
-
// Don't call render in beforeEach — call in each test
|
|
2201
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-render-in-lifecycle.md
|
|
2202
|
-
"testing-library/no-render-in-lifecycle": "error",
|
|
2203
|
-
// No unnecessary act() wrappers — TL handles this internally
|
|
2204
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-unnecessary-act.md
|
|
2205
|
-
"testing-library/no-unnecessary-act": "error",
|
|
2206
|
-
// One assertion per waitFor — multiple can mask failures
|
|
2207
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-multiple-assertions.md
|
|
2208
|
-
"testing-library/no-wait-for-multiple-assertions": "error",
|
|
2209
|
-
// No side effects in waitFor — only assertions
|
|
2210
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md
|
|
2211
|
-
"testing-library/no-wait-for-side-effects": "error",
|
|
2212
|
-
// Prefer findBy* over waitFor + getBy* — built-in combination
|
|
2213
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-find-by.md
|
|
2214
|
-
"testing-library/prefer-find-by": "error",
|
|
2215
|
-
// Use getBy* (throws) for present elements, queryBy* for absent
|
|
2216
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-presence-queries.md
|
|
2217
|
-
"testing-library/prefer-presence-queries": "error",
|
|
2218
|
-
// Use queryBy* for disappearance checks — returns null when gone
|
|
2219
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-query-by-disappearance.md
|
|
2220
|
-
"testing-library/prefer-query-by-disappearance": "error",
|
|
2221
|
-
// Use screen.getBy* over destructured render result — consistent
|
|
2222
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-screen-queries.md
|
|
2223
|
-
"testing-library/prefer-screen-queries": "error",
|
|
2224
|
-
// Name render result consistently (e.g., `const { getByText } = render(...)`)
|
|
2225
|
-
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/render-result-naming-convention.md
|
|
2226
|
-
"testing-library/render-result-naming-convention": "error"
|
|
2227
|
-
}
|
|
2266
|
+
rules: TESTING_LIBRARY_RULES
|
|
2228
2267
|
}
|
|
2229
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");
|
|
2230
2295
|
return configs;
|
|
2231
2296
|
}
|
|
2232
2297
|
|
|
@@ -2281,65 +2346,24 @@ function standardComplexity() {
|
|
|
2281
2346
|
];
|
|
2282
2347
|
}
|
|
2283
2348
|
|
|
2284
|
-
// src/oxlint/integration.ts
|
|
2285
|
-
import oxlintPlugin from "eslint-plugin-oxlint";
|
|
2286
|
-
function oxlintIntegration(opts) {
|
|
2287
|
-
const typedPlugin = oxlintPlugin;
|
|
2288
|
-
const configs = [];
|
|
2289
|
-
const add = (configName, blockName) => {
|
|
2290
|
-
const raw = typedPlugin.configs[configName];
|
|
2291
|
-
const items = Array.isArray(raw) ? raw : [raw];
|
|
2292
|
-
for (const item of items) {
|
|
2293
|
-
configs.push({ name: `eslint-config-setup/${blockName}`, ...item });
|
|
2294
|
-
}
|
|
2295
|
-
};
|
|
2296
|
-
add("flat/recommended", "oxlint");
|
|
2297
|
-
if (opts.react) {
|
|
2298
|
-
add("flat/react", "oxlint-react");
|
|
2299
|
-
add("flat/jsx-a11y", "oxlint-jsx-a11y");
|
|
2300
|
-
}
|
|
2301
|
-
if (opts.node) {
|
|
2302
|
-
add("flat/node", "oxlint-node");
|
|
2303
|
-
}
|
|
2304
|
-
add("flat/typescript", "oxlint-typescript");
|
|
2305
|
-
add("flat/unicorn", "oxlint-unicorn");
|
|
2306
|
-
add("flat/import", "oxlint-import");
|
|
2307
|
-
add("flat/jsdoc", "oxlint-jsdoc");
|
|
2308
|
-
return configs;
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
// src/configs/compat.ts
|
|
2312
|
-
import compatPlugin from "eslint-plugin-compat";
|
|
2313
|
-
function compatConfig() {
|
|
2314
|
-
return [
|
|
2315
|
-
{
|
|
2316
|
-
name: "eslint-config-setup/compat",
|
|
2317
|
-
plugins: {
|
|
2318
|
-
compat: compatPlugin
|
|
2319
|
-
},
|
|
2320
|
-
rules: {
|
|
2321
|
-
// Warn when using browser APIs not supported in browserslist targets
|
|
2322
|
-
// https://github.com/amilajack/eslint-plugin-compat#usage
|
|
2323
|
-
"compat/compat": "warn"
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
];
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
2349
|
// src/build/compose.ts
|
|
2330
|
-
function
|
|
2331
|
-
const
|
|
2332
|
-
config.push(
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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) {
|
|
2343
2367
|
if (!opts.node) {
|
|
2344
2368
|
config.push(...compatConfig());
|
|
2345
2369
|
}
|
|
@@ -2347,30 +2371,40 @@ function composeConfig(opts) {
|
|
|
2347
2371
|
config.push(...standardComplexity());
|
|
2348
2372
|
}
|
|
2349
2373
|
if (opts.node) {
|
|
2350
|
-
config.push(...nodeConfig());
|
|
2374
|
+
config.push(...nodeConfig({ ai: opts.ai }));
|
|
2351
2375
|
}
|
|
2352
2376
|
if (opts.react) {
|
|
2353
|
-
config.push(...reactConfig());
|
|
2377
|
+
config.push(...reactConfig({ ai: opts.ai }));
|
|
2354
2378
|
config.push(...reactEffectConfig());
|
|
2355
2379
|
}
|
|
2356
2380
|
if (opts.ai) {
|
|
2357
|
-
config.push(...aiConfig(
|
|
2381
|
+
config.push(...aiConfig());
|
|
2358
2382
|
config.push(...perfectionistAiConfig());
|
|
2359
2383
|
config.push(...packageJsonAiConfig());
|
|
2360
2384
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
config.push(
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
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());
|
|
2371
2398
|
if (opts.oxlint) {
|
|
2372
2399
|
config.push(...oxlintIntegration(opts));
|
|
2373
2400
|
}
|
|
2401
|
+
}
|
|
2402
|
+
function composeConfig(opts) {
|
|
2403
|
+
const config = [];
|
|
2404
|
+
addCoreConfigs(config, opts);
|
|
2405
|
+
addConditionalConfigs(config, opts);
|
|
2406
|
+
addFileTypeOverrides(config);
|
|
2407
|
+
addFinalConfigs(config, opts);
|
|
2374
2408
|
return config;
|
|
2375
2409
|
}
|
|
2376
2410
|
export {
|