eslint-config-setup 0.3.3 → 0.5.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 +27 -5
- package/dist/configs/0b23ff88.js +137 -42
- package/dist/configs/12c62446.js +154 -25
- package/dist/configs/196d687e.js +287 -70
- package/dist/configs/1c3f743c.js +157 -26
- package/dist/configs/2f6f3a82.js +308 -57
- package/dist/configs/4eb62e57.js +140 -40
- package/dist/configs/52762a42.js +280 -36
- package/dist/configs/532f50a4.js +485 -48
- package/dist/configs/5a302873.js +305 -56
- package/dist/configs/6bc0d588.js +456 -57
- package/dist/configs/91e82988.js +487 -48
- package/dist/configs/c2fecd3d.js +282 -33
- package/dist/configs/cde010b4.js +313 -17
- package/dist/configs/d537b683.js +458 -54
- package/dist/configs/db69ebb6.js +290 -68
- package/dist/configs/e4b137fa.js +311 -17
- 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 +42 -41
- package/dist/modules.js +1183 -1106
- package/dist/modules.js.map +1 -1
- package/dist/oxlint-configs/3047b7d6.json +268 -145
- package/dist/oxlint-configs/58d5e03b.json +266 -144
- package/dist/oxlint-configs/78b40daa.json +301 -155
- package/dist/oxlint-configs/7ff0d87e.json +361 -237
- package/dist/oxlint-configs/85912bf0.json +362 -237
- package/dist/oxlint-configs/9dc42dc3.json +300 -155
- package/dist/oxlint-configs/d46cb9b7.json +397 -241
- package/dist/oxlint-configs/ef643c60.json +398 -241
- package/dist/{types-CAUUIuJR.d.ts → types-CAO1PbsB.d.ts} +7 -7
- package/package.json +42 -39
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,359 @@ 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
840
|
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
|
841
|
+
import reactPerfPlugin from "eslint-plugin-react-perf";
|
|
1144
842
|
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
|
1145
843
|
import globals2 from "globals";
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
844
|
+
|
|
845
|
+
// src/file-patterns.ts
|
|
846
|
+
var TYPESCRIPT_SOURCE_FILES = [
|
|
847
|
+
"**/*.ts",
|
|
848
|
+
"**/*.tsx",
|
|
849
|
+
"**/*.mts",
|
|
850
|
+
"**/*.cts"
|
|
851
|
+
];
|
|
852
|
+
var MARKDOWN_CODE_BLOCK_FILES = ["**/*.{md,mdx}/**"];
|
|
853
|
+
|
|
854
|
+
// src/plugins/react-compat.ts
|
|
855
|
+
import eslintReactPlugin from "@eslint-react/eslint-plugin";
|
|
856
|
+
var coreRules = eslintReactPlugin.rules;
|
|
857
|
+
function selectPrefixedRules(prefix) {
|
|
858
|
+
const result = {};
|
|
859
|
+
for (const [name, rule] of Object.entries(coreRules)) {
|
|
860
|
+
if (name.startsWith(prefix)) {
|
|
861
|
+
result[name.slice(prefix.length)] = rule;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return result;
|
|
865
|
+
}
|
|
866
|
+
var domRules = selectPrefixedRules("dom-");
|
|
867
|
+
var jsxRules = selectPrefixedRules("jsx-");
|
|
868
|
+
var namingRules = selectPrefixedRules("naming-convention-");
|
|
869
|
+
var rscRules = selectPrefixedRules("rsc-");
|
|
870
|
+
var webApiRules = selectPrefixedRules("web-api-");
|
|
871
|
+
var LEGACY_ALIASES = {
|
|
872
|
+
// ── Core: identical names ──────────────────────────────────────────
|
|
873
|
+
"no-access-state-in-setstate": [coreRules, "no-access-state-in-setstate"],
|
|
874
|
+
"no-array-index-key": [coreRules, "no-array-index-key"],
|
|
875
|
+
"no-children-prop": [jsxRules, "no-children-prop"],
|
|
876
|
+
"no-direct-mutation-state": [coreRules, "no-direct-mutation-state"],
|
|
877
|
+
"no-unused-class-component-members": [
|
|
878
|
+
coreRules,
|
|
879
|
+
"no-unused-class-component-members"
|
|
880
|
+
],
|
|
881
|
+
"no-unused-state": [coreRules, "no-unused-state"],
|
|
882
|
+
"jsx-no-comment-textnodes": [jsxRules, "no-comment-textnodes"],
|
|
883
|
+
// ── Core: renamed 1:1 ──────────────────────────────────────────────
|
|
884
|
+
"jsx-key": [coreRules, "no-missing-key"],
|
|
885
|
+
"jsx-key-before-spread": [jsxRules, "no-key-after-spread"],
|
|
886
|
+
"jsx-no-constructed-context-values": [
|
|
887
|
+
coreRules,
|
|
888
|
+
"no-unstable-context-value"
|
|
889
|
+
],
|
|
890
|
+
"jsx-no-leaked-render": [coreRules, "no-leaked-conditional-rendering"],
|
|
891
|
+
"jsx-no-useless-fragment": [jsxRules, "no-useless-fragment"],
|
|
892
|
+
"no-object-type-as-default-prop": [coreRules, "no-unstable-default-props"],
|
|
893
|
+
"no-unstable-nested-components": [
|
|
894
|
+
coreRules,
|
|
895
|
+
"no-nested-component-definitions"
|
|
896
|
+
],
|
|
897
|
+
"display-name": [coreRules, "no-missing-component-display-name"],
|
|
898
|
+
"forward-ref-uses-ref": [coreRules, "no-forward-ref"],
|
|
899
|
+
"no-did-mount-set-state": [
|
|
900
|
+
coreRules,
|
|
901
|
+
"no-set-state-in-component-did-mount"
|
|
902
|
+
],
|
|
903
|
+
"no-did-update-set-state": [
|
|
904
|
+
coreRules,
|
|
905
|
+
"no-set-state-in-component-did-update"
|
|
906
|
+
],
|
|
907
|
+
"no-will-update-set-state": [
|
|
908
|
+
coreRules,
|
|
909
|
+
"no-set-state-in-component-will-update"
|
|
910
|
+
],
|
|
911
|
+
// ── Naming convention → legacy names ───────────────────────────────
|
|
912
|
+
"hook-use-state": [coreRules, "use-state"],
|
|
913
|
+
// ── DOM → legacy react/ names (not react-dom/) ─────────────────────
|
|
914
|
+
"no-danger": [domRules, "no-dangerously-set-innerhtml"],
|
|
915
|
+
"no-danger-with-children": [
|
|
916
|
+
domRules,
|
|
917
|
+
"no-dangerously-set-innerhtml-with-children"
|
|
918
|
+
],
|
|
919
|
+
"no-find-dom-node": [domRules, "no-find-dom-node"],
|
|
920
|
+
"no-namespace": [jsxRules, "no-namespace"],
|
|
921
|
+
"no-render-return-value": [domRules, "no-render-return-value"],
|
|
922
|
+
"jsx-no-script-url": [domRules, "no-script-url"],
|
|
923
|
+
"jsx-no-target-blank": [domRules, "no-unsafe-target-blank"],
|
|
924
|
+
"no-unknown-property": [domRules, "no-unknown-property"],
|
|
925
|
+
"void-dom-elements-no-children": [
|
|
926
|
+
domRules,
|
|
927
|
+
"no-void-elements-with-children"
|
|
928
|
+
],
|
|
929
|
+
"button-has-type": [domRules, "no-missing-button-type"],
|
|
930
|
+
"iframe-missing-sandbox": [domRules, "no-missing-iframe-sandbox"],
|
|
931
|
+
"style-prop-object": [domRules, "no-string-style-prop"]
|
|
932
|
+
};
|
|
933
|
+
var aliasedOriginals = /* @__PURE__ */ new Set();
|
|
934
|
+
for (const [source, originalName] of Object.values(LEGACY_ALIASES)) {
|
|
935
|
+
if (source === coreRules) {
|
|
936
|
+
aliasedOriginals.add(originalName);
|
|
937
|
+
} else if (source === domRules) {
|
|
938
|
+
aliasedOriginals.add(`dom-${originalName}`);
|
|
939
|
+
} else if (source === jsxRules) {
|
|
940
|
+
aliasedOriginals.add(`jsx-${originalName}`);
|
|
941
|
+
} else if (source === namingRules) {
|
|
942
|
+
aliasedOriginals.add(`naming-convention-${originalName}`);
|
|
943
|
+
} else if (source === rscRules) {
|
|
944
|
+
aliasedOriginals.add(`rsc-${originalName}`);
|
|
945
|
+
} else if (source === webApiRules) {
|
|
946
|
+
aliasedOriginals.add(`web-api-${originalName}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
var mergedRules = {};
|
|
950
|
+
for (const [name, rule] of Object.entries(coreRules)) {
|
|
951
|
+
if (!aliasedOriginals.has(name)) {
|
|
952
|
+
mergedRules[name] = rule;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
var subPlugins = [
|
|
956
|
+
[domRules, /* @__PURE__ */ new Set(["prefer-namespace-import"])],
|
|
957
|
+
[jsxRules, /* @__PURE__ */ new Set()],
|
|
958
|
+
[webApiRules, /* @__PURE__ */ new Set()],
|
|
959
|
+
[namingRules, /* @__PURE__ */ new Set()],
|
|
960
|
+
[rscRules, /* @__PURE__ */ new Set()]
|
|
961
|
+
];
|
|
962
|
+
for (const [rules, skip] of subPlugins) {
|
|
963
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
964
|
+
if (skip.has(name)) continue;
|
|
965
|
+
const isAliased = Object.values(LEGACY_ALIASES).some(
|
|
966
|
+
([source, originalName]) => source === rules && originalName === name
|
|
967
|
+
);
|
|
968
|
+
if (!isAliased) {
|
|
969
|
+
mergedRules[name] = rule;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
for (const [legacyName, [source, originalName]] of Object.entries(
|
|
974
|
+
LEGACY_ALIASES
|
|
975
|
+
)) {
|
|
976
|
+
mergedRules[legacyName] = source[originalName];
|
|
977
|
+
}
|
|
978
|
+
var originalToCompat = /* @__PURE__ */ new Map();
|
|
979
|
+
for (const [legacyName, [source, originalName]] of Object.entries(LEGACY_ALIASES)) {
|
|
980
|
+
originalToCompat.set(originalName, legacyName);
|
|
981
|
+
if (source === domRules) {
|
|
982
|
+
originalToCompat.set(`dom-${originalName}`, legacyName);
|
|
983
|
+
} else if (source === jsxRules) {
|
|
984
|
+
originalToCompat.set(`jsx-${originalName}`, legacyName);
|
|
985
|
+
} else if (source === namingRules) {
|
|
986
|
+
originalToCompat.set(`naming-convention-${originalName}`, legacyName);
|
|
987
|
+
} else if (source === rscRules) {
|
|
988
|
+
originalToCompat.set(`rsc-${originalName}`, legacyName);
|
|
989
|
+
} else if (source === webApiRules) {
|
|
990
|
+
originalToCompat.set(`web-api-${originalName}`, legacyName);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
function translatePresetRules(presetRules2) {
|
|
994
|
+
const result = {};
|
|
995
|
+
for (const [key, value] of Object.entries(presetRules2)) {
|
|
996
|
+
const shortName = key.replace(
|
|
997
|
+
/^@eslint-react\/(?:dom|web-api|naming-convention|rsc)\//,
|
|
998
|
+
""
|
|
999
|
+
).replace(/^@eslint-react\//, "").replace(/^(?:dom|web-api|naming-convention|rsc)-/, "");
|
|
1000
|
+
const compatName = originalToCompat.get(shortName) ?? shortName;
|
|
1001
|
+
if (compatName in mergedRules) {
|
|
1002
|
+
result[`react/${compatName}`] = value;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
var reactCompatPlugin = {
|
|
1008
|
+
meta: {
|
|
1009
|
+
name: "react-compat",
|
|
1010
|
+
version: "1.0.0"
|
|
1011
|
+
},
|
|
1012
|
+
rules: mergedRules
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
// src/configs/react.ts
|
|
1016
|
+
var typedConfigs = eslintReactPlugin2.configs;
|
|
1017
|
+
var strictTypeCheckedRules = typedConfigs["strict-type-checked"].rules;
|
|
1018
|
+
var REACT_HOOKS_REPLACED_ESLINT_REACT_RULES = [
|
|
1019
|
+
"error-boundaries",
|
|
1020
|
+
"exhaustive-deps",
|
|
1021
|
+
"purity",
|
|
1022
|
+
"rules-of-hooks",
|
|
1023
|
+
"set-state-in-effect",
|
|
1024
|
+
"set-state-in-render",
|
|
1025
|
+
"static-components",
|
|
1026
|
+
"unsupported-syntax",
|
|
1027
|
+
"use-memo"
|
|
1028
|
+
];
|
|
1029
|
+
var reactHooksReplacedRules = new Set(
|
|
1030
|
+
REACT_HOOKS_REPLACED_ESLINT_REACT_RULES
|
|
1031
|
+
);
|
|
1032
|
+
function filterReactHooksRules(rules) {
|
|
1033
|
+
const filtered = {};
|
|
1034
|
+
for (const [ruleName, value] of Object.entries(rules)) {
|
|
1035
|
+
const shortName = ruleName.replace(/^@eslint-react\//, "");
|
|
1036
|
+
if (!reactHooksReplacedRules.has(shortName)) {
|
|
1037
|
+
filtered[ruleName] = value;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return filtered;
|
|
1041
|
+
}
|
|
1042
|
+
var presetRules = translatePresetRules(filterReactHooksRules(strictTypeCheckedRules));
|
|
1043
|
+
var reactHooksRecommendedLatest = reactHooksPlugin.configs.flat["recommended-latest"];
|
|
1044
|
+
var reactHooksPluginForEslint = reactHooksPlugin;
|
|
1045
|
+
var EXTRA_REACT_RULES = {
|
|
1046
|
+
"react/no-duplicate-key": "error",
|
|
1047
|
+
"react/no-implicit-key": "error",
|
|
1048
|
+
"react/no-unknown-property": "error",
|
|
1049
|
+
"react/style-prop-object": "error"
|
|
1050
|
+
};
|
|
1051
|
+
var AI_ADDED_REACT_RULES = {
|
|
1052
|
+
"react/no-unused-state": "error"
|
|
1053
|
+
};
|
|
1054
|
+
var AI_REACT_PERF_RULES = {
|
|
1055
|
+
"react-perf/jsx-no-jsx-as-prop": "error",
|
|
1056
|
+
"react-perf/jsx-no-new-array-as-prop": "error",
|
|
1057
|
+
"react-perf/jsx-no-new-function-as-prop": "error",
|
|
1058
|
+
"react-perf/jsx-no-new-object-as-prop": "error"
|
|
1059
|
+
};
|
|
1060
|
+
var JSX_A11Y_ERROR_RULES = {
|
|
1061
|
+
"jsx-a11y/alt-text": "error",
|
|
1062
|
+
"jsx-a11y/anchor-has-content": "error",
|
|
1063
|
+
"jsx-a11y/anchor-is-valid": "error",
|
|
1064
|
+
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
|
1065
|
+
"jsx-a11y/aria-props": "error",
|
|
1066
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
1067
|
+
"jsx-a11y/aria-role": "error",
|
|
1068
|
+
"jsx-a11y/aria-unsupported-elements": "error",
|
|
1069
|
+
"jsx-a11y/click-events-have-key-events": "error",
|
|
1070
|
+
"jsx-a11y/heading-has-content": "error",
|
|
1071
|
+
"jsx-a11y/html-has-lang": "error",
|
|
1072
|
+
"jsx-a11y/img-redundant-alt": "error",
|
|
1073
|
+
"jsx-a11y/label-has-associated-control": "error",
|
|
1074
|
+
"jsx-a11y/mouse-events-have-key-events": "error",
|
|
1075
|
+
"jsx-a11y/no-access-key": "error",
|
|
1076
|
+
"jsx-a11y/no-distracting-elements": "error",
|
|
1077
|
+
"jsx-a11y/no-redundant-roles": "error",
|
|
1078
|
+
"jsx-a11y/role-has-required-aria-props": "error",
|
|
1079
|
+
"jsx-a11y/role-supports-aria-props": "error",
|
|
1080
|
+
"jsx-a11y/scope": "error",
|
|
1081
|
+
"jsx-a11y/tabindex-no-positive": "error",
|
|
1082
|
+
"jsx-a11y/lang": "error",
|
|
1083
|
+
"jsx-a11y/autocomplete-valid": "error"
|
|
1084
|
+
};
|
|
1085
|
+
var AI_PROMOTED_REACT_RULES = [
|
|
1086
|
+
"react/jsx-no-comment-textnodes",
|
|
1087
|
+
"react/jsx-no-useless-fragment",
|
|
1088
|
+
"react/jsx-no-constructed-context-values",
|
|
1089
|
+
"react/no-array-index-key",
|
|
1090
|
+
"react/no-object-type-as-default-prop",
|
|
1091
|
+
"react/jsx-no-target-blank",
|
|
1092
|
+
"react/button-has-type",
|
|
1093
|
+
"react/iframe-missing-sandbox",
|
|
1094
|
+
"react/forward-ref-uses-ref",
|
|
1095
|
+
"react/no-context-provider",
|
|
1096
|
+
"react/no-use-context",
|
|
1097
|
+
"react/no-leaked-event-listener",
|
|
1098
|
+
"react/no-leaked-interval",
|
|
1099
|
+
"react/no-leaked-timeout",
|
|
1100
|
+
"react/no-leaked-resize-observer"
|
|
1101
|
+
];
|
|
1102
|
+
var AI_A11Y_RULES = [
|
|
1103
|
+
"jsx-a11y/no-static-element-interactions",
|
|
1104
|
+
"jsx-a11y/no-noninteractive-element-interactions",
|
|
1105
|
+
"jsx-a11y/interactive-supports-focus"
|
|
1106
|
+
];
|
|
1107
|
+
function addRules2(builder, rules) {
|
|
1108
|
+
for (const [ruleName, value] of Object.entries(rules)) {
|
|
1109
|
+
builder.addRule(ruleName, value);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function addExtraReactRules(builder) {
|
|
1113
|
+
addRules2(builder, EXTRA_REACT_RULES);
|
|
1114
|
+
}
|
|
1115
|
+
function addStylisticReactRules(builder) {
|
|
1116
|
+
builder.addRule("@stylistic/jsx-self-closing-comp", "error");
|
|
1117
|
+
builder.addRule("@stylistic/jsx-curly-brace-presence", [
|
|
1118
|
+
"error",
|
|
1119
|
+
{ props: "never", children: "never" }
|
|
1120
|
+
]);
|
|
1121
|
+
}
|
|
1122
|
+
function addReactRefreshRules(builder) {
|
|
1123
|
+
builder.addRule("react-refresh/only-export-components", [
|
|
1124
|
+
"warn",
|
|
1125
|
+
{ allowConstantExport: true }
|
|
1126
|
+
]);
|
|
1127
|
+
}
|
|
1128
|
+
function addAccessibilityRules(builder) {
|
|
1129
|
+
addRules2(builder, JSX_A11Y_ERROR_RULES);
|
|
1130
|
+
builder.addRule("jsx-a11y/no-autofocus", ["error", { ignoreNonDOM: true }]);
|
|
1131
|
+
}
|
|
1132
|
+
function applyAiReactRules(builder) {
|
|
1133
|
+
for (const ruleName of AI_PROMOTED_REACT_RULES) {
|
|
1134
|
+
builder.overrideSeverity(ruleName, "error");
|
|
1135
|
+
}
|
|
1136
|
+
addRules2(builder, AI_ADDED_REACT_RULES);
|
|
1137
|
+
addRules2(builder, AI_REACT_PERF_RULES);
|
|
1138
|
+
for (const ruleName of AI_A11Y_RULES) {
|
|
1139
|
+
builder.addRule(ruleName, "error");
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function addReactFileOverrides(builder) {
|
|
1143
|
+
builder.addFileOverride(
|
|
1144
|
+
"eslint-config-setup/react-typescript",
|
|
1145
|
+
[...TYPESCRIPT_SOURCE_FILES],
|
|
1150
1146
|
{
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1147
|
+
// Allow async event handlers — onClick={async () => {...}} is idiomatic React
|
|
1148
|
+
"@typescript-eslint/no-misused-promises": [
|
|
1149
|
+
"error",
|
|
1150
|
+
{ checksVoidReturn: false }
|
|
1151
|
+
]
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
function reactConfig(opts) {
|
|
1156
|
+
const isAi = opts?.ai ?? false;
|
|
1157
|
+
const builder = createConfig({
|
|
1158
|
+
name: "eslint-config-setup/react",
|
|
1159
|
+
presets: [{ rules: presetRules }, reactHooksRecommendedLatest],
|
|
1160
|
+
plugins: {
|
|
1161
|
+
react: reactCompatPlugin,
|
|
1162
|
+
"@stylistic": stylisticPlugin,
|
|
1163
|
+
"react-hooks": reactHooksPluginForEslint,
|
|
1164
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Plugin from untyped module
|
|
1165
|
+
"react-perf": reactPerfPlugin,
|
|
1166
|
+
"react-refresh": reactRefreshPlugin,
|
|
1167
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Plugin from untyped module
|
|
1168
|
+
"jsx-a11y": jsxA11yPlugin
|
|
1169
|
+
},
|
|
1170
|
+
languageOptions: {
|
|
1171
|
+
globals: {
|
|
1172
|
+
...globals2.browser
|
|
1174
1173
|
},
|
|
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"
|
|
1174
|
+
parserOptions: {
|
|
1175
|
+
ecmaFeatures: {
|
|
1176
|
+
jsx: true
|
|
1177
|
+
}
|
|
1366
1178
|
}
|
|
1367
1179
|
}
|
|
1368
|
-
|
|
1180
|
+
});
|
|
1181
|
+
addExtraReactRules(builder);
|
|
1182
|
+
addStylisticReactRules(builder);
|
|
1183
|
+
addReactRefreshRules(builder);
|
|
1184
|
+
addAccessibilityRules(builder);
|
|
1185
|
+
if (isAi) {
|
|
1186
|
+
applyAiReactRules(builder);
|
|
1187
|
+
}
|
|
1188
|
+
addReactFileOverrides(builder);
|
|
1189
|
+
return builder.build();
|
|
1369
1190
|
}
|
|
1370
1191
|
|
|
1371
1192
|
// src/configs/react-effect.ts
|
|
@@ -1399,12 +1220,12 @@ function reactEffectConfig() {
|
|
|
1399
1220
|
// Disallow passing fetched data to parent via effect — fetch in parent instead
|
|
1400
1221
|
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1401
1222
|
"react-you-might-not-need-an-effect/no-pass-data-to-parent": "error",
|
|
1402
|
-
// Disallow
|
|
1223
|
+
// Disallow external-store subscriptions in an effect — use useSyncExternalStore
|
|
1403
1224
|
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1404
|
-
"react-you-might-not-need-an-effect/no-
|
|
1405
|
-
// Disallow
|
|
1225
|
+
"react-you-might-not-need-an-effect/no-external-store-subscription": "error",
|
|
1226
|
+
// Disallow initializing state in an effect — pass initial value to useState
|
|
1406
1227
|
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1407
|
-
"react-you-might-not-need-an-effect/no-
|
|
1228
|
+
"react-you-might-not-need-an-effect/no-initialize-state": "error"
|
|
1408
1229
|
}
|
|
1409
1230
|
}
|
|
1410
1231
|
];
|
|
@@ -1412,11 +1233,21 @@ function reactEffectConfig() {
|
|
|
1412
1233
|
|
|
1413
1234
|
// src/configs/regexp.ts
|
|
1414
1235
|
import { configs as regexpConfigs } from "eslint-plugin-regexp";
|
|
1415
|
-
function regexpConfig() {
|
|
1416
|
-
|
|
1236
|
+
function regexpConfig(opts) {
|
|
1237
|
+
const isAi = opts?.ai ?? false;
|
|
1238
|
+
const builder = createConfig({
|
|
1417
1239
|
name: "eslint-config-setup/regexp",
|
|
1418
1240
|
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")
|
|
1241
|
+
}).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");
|
|
1242
|
+
if (isAi) {
|
|
1243
|
+
builder.addRule("regexp/prefer-lookaround", "error");
|
|
1244
|
+
builder.addRule("regexp/prefer-named-backreference", "error");
|
|
1245
|
+
builder.addRule("regexp/prefer-named-replacement", "error");
|
|
1246
|
+
builder.addRule("regexp/prefer-quantifier", "error");
|
|
1247
|
+
builder.addRule("regexp/prefer-result-array-groups", "error");
|
|
1248
|
+
builder.addRule("regexp/require-unicode-sets-regexp", "error");
|
|
1249
|
+
}
|
|
1250
|
+
return builder.build();
|
|
1420
1251
|
}
|
|
1421
1252
|
|
|
1422
1253
|
// src/configs/security.ts
|
|
@@ -1482,13 +1313,12 @@ function securityConfig() {
|
|
|
1482
1313
|
|
|
1483
1314
|
// src/configs/sonarjs.ts
|
|
1484
1315
|
import sonarjsPlugin from "eslint-plugin-sonarjs";
|
|
1485
|
-
function sonarjsConfig() {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
},
|
|
1316
|
+
function sonarjsConfig(opts) {
|
|
1317
|
+
const isAi = opts?.ai === true;
|
|
1318
|
+
const builder = createConfig({
|
|
1319
|
+
name: "eslint-config-setup/sonarjs",
|
|
1320
|
+
presets: [{
|
|
1321
|
+
plugins: { sonarjs: sonarjsPlugin },
|
|
1492
1322
|
rules: {
|
|
1493
1323
|
// Detect copy-pasted functions — extract to shared helper instead
|
|
1494
1324
|
// https://sonarsource.github.io/rspec/#/rspec/S4144/javascript
|
|
@@ -1551,13 +1381,24 @@ function sonarjsConfig() {
|
|
|
1551
1381
|
// https://sonarsource.github.io/rspec/#/rspec/S6418/javascript
|
|
1552
1382
|
"sonarjs/no-hardcoded-secrets": "warn"
|
|
1553
1383
|
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1384
|
+
}]
|
|
1385
|
+
});
|
|
1386
|
+
if (isAi) {
|
|
1387
|
+
builder.addRule("sonarjs/no-nested-switch", "error");
|
|
1388
|
+
builder.addRule("sonarjs/no-nested-template-literals", "error");
|
|
1389
|
+
builder.addRule("sonarjs/max-union-size", ["error", { threshold: 5 }]);
|
|
1390
|
+
builder.addRule("sonarjs/prefer-type-guard", "error");
|
|
1391
|
+
builder.addRule("sonarjs/public-static-readonly", "error");
|
|
1392
|
+
builder.addRule("sonarjs/no-duplicate-string", ["error", { threshold: 3 }]);
|
|
1393
|
+
}
|
|
1394
|
+
return builder.build();
|
|
1556
1395
|
}
|
|
1557
1396
|
|
|
1558
1397
|
// src/configs/typescript.ts
|
|
1559
1398
|
import tseslint from "typescript-eslint";
|
|
1560
|
-
function typescriptConfig() {
|
|
1399
|
+
function typescriptConfig(opts) {
|
|
1400
|
+
const isAi = opts?.ai ?? false;
|
|
1401
|
+
const isReact = opts?.react ?? false;
|
|
1561
1402
|
const typeChecked = tseslint.configs.strictTypeChecked;
|
|
1562
1403
|
const stylistic = tseslint.configs.stylisticTypeChecked;
|
|
1563
1404
|
const structuralBlocks = typeChecked.slice(0, 2);
|
|
@@ -1566,6 +1407,8 @@ function typescriptConfig() {
|
|
|
1566
1407
|
name: "eslint-config-setup/typescript",
|
|
1567
1408
|
passthrough: structuralBlocks,
|
|
1568
1409
|
presets: ruleBlocks,
|
|
1410
|
+
files: [...TYPESCRIPT_SOURCE_FILES],
|
|
1411
|
+
ignores: [...MARKDOWN_CODE_BLOCK_FILES],
|
|
1569
1412
|
languageOptions: {
|
|
1570
1413
|
parserOptions: {
|
|
1571
1414
|
// Use project service for automatic tsconfig resolution
|
|
@@ -1594,10 +1437,25 @@ function typescriptConfig() {
|
|
|
1594
1437
|
"error",
|
|
1595
1438
|
"in-try-catch"
|
|
1596
1439
|
]);
|
|
1597
|
-
builder.
|
|
1440
|
+
builder.overrideSeverity("@typescript-eslint/no-deprecated", "warn");
|
|
1441
|
+
builder.overrideRule("@typescript-eslint/no-explicit-any", [
|
|
1442
|
+
"warn",
|
|
1443
|
+
{ fixToUnknown: true }
|
|
1444
|
+
]);
|
|
1445
|
+
builder.overrideRule("@typescript-eslint/consistent-type-definitions", [
|
|
1446
|
+
"error",
|
|
1447
|
+
"type"
|
|
1448
|
+
]);
|
|
1449
|
+
builder.overrideOptions("@typescript-eslint/ban-ts-comment", {
|
|
1450
|
+
"ts-expect-error": "allow-with-description"
|
|
1451
|
+
});
|
|
1452
|
+
builder.overrideOptions("@typescript-eslint/no-floating-promises", {
|
|
1453
|
+
checkThenables: true,
|
|
1454
|
+
ignoreIIFE: true
|
|
1455
|
+
});
|
|
1598
1456
|
builder.addRule("@typescript-eslint/consistent-type-imports", [
|
|
1599
1457
|
"error",
|
|
1600
|
-
{ fixStyle: "inline-type-imports" }
|
|
1458
|
+
{ fixStyle: isAi ? "inline-type-imports" : "separate-type-imports" }
|
|
1601
1459
|
]);
|
|
1602
1460
|
builder.addRule("@typescript-eslint/consistent-type-exports", [
|
|
1603
1461
|
"error",
|
|
@@ -1611,6 +1469,24 @@ function typescriptConfig() {
|
|
|
1611
1469
|
"error"
|
|
1612
1470
|
);
|
|
1613
1471
|
builder.addRule("@typescript-eslint/strict-void-return", "error");
|
|
1472
|
+
builder.addRule("@typescript-eslint/prefer-readonly", isAi ? "error" : "warn");
|
|
1473
|
+
builder.addRule("@typescript-eslint/require-array-sort-compare", [
|
|
1474
|
+
"error",
|
|
1475
|
+
{ ignoreStringArrays: true }
|
|
1476
|
+
]);
|
|
1477
|
+
builder.addRule("@typescript-eslint/no-unsafe-type-assertion", "error");
|
|
1478
|
+
builder.addRule("@typescript-eslint/switch-exhaustiveness-check", [
|
|
1479
|
+
"error",
|
|
1480
|
+
{
|
|
1481
|
+
allowDefaultCaseForExhaustiveSwitch: false,
|
|
1482
|
+
requireDefaultForNonUnion: true
|
|
1483
|
+
}
|
|
1484
|
+
]);
|
|
1485
|
+
builder.addRule("@typescript-eslint/promise-function-async", "error");
|
|
1486
|
+
builder.addRule("@typescript-eslint/method-signature-style", [
|
|
1487
|
+
"error",
|
|
1488
|
+
"property"
|
|
1489
|
+
]);
|
|
1614
1490
|
builder.addRule("no-shadow", "off");
|
|
1615
1491
|
builder.addRule("@typescript-eslint/no-shadow", [
|
|
1616
1492
|
"error",
|
|
@@ -1621,10 +1497,7 @@ function typescriptConfig() {
|
|
|
1621
1497
|
ignoreFunctionTypeParameterNameValueShadow: true
|
|
1622
1498
|
}
|
|
1623
1499
|
]);
|
|
1624
|
-
builder.
|
|
1625
|
-
"error",
|
|
1626
|
-
{ allowNumber: true }
|
|
1627
|
-
]);
|
|
1500
|
+
builder.overrideOptions("@typescript-eslint/restrict-template-expressions", { allowNumber: true });
|
|
1628
1501
|
builder.addRule("@typescript-eslint/strict-boolean-expressions", [
|
|
1629
1502
|
"error",
|
|
1630
1503
|
{ allowNullableBoolean: true, allowNullableObject: true }
|
|
@@ -1634,18 +1507,168 @@ function typescriptConfig() {
|
|
|
1634
1507
|
["**/*.{js,mjs,cjs}"],
|
|
1635
1508
|
tseslint.configs.disableTypeChecked.rules ?? {}
|
|
1636
1509
|
);
|
|
1510
|
+
if (isAi) {
|
|
1511
|
+
builder.overrideSeverity("@typescript-eslint/no-explicit-any", "error");
|
|
1512
|
+
builder.overrideRule("@typescript-eslint/no-floating-promises", [
|
|
1513
|
+
"error",
|
|
1514
|
+
{ checkThenables: true, ignoreVoid: true }
|
|
1515
|
+
]);
|
|
1516
|
+
builder.addRule("@typescript-eslint/no-magic-numbers", [
|
|
1517
|
+
"error",
|
|
1518
|
+
{
|
|
1519
|
+
ignore: [-1, 0, 1, 2],
|
|
1520
|
+
ignoreArrayIndexes: true,
|
|
1521
|
+
ignoreDefaultValues: true,
|
|
1522
|
+
enforceConst: true,
|
|
1523
|
+
ignoreClassFieldInitialValues: true,
|
|
1524
|
+
ignoreEnums: true,
|
|
1525
|
+
ignoreNumericLiteralTypes: true,
|
|
1526
|
+
ignoreReadonlyClassProperties: true,
|
|
1527
|
+
ignoreTypeIndexes: true
|
|
1528
|
+
}
|
|
1529
|
+
]);
|
|
1530
|
+
builder.addRule("@typescript-eslint/explicit-function-return-type", [
|
|
1531
|
+
"error",
|
|
1532
|
+
{
|
|
1533
|
+
allowExpressions: true,
|
|
1534
|
+
allowTypedFunctionExpressions: true,
|
|
1535
|
+
allowHigherOrderFunctions: true,
|
|
1536
|
+
allowIIFEs: true
|
|
1537
|
+
}
|
|
1538
|
+
]);
|
|
1539
|
+
builder.addRule(
|
|
1540
|
+
"@typescript-eslint/explicit-member-accessibility",
|
|
1541
|
+
"error"
|
|
1542
|
+
);
|
|
1543
|
+
builder.addRule(
|
|
1544
|
+
"@typescript-eslint/prefer-enum-initializers",
|
|
1545
|
+
"error"
|
|
1546
|
+
);
|
|
1547
|
+
builder.addRule("@typescript-eslint/naming-convention", [
|
|
1548
|
+
"error",
|
|
1549
|
+
{
|
|
1550
|
+
selector: "variable",
|
|
1551
|
+
format: isReact ? ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] : ["strictCamelCase", "UPPER_CASE"],
|
|
1552
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1553
|
+
trailingUnderscore: "allow",
|
|
1554
|
+
filter: { regex: "[- ]", match: false }
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
selector: "function",
|
|
1558
|
+
format: isReact ? ["strictCamelCase", "StrictPascalCase"] : ["strictCamelCase"]
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
selector: "parameter",
|
|
1562
|
+
format: ["strictCamelCase"],
|
|
1563
|
+
leadingUnderscore: "allow"
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
selector: "import",
|
|
1567
|
+
format: ["strictCamelCase", "StrictPascalCase", "UPPER_CASE"]
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
selector: [
|
|
1571
|
+
"classProperty",
|
|
1572
|
+
"parameterProperty",
|
|
1573
|
+
"classMethod",
|
|
1574
|
+
"objectLiteralMethod",
|
|
1575
|
+
"typeMethod",
|
|
1576
|
+
"accessor"
|
|
1577
|
+
],
|
|
1578
|
+
format: ["strictCamelCase"],
|
|
1579
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1580
|
+
trailingUnderscore: "allow",
|
|
1581
|
+
filter: { regex: "[- ]", match: false }
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
selector: ["objectLiteralProperty", "typeProperty"],
|
|
1585
|
+
format: null
|
|
1586
|
+
},
|
|
1587
|
+
{ selector: "typeLike", format: ["StrictPascalCase"] },
|
|
1588
|
+
{
|
|
1589
|
+
selector: "interface",
|
|
1590
|
+
format: ["StrictPascalCase"],
|
|
1591
|
+
custom: { regex: "^I[A-Z]", match: false }
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
selector: "typeParameter",
|
|
1595
|
+
format: ["PascalCase"],
|
|
1596
|
+
custom: {
|
|
1597
|
+
regex: "^(T([A-Z][a-zA-Z]*)?|[A-Z])$",
|
|
1598
|
+
match: true
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
{
|
|
1602
|
+
selector: "variable",
|
|
1603
|
+
types: ["boolean"],
|
|
1604
|
+
format: ["StrictPascalCase"],
|
|
1605
|
+
prefix: ["is", "has", "can", "should", "will", "did"]
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
selector: ["classProperty", "objectLiteralProperty"],
|
|
1609
|
+
format: null,
|
|
1610
|
+
modifiers: ["requiresQuotes"]
|
|
1611
|
+
}
|
|
1612
|
+
]);
|
|
1613
|
+
builder.addRule("@typescript-eslint/member-ordering", [
|
|
1614
|
+
"warn",
|
|
1615
|
+
{
|
|
1616
|
+
default: [
|
|
1617
|
+
"signature",
|
|
1618
|
+
"call-signature",
|
|
1619
|
+
"public-static-field",
|
|
1620
|
+
"protected-static-field",
|
|
1621
|
+
"private-static-field",
|
|
1622
|
+
"#private-static-field",
|
|
1623
|
+
"static-field",
|
|
1624
|
+
"public-static-method",
|
|
1625
|
+
"protected-static-method",
|
|
1626
|
+
"private-static-method",
|
|
1627
|
+
"#private-static-method",
|
|
1628
|
+
"static-method",
|
|
1629
|
+
"public-decorated-field",
|
|
1630
|
+
"protected-decorated-field",
|
|
1631
|
+
"private-decorated-field",
|
|
1632
|
+
"public-instance-field",
|
|
1633
|
+
"protected-instance-field",
|
|
1634
|
+
"private-instance-field",
|
|
1635
|
+
"#private-instance-field",
|
|
1636
|
+
"public-abstract-field",
|
|
1637
|
+
"protected-abstract-field",
|
|
1638
|
+
"field",
|
|
1639
|
+
"public-constructor",
|
|
1640
|
+
"protected-constructor",
|
|
1641
|
+
"private-constructor",
|
|
1642
|
+
"constructor",
|
|
1643
|
+
["public-get", "public-set"],
|
|
1644
|
+
["protected-get", "protected-set"],
|
|
1645
|
+
["private-get", "private-set"],
|
|
1646
|
+
["#private-get", "#private-set"],
|
|
1647
|
+
"public-decorated-method",
|
|
1648
|
+
"protected-decorated-method",
|
|
1649
|
+
"private-decorated-method",
|
|
1650
|
+
"public-instance-method",
|
|
1651
|
+
"protected-instance-method",
|
|
1652
|
+
"private-instance-method",
|
|
1653
|
+
"#private-instance-method",
|
|
1654
|
+
"public-abstract-method",
|
|
1655
|
+
"protected-abstract-method",
|
|
1656
|
+
"method"
|
|
1657
|
+
]
|
|
1658
|
+
}
|
|
1659
|
+
]);
|
|
1660
|
+
}
|
|
1637
1661
|
return builder.build();
|
|
1638
1662
|
}
|
|
1639
1663
|
|
|
1640
1664
|
// src/configs/unicorn.ts
|
|
1641
1665
|
import unicornPlugin from "eslint-plugin-unicorn";
|
|
1642
|
-
function unicornConfig() {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
},
|
|
1666
|
+
function unicornConfig(opts) {
|
|
1667
|
+
const isAi = opts?.ai === true;
|
|
1668
|
+
const builder = createConfig({
|
|
1669
|
+
name: "eslint-config-setup/unicorn",
|
|
1670
|
+
presets: [{
|
|
1671
|
+
plugins: { unicorn: unicornPlugin },
|
|
1649
1672
|
rules: {
|
|
1650
1673
|
// ── Error prevention ──────────────────────────────────────────
|
|
1651
1674
|
// Forbid `/* eslint-disable */` without specific rule — too broad
|
|
@@ -1675,9 +1698,11 @@ function unicornConfig() {
|
|
|
1675
1698
|
// Remove useless Promise.resolve/reject wrappers — simplify async code
|
|
1676
1699
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-promise-resolve-reject.md
|
|
1677
1700
|
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
1678
|
-
// Disallow
|
|
1701
|
+
// Disallow redundant undefined where omission preserves semantics.
|
|
1702
|
+
// Keep call arguments unchecked: TypeScript often needs explicit
|
|
1703
|
+
// `undefined` for required parameters typed as `T | undefined`.
|
|
1679
1704
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-undefined.md
|
|
1680
|
-
"unicorn/no-useless-undefined": "
|
|
1705
|
+
"unicorn/no-useless-undefined": ["warn", { checkArguments: false }],
|
|
1681
1706
|
// Disallow `await` in Promise.all/race arguments — already a promise
|
|
1682
1707
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-await-in-promise-methods.md
|
|
1683
1708
|
"unicorn/no-await-in-promise-methods": "error",
|
|
@@ -1733,6 +1758,29 @@ function unicornConfig() {
|
|
|
1733
1758
|
// Enforce consistent style for indexOf/findIndex existence checks
|
|
1734
1759
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-existence-index-check.md
|
|
1735
1760
|
"unicorn/consistent-existence-index-check": "error",
|
|
1761
|
+
// Move functions to the smallest possible scope — avoids re-creation on every call,
|
|
1762
|
+
// improves testability, and makes dependencies explicit
|
|
1763
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md
|
|
1764
|
+
"unicorn/consistent-function-scoping": "warn",
|
|
1765
|
+
// Enforce consistent filename casing — camelCase, PascalCase, or kebab-case (no snake_case)
|
|
1766
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/filename-case.md
|
|
1767
|
+
"unicorn/filename-case": [
|
|
1768
|
+
"error",
|
|
1769
|
+
{
|
|
1770
|
+
cases: {
|
|
1771
|
+
camelCase: true,
|
|
1772
|
+
pascalCase: true,
|
|
1773
|
+
kebabCase: true
|
|
1774
|
+
},
|
|
1775
|
+
ignore: ["__tests__"]
|
|
1776
|
+
}
|
|
1777
|
+
],
|
|
1778
|
+
// Discourage Array.reduce() — hard to read for many developers, prefer for...of or other methods
|
|
1779
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-reduce.md
|
|
1780
|
+
"unicorn/no-array-reduce": "warn",
|
|
1781
|
+
// Prefer for...of over C-style for loops — more readable when index isn't needed
|
|
1782
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md
|
|
1783
|
+
"unicorn/no-for-loop": "warn",
|
|
1736
1784
|
// ── Modern API preferences ────────────────────────────────────
|
|
1737
1785
|
// Prefer .flatMap() over .map().flat() — single pass, more readable
|
|
1738
1786
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat-map.md
|
|
@@ -1758,6 +1806,9 @@ function unicornConfig() {
|
|
|
1758
1806
|
// Prefer .before()/.after()/.replaceWith() over parent.insertBefore()
|
|
1759
1807
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md
|
|
1760
1808
|
"unicorn/prefer-modern-dom-apis": "error",
|
|
1809
|
+
// Prefer element.dataset.foo over setAttribute('data-foo') — modern, readable
|
|
1810
|
+
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-dom-node-dataset.md
|
|
1811
|
+
"unicorn/prefer-dom-node-dataset": "error",
|
|
1761
1812
|
// Prefer Math.log10/Math.hypot over manual math — accurate and readable
|
|
1762
1813
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-math-apis.md
|
|
1763
1814
|
"unicorn/prefer-modern-math-apis": "error",
|
|
@@ -1827,7 +1878,7 @@ function unicornConfig() {
|
|
|
1827
1878
|
"unicorn/numeric-separators-style": "error",
|
|
1828
1879
|
// Prefer `export { x } from 'y'` over import then re-export
|
|
1829
1880
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-export-from.md
|
|
1830
|
-
"unicorn/prefer-export-from": ["error", {
|
|
1881
|
+
"unicorn/prefer-export-from": ["error", { checkUsedVariables: false }],
|
|
1831
1882
|
// Prefer built-in coercion (String, Number, Boolean) over wrapper functions
|
|
1832
1883
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-native-coercion-functions.md
|
|
1833
1884
|
"unicorn/prefer-native-coercion-functions": "error",
|
|
@@ -1863,8 +1914,27 @@ function unicornConfig() {
|
|
|
1863
1914
|
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-call.md
|
|
1864
1915
|
"unicorn/prefer-single-call": "off"
|
|
1865
1916
|
}
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1917
|
+
}]
|
|
1918
|
+
});
|
|
1919
|
+
if (isAi) {
|
|
1920
|
+
builder.overrideSeverity("unicorn/consistent-function-scoping", "error");
|
|
1921
|
+
builder.overrideSeverity("unicorn/no-array-reduce", "error");
|
|
1922
|
+
builder.overrideSeverity("unicorn/no-for-loop", "error");
|
|
1923
|
+
builder.addRule("unicorn/no-for-each", "error");
|
|
1924
|
+
builder.addRule("unicorn/prefer-ternary", ["error", "only-single-line"]);
|
|
1925
|
+
builder.addRule("unicorn/prefer-switch", ["error", { minimumCases: 3 }]);
|
|
1926
|
+
builder.addRule("unicorn/prevent-abbreviations", "error");
|
|
1927
|
+
builder.addRule("unicorn/no-useless-switch-case", "error");
|
|
1928
|
+
builder.addRule("unicorn/custom-error-definition", "error");
|
|
1929
|
+
builder.addRule("unicorn/prefer-default-parameters", "error");
|
|
1930
|
+
builder.addRule("unicorn/prefer-logical-operator-over-ternary", "error");
|
|
1931
|
+
builder.addRule("unicorn/prefer-math-min-max", "error");
|
|
1932
|
+
builder.addRule("unicorn/prefer-set-size", "error");
|
|
1933
|
+
builder.addRule("unicorn/explicit-length-check", "error");
|
|
1934
|
+
builder.addRule("unicorn/switch-case-braces", "error");
|
|
1935
|
+
builder.addRule("unicorn/no-array-push-push", "error");
|
|
1936
|
+
}
|
|
1937
|
+
return builder.build();
|
|
1868
1938
|
}
|
|
1869
1939
|
|
|
1870
1940
|
// src/overrides/config-files.ts
|
|
@@ -2097,136 +2167,174 @@ function storiesOverride() {
|
|
|
2097
2167
|
// src/overrides/tests.ts
|
|
2098
2168
|
import vitestPlugin from "@vitest/eslint-plugin";
|
|
2099
2169
|
import testingLibraryPlugin from "eslint-plugin-testing-library";
|
|
2170
|
+
var TEST_FILES2 = ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"];
|
|
2171
|
+
var VITEST_RULES = {
|
|
2172
|
+
// ── Vitest rules ──────────────────────────────────────────────
|
|
2173
|
+
// Every test must contain at least one assertion
|
|
2174
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md
|
|
2175
|
+
"vitest/expect-expect": "error",
|
|
2176
|
+
// Prevent duplicate test titles within a describe block
|
|
2177
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-identical-title.md
|
|
2178
|
+
"vitest/no-identical-title": "error",
|
|
2179
|
+
// No it.only / describe.only — prevents accidentally skipping tests in CI
|
|
2180
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-focused-tests.md
|
|
2181
|
+
"vitest/no-focused-tests": "error",
|
|
2182
|
+
// Warn on it.skip / describe.skip — track disabled tests
|
|
2183
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-disabled-tests.md
|
|
2184
|
+
"vitest/no-disabled-tests": "warn",
|
|
2185
|
+
// No duplicate beforeEach/afterEach hooks — merge them
|
|
2186
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-duplicate-hooks.md
|
|
2187
|
+
"vitest/no-duplicate-hooks": "error",
|
|
2188
|
+
// Prefer .toBe() over .toEqual() for primitives — clearer intent
|
|
2189
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-be.md
|
|
2190
|
+
"vitest/prefer-to-be": "error",
|
|
2191
|
+
// Prefer .toHaveLength() over .toBe(arr.length) — better errors
|
|
2192
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-to-have-length.md
|
|
2193
|
+
"vitest/prefer-to-have-length": "error",
|
|
2194
|
+
// Validate expect() usage — no dangling expect without assertion
|
|
2195
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md
|
|
2196
|
+
"vitest/valid-expect": "error",
|
|
2197
|
+
// Validate test/describe title format — no empty or invalid titles
|
|
2198
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/valid-title.md
|
|
2199
|
+
"vitest/valid-title": "error",
|
|
2200
|
+
// No conditional logic (if/else) inside tests — split into separate tests
|
|
2201
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md
|
|
2202
|
+
"vitest/no-conditional-in-test": "error",
|
|
2203
|
+
// No expect() inside conditional blocks — always assert unconditionally
|
|
2204
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md
|
|
2205
|
+
"vitest/no-conditional-expect": "error",
|
|
2206
|
+
// No standalone expect() outside test blocks — always wrap in it/test
|
|
2207
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md
|
|
2208
|
+
"vitest/no-standalone-expect": "error",
|
|
2209
|
+
// Prefer .toStrictEqual() over .toEqual() — catches undefined vs missing
|
|
2210
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-strict-equal.md
|
|
2211
|
+
"vitest/prefer-strict-equal": "error",
|
|
2212
|
+
// Prefer vi.spyOn() over vi.fn() for method mocks — preserves original
|
|
2213
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/prefer-spy-on.md
|
|
2214
|
+
"vitest/prefer-spy-on": "error",
|
|
2215
|
+
// Require message argument in toThrow/toThrowError — verify correct error
|
|
2216
|
+
// https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/require-to-throw-message.md
|
|
2217
|
+
"vitest/require-to-throw-message": "error",
|
|
2218
|
+
// ── Relaxed rules for tests ───────────────────────────────────
|
|
2219
|
+
// Tests are naturally verbose and use patterns banned in prod code
|
|
2220
|
+
// Tests can be long (setup + many assertions)
|
|
2221
|
+
"max-lines": "off",
|
|
2222
|
+
"max-lines-per-function": "off",
|
|
2223
|
+
"max-statements": "off",
|
|
2224
|
+
// Tests often need `any` for mocking and type assertions
|
|
2225
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
2226
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
2227
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
2228
|
+
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
2229
|
+
};
|
|
2230
|
+
var TESTING_LIBRARY_RULES = {
|
|
2231
|
+
// ── Testing Library rules ───────────────────────────────────
|
|
2232
|
+
// Await async events (userEvent.click, etc.) — prevents race conditions
|
|
2233
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-events.md
|
|
2234
|
+
"testing-library/await-async-events": "error",
|
|
2235
|
+
// Await async queries (findBy*) — they return promises
|
|
2236
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-queries.md
|
|
2237
|
+
"testing-library/await-async-queries": "error",
|
|
2238
|
+
// Await async utilities (waitFor, waitForElementToBeRemoved)
|
|
2239
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md
|
|
2240
|
+
"testing-library/await-async-utils": "error",
|
|
2241
|
+
// Don't await synchronous events — misleading
|
|
2242
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-events.md
|
|
2243
|
+
"testing-library/no-await-sync-events": "error",
|
|
2244
|
+
// Don't await synchronous queries (getBy*, queryBy*) — not promises
|
|
2245
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-await-sync-queries.md
|
|
2246
|
+
"testing-library/no-await-sync-queries": "error",
|
|
2247
|
+
// Don't use container.querySelector — use queries instead
|
|
2248
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-container.md
|
|
2249
|
+
"testing-library/no-container": "error",
|
|
2250
|
+
// Warn on debug()/prettyDOM() left in tests — debugging artifacts
|
|
2251
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-debugging-utils.md
|
|
2252
|
+
"testing-library/no-debugging-utils": "warn",
|
|
2253
|
+
// Don't access DOM nodes directly — use queries
|
|
2254
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-node-access.md
|
|
2255
|
+
"testing-library/no-node-access": "error",
|
|
2256
|
+
// Don't call render in beforeEach — call in each test
|
|
2257
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-render-in-lifecycle.md
|
|
2258
|
+
"testing-library/no-render-in-lifecycle": "error",
|
|
2259
|
+
// No unnecessary act() wrappers — TL handles this internally
|
|
2260
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-unnecessary-act.md
|
|
2261
|
+
"testing-library/no-unnecessary-act": "error",
|
|
2262
|
+
// One assertion per waitFor — multiple can mask failures
|
|
2263
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-multiple-assertions.md
|
|
2264
|
+
"testing-library/no-wait-for-multiple-assertions": "error",
|
|
2265
|
+
// No side effects in waitFor — only assertions
|
|
2266
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md
|
|
2267
|
+
"testing-library/no-wait-for-side-effects": "error",
|
|
2268
|
+
// Prefer findBy* over waitFor + getBy* — built-in combination
|
|
2269
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-find-by.md
|
|
2270
|
+
"testing-library/prefer-find-by": "error",
|
|
2271
|
+
// Use getBy* (throws) for present elements, queryBy* for absent
|
|
2272
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-presence-queries.md
|
|
2273
|
+
"testing-library/prefer-presence-queries": "error",
|
|
2274
|
+
// Use queryBy* for disappearance checks — returns null when gone
|
|
2275
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-query-by-disappearance.md
|
|
2276
|
+
"testing-library/prefer-query-by-disappearance": "error",
|
|
2277
|
+
// Use screen.getBy* over destructured render result — consistent
|
|
2278
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/prefer-screen-queries.md
|
|
2279
|
+
"testing-library/prefer-screen-queries": "error",
|
|
2280
|
+
// Name render result consistently (e.g., `const { getByText } = render(...)`)
|
|
2281
|
+
// https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/render-result-naming-convention.md
|
|
2282
|
+
"testing-library/render-result-naming-convention": "error"
|
|
2283
|
+
};
|
|
2100
2284
|
function testsOverride() {
|
|
2101
|
-
|
|
2285
|
+
return [
|
|
2102
2286
|
{
|
|
2103
2287
|
name: "eslint-config-setup/tests",
|
|
2104
|
-
files:
|
|
2288
|
+
files: TEST_FILES2,
|
|
2105
2289
|
plugins: {
|
|
2106
2290
|
vitest: vitestPlugin
|
|
2107
2291
|
},
|
|
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
|
-
}
|
|
2292
|
+
rules: VITEST_RULES
|
|
2167
2293
|
},
|
|
2168
2294
|
{
|
|
2169
2295
|
name: "eslint-config-setup/tests-testing-library",
|
|
2170
|
-
files:
|
|
2296
|
+
files: TEST_FILES2,
|
|
2171
2297
|
plugins: {
|
|
2172
2298
|
"testing-library": testingLibraryPlugin
|
|
2173
2299
|
},
|
|
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
|
-
}
|
|
2300
|
+
rules: TESTING_LIBRARY_RULES
|
|
2228
2301
|
}
|
|
2229
2302
|
];
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/oxlint/integration.ts
|
|
2306
|
+
import oxlintPlugin from "eslint-plugin-oxlint";
|
|
2307
|
+
var typedPlugin = oxlintPlugin;
|
|
2308
|
+
function addOxlintConfig(configs, configName, blockName) {
|
|
2309
|
+
const raw = typedPlugin.configs[configName];
|
|
2310
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
2311
|
+
for (const [index, item] of items.entries()) {
|
|
2312
|
+
const suffix = items.length > 1 ? `-${index + 1}` : "";
|
|
2313
|
+
configs.push({ ...item, name: `eslint-config-setup/${blockName}${suffix}` });
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
function addReactOxlintConfigs(configs, opts) {
|
|
2317
|
+
if (!opts.react) {
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
addOxlintConfig(configs, "flat/react", "oxlint-react");
|
|
2321
|
+
addOxlintConfig(configs, "flat/jsx-a11y", "oxlint-jsx-a11y");
|
|
2322
|
+
addOxlintConfig(configs, "flat/react-hooks", "oxlint-react-hooks");
|
|
2323
|
+
if (opts.ai) {
|
|
2324
|
+
addOxlintConfig(configs, "flat/react-perf", "oxlint-react-perf");
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
function oxlintIntegration(opts) {
|
|
2328
|
+
const configs = [];
|
|
2329
|
+
addOxlintConfig(configs, "flat/recommended", "oxlint");
|
|
2330
|
+
addReactOxlintConfigs(configs, opts);
|
|
2331
|
+
if (opts.node) {
|
|
2332
|
+
addOxlintConfig(configs, "flat/node", "oxlint-node");
|
|
2333
|
+
}
|
|
2334
|
+
addOxlintConfig(configs, "flat/typescript", "oxlint-typescript");
|
|
2335
|
+
addOxlintConfig(configs, "flat/unicorn", "oxlint-unicorn");
|
|
2336
|
+
addOxlintConfig(configs, "flat/import", "oxlint-import");
|
|
2337
|
+
addOxlintConfig(configs, "flat/jsdoc", "oxlint-jsdoc");
|
|
2230
2338
|
return configs;
|
|
2231
2339
|
}
|
|
2232
2340
|
|
|
@@ -2281,65 +2389,24 @@ function standardComplexity() {
|
|
|
2281
2389
|
];
|
|
2282
2390
|
}
|
|
2283
2391
|
|
|
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
2392
|
// src/build/compose.ts
|
|
2330
|
-
function
|
|
2331
|
-
const
|
|
2332
|
-
config.push(
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2393
|
+
function addCoreConfigs(config, opts) {
|
|
2394
|
+
const ai = opts.ai;
|
|
2395
|
+
config.push(
|
|
2396
|
+
...baseConfig({ ai }),
|
|
2397
|
+
...typescriptConfig({ ai, react: opts.react }),
|
|
2398
|
+
...importsConfig(),
|
|
2399
|
+
...perfectionistConfig(),
|
|
2400
|
+
...unicornConfig({ ai }),
|
|
2401
|
+
...regexpConfig({ ai }),
|
|
2402
|
+
...jsdocConfig({ ai }),
|
|
2403
|
+
...cspellConfig(),
|
|
2404
|
+
...sonarjsConfig({ ai }),
|
|
2405
|
+
...securityConfig(),
|
|
2406
|
+
...deMorganConfig()
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
function addConditionalConfigs(config, opts) {
|
|
2343
2410
|
if (!opts.node) {
|
|
2344
2411
|
config.push(...compatConfig());
|
|
2345
2412
|
}
|
|
@@ -2347,30 +2414,40 @@ function composeConfig(opts) {
|
|
|
2347
2414
|
config.push(...standardComplexity());
|
|
2348
2415
|
}
|
|
2349
2416
|
if (opts.node) {
|
|
2350
|
-
config.push(...nodeConfig());
|
|
2417
|
+
config.push(...nodeConfig({ ai: opts.ai }));
|
|
2351
2418
|
}
|
|
2352
2419
|
if (opts.react) {
|
|
2353
|
-
config.push(...reactConfig());
|
|
2420
|
+
config.push(...reactConfig({ ai: opts.ai }));
|
|
2354
2421
|
config.push(...reactEffectConfig());
|
|
2355
2422
|
}
|
|
2356
2423
|
if (opts.ai) {
|
|
2357
|
-
config.push(...aiConfig(
|
|
2424
|
+
config.push(...aiConfig());
|
|
2358
2425
|
config.push(...perfectionistAiConfig());
|
|
2359
2426
|
config.push(...packageJsonAiConfig());
|
|
2360
2427
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
config.push(
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2428
|
+
}
|
|
2429
|
+
function addFileTypeOverrides(config) {
|
|
2430
|
+
config.push(
|
|
2431
|
+
...testsOverride(),
|
|
2432
|
+
...e2eOverride(),
|
|
2433
|
+
...storiesOverride(),
|
|
2434
|
+
...configFilesOverride(),
|
|
2435
|
+
...declarationsOverride(),
|
|
2436
|
+
...scriptsOverride()
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
function addFinalConfigs(config, opts) {
|
|
2440
|
+
config.push(...jsonConfig(), ...packageJsonConfig(), ...markdownConfig(), ...prettierCompatConfig());
|
|
2371
2441
|
if (opts.oxlint) {
|
|
2372
2442
|
config.push(...oxlintIntegration(opts));
|
|
2373
2443
|
}
|
|
2444
|
+
}
|
|
2445
|
+
function composeConfig(opts) {
|
|
2446
|
+
const config = [];
|
|
2447
|
+
addCoreConfigs(config, opts);
|
|
2448
|
+
addConditionalConfigs(config, opts);
|
|
2449
|
+
addFileTypeOverrides(config);
|
|
2450
|
+
addFinalConfigs(config, opts);
|
|
2374
2451
|
return config;
|
|
2375
2452
|
}
|
|
2376
2453
|
export {
|