eslint-config-setup 0.5.0 → 0.5.2
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 +1 -1
- package/dist/configs/0b23ff88.js +0 -1
- package/dist/configs/196d687e.js +0 -1
- package/dist/configs/4eb62e57.js +0 -1
- package/dist/configs/52762a42.js +0 -1
- package/dist/configs/6bc0d588.js +0 -1
- package/dist/configs/c2fecd3d.js +0 -1
- package/dist/configs/d537b683.js +0 -1
- package/dist/configs/db69ebb6.js +0 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +156 -130
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +67 -39
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +1757 -2358
- package/dist/modules.js.map +1 -1
- package/dist/types-DElYtiZE.d.ts +32 -0
- package/dist/types-DElYtiZE.d.ts.map +1 -0
- package/package.json +3 -3
- package/dist/types-CAO1PbsB.d.ts +0 -34
package/dist/modules.js
CHANGED
|
@@ -1,2485 +1,1884 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import compatPlugin from "eslint-plugin-compat";
|
|
3
|
+
import cspellPlugin from "@cspell/eslint-plugin";
|
|
4
|
+
import deMorganPlugin from "eslint-plugin-de-morgan";
|
|
5
|
+
import importXPlugin from "eslint-plugin-import-x";
|
|
6
|
+
import unusedImportsPlugin from "eslint-plugin-unused-imports";
|
|
7
|
+
import jsdocPlugin from "eslint-plugin-jsdoc";
|
|
8
|
+
import jsonPlugin from "@eslint/json";
|
|
9
|
+
import * as mdxPlugin from "eslint-plugin-mdx";
|
|
10
|
+
import nodePlugin from "eslint-plugin-n";
|
|
11
|
+
import globals from "globals";
|
|
12
|
+
import { configs } from "eslint-plugin-package-json";
|
|
13
|
+
import perfectionistPlugin from "eslint-plugin-perfectionist";
|
|
14
|
+
import prettierConfig from "eslint-config-prettier";
|
|
15
|
+
import eslintReactPlugin from "@eslint-react/eslint-plugin";
|
|
16
|
+
import stylisticPlugin from "@stylistic/eslint-plugin";
|
|
17
|
+
import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
|
|
18
|
+
import reactHooksPlugin from "eslint-plugin-react-hooks";
|
|
19
|
+
import reactPerfPlugin from "eslint-plugin-react-perf";
|
|
20
|
+
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
|
21
|
+
import reactEffectPlugin from "eslint-plugin-react-you-might-not-need-an-effect";
|
|
22
|
+
import { configs as configs$1 } from "eslint-plugin-regexp";
|
|
23
|
+
import securityPlugin from "eslint-plugin-security";
|
|
24
|
+
import sonarjsPlugin from "eslint-plugin-sonarjs";
|
|
25
|
+
import tseslint from "typescript-eslint";
|
|
26
|
+
import unicornPlugin from "eslint-plugin-unicorn";
|
|
27
|
+
import playwrightPlugin from "eslint-plugin-playwright";
|
|
28
|
+
import storybookPlugin from "eslint-plugin-storybook";
|
|
29
|
+
import vitestPlugin from "@vitest/eslint-plugin";
|
|
30
|
+
import testingLibraryPlugin from "eslint-plugin-testing-library";
|
|
31
|
+
import oxlintPlugin from "eslint-plugin-oxlint";
|
|
32
|
+
//#region src/configs/ai.ts
|
|
33
|
+
/**
|
|
34
|
+
* AI mode — cross-cutting concerns that span multiple domains.
|
|
35
|
+
*
|
|
36
|
+
* Domain-specific AI rules live in their respective configs (base.ts,
|
|
37
|
+
* typescript.ts, react.ts, unicorn.ts, sonarjs.ts, regexp.ts, jsdoc.ts,
|
|
38
|
+
* node.ts) behind an `ai` flag. This file only contains:
|
|
39
|
+
*
|
|
40
|
+
* 1. Test framework rules (vitest) — no dedicated domain config
|
|
41
|
+
* 2. File-scoped relaxations — cross-domain, must come last to override
|
|
42
|
+
*
|
|
43
|
+
* @see ADR-0006: docs/adr/0006-ai-mode-as-dedicated-flag.md
|
|
44
|
+
*/
|
|
45
|
+
const TEST_FILES$1 = ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"];
|
|
46
|
+
const E2E_FILES = ["**/*.spec.ts"];
|
|
47
|
+
const CONFIG_FILES = [
|
|
48
|
+
"**/*.config.{ts,mts,cts,js,mjs,cjs}",
|
|
49
|
+
"**/vite.config.*",
|
|
50
|
+
"**/vitest.config.*",
|
|
51
|
+
"**/next.config.*"
|
|
9
52
|
];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"vitest/prefer-hooks-on-top": "error"
|
|
18
|
-
}
|
|
53
|
+
const AI_TESTS_STRICT_CONFIG = {
|
|
54
|
+
name: "eslint-config-setup/ai-tests-strict",
|
|
55
|
+
files: TEST_FILES$1,
|
|
56
|
+
rules: {
|
|
57
|
+
"vitest/require-top-level-describe": "error",
|
|
58
|
+
"vitest/prefer-hooks-on-top": "error"
|
|
59
|
+
}
|
|
19
60
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
const AI_TESTS_RELAXED_CONFIG = {
|
|
62
|
+
name: "eslint-config-setup/ai-tests-relaxed",
|
|
63
|
+
files: TEST_FILES$1,
|
|
64
|
+
rules: {
|
|
65
|
+
"max-lines": "off",
|
|
66
|
+
"max-lines-per-function": "off",
|
|
67
|
+
"max-statements": "off",
|
|
68
|
+
"max-nested-callbacks": "off",
|
|
69
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
70
|
+
"sonarjs/no-duplicate-string": "off",
|
|
71
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
72
|
+
"@typescript-eslint/naming-convention": "off",
|
|
73
|
+
"unicorn/prevent-abbreviations": "off"
|
|
74
|
+
}
|
|
34
75
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
76
|
+
const AI_E2E_RELAXED_CONFIG = {
|
|
77
|
+
name: "eslint-config-setup/ai-e2e-relaxed",
|
|
78
|
+
files: E2E_FILES,
|
|
79
|
+
rules: {
|
|
80
|
+
"max-lines": "off",
|
|
81
|
+
"max-lines-per-function": "off",
|
|
82
|
+
"max-statements": "off",
|
|
83
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
84
|
+
"@typescript-eslint/explicit-function-return-type": "off"
|
|
85
|
+
}
|
|
45
86
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
const AI_CONFIG_RELAXED_CONFIG = {
|
|
88
|
+
name: "eslint-config-setup/ai-config-relaxed",
|
|
89
|
+
files: CONFIG_FILES,
|
|
90
|
+
rules: {
|
|
91
|
+
complexity: "off",
|
|
92
|
+
"max-lines": "off",
|
|
93
|
+
"max-lines-per-function": "off",
|
|
94
|
+
"max-statements": "off",
|
|
95
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
96
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
97
|
+
"@typescript-eslint/naming-convention": "off"
|
|
98
|
+
}
|
|
58
99
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
const AI_DECLARATIONS_RELAXED_CONFIG = {
|
|
101
|
+
name: "eslint-config-setup/ai-declarations-relaxed",
|
|
102
|
+
files: ["**/*.d.ts"],
|
|
103
|
+
rules: {
|
|
104
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
105
|
+
"@typescript-eslint/naming-convention": "off",
|
|
106
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
107
|
+
"@typescript-eslint/no-magic-numbers": "off",
|
|
108
|
+
"unicorn/prevent-abbreviations": "off",
|
|
109
|
+
"unicorn/filename-case": "off"
|
|
110
|
+
}
|
|
70
111
|
};
|
|
71
112
|
function aiConfig() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
return [
|
|
114
|
+
AI_TESTS_STRICT_CONFIG,
|
|
115
|
+
AI_TESTS_RELAXED_CONFIG,
|
|
116
|
+
AI_E2E_RELAXED_CONFIG,
|
|
117
|
+
AI_CONFIG_RELAXED_CONFIG,
|
|
118
|
+
AI_DECLARATIONS_RELAXED_CONFIG
|
|
119
|
+
];
|
|
79
120
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
import eslint from "@eslint/js";
|
|
83
|
-
|
|
84
|
-
// src/build/config-builder.ts
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/build/config-builder.ts
|
|
85
123
|
function createConfig(options) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
fileOverrides.push({ name, files, rules });
|
|
177
|
-
return builder;
|
|
178
|
-
},
|
|
179
|
-
build() {
|
|
180
|
-
const rules = {};
|
|
181
|
-
for (const [name, value] of presetRules2) {
|
|
182
|
-
if (!removed.has(name)) {
|
|
183
|
-
rules[name] = value;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
for (const [name, value] of overrides) {
|
|
187
|
-
if (!removed.has(name)) {
|
|
188
|
-
rules[name] = value;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
for (const [name, value] of additions) {
|
|
192
|
-
if (!removed.has(name)) {
|
|
193
|
-
rules[name] = value;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
for (const name of disabled) {
|
|
197
|
-
if (!removed.has(name)) {
|
|
198
|
-
rules[name] = "off";
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const mainBlock = {
|
|
202
|
-
name: options.name,
|
|
203
|
-
rules
|
|
204
|
-
};
|
|
205
|
-
const mergedPlugins = { ...presetPlugins, ...options.plugins };
|
|
206
|
-
if (Object.keys(mergedPlugins).length > 0) {
|
|
207
|
-
mainBlock.plugins = mergedPlugins;
|
|
208
|
-
}
|
|
209
|
-
if (options.languageOptions) {
|
|
210
|
-
mainBlock.languageOptions = options.languageOptions;
|
|
211
|
-
}
|
|
212
|
-
if (options.settings) {
|
|
213
|
-
mainBlock.settings = options.settings;
|
|
214
|
-
}
|
|
215
|
-
if (options.files) {
|
|
216
|
-
mainBlock.files = options.files;
|
|
217
|
-
}
|
|
218
|
-
if (options.ignores) {
|
|
219
|
-
mainBlock.ignores = options.ignores;
|
|
220
|
-
}
|
|
221
|
-
const result = [];
|
|
222
|
-
if (options.passthrough) {
|
|
223
|
-
result.push(...options.passthrough);
|
|
224
|
-
}
|
|
225
|
-
result.push(mainBlock);
|
|
226
|
-
for (const fo of fileOverrides) {
|
|
227
|
-
result.push({
|
|
228
|
-
name: fo.name,
|
|
229
|
-
files: fo.files,
|
|
230
|
-
rules: fo.rules
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
return result;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
return builder;
|
|
124
|
+
const presetRules = /* @__PURE__ */ new Map();
|
|
125
|
+
const presetPlugins = {};
|
|
126
|
+
if (options.presets) for (const preset of options.presets) {
|
|
127
|
+
if (preset.plugins) Object.assign(presetPlugins, preset.plugins);
|
|
128
|
+
if (preset.rules) {
|
|
129
|
+
for (const [name, value] of Object.entries(preset.rules)) if (value !== void 0) presetRules.set(name, value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
133
|
+
const additions = /* @__PURE__ */ new Map();
|
|
134
|
+
const disabled = /* @__PURE__ */ new Set();
|
|
135
|
+
const removed = /* @__PURE__ */ new Set();
|
|
136
|
+
const fileOverrides = [];
|
|
137
|
+
const builder = {
|
|
138
|
+
overrideRule(name, value) {
|
|
139
|
+
if (!presetRules.has(name)) throw new Error(`overrideRule("${name}"): rule not found in preset. Cannot override a rule that doesn't exist in the preset.`);
|
|
140
|
+
overrides.set(name, value);
|
|
141
|
+
return builder;
|
|
142
|
+
},
|
|
143
|
+
overrideSeverity(name, severity) {
|
|
144
|
+
if (!presetRules.has(name)) throw new Error(`overrideSeverity("${name}"): rule not found in preset. Cannot override severity of a rule that doesn't exist in the preset.`);
|
|
145
|
+
const existing = presetRules.get(name);
|
|
146
|
+
if (Array.isArray(existing)) {
|
|
147
|
+
const [, ...restOptions] = existing;
|
|
148
|
+
overrides.set(name, [severity, ...restOptions]);
|
|
149
|
+
} else overrides.set(name, severity);
|
|
150
|
+
return builder;
|
|
151
|
+
},
|
|
152
|
+
overrideOptions(name, ...ruleOptions) {
|
|
153
|
+
if (!presetRules.has(name)) throw new Error(`overrideOptions("${name}"): rule not found in preset. Cannot override options of a rule that doesn't exist in the preset.`);
|
|
154
|
+
const existing = presetRules.get(name);
|
|
155
|
+
const severity = Array.isArray(existing) ? existing[0] : existing;
|
|
156
|
+
overrides.set(name, [severity, ...ruleOptions]);
|
|
157
|
+
return builder;
|
|
158
|
+
},
|
|
159
|
+
addRule(name, value) {
|
|
160
|
+
if (presetRules.has(name)) throw new Error(`addRule("${name}"): rule already exists in preset. Use overrideRule() to change its value, or removeRule() to drop it.`);
|
|
161
|
+
if (additions.has(name)) throw new Error(`addRule("${name}"): rule was already added. Each rule can only be added once.`);
|
|
162
|
+
additions.set(name, value);
|
|
163
|
+
return builder;
|
|
164
|
+
},
|
|
165
|
+
disableRule(name) {
|
|
166
|
+
if (!presetRules.has(name) && !additions.has(name)) throw new Error(`disableRule("${name}"): rule not found in preset or additions. Cannot disable a rule that doesn't exist.`);
|
|
167
|
+
disabled.add(name);
|
|
168
|
+
return builder;
|
|
169
|
+
},
|
|
170
|
+
removeRule(name) {
|
|
171
|
+
if (!presetRules.has(name) && !additions.has(name)) throw new Error(`removeRule("${name}"): rule not found in preset or additions. Cannot remove a rule that doesn't exist.`);
|
|
172
|
+
removed.add(name);
|
|
173
|
+
return builder;
|
|
174
|
+
},
|
|
175
|
+
addFileOverride(name, files, rules) {
|
|
176
|
+
fileOverrides.push({
|
|
177
|
+
name,
|
|
178
|
+
files,
|
|
179
|
+
rules
|
|
180
|
+
});
|
|
181
|
+
return builder;
|
|
182
|
+
},
|
|
183
|
+
build() {
|
|
184
|
+
const rules = {};
|
|
185
|
+
for (const [name, value] of presetRules) if (!removed.has(name)) rules[name] = value;
|
|
186
|
+
for (const [name, value] of overrides) if (!removed.has(name)) rules[name] = value;
|
|
187
|
+
for (const [name, value] of additions) if (!removed.has(name)) rules[name] = value;
|
|
188
|
+
for (const name of disabled) if (!removed.has(name)) rules[name] = "off";
|
|
189
|
+
const mainBlock = {
|
|
190
|
+
name: options.name,
|
|
191
|
+
rules
|
|
192
|
+
};
|
|
193
|
+
const mergedPlugins = {
|
|
194
|
+
...presetPlugins,
|
|
195
|
+
...options.plugins
|
|
196
|
+
};
|
|
197
|
+
if (Object.keys(mergedPlugins).length > 0) mainBlock.plugins = mergedPlugins;
|
|
198
|
+
if (options.languageOptions) mainBlock.languageOptions = options.languageOptions;
|
|
199
|
+
if (options.settings) mainBlock.settings = options.settings;
|
|
200
|
+
if (options.files) mainBlock.files = options.files;
|
|
201
|
+
if (options.ignores) mainBlock.ignores = options.ignores;
|
|
202
|
+
const result = [];
|
|
203
|
+
if (options.passthrough) result.push(...options.passthrough);
|
|
204
|
+
result.push(mainBlock);
|
|
205
|
+
for (const fo of fileOverrides) result.push({
|
|
206
|
+
name: fo.name,
|
|
207
|
+
files: fo.files,
|
|
208
|
+
rules: fo.rules
|
|
209
|
+
});
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return builder;
|
|
237
214
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/configs/base.ts
|
|
217
|
+
const ERROR_PREVENTION_RULES = {
|
|
218
|
+
"accessor-pairs": ["error", { enforceForClassMembers: true }],
|
|
219
|
+
"array-callback-return": ["error", { allowImplicit: true }],
|
|
220
|
+
"no-constructor-return": "error",
|
|
221
|
+
"no-promise-executor-return": "error",
|
|
222
|
+
"no-self-compare": "error",
|
|
223
|
+
"no-template-curly-in-string": "error",
|
|
224
|
+
"no-unreachable-loop": "error",
|
|
225
|
+
"require-atomic-updates": "error",
|
|
226
|
+
"no-unmodified-loop-condition": "error",
|
|
227
|
+
"grouped-accessor-pairs": ["error", "getBeforeSet"],
|
|
228
|
+
"no-useless-rename": "error",
|
|
229
|
+
"no-useless-computed-key": ["error", { enforceForClassMembers: true }]
|
|
253
230
|
};
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
231
|
+
const DANGEROUS_PATTERN_RULES = {
|
|
232
|
+
"no-eval": "error",
|
|
233
|
+
"no-alert": "error",
|
|
234
|
+
"no-caller": "error",
|
|
235
|
+
"no-extend-native": "error",
|
|
236
|
+
"no-new-func": "error",
|
|
237
|
+
"no-new-wrappers": "error",
|
|
238
|
+
"no-object-constructor": "error",
|
|
239
|
+
"no-proto": "error",
|
|
240
|
+
"no-iterator": "error",
|
|
241
|
+
"no-script-url": "error",
|
|
242
|
+
"no-octal-escape": "error",
|
|
243
|
+
"no-implicit-globals": "error"
|
|
267
244
|
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
245
|
+
const CODE_QUALITY_RULES = {
|
|
246
|
+
eqeqeq: ["error", "smart"],
|
|
247
|
+
"guard-for-in": "error",
|
|
248
|
+
"default-case-last": "error",
|
|
249
|
+
radix: "error",
|
|
250
|
+
yoda: "error",
|
|
251
|
+
"no-sequences": ["error", { allowInParentheses: false }],
|
|
252
|
+
"no-new": "error",
|
|
253
|
+
"no-labels": "error",
|
|
254
|
+
"no-extra-bind": "error",
|
|
255
|
+
"no-lone-blocks": "error",
|
|
256
|
+
"no-useless-call": "error",
|
|
257
|
+
"no-useless-concat": "error",
|
|
258
|
+
"no-useless-return": "error",
|
|
259
|
+
"no-return-assign": ["error", "always"],
|
|
260
|
+
"no-multi-str": "error",
|
|
261
|
+
"prefer-regex-literals": ["error", { disallowRedundantWrapping: true }]
|
|
285
262
|
};
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
263
|
+
const MODERN_STYLE_RULES = {
|
|
264
|
+
"no-var": "error",
|
|
265
|
+
"prefer-const": ["error", { destructuring: "all" }],
|
|
266
|
+
"prefer-object-has-own": "error",
|
|
267
|
+
"prefer-object-spread": "error",
|
|
268
|
+
"prefer-rest-params": "error",
|
|
269
|
+
"prefer-spread": "error",
|
|
270
|
+
"symbol-description": "error",
|
|
271
|
+
"prefer-numeric-literals": "error",
|
|
272
|
+
"object-shorthand": [
|
|
273
|
+
"error",
|
|
274
|
+
"always",
|
|
275
|
+
{
|
|
276
|
+
avoidExplicitReturnArrows: true,
|
|
277
|
+
avoidQuotes: true
|
|
278
|
+
}
|
|
279
|
+
]
|
|
296
280
|
};
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
281
|
+
const AI_STRUCTURAL_RULES = {
|
|
282
|
+
curly: ["error", "all"],
|
|
283
|
+
"no-else-return": ["error", { allowElseIf: false }],
|
|
284
|
+
"no-nested-ternary": "error",
|
|
285
|
+
"no-unneeded-ternary": "error",
|
|
286
|
+
"no-negated-condition": "error",
|
|
287
|
+
"no-lonely-if": "error",
|
|
288
|
+
"no-param-reassign": ["error", { props: true }],
|
|
289
|
+
"no-multi-assign": "error",
|
|
290
|
+
"one-var": ["error", "never"],
|
|
291
|
+
"no-implicit-coercion": "error",
|
|
292
|
+
"arrow-body-style": "error",
|
|
293
|
+
"prefer-arrow-callback": ["error", { allowNamedFunctions: true }],
|
|
294
|
+
"logical-assignment-operators": [
|
|
295
|
+
"error",
|
|
296
|
+
"always",
|
|
297
|
+
{ enforceForIfStatements: true }
|
|
298
|
+
],
|
|
299
|
+
"max-statements-per-line": ["error", { max: 1 }],
|
|
300
|
+
"prefer-exponentiation-operator": "error",
|
|
301
|
+
"prefer-named-capture-group": "error",
|
|
302
|
+
"require-unicode-regexp": "error",
|
|
303
|
+
"no-warning-comments": "warn",
|
|
304
|
+
"no-await-in-loop": "error"
|
|
317
305
|
};
|
|
318
|
-
function addRules(builder, rules) {
|
|
319
|
-
|
|
320
|
-
builder.addRule(ruleName, value);
|
|
321
|
-
}
|
|
306
|
+
function addRules$1(builder, rules) {
|
|
307
|
+
for (const [ruleName, value] of Object.entries(rules)) builder.addRule(ruleName, value);
|
|
322
308
|
}
|
|
323
309
|
function addBaseOverrides(builder) {
|
|
324
|
-
|
|
325
|
-
|
|
310
|
+
builder.overrideRule("use-isnan", ["error", {
|
|
311
|
+
enforceForIndexOf: true,
|
|
312
|
+
enforceForSwitchCase: true
|
|
313
|
+
}]);
|
|
314
|
+
builder.overrideRule("valid-typeof", ["error", { requireStringLiterals: true }]);
|
|
326
315
|
}
|
|
327
316
|
function addComplexityRules(builder, isAi) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
317
|
+
builder.addRule("complexity", ["error", isAi ? 10 : 20]);
|
|
318
|
+
builder.addRule("max-depth", ["error", isAi ? 3 : 5]);
|
|
319
|
+
builder.addRule("max-nested-callbacks", ["error", isAi ? 2 : 4]);
|
|
320
|
+
builder.addRule("max-params", ["error", isAi ? 3 : 5]);
|
|
321
|
+
builder.addRule("max-statements", ["error", isAi ? 15 : 25]);
|
|
322
|
+
builder.addRule("max-lines-per-function", ["error", {
|
|
323
|
+
max: isAi ? 100 : 200,
|
|
324
|
+
skipBlankLines: true,
|
|
325
|
+
skipComments: true
|
|
326
|
+
}]);
|
|
327
|
+
builder.addRule("max-lines", ["error", {
|
|
328
|
+
max: isAi ? 300 : 500,
|
|
329
|
+
skipBlankLines: true,
|
|
330
|
+
skipComments: true
|
|
331
|
+
}]);
|
|
332
|
+
builder.addRule("sonarjs/cognitive-complexity", ["error", isAi ? 10 : 20]);
|
|
342
333
|
}
|
|
343
334
|
function addModernStyleRules(builder, isAi) {
|
|
344
|
-
|
|
345
|
-
|
|
335
|
+
addRules$1(builder, MODERN_STYLE_RULES);
|
|
336
|
+
builder.addRule("prefer-template", isAi ? "error" : "warn");
|
|
346
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Base ESLint config — extends `eslint.configs.recommended` with additional
|
|
340
|
+
* best-practice rules for error prevention and modern JS style.
|
|
341
|
+
*
|
|
342
|
+
* Rules with TypeScript equivalents (no-implied-eval, dot-notation, etc.)
|
|
343
|
+
* are NOT included here — they are handled by the typescript-eslint presets.
|
|
344
|
+
*
|
|
345
|
+
* Preset: `@eslint/js` recommended
|
|
346
|
+
* @see https://eslint.org/docs/latest/rules/
|
|
347
|
+
*/
|
|
347
348
|
function baseConfig(opts) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
return builder.build();
|
|
349
|
+
const isAi = opts?.ai ?? false;
|
|
350
|
+
const builder = createConfig({
|
|
351
|
+
name: "eslint-config-setup/base",
|
|
352
|
+
presets: [eslint.configs.recommended]
|
|
353
|
+
});
|
|
354
|
+
addRules$1(builder, ERROR_PREVENTION_RULES);
|
|
355
|
+
addBaseOverrides(builder);
|
|
356
|
+
addRules$1(builder, DANGEROUS_PATTERN_RULES);
|
|
357
|
+
addRules$1(builder, CODE_QUALITY_RULES);
|
|
358
|
+
addComplexityRules(builder, isAi);
|
|
359
|
+
addModernStyleRules(builder, isAi);
|
|
360
|
+
if (isAi) addRules$1(builder, AI_STRUCTURAL_RULES);
|
|
361
|
+
return builder.build();
|
|
363
362
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/configs/compat.ts
|
|
365
|
+
/**
|
|
366
|
+
* Browser compatibility config — checks that browser APIs are available
|
|
367
|
+
* in the project's browserslist targets. Uses MDN compatibility data.
|
|
368
|
+
*
|
|
369
|
+
* Activated automatically for non-Node projects (when `node: false`).
|
|
370
|
+
* If no browserslist config exists, the default applies:
|
|
371
|
+
* `> 0.5%, last 2 versions, Firefox ESR, not dead`
|
|
372
|
+
*
|
|
373
|
+
* @see https://github.com/amilajack/eslint-plugin-compat
|
|
374
|
+
* @see https://browsersl.ist/
|
|
375
|
+
*/
|
|
367
376
|
function compatConfig() {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
];
|
|
377
|
+
return [{
|
|
378
|
+
name: "eslint-config-setup/compat",
|
|
379
|
+
plugins: { compat: compatPlugin },
|
|
380
|
+
rules: { "compat/compat": "warn" }
|
|
381
|
+
}];
|
|
381
382
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/configs/cspell.ts
|
|
385
|
+
/**
|
|
386
|
+
* CSpell config — spell checking for identifiers and comments.
|
|
387
|
+
* Catches typos in variable names, function names, and documentation.
|
|
388
|
+
* Severity is "warn" because dictionary misses are common for domain terms.
|
|
389
|
+
*
|
|
390
|
+
* @see https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-eslint-plugin#readme
|
|
391
|
+
*/
|
|
385
392
|
function cspellConfig() {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// https://github.com/streetsidesoftware/cspell/tree/main/packages/cspell-eslint-plugin#rules
|
|
397
|
-
"@cspell/spellchecker": [
|
|
398
|
-
"warn",
|
|
399
|
-
{
|
|
400
|
-
checkComments: true,
|
|
401
|
-
checkIdentifiers: true,
|
|
402
|
-
checkStrings: false,
|
|
403
|
-
autoFix: false
|
|
404
|
-
}
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
];
|
|
393
|
+
return [{
|
|
394
|
+
name: "eslint-config-setup/cspell",
|
|
395
|
+
plugins: { "@cspell": cspellPlugin },
|
|
396
|
+
rules: { "@cspell/spellchecker": ["warn", {
|
|
397
|
+
checkComments: true,
|
|
398
|
+
checkIdentifiers: true,
|
|
399
|
+
checkStrings: false,
|
|
400
|
+
autoFix: false
|
|
401
|
+
}] }
|
|
402
|
+
}];
|
|
409
403
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/configs/de-morgan.ts
|
|
406
|
+
/**
|
|
407
|
+
* De Morgan config — enforces De Morgan's laws on negated boolean expressions.
|
|
408
|
+
* Both rules are auto-fixable: !(A && B) → !A || !B and !(A || B) → !A && !B.
|
|
409
|
+
*
|
|
410
|
+
* Complements sonarjs/no-inverted-boolean-check (which handles simple !(a === b) → a !== b)
|
|
411
|
+
* by targeting compound expressions with && and ||.
|
|
412
|
+
*
|
|
413
|
+
* @see https://github.com/azat-io/eslint-plugin-de-morgan
|
|
414
|
+
*/
|
|
413
415
|
function deMorganConfig() {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
// https://github.com/azat-io/eslint-plugin-de-morgan/blob/main/docs/no-negated-conjunction.md
|
|
423
|
-
"de-morgan/no-negated-conjunction": "error",
|
|
424
|
-
// Transform !(A || B) → !A && !B — more readable negated disjunction
|
|
425
|
-
// https://github.com/azat-io/eslint-plugin-de-morgan/blob/main/docs/no-negated-disjunction.md
|
|
426
|
-
"de-morgan/no-negated-disjunction": "error"
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
];
|
|
416
|
+
return [{
|
|
417
|
+
name: "eslint-config-setup/de-morgan",
|
|
418
|
+
plugins: { "de-morgan": deMorganPlugin },
|
|
419
|
+
rules: {
|
|
420
|
+
"de-morgan/no-negated-conjunction": "error",
|
|
421
|
+
"de-morgan/no-negated-disjunction": "error"
|
|
422
|
+
}
|
|
423
|
+
}];
|
|
430
424
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/configs/imports.ts
|
|
427
|
+
/**
|
|
428
|
+
* Import/export config — two plugins with clear separation of concerns:
|
|
429
|
+
* - `import-x` handles import **validation** (cycles, duplicates, etc.)
|
|
430
|
+
* - `unused-imports` handles **removal** of unused imports (auto-fixable)
|
|
431
|
+
*
|
|
432
|
+
* Import/export **ordering** is handled by perfectionist (see perfectionist.ts).
|
|
433
|
+
*
|
|
434
|
+
* @see https://github.com/un-ts/eslint-plugin-import-x
|
|
435
|
+
* @see https://github.com/sweepline/eslint-plugin-unused-imports
|
|
436
|
+
*/
|
|
435
437
|
function importsConfig() {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"import/no-named-as-default-member": "error",
|
|
473
|
-
// Detect empty `import {} from 'foo'` — leftover after refactoring
|
|
474
|
-
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-empty-named-blocks.md
|
|
475
|
-
"import/no-empty-named-blocks": "error",
|
|
476
|
-
// Forbid absolute file paths in imports — not portable across machines
|
|
477
|
-
// https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-absolute-path.md
|
|
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",
|
|
503
|
-
// ── Disabled: ordering handled by perfectionist ────────────────
|
|
504
|
-
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/order.md
|
|
505
|
-
"import/order": "off",
|
|
506
|
-
// https://github.com/un-ts/eslint-plugin-import/blob/master/docs/rules/sort-imports.md
|
|
507
|
-
"import/sort-imports": "off",
|
|
508
|
-
// ── Unused import removal ─────────────────────────────────────
|
|
509
|
-
// Auto-remove unused imports — keeps imports clean (auto-fixable)
|
|
510
|
-
// https://github.com/sweepline/eslint-plugin-unused-imports#usage
|
|
511
|
-
"unused-imports/no-unused-imports": "error"
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
];
|
|
438
|
+
return [{
|
|
439
|
+
name: "eslint-config-setup/imports",
|
|
440
|
+
plugins: {
|
|
441
|
+
"import": importXPlugin,
|
|
442
|
+
"unused-imports": unusedImportsPlugin
|
|
443
|
+
},
|
|
444
|
+
rules: {
|
|
445
|
+
"import/no-duplicates": "error",
|
|
446
|
+
"import/no-self-import": "error",
|
|
447
|
+
"import/no-cycle": ["error", { maxDepth: 3 }],
|
|
448
|
+
"import/no-useless-path-segments": "error",
|
|
449
|
+
"import/no-mutable-exports": "error",
|
|
450
|
+
"import/first": "error",
|
|
451
|
+
"import/newline-after-import": "error",
|
|
452
|
+
"import/no-named-as-default": "error",
|
|
453
|
+
"import/no-named-as-default-member": "error",
|
|
454
|
+
"import/no-empty-named-blocks": "error",
|
|
455
|
+
"import/no-absolute-path": "error",
|
|
456
|
+
"import/no-extraneous-dependencies": ["error", { includeTypes: true }],
|
|
457
|
+
"import/no-unassigned-import": ["error", { allow: [
|
|
458
|
+
"@babel/polyfill",
|
|
459
|
+
"**/register",
|
|
460
|
+
"**/register.*",
|
|
461
|
+
"**/register/**",
|
|
462
|
+
"**/register/**.*",
|
|
463
|
+
"**/*.css",
|
|
464
|
+
"**/*.scss",
|
|
465
|
+
"**/*.sass",
|
|
466
|
+
"**/*.less"
|
|
467
|
+
] }],
|
|
468
|
+
"import/no-named-default": "error",
|
|
469
|
+
"import/order": "off",
|
|
470
|
+
"import/sort-imports": "off",
|
|
471
|
+
"unused-imports/no-unused-imports": "error"
|
|
472
|
+
}
|
|
473
|
+
}];
|
|
515
474
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/configs/jsdoc.ts
|
|
477
|
+
/**
|
|
478
|
+
* JSDoc config — validates existing JSDoc annotations without requiring them.
|
|
479
|
+
*
|
|
480
|
+
* Preset: `flat/recommended-typescript-error` — all recommended JSDoc rules
|
|
481
|
+
* adapted for TypeScript (types are in TS, not JSDoc).
|
|
482
|
+
*
|
|
483
|
+
* Overrides:
|
|
484
|
+
* - `require-jsdoc` OFF — we validate existing JSDoc, we don't mandate it
|
|
485
|
+
* - param/return descriptions downgraded to warn — helpful but not blocking
|
|
486
|
+
* @see https://github.com/gajus/eslint-plugin-jsdoc#rules
|
|
487
|
+
*/
|
|
519
488
|
function jsdocConfig(opts) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
489
|
+
const isAi = opts?.ai ?? false;
|
|
490
|
+
const builder = createConfig({
|
|
491
|
+
name: "eslint-config-setup/jsdoc",
|
|
492
|
+
presets: [jsdocPlugin.configs["flat/recommended-typescript-error"]]
|
|
493
|
+
}).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");
|
|
494
|
+
if (isAi) {
|
|
495
|
+
builder.overrideRule("jsdoc/require-param", "error");
|
|
496
|
+
builder.overrideRule("jsdoc/require-returns", "error");
|
|
497
|
+
builder.overrideRule("jsdoc/informative-docs", "error");
|
|
498
|
+
}
|
|
499
|
+
return builder.build();
|
|
531
500
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/configs/json.ts
|
|
503
|
+
const plugin$1 = jsonPlugin;
|
|
504
|
+
/**
|
|
505
|
+
* JSON/JSONC config — native JSON linting using the official `@eslint/json` plugin.
|
|
506
|
+
* Two blocks: strict JSON for most files, JSONC (with comments) for tsconfig etc.
|
|
507
|
+
*
|
|
508
|
+
* @see https://github.com/eslint/json#rules
|
|
509
|
+
*/
|
|
536
510
|
function jsonConfig() {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
"**/.vscode/*.json",
|
|
567
|
-
"**/turbo.json"
|
|
568
|
-
],
|
|
569
|
-
language: "json/jsonc",
|
|
570
|
-
plugins: {
|
|
571
|
-
json: plugin
|
|
572
|
-
},
|
|
573
|
-
rules: {
|
|
574
|
-
// Detect duplicate keys in JSONC — same rationale as JSON
|
|
575
|
-
// https://github.com/eslint/json#rules
|
|
576
|
-
"json/no-duplicate-keys": "error",
|
|
577
|
-
// Detect empty string keys — likely a mistake
|
|
578
|
-
// https://github.com/eslint/json#rules
|
|
579
|
-
"json/no-empty-keys": "error",
|
|
580
|
-
// Detect unsafe values (NaN, Infinity, lone surrogates) — invalid JSON
|
|
581
|
-
// https://github.com/eslint/json#rules
|
|
582
|
-
"json/no-unsafe-values": "error",
|
|
583
|
-
// Detect unnormalized Unicode keys — prevents invisible key mismatches
|
|
584
|
-
// https://github.com/eslint/json#rules
|
|
585
|
-
"json/no-unnormalized-keys": "error"
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
];
|
|
511
|
+
return [{
|
|
512
|
+
name: "eslint-config-setup/json",
|
|
513
|
+
files: ["**/*.json"],
|
|
514
|
+
ignores: ["**/package-lock.json"],
|
|
515
|
+
language: "json/json",
|
|
516
|
+
plugins: { json: plugin$1 },
|
|
517
|
+
rules: {
|
|
518
|
+
"json/no-duplicate-keys": "error",
|
|
519
|
+
"json/no-empty-keys": "error",
|
|
520
|
+
"json/no-unsafe-values": "error",
|
|
521
|
+
"json/no-unnormalized-keys": "error"
|
|
522
|
+
}
|
|
523
|
+
}, {
|
|
524
|
+
name: "eslint-config-setup/jsonc",
|
|
525
|
+
files: [
|
|
526
|
+
"**/tsconfig.json",
|
|
527
|
+
"**/tsconfig.*.json",
|
|
528
|
+
"**/.vscode/*.json",
|
|
529
|
+
"**/turbo.json"
|
|
530
|
+
],
|
|
531
|
+
language: "json/jsonc",
|
|
532
|
+
plugins: { json: plugin$1 },
|
|
533
|
+
rules: {
|
|
534
|
+
"json/no-duplicate-keys": "error",
|
|
535
|
+
"json/no-empty-keys": "error",
|
|
536
|
+
"json/no-unsafe-values": "error",
|
|
537
|
+
"json/no-unnormalized-keys": "error"
|
|
538
|
+
}
|
|
539
|
+
}];
|
|
589
540
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/configs/markdown.ts
|
|
543
|
+
/**
|
|
544
|
+
* Markdown & MDX config — lints code blocks inside Markdown and MDX files
|
|
545
|
+
* using eslint-plugin-mdx. Code examples in docs get the same ESLint rules
|
|
546
|
+
* as your source code. Covers both `.md` and `.mdx` files.
|
|
547
|
+
*
|
|
548
|
+
* Markdown structure linting (headings, links, etc.) is intentionally left
|
|
549
|
+
* to dedicated tools like markdownlint.
|
|
550
|
+
*
|
|
551
|
+
* @see https://github.com/mdx-js/eslint-mdx
|
|
552
|
+
* @see https://github.com/DavidAnson/markdownlint-cli2 (recommended for Markdown structure linting)
|
|
553
|
+
*/
|
|
593
554
|
function markdownConfig() {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
"no-undef": "off",
|
|
623
|
-
// Snippets often show standalone expressions
|
|
624
|
-
"no-unused-expressions": "off",
|
|
625
|
-
// Variables are often declared for demonstration
|
|
626
|
-
"no-unused-vars": "off",
|
|
627
|
-
// Padding is irrelevant in snippets
|
|
628
|
-
"padded-blocks": "off",
|
|
629
|
-
// Strict mode is irrelevant in snippets
|
|
630
|
-
strict: "off",
|
|
631
|
-
// BOM is irrelevant in snippets
|
|
632
|
-
"unicode-bom": "off"
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
];
|
|
555
|
+
const codeBlockLanguageOptions = mdxPlugin.flatCodeBlocks.languageOptions ?? {};
|
|
556
|
+
const codeBlockParserOptions = codeBlockLanguageOptions.parserOptions ?? {};
|
|
557
|
+
return [{
|
|
558
|
+
...mdxPlugin.flat,
|
|
559
|
+
processor: mdxPlugin.createRemarkProcessor({
|
|
560
|
+
lintCodeBlocks: true,
|
|
561
|
+
languageMapper: {}
|
|
562
|
+
})
|
|
563
|
+
}, {
|
|
564
|
+
...mdxPlugin.flatCodeBlocks,
|
|
565
|
+
languageOptions: {
|
|
566
|
+
...codeBlockLanguageOptions,
|
|
567
|
+
parserOptions: {
|
|
568
|
+
...codeBlockParserOptions,
|
|
569
|
+
projectService: false
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
rules: {
|
|
573
|
+
...mdxPlugin.flatCodeBlocks.rules,
|
|
574
|
+
"eol-last": "off",
|
|
575
|
+
"no-undef": "off",
|
|
576
|
+
"no-unused-expressions": "off",
|
|
577
|
+
"no-unused-vars": "off",
|
|
578
|
+
"padded-blocks": "off",
|
|
579
|
+
strict: "off",
|
|
580
|
+
"unicode-bom": "off"
|
|
581
|
+
}
|
|
582
|
+
}];
|
|
636
583
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/configs/node.ts
|
|
586
|
+
/**
|
|
587
|
+
* Node.js config — rules for server-side JavaScript/TypeScript.
|
|
588
|
+
* Hand-picked subset of eslint-plugin-n. We don't use the full preset because
|
|
589
|
+
* module resolution rules (`no-missing-import`, `no-unpublished-import`) are
|
|
590
|
+
* handled better by TypeScript.
|
|
591
|
+
*
|
|
592
|
+
* @see https://github.com/eslint-community/eslint-plugin-n#-rules
|
|
593
|
+
*/
|
|
641
594
|
function nodeConfig(opts) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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"
|
|
701
|
-
}
|
|
702
|
-
}
|
|
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();
|
|
595
|
+
const isAi = opts?.ai ?? false;
|
|
596
|
+
const builder = createConfig({
|
|
597
|
+
name: "eslint-config-setup/node",
|
|
598
|
+
presets: [{ rules: {
|
|
599
|
+
"node/no-deprecated-api": "error",
|
|
600
|
+
"node/no-exports-assign": "error",
|
|
601
|
+
"node/no-missing-import": "off",
|
|
602
|
+
"node/no-missing-require": "off",
|
|
603
|
+
"node/no-process-exit": "warn",
|
|
604
|
+
"node/no-unpublished-import": "off",
|
|
605
|
+
"node/hashbang": "error",
|
|
606
|
+
"node/no-path-concat": "error",
|
|
607
|
+
"node/process-exit-as-throw": "error",
|
|
608
|
+
"node/handle-callback-err": "error",
|
|
609
|
+
"node/prefer-global/buffer": ["error", "always"],
|
|
610
|
+
"node/prefer-global/console": ["error", "always"],
|
|
611
|
+
"node/prefer-global/process": ["error", "always"],
|
|
612
|
+
"node/prefer-global/url": ["error", "always"],
|
|
613
|
+
"node/prefer-global/url-search-params": ["error", "always"],
|
|
614
|
+
"node/prefer-promises/dns": "error",
|
|
615
|
+
"node/prefer-promises/fs": "error"
|
|
616
|
+
} }],
|
|
617
|
+
plugins: { node: nodePlugin },
|
|
618
|
+
languageOptions: { globals: { ...globals.node } }
|
|
619
|
+
});
|
|
620
|
+
if (isAi) builder.addRule("node/no-unsupported-features/node-builtins", "error");
|
|
621
|
+
return builder.build();
|
|
711
622
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/configs/package-json.ts
|
|
625
|
+
/**
|
|
626
|
+
* Package.json config — semantic validation of package.json files.
|
|
627
|
+
*
|
|
628
|
+
* Uses the recommended preset from eslint-plugin-package-json which includes:
|
|
629
|
+
* - All valid-* rules (only fire on malformed existing fields, never on missing)
|
|
630
|
+
* - Conservative require-* rules with ignorePrivate: true by default
|
|
631
|
+
* - Quality rules (no-empty-fields, unique-dependencies, etc.)
|
|
632
|
+
*
|
|
633
|
+
* Sorting/ordering rules are off by default — enabled only in AI mode.
|
|
634
|
+
*
|
|
635
|
+
* @see https://github.com/JoshuaKGoldberg/eslint-plugin-package-json
|
|
636
|
+
*/
|
|
715
637
|
function packageJsonConfig() {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
name: "eslint-config-setup/package-json"
|
|
721
|
-
}
|
|
722
|
-
];
|
|
638
|
+
return [{
|
|
639
|
+
...configs.recommended,
|
|
640
|
+
name: "eslint-config-setup/package-json"
|
|
641
|
+
}];
|
|
723
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Package.json AI config — enables ordering and sorting rules.
|
|
645
|
+
* Only active in AI mode where deterministic structure is enforced.
|
|
646
|
+
*/
|
|
724
647
|
function packageJsonAiConfig() {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
// Sort dependencies, scripts, exports, etc. alphabetically
|
|
734
|
-
// https://github.com/JoshuaKGoldberg/eslint-plugin-package-json
|
|
735
|
-
"package-json/sort-collections": "error"
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
];
|
|
648
|
+
return [{
|
|
649
|
+
name: "eslint-config-setup/package-json-ai",
|
|
650
|
+
files: ["**/package.json"],
|
|
651
|
+
rules: {
|
|
652
|
+
"package-json/order-properties": "error",
|
|
653
|
+
"package-json/sort-collections": "error"
|
|
654
|
+
}
|
|
655
|
+
}];
|
|
739
656
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/configs/perfectionist.ts
|
|
659
|
+
/**
|
|
660
|
+
* Perfectionist config — deterministic sorting of code elements.
|
|
661
|
+
*
|
|
662
|
+
* Sorts everything Prettier doesn't: imports, exports, union types,
|
|
663
|
+
* object keys, interface members, etc. Especially valuable for
|
|
664
|
+
* AI-generated code where ordering is arbitrary.
|
|
665
|
+
*
|
|
666
|
+
* Replaces `simple-import-sort` — perfectionist handles everything
|
|
667
|
+
* simple-import-sort does, plus TypeScript path alias recognition
|
|
668
|
+
* and named import sorting.
|
|
669
|
+
*
|
|
670
|
+
* Core philosophy: `partitionByNewLine: true` globally — semantic
|
|
671
|
+
* grouping via blank lines is preserved, within groups natural sort.
|
|
672
|
+
*
|
|
673
|
+
* @see https://perfectionist.dev
|
|
674
|
+
*/
|
|
675
|
+
/**
|
|
676
|
+
* Mechanical sorting rules — always active.
|
|
677
|
+
* These have no semantic dimension: the order of imports, named exports,
|
|
678
|
+
* or union type members carries no meaning.
|
|
679
|
+
*/
|
|
743
680
|
function perfectionistConfig() {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
"perfectionist/sort-imports": [
|
|
762
|
-
"error",
|
|
763
|
-
{
|
|
764
|
-
partitionByNewLine: false
|
|
765
|
-
}
|
|
766
|
-
],
|
|
767
|
-
// Sort specifiers inside `import { a, b, c }` — deterministic
|
|
768
|
-
// https://perfectionist.dev/rules/sort-named-imports
|
|
769
|
-
"perfectionist/sort-named-imports": "error",
|
|
770
|
-
// Sort specifiers inside `export { a, b, c }` — deterministic
|
|
771
|
-
// https://perfectionist.dev/rules/sort-named-exports
|
|
772
|
-
"perfectionist/sort-named-exports": "error",
|
|
773
|
-
// Auto-sort export statements — deterministic ordering
|
|
774
|
-
// Replaces simple-import-sort/exports
|
|
775
|
-
// https://perfectionist.dev/rules/sort-exports
|
|
776
|
-
"perfectionist/sort-exports": "error",
|
|
777
|
-
// Sort union type members — `number | string` has no semantic order
|
|
778
|
-
// https://perfectionist.dev/rules/sort-union-types
|
|
779
|
-
"perfectionist/sort-union-types": "error",
|
|
780
|
-
// Sort intersection type members — `A & B` has no semantic order
|
|
781
|
-
// https://perfectionist.dev/rules/sort-intersection-types
|
|
782
|
-
"perfectionist/sort-intersection-types": "error"
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
];
|
|
681
|
+
return [{
|
|
682
|
+
name: "eslint-config-setup/perfectionist",
|
|
683
|
+
plugins: { perfectionist: perfectionistPlugin },
|
|
684
|
+
settings: { perfectionist: {
|
|
685
|
+
type: "natural",
|
|
686
|
+
order: "asc",
|
|
687
|
+
partitionByNewLine: true
|
|
688
|
+
} },
|
|
689
|
+
rules: {
|
|
690
|
+
"perfectionist/sort-imports": ["error", { partitionByNewLine: false }],
|
|
691
|
+
"perfectionist/sort-named-imports": "error",
|
|
692
|
+
"perfectionist/sort-named-exports": "error",
|
|
693
|
+
"perfectionist/sort-exports": "error",
|
|
694
|
+
"perfectionist/sort-union-types": "error",
|
|
695
|
+
"perfectionist/sort-intersection-types": "error"
|
|
696
|
+
}
|
|
697
|
+
}];
|
|
786
698
|
}
|
|
699
|
+
/**
|
|
700
|
+
* Structural sorting rules — AI mode only.
|
|
701
|
+
* These sort code elements that *could* have semantic ordering
|
|
702
|
+
* (e.g., object keys grouped by concern). Enforcing alphabetical
|
|
703
|
+
* order here is a trade-off: consistency over intent. Worth it
|
|
704
|
+
* for AI-generated code where "intent" is often random.
|
|
705
|
+
*/
|
|
787
706
|
function perfectionistAiConfig() {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
"perfectionist/sort-jsx-props": "error",
|
|
804
|
-
// Sort object keys — consistent object literals
|
|
805
|
-
// https://perfectionist.dev/rules/sort-objects
|
|
806
|
-
"perfectionist/sort-objects": "error",
|
|
807
|
-
// Sort class members — consistent class structure
|
|
808
|
-
// https://perfectionist.dev/rules/sort-classes
|
|
809
|
-
"perfectionist/sort-classes": "error",
|
|
810
|
-
// Sort switch cases — consistent case ordering
|
|
811
|
-
// https://perfectionist.dev/rules/sort-switch-case
|
|
812
|
-
"perfectionist/sort-switch-case": "error",
|
|
813
|
-
// Sort Map entries — consistent Map initialization
|
|
814
|
-
// https://perfectionist.dev/rules/sort-maps
|
|
815
|
-
"perfectionist/sort-maps": "error",
|
|
816
|
-
// Sort Set entries — consistent Set initialization
|
|
817
|
-
// https://perfectionist.dev/rules/sort-sets
|
|
818
|
-
"perfectionist/sort-sets": "error",
|
|
819
|
-
// Sort array.includes() members — consistent membership checks
|
|
820
|
-
// https://perfectionist.dev/rules/sort-array-includes
|
|
821
|
-
"perfectionist/sort-array-includes": "error"
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
];
|
|
707
|
+
return [{
|
|
708
|
+
name: "eslint-config-setup/perfectionist-ai",
|
|
709
|
+
rules: {
|
|
710
|
+
"perfectionist/sort-interfaces": "error",
|
|
711
|
+
"perfectionist/sort-object-types": "error",
|
|
712
|
+
"perfectionist/sort-enums": "error",
|
|
713
|
+
"perfectionist/sort-jsx-props": "error",
|
|
714
|
+
"perfectionist/sort-objects": "error",
|
|
715
|
+
"perfectionist/sort-classes": "error",
|
|
716
|
+
"perfectionist/sort-switch-case": "error",
|
|
717
|
+
"perfectionist/sort-maps": "error",
|
|
718
|
+
"perfectionist/sort-sets": "error",
|
|
719
|
+
"perfectionist/sort-array-includes": "error"
|
|
720
|
+
}
|
|
721
|
+
}];
|
|
825
722
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/configs/prettier.ts
|
|
725
|
+
/**
|
|
726
|
+
* Prettier compat config — disables all ESLint rules that conflict with Prettier.
|
|
727
|
+
* Must be the last config block (before OxLint if enabled).
|
|
728
|
+
*
|
|
729
|
+
* Preset: eslint-config-prettier (turns off ~30 formatting rules)
|
|
730
|
+
* @see https://github.com/prettier/eslint-config-prettier#readme
|
|
731
|
+
*/
|
|
829
732
|
function prettierCompatConfig() {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
733
|
+
return createConfig({
|
|
734
|
+
name: "eslint-config-setup/prettier",
|
|
735
|
+
presets: [prettierConfig]
|
|
736
|
+
}).build();
|
|
834
737
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
import reactRefreshPlugin from "eslint-plugin-react-refresh";
|
|
843
|
-
import globals2 from "globals";
|
|
844
|
-
|
|
845
|
-
// src/file-patterns.ts
|
|
846
|
-
var TYPESCRIPT_SOURCE_FILES = [
|
|
847
|
-
"**/*.ts",
|
|
848
|
-
"**/*.tsx",
|
|
849
|
-
"**/*.mts",
|
|
850
|
-
"**/*.cts"
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/file-patterns.ts
|
|
740
|
+
const TYPESCRIPT_SOURCE_FILES = [
|
|
741
|
+
"**/*.ts",
|
|
742
|
+
"**/*.tsx",
|
|
743
|
+
"**/*.mts",
|
|
744
|
+
"**/*.cts"
|
|
851
745
|
];
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
746
|
+
const MARKDOWN_CODE_BLOCK_FILES = ["**/*.{md,mdx}/**"];
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/plugins/react-compat.ts
|
|
749
|
+
/**
|
|
750
|
+
* React Compat Plugin — merges all `@eslint-react` sub-plugins into a single
|
|
751
|
+
* `react` namespace and re-exports rules under legacy `eslint-plugin-react`
|
|
752
|
+
* names where a 1:1 equivalent exists.
|
|
753
|
+
*
|
|
754
|
+
* **Why?** OxLint implements many React rules under the classic `react/` prefix
|
|
755
|
+
* with legacy names (e.g. `react/jsx-key`, `react/no-danger`). By registering
|
|
756
|
+
* the modern `@eslint-react` rule implementations under those legacy names, we
|
|
757
|
+
* get automatic OxLint coverage — OxLint runs the fast Rust check and ESLint
|
|
758
|
+
* skips the JS version via `eslint-plugin-oxlint`'s `flat/react` config.
|
|
759
|
+
*
|
|
760
|
+
* Rules without a legacy equivalent keep their `@eslint-react` short name
|
|
761
|
+
* (e.g. `react/no-context-provider`, `react/no-leaked-event-listener`).
|
|
762
|
+
* As OxLint expands React support, more rules will automatically be covered.
|
|
763
|
+
*
|
|
764
|
+
* @see https://eslint-react.xyz/docs/rules/overview - `@eslint-react` rules overview
|
|
765
|
+
* @see https://oxc.rs/docs/guide/usage/linter/rules.html - OxLint rules
|
|
766
|
+
*/
|
|
767
|
+
const coreRules = eslintReactPlugin.rules;
|
|
857
768
|
function selectPrefixedRules(prefix) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
result[name.slice(prefix.length)] = rule;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
return result;
|
|
769
|
+
const result = {};
|
|
770
|
+
for (const [name, rule] of Object.entries(coreRules)) if (name.startsWith(prefix)) result[name.slice(prefix.length)] = rule;
|
|
771
|
+
return result;
|
|
865
772
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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"]
|
|
773
|
+
const domRules = selectPrefixedRules("dom-");
|
|
774
|
+
const jsxRules = selectPrefixedRules("jsx-");
|
|
775
|
+
const namingRules = selectPrefixedRules("naming-convention-");
|
|
776
|
+
const rscRules = selectPrefixedRules("rsc-");
|
|
777
|
+
const webApiRules = selectPrefixedRules("web-api-");
|
|
778
|
+
const LEGACY_ALIASES = {
|
|
779
|
+
"no-access-state-in-setstate": [coreRules, "no-access-state-in-setstate"],
|
|
780
|
+
"no-array-index-key": [coreRules, "no-array-index-key"],
|
|
781
|
+
"no-children-prop": [jsxRules, "no-children-prop"],
|
|
782
|
+
"no-direct-mutation-state": [coreRules, "no-direct-mutation-state"],
|
|
783
|
+
"no-unused-class-component-members": [coreRules, "no-unused-class-component-members"],
|
|
784
|
+
"no-unused-state": [coreRules, "no-unused-state"],
|
|
785
|
+
"jsx-no-comment-textnodes": [jsxRules, "no-comment-textnodes"],
|
|
786
|
+
"jsx-key": [coreRules, "no-missing-key"],
|
|
787
|
+
"jsx-key-before-spread": [jsxRules, "no-key-after-spread"],
|
|
788
|
+
"jsx-no-constructed-context-values": [coreRules, "no-unstable-context-value"],
|
|
789
|
+
"jsx-no-leaked-render": [coreRules, "no-leaked-conditional-rendering"],
|
|
790
|
+
"jsx-no-useless-fragment": [jsxRules, "no-useless-fragment"],
|
|
791
|
+
"no-object-type-as-default-prop": [coreRules, "no-unstable-default-props"],
|
|
792
|
+
"no-unstable-nested-components": [coreRules, "no-nested-component-definitions"],
|
|
793
|
+
"display-name": [coreRules, "no-missing-component-display-name"],
|
|
794
|
+
"forward-ref-uses-ref": [coreRules, "no-forward-ref"],
|
|
795
|
+
"no-did-mount-set-state": [coreRules, "no-set-state-in-component-did-mount"],
|
|
796
|
+
"no-did-update-set-state": [coreRules, "no-set-state-in-component-did-update"],
|
|
797
|
+
"no-will-update-set-state": [coreRules, "no-set-state-in-component-will-update"],
|
|
798
|
+
"hook-use-state": [coreRules, "use-state"],
|
|
799
|
+
"no-danger": [domRules, "no-dangerously-set-innerhtml"],
|
|
800
|
+
"no-danger-with-children": [domRules, "no-dangerously-set-innerhtml-with-children"],
|
|
801
|
+
"no-find-dom-node": [domRules, "no-find-dom-node"],
|
|
802
|
+
"no-namespace": [jsxRules, "no-namespace"],
|
|
803
|
+
"no-render-return-value": [domRules, "no-render-return-value"],
|
|
804
|
+
"jsx-no-script-url": [domRules, "no-script-url"],
|
|
805
|
+
"jsx-no-target-blank": [domRules, "no-unsafe-target-blank"],
|
|
806
|
+
"no-unknown-property": [domRules, "no-unknown-property"],
|
|
807
|
+
"void-dom-elements-no-children": [domRules, "no-void-elements-with-children"],
|
|
808
|
+
"button-has-type": [domRules, "no-missing-button-type"],
|
|
809
|
+
"iframe-missing-sandbox": [domRules, "no-missing-iframe-sandbox"],
|
|
810
|
+
"style-prop-object": [domRules, "no-string-style-prop"]
|
|
932
811
|
};
|
|
933
|
-
|
|
934
|
-
for (const [source, originalName] of Object.values(LEGACY_ALIASES))
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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()]
|
|
812
|
+
const aliasedOriginals = /* @__PURE__ */ new Set();
|
|
813
|
+
for (const [source, originalName] of Object.values(LEGACY_ALIASES)) if (source === coreRules) aliasedOriginals.add(originalName);
|
|
814
|
+
else if (source === domRules) aliasedOriginals.add(`dom-${originalName}`);
|
|
815
|
+
else if (source === jsxRules) aliasedOriginals.add(`jsx-${originalName}`);
|
|
816
|
+
else if (source === namingRules) aliasedOriginals.add(`naming-convention-${originalName}`);
|
|
817
|
+
else if (source === rscRules) aliasedOriginals.add(`rsc-${originalName}`);
|
|
818
|
+
else if (source === webApiRules) aliasedOriginals.add(`web-api-${originalName}`);
|
|
819
|
+
const mergedRules = {};
|
|
820
|
+
for (const [name, rule] of Object.entries(coreRules)) if (!aliasedOriginals.has(name)) mergedRules[name] = rule;
|
|
821
|
+
const subPlugins = [
|
|
822
|
+
[domRules, /* @__PURE__ */ new Set(["prefer-namespace-import"])],
|
|
823
|
+
[jsxRules, /* @__PURE__ */ new Set()],
|
|
824
|
+
[webApiRules, /* @__PURE__ */ new Set()],
|
|
825
|
+
[namingRules, /* @__PURE__ */ new Set()],
|
|
826
|
+
[rscRules, /* @__PURE__ */ new Set()]
|
|
961
827
|
];
|
|
962
|
-
for (const [rules, skip] of subPlugins) {
|
|
963
|
-
|
|
964
|
-
|
|
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];
|
|
828
|
+
for (const [rules, skip] of subPlugins) for (const [name, rule] of Object.entries(rules)) {
|
|
829
|
+
if (skip.has(name)) continue;
|
|
830
|
+
if (!Object.values(LEGACY_ALIASES).some(([source, originalName]) => source === rules && originalName === name)) mergedRules[name] = rule;
|
|
977
831
|
}
|
|
978
|
-
|
|
832
|
+
for (const [legacyName, [source, originalName]] of Object.entries(LEGACY_ALIASES)) mergedRules[legacyName] = source[originalName];
|
|
833
|
+
const originalToCompat = /* @__PURE__ */ new Map();
|
|
979
834
|
for (const [legacyName, [source, originalName]] of Object.entries(LEGACY_ALIASES)) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
}
|
|
835
|
+
originalToCompat.set(originalName, legacyName);
|
|
836
|
+
if (source === domRules) originalToCompat.set(`dom-${originalName}`, legacyName);
|
|
837
|
+
else if (source === jsxRules) originalToCompat.set(`jsx-${originalName}`, legacyName);
|
|
838
|
+
else if (source === namingRules) originalToCompat.set(`naming-convention-${originalName}`, legacyName);
|
|
839
|
+
else if (source === rscRules) originalToCompat.set(`rsc-${originalName}`, legacyName);
|
|
840
|
+
else if (source === webApiRules) originalToCompat.set(`web-api-${originalName}`, legacyName);
|
|
992
841
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
842
|
+
/**
|
|
843
|
+
* Translates rules from an `@eslint-react` preset (e.g. `recommended`, `strict`)
|
|
844
|
+
* into `react/` compat names that match our unified plugin namespace.
|
|
845
|
+
*
|
|
846
|
+
* Example: `"@eslint-react/no-missing-key": "error"` → `"react/jsx-key": "error"`
|
|
847
|
+
*/
|
|
848
|
+
function translatePresetRules(presetRules) {
|
|
849
|
+
const result = {};
|
|
850
|
+
for (const [key, value] of Object.entries(presetRules)) {
|
|
851
|
+
const shortName = key.replace(/^@eslint-react\/(?:dom|web-api|naming-convention|rsc)\//, "").replace(/^@eslint-react\//, "").replace(/^(?:dom|web-api|naming-convention|rsc)-/, "");
|
|
852
|
+
const compatName = originalToCompat.get(shortName) ?? shortName;
|
|
853
|
+
if (compatName in mergedRules) result[`react/${compatName}`] = value;
|
|
854
|
+
}
|
|
855
|
+
return result;
|
|
1006
856
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
857
|
+
/**
|
|
858
|
+
* Unified React plugin registered as `react` in ESLint flat config.
|
|
859
|
+
* Contains all `@eslint-react` rule implementations under OxLint-compatible names.
|
|
860
|
+
*/
|
|
861
|
+
const reactCompatPlugin = {
|
|
862
|
+
meta: {
|
|
863
|
+
name: "react-compat",
|
|
864
|
+
version: "1.0.0"
|
|
865
|
+
},
|
|
866
|
+
rules: mergedRules
|
|
1013
867
|
};
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
];
|
|
1029
|
-
var reactHooksReplacedRules = new Set(
|
|
1030
|
-
REACT_HOOKS_REPLACED_ESLINT_REACT_RULES
|
|
1031
|
-
);
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region src/configs/react.ts
|
|
870
|
+
const strictTypeCheckedRules = eslintReactPlugin.configs["strict-type-checked"].rules;
|
|
871
|
+
const reactHooksReplacedRules = /* @__PURE__ */ new Set([
|
|
872
|
+
"error-boundaries",
|
|
873
|
+
"exhaustive-deps",
|
|
874
|
+
"purity",
|
|
875
|
+
"rules-of-hooks",
|
|
876
|
+
"set-state-in-effect",
|
|
877
|
+
"set-state-in-render",
|
|
878
|
+
"static-components",
|
|
879
|
+
"unsupported-syntax",
|
|
880
|
+
"use-memo"
|
|
881
|
+
]);
|
|
1032
882
|
function filterReactHooksRules(rules) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
return filtered;
|
|
883
|
+
const filtered = {};
|
|
884
|
+
for (const [ruleName, value] of Object.entries(rules)) {
|
|
885
|
+
const shortName = ruleName.replace(/^@eslint-react\//, "");
|
|
886
|
+
if (!reactHooksReplacedRules.has(shortName)) filtered[ruleName] = value;
|
|
887
|
+
}
|
|
888
|
+
return filtered;
|
|
1041
889
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
};
|
|
1051
|
-
var AI_ADDED_REACT_RULES = {
|
|
1052
|
-
"react/no-unused-state": "error"
|
|
890
|
+
const presetRules = translatePresetRules(filterReactHooksRules(strictTypeCheckedRules));
|
|
891
|
+
const reactHooksRecommendedLatest = reactHooksPlugin.configs.flat["recommended-latest"];
|
|
892
|
+
const reactHooksPluginForEslint = reactHooksPlugin;
|
|
893
|
+
const EXTRA_REACT_RULES = {
|
|
894
|
+
"react/no-duplicate-key": "error",
|
|
895
|
+
"react/no-implicit-key": "error",
|
|
896
|
+
"react/no-unknown-property": "error",
|
|
897
|
+
"react/style-prop-object": "error"
|
|
1053
898
|
};
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
899
|
+
const AI_ADDED_REACT_RULES = { "react/no-unused-state": "error" };
|
|
900
|
+
const AI_REACT_PERF_RULES = {
|
|
901
|
+
"react-perf/jsx-no-jsx-as-prop": "error",
|
|
902
|
+
"react-perf/jsx-no-new-array-as-prop": "error",
|
|
903
|
+
"react-perf/jsx-no-new-function-as-prop": "error",
|
|
904
|
+
"react-perf/jsx-no-new-object-as-prop": "error"
|
|
1059
905
|
};
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
906
|
+
const JSX_A11Y_ERROR_RULES = {
|
|
907
|
+
"jsx-a11y/alt-text": "error",
|
|
908
|
+
"jsx-a11y/anchor-has-content": "error",
|
|
909
|
+
"jsx-a11y/anchor-is-valid": "error",
|
|
910
|
+
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
|
911
|
+
"jsx-a11y/aria-props": "error",
|
|
912
|
+
"jsx-a11y/aria-proptypes": "error",
|
|
913
|
+
"jsx-a11y/aria-role": "error",
|
|
914
|
+
"jsx-a11y/aria-unsupported-elements": "error",
|
|
915
|
+
"jsx-a11y/click-events-have-key-events": "error",
|
|
916
|
+
"jsx-a11y/heading-has-content": "error",
|
|
917
|
+
"jsx-a11y/html-has-lang": "error",
|
|
918
|
+
"jsx-a11y/img-redundant-alt": "error",
|
|
919
|
+
"jsx-a11y/label-has-associated-control": "error",
|
|
920
|
+
"jsx-a11y/mouse-events-have-key-events": "error",
|
|
921
|
+
"jsx-a11y/no-access-key": "error",
|
|
922
|
+
"jsx-a11y/no-distracting-elements": "error",
|
|
923
|
+
"jsx-a11y/no-redundant-roles": "error",
|
|
924
|
+
"jsx-a11y/role-has-required-aria-props": "error",
|
|
925
|
+
"jsx-a11y/role-supports-aria-props": "error",
|
|
926
|
+
"jsx-a11y/scope": "error",
|
|
927
|
+
"jsx-a11y/tabindex-no-positive": "error",
|
|
928
|
+
"jsx-a11y/lang": "error",
|
|
929
|
+
"jsx-a11y/autocomplete-valid": "error"
|
|
1084
930
|
};
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
931
|
+
const AI_PROMOTED_REACT_RULES = [
|
|
932
|
+
"react/jsx-no-comment-textnodes",
|
|
933
|
+
"react/jsx-no-useless-fragment",
|
|
934
|
+
"react/jsx-no-constructed-context-values",
|
|
935
|
+
"react/no-array-index-key",
|
|
936
|
+
"react/no-object-type-as-default-prop",
|
|
937
|
+
"react/jsx-no-target-blank",
|
|
938
|
+
"react/button-has-type",
|
|
939
|
+
"react/iframe-missing-sandbox",
|
|
940
|
+
"react/forward-ref-uses-ref",
|
|
941
|
+
"react/no-context-provider",
|
|
942
|
+
"react/no-use-context",
|
|
943
|
+
"react/no-leaked-event-listener",
|
|
944
|
+
"react/no-leaked-interval",
|
|
945
|
+
"react/no-leaked-timeout",
|
|
946
|
+
"react/no-leaked-resize-observer"
|
|
1101
947
|
];
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
948
|
+
const AI_A11Y_RULES = [
|
|
949
|
+
"jsx-a11y/no-static-element-interactions",
|
|
950
|
+
"jsx-a11y/no-noninteractive-element-interactions",
|
|
951
|
+
"jsx-a11y/interactive-supports-focus"
|
|
1106
952
|
];
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1109
|
-
builder.addRule(ruleName, value);
|
|
1110
|
-
}
|
|
953
|
+
function addRules(builder, rules) {
|
|
954
|
+
for (const [ruleName, value] of Object.entries(rules)) builder.addRule(ruleName, value);
|
|
1111
955
|
}
|
|
1112
956
|
function addExtraReactRules(builder) {
|
|
1113
|
-
|
|
957
|
+
addRules(builder, EXTRA_REACT_RULES);
|
|
1114
958
|
}
|
|
1115
959
|
function addStylisticReactRules(builder) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
960
|
+
builder.addRule("@stylistic/jsx-self-closing-comp", "error");
|
|
961
|
+
builder.addRule("@stylistic/jsx-curly-brace-presence", ["error", {
|
|
962
|
+
props: "never",
|
|
963
|
+
children: "never"
|
|
964
|
+
}]);
|
|
1121
965
|
}
|
|
1122
966
|
function addReactRefreshRules(builder) {
|
|
1123
|
-
|
|
1124
|
-
"warn",
|
|
1125
|
-
{ allowConstantExport: true }
|
|
1126
|
-
]);
|
|
967
|
+
builder.addRule("react-refresh/only-export-components", ["warn", { allowConstantExport: true }]);
|
|
1127
968
|
}
|
|
1128
969
|
function addAccessibilityRules(builder) {
|
|
1129
|
-
|
|
1130
|
-
|
|
970
|
+
addRules(builder, JSX_A11Y_ERROR_RULES);
|
|
971
|
+
builder.addRule("jsx-a11y/no-autofocus", ["error", { ignoreNonDOM: true }]);
|
|
1131
972
|
}
|
|
1132
973
|
function applyAiReactRules(builder) {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
addRules2(builder, AI_REACT_PERF_RULES);
|
|
1138
|
-
for (const ruleName of AI_A11Y_RULES) {
|
|
1139
|
-
builder.addRule(ruleName, "error");
|
|
1140
|
-
}
|
|
974
|
+
for (const ruleName of AI_PROMOTED_REACT_RULES) builder.overrideSeverity(ruleName, "error");
|
|
975
|
+
addRules(builder, AI_ADDED_REACT_RULES);
|
|
976
|
+
addRules(builder, AI_REACT_PERF_RULES);
|
|
977
|
+
for (const ruleName of AI_A11Y_RULES) builder.addRule(ruleName, "error");
|
|
1141
978
|
}
|
|
1142
979
|
function addReactFileOverrides(builder) {
|
|
1143
|
-
|
|
1144
|
-
"eslint-config-setup/react-typescript",
|
|
1145
|
-
[...TYPESCRIPT_SOURCE_FILES],
|
|
1146
|
-
{
|
|
1147
|
-
// Allow async event handlers — onClick={async () => {...}} is idiomatic React
|
|
1148
|
-
"@typescript-eslint/no-misused-promises": [
|
|
1149
|
-
"error",
|
|
1150
|
-
{ checksVoidReturn: false }
|
|
1151
|
-
]
|
|
1152
|
-
}
|
|
1153
|
-
);
|
|
980
|
+
builder.addFileOverride("eslint-config-setup/react-typescript", [...TYPESCRIPT_SOURCE_FILES], { "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }] });
|
|
1154
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* React config — React 19+, Hooks, JSX accessibility, and Web API leak detection.
|
|
984
|
+
*
|
|
985
|
+
* Uses `@eslint-react` (eslint-plugin-react-x) for React, DOM, Web API,
|
|
986
|
+
* naming-convention, and RSC rules under the `react/` compat namespace.
|
|
987
|
+
* Rules of Hooks and React Compiler linting come from Meta's
|
|
988
|
+
* eslint-plugin-react-hooks under the native `react-hooks/` namespace.
|
|
989
|
+
*
|
|
990
|
+
* Base React severities come from the `@eslint-react` `strict-type-checked` preset.
|
|
991
|
+
* When `ai: true`, preset "warn" rules that AI should always get right are
|
|
992
|
+
* promoted to "error", and React performance rules are enabled as errors.
|
|
993
|
+
*
|
|
994
|
+
* @see https://eslint-react.xyz/docs/rules/overview
|
|
995
|
+
* @see https://www.npmjs.com/package/eslint-plugin-react-hooks
|
|
996
|
+
* @see https://github.com/cvazac/eslint-plugin-react-perf
|
|
997
|
+
* @see https://github.com/ArnaudBarre/eslint-plugin-react-refresh
|
|
998
|
+
* @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules
|
|
999
|
+
*/
|
|
1155
1000
|
function reactConfig(opts) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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();
|
|
1001
|
+
const isAi = opts?.ai ?? false;
|
|
1002
|
+
const builder = createConfig({
|
|
1003
|
+
name: "eslint-config-setup/react",
|
|
1004
|
+
presets: [{ rules: presetRules }, reactHooksRecommendedLatest],
|
|
1005
|
+
plugins: {
|
|
1006
|
+
react: reactCompatPlugin,
|
|
1007
|
+
"@stylistic": stylisticPlugin,
|
|
1008
|
+
"react-hooks": reactHooksPluginForEslint,
|
|
1009
|
+
"react-perf": reactPerfPlugin,
|
|
1010
|
+
"react-refresh": reactRefreshPlugin,
|
|
1011
|
+
"jsx-a11y": jsxA11yPlugin
|
|
1012
|
+
},
|
|
1013
|
+
languageOptions: {
|
|
1014
|
+
globals: { ...globals.browser },
|
|
1015
|
+
parserOptions: { ecmaFeatures: { jsx: true } }
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
addExtraReactRules(builder);
|
|
1019
|
+
addStylisticReactRules(builder);
|
|
1020
|
+
addReactRefreshRules(builder);
|
|
1021
|
+
addAccessibilityRules(builder);
|
|
1022
|
+
if (isAi) applyAiReactRules(builder);
|
|
1023
|
+
addReactFileOverrides(builder);
|
|
1024
|
+
return builder.build();
|
|
1190
1025
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1026
|
+
//#endregion
|
|
1027
|
+
//#region src/configs/react-effect.ts
|
|
1028
|
+
/**
|
|
1029
|
+
* React effect config — catches unnecessary useEffect anti-patterns.
|
|
1030
|
+
* Codifies the patterns from the React docs article "You Might Not Need an Effect".
|
|
1031
|
+
*
|
|
1032
|
+
* Complements react-hooks (which checks dependency arrays are correct) by
|
|
1033
|
+
* detecting when the entire effect is unnecessary — derived state, chained
|
|
1034
|
+
* state updates, prop-triggered resets, and more.
|
|
1035
|
+
*
|
|
1036
|
+
* @see https://react.dev/learn/you-might-not-need-an-effect
|
|
1037
|
+
* @see https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1038
|
+
*/
|
|
1194
1039
|
function reactEffectConfig() {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
"react-you-might-not-need-an-effect/no-event-handler": "error",
|
|
1211
|
-
// Disallow adjusting state when a prop changes — compute inline during render
|
|
1212
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1213
|
-
"react-you-might-not-need-an-effect/no-adjust-state-on-prop-change": "warn",
|
|
1214
|
-
// Disallow resetting all state when a prop changes — use the key prop instead
|
|
1215
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1216
|
-
"react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change": "error",
|
|
1217
|
-
// Disallow passing live state to parent via effect — lift state up instead
|
|
1218
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1219
|
-
"react-you-might-not-need-an-effect/no-pass-live-state-to-parent": "error",
|
|
1220
|
-
// Disallow passing fetched data to parent via effect — fetch in parent instead
|
|
1221
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1222
|
-
"react-you-might-not-need-an-effect/no-pass-data-to-parent": "error",
|
|
1223
|
-
// Disallow external-store subscriptions in an effect — use useSyncExternalStore
|
|
1224
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
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
|
|
1227
|
-
// https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect
|
|
1228
|
-
"react-you-might-not-need-an-effect/no-initialize-state": "error"
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
];
|
|
1040
|
+
return [{
|
|
1041
|
+
name: "eslint-config-setup/react-effect",
|
|
1042
|
+
plugins: { "react-you-might-not-need-an-effect": reactEffectPlugin },
|
|
1043
|
+
rules: {
|
|
1044
|
+
"react-you-might-not-need-an-effect/no-derived-state": "error",
|
|
1045
|
+
"react-you-might-not-need-an-effect/no-chain-state-updates": "error",
|
|
1046
|
+
"react-you-might-not-need-an-effect/no-event-handler": "error",
|
|
1047
|
+
"react-you-might-not-need-an-effect/no-adjust-state-on-prop-change": "warn",
|
|
1048
|
+
"react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change": "error",
|
|
1049
|
+
"react-you-might-not-need-an-effect/no-pass-live-state-to-parent": "error",
|
|
1050
|
+
"react-you-might-not-need-an-effect/no-pass-data-to-parent": "error",
|
|
1051
|
+
"react-you-might-not-need-an-effect/no-external-store-subscription": "error",
|
|
1052
|
+
"react-you-might-not-need-an-effect/no-initialize-state": "error"
|
|
1053
|
+
}
|
|
1054
|
+
}];
|
|
1232
1055
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/configs/regexp.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* RegExp config — uses the full `flat/recommended` preset from eslint-plugin-regexp.
|
|
1060
|
+
* Validates regular expression syntax, detects common mistakes, and suggests
|
|
1061
|
+
* simpler patterns.
|
|
1062
|
+
*
|
|
1063
|
+
* Preset: eslint-plugin-regexp flat/recommended (all rules at their default severity)
|
|
1064
|
+
* @see https://ota-meshi.github.io/eslint-plugin-regexp/
|
|
1065
|
+
* @see https://ota-meshi.github.io/eslint-plugin-regexp/rules/
|
|
1066
|
+
*/
|
|
1236
1067
|
function regexpConfig(opts) {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1068
|
+
const isAi = opts?.ai ?? false;
|
|
1069
|
+
const builder = createConfig({
|
|
1070
|
+
name: "eslint-config-setup/regexp",
|
|
1071
|
+
presets: [configs$1["flat/recommended"]]
|
|
1072
|
+
}).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");
|
|
1073
|
+
if (isAi) {
|
|
1074
|
+
builder.addRule("regexp/prefer-lookaround", "error");
|
|
1075
|
+
builder.addRule("regexp/prefer-named-backreference", "error");
|
|
1076
|
+
builder.addRule("regexp/prefer-named-replacement", "error");
|
|
1077
|
+
builder.addRule("regexp/prefer-quantifier", "error");
|
|
1078
|
+
builder.addRule("regexp/prefer-result-array-groups", "error");
|
|
1079
|
+
builder.addRule("regexp/require-unicode-sets-regexp", "error");
|
|
1080
|
+
}
|
|
1081
|
+
return builder.build();
|
|
1251
1082
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/configs/security.ts
|
|
1085
|
+
const plugin = securityPlugin;
|
|
1086
|
+
/**
|
|
1087
|
+
* Security config — Node.js security patterns from eslint-plugin-security.
|
|
1088
|
+
* We manually select rules instead of using the preset because the preset
|
|
1089
|
+
* enables `detect-object-injection` which produces too many false positives.
|
|
1090
|
+
*
|
|
1091
|
+
* @see https://github.com/eslint-community/eslint-plugin-security#rules
|
|
1092
|
+
*/
|
|
1256
1093
|
function securityConfig() {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
// Detect new Buffer(n) — deprecated, use Buffer.alloc() instead
|
|
1278
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-new-buffer.md
|
|
1279
|
-
"security/detect-new-buffer": "error",
|
|
1280
|
-
// Detect CSRF middleware placed after method-override — CSRF bypass
|
|
1281
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-no-csrf-before-method-override.md
|
|
1282
|
-
"security/detect-no-csrf-before-method-override": "error",
|
|
1283
|
-
// Detect Math.random() / pseudoRandomBytes — not cryptographically secure
|
|
1284
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-pseudoRandomBytes.md
|
|
1285
|
-
"security/detect-pseudoRandomBytes": "error",
|
|
1286
|
-
// Detect regexes vulnerable to ReDoS (catastrophic backtracking)
|
|
1287
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-unsafe-regex.md
|
|
1288
|
-
"security/detect-unsafe-regex": "error",
|
|
1289
|
-
// ── Warnings: worth reviewing but may have legitimate uses ─────
|
|
1290
|
-
// Detect dynamic fs paths — potential path traversal (many false positives)
|
|
1291
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-fs-filename.md
|
|
1292
|
-
"security/detect-non-literal-fs-filename": "warn",
|
|
1293
|
-
// Detect dynamic regex construction — potential ReDoS
|
|
1294
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-regexp.md
|
|
1295
|
-
"security/detect-non-literal-regexp": "warn",
|
|
1296
|
-
// Detect dynamic require() — potential code injection
|
|
1297
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-non-literal-require.md
|
|
1298
|
-
"security/detect-non-literal-require": "warn",
|
|
1299
|
-
// Detect string comparisons that may leak timing info — side-channel risk
|
|
1300
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-possible-timing-attacks.md
|
|
1301
|
-
"security/detect-possible-timing-attacks": "warn",
|
|
1302
|
-
// Detect Unicode bidirectional control characters — source code trojan attack
|
|
1303
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-bidi-characters.md
|
|
1304
|
-
"security/detect-bidi-characters": "error",
|
|
1305
|
-
// ── Disabled: too many false positives ────────────────────────
|
|
1306
|
-
// Flags all bracket notation (obj[key]) — nearly every codebase triggers this
|
|
1307
|
-
// https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/rules/detect-object-injection.md
|
|
1308
|
-
"security/detect-object-injection": "off"
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
];
|
|
1094
|
+
return [{
|
|
1095
|
+
name: "eslint-config-setup/security",
|
|
1096
|
+
plugins: { security: plugin },
|
|
1097
|
+
rules: {
|
|
1098
|
+
"security/detect-buffer-noassert": "error",
|
|
1099
|
+
"security/detect-child-process": "error",
|
|
1100
|
+
"security/detect-disable-mustache-escape": "error",
|
|
1101
|
+
"security/detect-eval-with-expression": "error",
|
|
1102
|
+
"security/detect-new-buffer": "error",
|
|
1103
|
+
"security/detect-no-csrf-before-method-override": "error",
|
|
1104
|
+
"security/detect-pseudoRandomBytes": "error",
|
|
1105
|
+
"security/detect-unsafe-regex": "error",
|
|
1106
|
+
"security/detect-non-literal-fs-filename": "warn",
|
|
1107
|
+
"security/detect-non-literal-regexp": "warn",
|
|
1108
|
+
"security/detect-non-literal-require": "warn",
|
|
1109
|
+
"security/detect-possible-timing-attacks": "warn",
|
|
1110
|
+
"security/detect-bidi-characters": "error",
|
|
1111
|
+
"security/detect-object-injection": "off"
|
|
1112
|
+
}
|
|
1113
|
+
}];
|
|
1312
1114
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1115
|
+
//#endregion
|
|
1116
|
+
//#region src/configs/sonarjs.ts
|
|
1117
|
+
/**
|
|
1118
|
+
* SonarJS config — code-quality rules from SonarSource.
|
|
1119
|
+
* Hand-picked subset focused on bug detection and code smells.
|
|
1120
|
+
* We don't use the full preset because many SonarJS rules overlap with
|
|
1121
|
+
* typescript-eslint and unicorn.
|
|
1122
|
+
*
|
|
1123
|
+
* @see https://github.com/SonarSource/SonarJS/tree/master/packages/jsts/src/rules#readme
|
|
1124
|
+
*/
|
|
1316
1125
|
function sonarjsConfig(opts) {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
"sonarjs/no-duplicated-branches": "error",
|
|
1356
|
-
// Detect .filter()/.map()/.slice() called without using the result
|
|
1357
|
-
// https://sonarsource.github.io/rspec/#/rspec/S2201/javascript
|
|
1358
|
-
"sonarjs/no-ignored-return": "error",
|
|
1359
|
-
// Detect redundant return/break/continue at end of block
|
|
1360
|
-
// https://sonarsource.github.io/rspec/#/rspec/S3626/javascript
|
|
1361
|
-
"sonarjs/no-redundant-jump": "error",
|
|
1362
|
-
// Prevent .only() from being committed — blocks CI for others
|
|
1363
|
-
// https://sonarsource.github.io/rspec/#/rspec/S6426/javascript
|
|
1364
|
-
"sonarjs/no-exclusive-tests": "error",
|
|
1365
|
-
// Detect `const sorted = arr.sort()` — .sort() mutates the original
|
|
1366
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4043/javascript
|
|
1367
|
-
"sonarjs/no-misleading-array-reverse": "error",
|
|
1368
|
-
// Require initial value for .reduce() — crashes on empty arrays without it
|
|
1369
|
-
// https://sonarsource.github.io/rspec/#/rspec/S6959/javascript
|
|
1370
|
-
"sonarjs/reduce-initial-value": "error",
|
|
1371
|
-
// Disallow async operations in constructors — use factory methods
|
|
1372
|
-
// https://sonarsource.github.io/rspec/#/rspec/S7059/javascript
|
|
1373
|
-
"sonarjs/no-async-constructor": "error",
|
|
1374
|
-
// Detect `field?: string | undefined` — the `?` already implies undefined
|
|
1375
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4782/javascript
|
|
1376
|
-
"sonarjs/no-redundant-optional": "error",
|
|
1377
|
-
// Detect `string | number | string` — duplicate constituents in unions
|
|
1378
|
-
// https://sonarsource.github.io/rspec/#/rspec/S4621/javascript
|
|
1379
|
-
"sonarjs/no-duplicate-in-composite": "error",
|
|
1380
|
-
// Detect hardcoded secrets (API keys, passwords, tokens) in source code
|
|
1381
|
-
// https://sonarsource.github.io/rspec/#/rspec/S6418/javascript
|
|
1382
|
-
"sonarjs/no-hardcoded-secrets": "warn"
|
|
1383
|
-
}
|
|
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();
|
|
1126
|
+
const isAi = opts?.ai === true;
|
|
1127
|
+
const builder = createConfig({
|
|
1128
|
+
name: "eslint-config-setup/sonarjs",
|
|
1129
|
+
presets: [{
|
|
1130
|
+
plugins: { sonarjs: sonarjsPlugin },
|
|
1131
|
+
rules: {
|
|
1132
|
+
"sonarjs/no-identical-functions": "error",
|
|
1133
|
+
"sonarjs/no-collapsible-if": "error",
|
|
1134
|
+
"sonarjs/no-redundant-boolean": "error",
|
|
1135
|
+
"sonarjs/no-unused-collection": "error",
|
|
1136
|
+
"sonarjs/prefer-immediate-return": "error",
|
|
1137
|
+
"sonarjs/prefer-single-boolean-return": "error",
|
|
1138
|
+
"sonarjs/no-identical-expressions": "error",
|
|
1139
|
+
"sonarjs/no-inverted-boolean-check": "error",
|
|
1140
|
+
"sonarjs/no-collection-size-mischeck": "error",
|
|
1141
|
+
"sonarjs/no-identical-conditions": "error",
|
|
1142
|
+
"sonarjs/no-duplicated-branches": "error",
|
|
1143
|
+
"sonarjs/no-ignored-return": "error",
|
|
1144
|
+
"sonarjs/no-redundant-jump": "error",
|
|
1145
|
+
"sonarjs/no-exclusive-tests": "error",
|
|
1146
|
+
"sonarjs/no-misleading-array-reverse": "error",
|
|
1147
|
+
"sonarjs/reduce-initial-value": "error",
|
|
1148
|
+
"sonarjs/no-async-constructor": "error",
|
|
1149
|
+
"sonarjs/no-redundant-optional": "error",
|
|
1150
|
+
"sonarjs/no-duplicate-in-composite": "error",
|
|
1151
|
+
"sonarjs/no-hardcoded-secrets": "warn"
|
|
1152
|
+
}
|
|
1153
|
+
}]
|
|
1154
|
+
});
|
|
1155
|
+
if (isAi) {
|
|
1156
|
+
builder.addRule("sonarjs/no-nested-switch", "error");
|
|
1157
|
+
builder.addRule("sonarjs/no-nested-template-literals", "error");
|
|
1158
|
+
builder.addRule("sonarjs/max-union-size", ["error", { threshold: 5 }]);
|
|
1159
|
+
builder.addRule("sonarjs/prefer-type-guard", "error");
|
|
1160
|
+
builder.addRule("sonarjs/public-static-readonly", "error");
|
|
1161
|
+
builder.addRule("sonarjs/no-duplicate-string", ["error", { threshold: 3 }]);
|
|
1162
|
+
}
|
|
1163
|
+
return builder.build();
|
|
1395
1164
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1165
|
+
//#endregion
|
|
1166
|
+
//#region src/configs/typescript.ts
|
|
1167
|
+
/**
|
|
1168
|
+
* TypeScript config — extends typescript-eslint strict presets with project-wide type checking.
|
|
1169
|
+
*
|
|
1170
|
+
* Presets used:
|
|
1171
|
+
* - `tseslint.configs.strictTypeChecked` — all recommended + strict rules with type info
|
|
1172
|
+
* - `tseslint.configs.stylisticTypeChecked` — consistent code style with type info
|
|
1173
|
+
*
|
|
1174
|
+
* The presets already handle many core ESLint rules by disabling them and enabling
|
|
1175
|
+
* TS-aware equivalents (no-implied-eval, dot-notation, no-throw-literal, etc.).
|
|
1176
|
+
*
|
|
1177
|
+
* @see https://typescript-eslint.io/getting-started/
|
|
1178
|
+
* @see https://typescript-eslint.io/rules/
|
|
1179
|
+
*/
|
|
1399
1180
|
function typescriptConfig(opts) {
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
-
}
|
|
1661
|
-
return builder.build();
|
|
1181
|
+
const isAi = opts?.ai ?? false;
|
|
1182
|
+
const isReact = opts?.react ?? false;
|
|
1183
|
+
const typeChecked = tseslint.configs.strictTypeChecked;
|
|
1184
|
+
const stylistic = tseslint.configs.stylisticTypeChecked;
|
|
1185
|
+
const builder = createConfig({
|
|
1186
|
+
name: "eslint-config-setup/typescript",
|
|
1187
|
+
passthrough: typeChecked.slice(0, 2),
|
|
1188
|
+
presets: [typeChecked[2], stylistic[2]],
|
|
1189
|
+
files: [...TYPESCRIPT_SOURCE_FILES],
|
|
1190
|
+
ignores: [...MARKDOWN_CODE_BLOCK_FILES],
|
|
1191
|
+
languageOptions: { parserOptions: { projectService: true } }
|
|
1192
|
+
});
|
|
1193
|
+
builder.overrideRule("@typescript-eslint/no-unused-vars", ["error", {
|
|
1194
|
+
args: "all",
|
|
1195
|
+
argsIgnorePattern: "^_",
|
|
1196
|
+
caughtErrors: "all",
|
|
1197
|
+
caughtErrorsIgnorePattern: "^_",
|
|
1198
|
+
destructuredArrayIgnorePattern: "^_",
|
|
1199
|
+
varsIgnorePattern: "^_",
|
|
1200
|
+
ignoreRestSiblings: true
|
|
1201
|
+
}]);
|
|
1202
|
+
builder.overrideRule("@typescript-eslint/array-type", ["error", { default: "array-simple" }]);
|
|
1203
|
+
builder.overrideRule("@typescript-eslint/return-await", ["error", "in-try-catch"]);
|
|
1204
|
+
builder.overrideSeverity("@typescript-eslint/no-deprecated", "warn");
|
|
1205
|
+
builder.overrideRule("@typescript-eslint/no-explicit-any", ["warn", { fixToUnknown: true }]);
|
|
1206
|
+
builder.overrideRule("@typescript-eslint/consistent-type-definitions", ["error", "type"]);
|
|
1207
|
+
builder.overrideOptions("@typescript-eslint/ban-ts-comment", { "ts-expect-error": "allow-with-description" });
|
|
1208
|
+
builder.overrideOptions("@typescript-eslint/no-floating-promises", {
|
|
1209
|
+
checkThenables: true,
|
|
1210
|
+
ignoreIIFE: true
|
|
1211
|
+
});
|
|
1212
|
+
builder.addRule("@typescript-eslint/consistent-type-imports", ["error", { fixStyle: isAi ? "inline-type-imports" : "separate-type-imports" }]);
|
|
1213
|
+
builder.addRule("@typescript-eslint/consistent-type-exports", ["error", { fixMixedExportsWithInlineTypeSpecifier: true }]);
|
|
1214
|
+
builder.addRule("@typescript-eslint/no-import-type-side-effects", "error");
|
|
1215
|
+
builder.addRule("@typescript-eslint/no-unnecessary-qualifier", "error");
|
|
1216
|
+
builder.addRule("@typescript-eslint/no-useless-empty-export", "error");
|
|
1217
|
+
builder.addRule("@typescript-eslint/no-unnecessary-parameter-property-assignment", "error");
|
|
1218
|
+
builder.addRule("@typescript-eslint/strict-void-return", "error");
|
|
1219
|
+
builder.addRule("@typescript-eslint/prefer-readonly", isAi ? "error" : "warn");
|
|
1220
|
+
builder.addRule("@typescript-eslint/require-array-sort-compare", ["error", { ignoreStringArrays: true }]);
|
|
1221
|
+
builder.addRule("@typescript-eslint/no-unsafe-type-assertion", "error");
|
|
1222
|
+
builder.addRule("@typescript-eslint/switch-exhaustiveness-check", ["error", {
|
|
1223
|
+
allowDefaultCaseForExhaustiveSwitch: false,
|
|
1224
|
+
requireDefaultForNonUnion: true
|
|
1225
|
+
}]);
|
|
1226
|
+
builder.addRule("@typescript-eslint/promise-function-async", "error");
|
|
1227
|
+
builder.addRule("@typescript-eslint/method-signature-style", ["error", "property"]);
|
|
1228
|
+
builder.addRule("no-shadow", "off");
|
|
1229
|
+
builder.addRule("@typescript-eslint/no-shadow", ["error", {
|
|
1230
|
+
hoist: "all",
|
|
1231
|
+
allow: [
|
|
1232
|
+
"resolve",
|
|
1233
|
+
"reject",
|
|
1234
|
+
"done",
|
|
1235
|
+
"next",
|
|
1236
|
+
"error"
|
|
1237
|
+
],
|
|
1238
|
+
ignoreTypeValueShadow: true,
|
|
1239
|
+
ignoreFunctionTypeParameterNameValueShadow: true
|
|
1240
|
+
}]);
|
|
1241
|
+
builder.overrideOptions("@typescript-eslint/restrict-template-expressions", { allowNumber: true });
|
|
1242
|
+
builder.addRule("@typescript-eslint/strict-boolean-expressions", ["error", {
|
|
1243
|
+
allowNullableBoolean: true,
|
|
1244
|
+
allowNullableObject: true
|
|
1245
|
+
}]);
|
|
1246
|
+
builder.addFileOverride("eslint-config-setup/typescript-js-compat", ["**/*.{js,mjs,cjs}"], tseslint.configs.disableTypeChecked.rules ?? {});
|
|
1247
|
+
if (isAi) {
|
|
1248
|
+
builder.overrideSeverity("@typescript-eslint/no-explicit-any", "error");
|
|
1249
|
+
builder.overrideRule("@typescript-eslint/no-floating-promises", ["error", {
|
|
1250
|
+
checkThenables: true,
|
|
1251
|
+
ignoreVoid: true
|
|
1252
|
+
}]);
|
|
1253
|
+
builder.addRule("@typescript-eslint/no-magic-numbers", ["error", {
|
|
1254
|
+
ignore: [
|
|
1255
|
+
-1,
|
|
1256
|
+
0,
|
|
1257
|
+
1,
|
|
1258
|
+
2
|
|
1259
|
+
],
|
|
1260
|
+
ignoreArrayIndexes: true,
|
|
1261
|
+
ignoreDefaultValues: true,
|
|
1262
|
+
enforceConst: true,
|
|
1263
|
+
ignoreClassFieldInitialValues: true,
|
|
1264
|
+
ignoreEnums: true,
|
|
1265
|
+
ignoreNumericLiteralTypes: true,
|
|
1266
|
+
ignoreReadonlyClassProperties: true,
|
|
1267
|
+
ignoreTypeIndexes: true
|
|
1268
|
+
}]);
|
|
1269
|
+
builder.addRule("@typescript-eslint/explicit-function-return-type", ["error", {
|
|
1270
|
+
allowExpressions: true,
|
|
1271
|
+
allowTypedFunctionExpressions: true,
|
|
1272
|
+
allowHigherOrderFunctions: true,
|
|
1273
|
+
allowIIFEs: true
|
|
1274
|
+
}]);
|
|
1275
|
+
builder.addRule("@typescript-eslint/explicit-member-accessibility", "error");
|
|
1276
|
+
builder.addRule("@typescript-eslint/prefer-enum-initializers", "error");
|
|
1277
|
+
builder.addRule("@typescript-eslint/naming-convention", [
|
|
1278
|
+
"error",
|
|
1279
|
+
{
|
|
1280
|
+
selector: "variable",
|
|
1281
|
+
format: isReact ? [
|
|
1282
|
+
"strictCamelCase",
|
|
1283
|
+
"UPPER_CASE",
|
|
1284
|
+
"StrictPascalCase"
|
|
1285
|
+
] : ["strictCamelCase", "UPPER_CASE"],
|
|
1286
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1287
|
+
trailingUnderscore: "allow",
|
|
1288
|
+
filter: {
|
|
1289
|
+
regex: "[- ]",
|
|
1290
|
+
match: false
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
selector: "function",
|
|
1295
|
+
format: isReact ? ["strictCamelCase", "StrictPascalCase"] : ["strictCamelCase"]
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
selector: "parameter",
|
|
1299
|
+
format: ["strictCamelCase"],
|
|
1300
|
+
leadingUnderscore: "allow"
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
selector: "import",
|
|
1304
|
+
format: [
|
|
1305
|
+
"strictCamelCase",
|
|
1306
|
+
"StrictPascalCase",
|
|
1307
|
+
"UPPER_CASE"
|
|
1308
|
+
]
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
selector: [
|
|
1312
|
+
"classProperty",
|
|
1313
|
+
"parameterProperty",
|
|
1314
|
+
"classMethod",
|
|
1315
|
+
"objectLiteralMethod",
|
|
1316
|
+
"typeMethod",
|
|
1317
|
+
"accessor"
|
|
1318
|
+
],
|
|
1319
|
+
format: ["strictCamelCase"],
|
|
1320
|
+
leadingUnderscore: "allowSingleOrDouble",
|
|
1321
|
+
trailingUnderscore: "allow",
|
|
1322
|
+
filter: {
|
|
1323
|
+
regex: "[- ]",
|
|
1324
|
+
match: false
|
|
1325
|
+
}
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
selector: ["objectLiteralProperty", "typeProperty"],
|
|
1329
|
+
format: null
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
selector: "typeLike",
|
|
1333
|
+
format: ["StrictPascalCase"]
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
selector: "interface",
|
|
1337
|
+
format: ["StrictPascalCase"],
|
|
1338
|
+
custom: {
|
|
1339
|
+
regex: "^I[A-Z]",
|
|
1340
|
+
match: false
|
|
1341
|
+
}
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
selector: "typeParameter",
|
|
1345
|
+
format: ["PascalCase"],
|
|
1346
|
+
custom: {
|
|
1347
|
+
regex: "^(T([A-Z][a-zA-Z]*)?|[A-Z])$",
|
|
1348
|
+
match: true
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
selector: "variable",
|
|
1353
|
+
types: ["boolean"],
|
|
1354
|
+
format: ["StrictPascalCase"],
|
|
1355
|
+
prefix: [
|
|
1356
|
+
"is",
|
|
1357
|
+
"has",
|
|
1358
|
+
"can",
|
|
1359
|
+
"should",
|
|
1360
|
+
"will",
|
|
1361
|
+
"did"
|
|
1362
|
+
]
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
selector: ["classProperty", "objectLiteralProperty"],
|
|
1366
|
+
format: null,
|
|
1367
|
+
modifiers: ["requiresQuotes"]
|
|
1368
|
+
}
|
|
1369
|
+
]);
|
|
1370
|
+
builder.addRule("@typescript-eslint/member-ordering", ["warn", { default: [
|
|
1371
|
+
"signature",
|
|
1372
|
+
"call-signature",
|
|
1373
|
+
"public-static-field",
|
|
1374
|
+
"protected-static-field",
|
|
1375
|
+
"private-static-field",
|
|
1376
|
+
"#private-static-field",
|
|
1377
|
+
"static-field",
|
|
1378
|
+
"public-static-method",
|
|
1379
|
+
"protected-static-method",
|
|
1380
|
+
"private-static-method",
|
|
1381
|
+
"#private-static-method",
|
|
1382
|
+
"static-method",
|
|
1383
|
+
"public-decorated-field",
|
|
1384
|
+
"protected-decorated-field",
|
|
1385
|
+
"private-decorated-field",
|
|
1386
|
+
"public-instance-field",
|
|
1387
|
+
"protected-instance-field",
|
|
1388
|
+
"private-instance-field",
|
|
1389
|
+
"#private-instance-field",
|
|
1390
|
+
"public-abstract-field",
|
|
1391
|
+
"protected-abstract-field",
|
|
1392
|
+
"field",
|
|
1393
|
+
"public-constructor",
|
|
1394
|
+
"protected-constructor",
|
|
1395
|
+
"private-constructor",
|
|
1396
|
+
"constructor",
|
|
1397
|
+
["public-get", "public-set"],
|
|
1398
|
+
["protected-get", "protected-set"],
|
|
1399
|
+
["private-get", "private-set"],
|
|
1400
|
+
["#private-get", "#private-set"],
|
|
1401
|
+
"public-decorated-method",
|
|
1402
|
+
"protected-decorated-method",
|
|
1403
|
+
"private-decorated-method",
|
|
1404
|
+
"public-instance-method",
|
|
1405
|
+
"protected-instance-method",
|
|
1406
|
+
"private-instance-method",
|
|
1407
|
+
"#private-instance-method",
|
|
1408
|
+
"public-abstract-method",
|
|
1409
|
+
"protected-abstract-method",
|
|
1410
|
+
"method"
|
|
1411
|
+
] }]);
|
|
1412
|
+
}
|
|
1413
|
+
return builder.build();
|
|
1662
1414
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/configs/unicorn.ts
|
|
1417
|
+
/**
|
|
1418
|
+
* Unicorn config — modern JavaScript idioms and best practices.
|
|
1419
|
+
* We hand-pick rules instead of using `flat/recommended` because the
|
|
1420
|
+
* recommended preset includes opinionated rules we disagree with
|
|
1421
|
+
* (e.g., `no-null`, `no-nested-ternary`, `filename-case`).
|
|
1422
|
+
*
|
|
1423
|
+
* @see https://github.com/sindresorhus/eslint-plugin-unicorn#rules
|
|
1424
|
+
*/
|
|
1666
1425
|
function unicornConfig(opts) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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",
|
|
1784
|
-
// ── Modern API preferences ────────────────────────────────────
|
|
1785
|
-
// Prefer .flatMap() over .map().flat() — single pass, more readable
|
|
1786
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat-map.md
|
|
1787
|
-
"unicorn/prefer-array-flat-map": "error",
|
|
1788
|
-
// Prefer .find() over .filter()[0] — stops at first match
|
|
1789
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-find.md
|
|
1790
|
-
"unicorn/prefer-array-find": "error",
|
|
1791
|
-
// Prefer .flat() over manual recursive flattening
|
|
1792
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-flat.md
|
|
1793
|
-
"unicorn/prefer-array-flat": "error",
|
|
1794
|
-
// Prefer .indexOf() over manual search loops
|
|
1795
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-index-of.md
|
|
1796
|
-
"unicorn/prefer-array-index-of": "error",
|
|
1797
|
-
// Prefer .some() over .find() !== undefined — semantic intent
|
|
1798
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-array-some.md
|
|
1799
|
-
"unicorn/prefer-array-some": "error",
|
|
1800
|
-
// Prefer .at(-1) over arr[arr.length - 1] — cleaner negative indexing
|
|
1801
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-at.md
|
|
1802
|
-
"unicorn/prefer-at": "error",
|
|
1803
|
-
// Prefer .includes() over .indexOf() !== -1 — boolean intent
|
|
1804
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-includes.md
|
|
1805
|
-
"unicorn/prefer-includes": "error",
|
|
1806
|
-
// Prefer .before()/.after()/.replaceWith() over parent.insertBefore()
|
|
1807
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md
|
|
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",
|
|
1812
|
-
// Prefer Math.log10/Math.hypot over manual math — accurate and readable
|
|
1813
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-math-apis.md
|
|
1814
|
-
"unicorn/prefer-modern-math-apis": "error",
|
|
1815
|
-
// Prefer negative index over length-based — cleaner with .slice(-n)
|
|
1816
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-negative-index.md
|
|
1817
|
-
"unicorn/prefer-negative-index": "error",
|
|
1818
|
-
// Prefer Number.isFinite/Number.isNaN over global — no coercion
|
|
1819
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-number-properties.md
|
|
1820
|
-
"unicorn/prefer-number-properties": "error",
|
|
1821
|
-
// Prefer Object.fromEntries() over manual reduce for key-value mapping
|
|
1822
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-object-from-entries.md
|
|
1823
|
-
"unicorn/prefer-object-from-entries": "error",
|
|
1824
|
-
// Prefer Set.has() over Array.includes() for repeated lookups — O(1)
|
|
1825
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-set-has.md
|
|
1826
|
-
"unicorn/prefer-set-has": "error",
|
|
1827
|
-
// Prefer .replaceAll() over regex with global flag — clearer intent
|
|
1828
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-replace-all.md
|
|
1829
|
-
"unicorn/prefer-string-replace-all": "error",
|
|
1830
|
-
// Prefer .slice() over .substr()/.substring() — consistent, no gotchas
|
|
1831
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-slice.md
|
|
1832
|
-
"unicorn/prefer-string-slice": "error",
|
|
1833
|
-
// Prefer .startsWith()/.endsWith() over regex — simpler, faster
|
|
1834
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-starts-ends-with.md
|
|
1835
|
-
"unicorn/prefer-string-starts-ends-with": "error",
|
|
1836
|
-
// Prefer .trimStart()/.trimEnd() over .trimLeft()/.trimRight()
|
|
1837
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-string-trim-start-end.md
|
|
1838
|
-
"unicorn/prefer-string-trim-start-end": "error",
|
|
1839
|
-
// Prefer structuredClone() over JSON.parse(JSON.stringify()) — handles more types
|
|
1840
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-structured-clone.md
|
|
1841
|
-
"unicorn/prefer-structured-clone": "error",
|
|
1842
|
-
// Prefer top-level await over async IIFE — cleaner module pattern
|
|
1843
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-top-level-await.md
|
|
1844
|
-
"unicorn/prefer-top-level-await": "error",
|
|
1845
|
-
// Throw TypeError for type checks — correct error type
|
|
1846
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-type-error.md
|
|
1847
|
-
"unicorn/prefer-type-error": "error",
|
|
1848
|
-
// ── Regex ─────────────────────────────────────────────────────
|
|
1849
|
-
// Disabled — conflicts with regexp/strict from eslint-plugin-regexp.
|
|
1850
|
-
// Both rules auto-fix regex escaping but disagree on whether characters
|
|
1851
|
-
// like { } ] should be escaped, causing circular fixes.
|
|
1852
|
-
// regexp/strict is the more thorough rule, so we defer to it.
|
|
1853
|
-
"unicorn/better-regex": "off",
|
|
1854
|
-
// ── Misc ──────────────────────────────────────────────────────
|
|
1855
|
-
// Enforce `error` name in catch blocks — consistent naming
|
|
1856
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/catch-error-name.md
|
|
1857
|
-
"unicorn/catch-error-name": ["error", { name: "error" }],
|
|
1858
|
-
// Enforce `new` for builtins that require it (Map, Set, WeakMap, etc.)
|
|
1859
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/new-for-builtins.md
|
|
1860
|
-
"unicorn/new-for-builtins": "error",
|
|
1861
|
-
// Prefer Array.from({length}) or fill() over new Array(n) — explicit intent
|
|
1862
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-new-array.md
|
|
1863
|
-
"unicorn/no-new-array": "error",
|
|
1864
|
-
// Prefer Buffer.from()/Buffer.alloc() over new Buffer() — deprecated
|
|
1865
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-new-buffer.md
|
|
1866
|
-
"unicorn/no-new-buffer": "error",
|
|
1867
|
-
// Forbid unreadable destructuring like `const [,,, d] = arr`
|
|
1868
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-unreadable-array-destructuring.md
|
|
1869
|
-
"unicorn/no-unreadable-array-destructuring": "error",
|
|
1870
|
-
// Remove unnecessary `.0` in numbers (1.0 → 1) — cleaner
|
|
1871
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-zero-fractions.md
|
|
1872
|
-
"unicorn/no-zero-fractions": "error",
|
|
1873
|
-
// Enforce lowercase hex (0xff not 0xFF) — consistent
|
|
1874
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/number-literal-case.md
|
|
1875
|
-
"unicorn/number-literal-case": "error",
|
|
1876
|
-
// Enforce numeric separators (1_000_000 not 1000000) — readable large numbers
|
|
1877
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/numeric-separators-style.md
|
|
1878
|
-
"unicorn/numeric-separators-style": "error",
|
|
1879
|
-
// Prefer `export { x } from 'y'` over import then re-export
|
|
1880
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-export-from.md
|
|
1881
|
-
"unicorn/prefer-export-from": ["error", { checkUsedVariables: false }],
|
|
1882
|
-
// Prefer built-in coercion (String, Number, Boolean) over wrapper functions
|
|
1883
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-native-coercion-functions.md
|
|
1884
|
-
"unicorn/prefer-native-coercion-functions": "error",
|
|
1885
|
-
// Prefer .test() over .match() for boolean regex checks
|
|
1886
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md
|
|
1887
|
-
"unicorn/prefer-regexp-test": "error",
|
|
1888
|
-
// Prefer [...iterable] spread over Array.from(iterable) — concise
|
|
1889
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-spread.md
|
|
1890
|
-
"unicorn/prefer-spread": "error",
|
|
1891
|
-
// Prefer relative URLs over absolute same-origin URLs
|
|
1892
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/relative-url-style.md
|
|
1893
|
-
"unicorn/relative-url-style": "error",
|
|
1894
|
-
// Prefer `node:fs` over `fs` — explicit built-in module protocol
|
|
1895
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md
|
|
1896
|
-
"unicorn/prefer-node-protocol": "error",
|
|
1897
|
-
// Prefer `globalThis` over `window`/`self`/`global` — universal
|
|
1898
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-global-this.md
|
|
1899
|
-
"unicorn/prefer-global-this": "error",
|
|
1900
|
-
// Prefer omitting unused catch binding — cleaner syntax
|
|
1901
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-optional-catch-binding.md
|
|
1902
|
-
"unicorn/prefer-optional-catch-binding": "error",
|
|
1903
|
-
// Prefer `Date.now()` over `new Date().getTime()` — direct and readable
|
|
1904
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-date-now.md
|
|
1905
|
-
"unicorn/prefer-date-now": "error",
|
|
1906
|
-
// Enforce consistent `utf-8` casing for text encoding identifiers
|
|
1907
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/text-encoding-identifier-case.md
|
|
1908
|
-
"unicorn/text-encoding-identifier-case": "error",
|
|
1909
|
-
// Prefer `import.meta.url`/`import.meta.dirname` over `__filename`/`__dirname`
|
|
1910
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-import-meta-properties.md
|
|
1911
|
-
"unicorn/prefer-import-meta-properties": "error",
|
|
1912
|
-
// OFF: Sequential .push() is natural in codegen and conditional builders.
|
|
1913
|
-
// No real performance benefit in modern V8 — purely stylistic.
|
|
1914
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-single-call.md
|
|
1915
|
-
"unicorn/prefer-single-call": "off"
|
|
1916
|
-
}
|
|
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();
|
|
1426
|
+
const isAi = opts?.ai === true;
|
|
1427
|
+
const builder = createConfig({
|
|
1428
|
+
name: "eslint-config-setup/unicorn",
|
|
1429
|
+
presets: [{
|
|
1430
|
+
plugins: { unicorn: unicornPlugin },
|
|
1431
|
+
rules: {
|
|
1432
|
+
"unicorn/no-abusive-eslint-disable": "error",
|
|
1433
|
+
"unicorn/no-instanceof-builtins": "error",
|
|
1434
|
+
"unicorn/no-invalid-remove-event-listener": "error",
|
|
1435
|
+
"unicorn/no-invalid-fetch-options": "error",
|
|
1436
|
+
"unicorn/no-typeof-undefined": "error",
|
|
1437
|
+
"unicorn/no-useless-fallback-in-spread": "error",
|
|
1438
|
+
"unicorn/no-useless-length-check": "error",
|
|
1439
|
+
"unicorn/no-useless-spread": "error",
|
|
1440
|
+
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
1441
|
+
"unicorn/no-useless-undefined": ["warn", { checkArguments: false }],
|
|
1442
|
+
"unicorn/no-await-in-promise-methods": "error",
|
|
1443
|
+
"unicorn/no-negation-in-equality-check": "error",
|
|
1444
|
+
"unicorn/no-thenable": "error",
|
|
1445
|
+
"unicorn/no-await-expression-member": "error",
|
|
1446
|
+
"unicorn/no-this-assignment": "error",
|
|
1447
|
+
"unicorn/no-unnecessary-await": "error",
|
|
1448
|
+
"unicorn/no-unnecessary-slice-end": "error",
|
|
1449
|
+
"unicorn/no-immediate-mutation": "off",
|
|
1450
|
+
"unicorn/no-accessor-recursion": "error",
|
|
1451
|
+
"unicorn/no-anonymous-default-export": "error",
|
|
1452
|
+
"unicorn/no-array-method-this-argument": "error",
|
|
1453
|
+
"unicorn/no-array-callback-reference": "error",
|
|
1454
|
+
"unicorn/no-unreadable-iife": "error",
|
|
1455
|
+
"unicorn/error-message": "error",
|
|
1456
|
+
"unicorn/throw-new-error": "error",
|
|
1457
|
+
"unicorn/consistent-empty-array-spread": "error",
|
|
1458
|
+
"unicorn/consistent-date-clone": "error",
|
|
1459
|
+
"unicorn/consistent-existence-index-check": "error",
|
|
1460
|
+
"unicorn/consistent-function-scoping": "warn",
|
|
1461
|
+
"unicorn/filename-case": ["error", {
|
|
1462
|
+
cases: {
|
|
1463
|
+
camelCase: true,
|
|
1464
|
+
pascalCase: true,
|
|
1465
|
+
kebabCase: true
|
|
1466
|
+
},
|
|
1467
|
+
ignore: ["__tests__"]
|
|
1468
|
+
}],
|
|
1469
|
+
"unicorn/no-array-reduce": "warn",
|
|
1470
|
+
"unicorn/no-for-loop": "warn",
|
|
1471
|
+
"unicorn/prefer-array-flat-map": "error",
|
|
1472
|
+
"unicorn/prefer-array-find": "error",
|
|
1473
|
+
"unicorn/prefer-array-flat": "error",
|
|
1474
|
+
"unicorn/prefer-array-index-of": "error",
|
|
1475
|
+
"unicorn/prefer-array-some": "error",
|
|
1476
|
+
"unicorn/prefer-at": "error",
|
|
1477
|
+
"unicorn/prefer-includes": "error",
|
|
1478
|
+
"unicorn/prefer-modern-dom-apis": "error",
|
|
1479
|
+
"unicorn/prefer-dom-node-dataset": "error",
|
|
1480
|
+
"unicorn/prefer-modern-math-apis": "error",
|
|
1481
|
+
"unicorn/prefer-negative-index": "error",
|
|
1482
|
+
"unicorn/prefer-number-properties": "error",
|
|
1483
|
+
"unicorn/prefer-object-from-entries": "error",
|
|
1484
|
+
"unicorn/prefer-set-has": "error",
|
|
1485
|
+
"unicorn/prefer-string-replace-all": "error",
|
|
1486
|
+
"unicorn/prefer-string-slice": "error",
|
|
1487
|
+
"unicorn/prefer-string-starts-ends-with": "error",
|
|
1488
|
+
"unicorn/prefer-string-trim-start-end": "error",
|
|
1489
|
+
"unicorn/prefer-structured-clone": "error",
|
|
1490
|
+
"unicorn/prefer-top-level-await": "error",
|
|
1491
|
+
"unicorn/prefer-type-error": "error",
|
|
1492
|
+
"unicorn/better-regex": "off",
|
|
1493
|
+
"unicorn/catch-error-name": ["error", { name: "error" }],
|
|
1494
|
+
"unicorn/new-for-builtins": "error",
|
|
1495
|
+
"unicorn/no-new-array": "error",
|
|
1496
|
+
"unicorn/no-new-buffer": "error",
|
|
1497
|
+
"unicorn/no-unreadable-array-destructuring": "error",
|
|
1498
|
+
"unicorn/no-zero-fractions": "error",
|
|
1499
|
+
"unicorn/number-literal-case": "error",
|
|
1500
|
+
"unicorn/numeric-separators-style": "error",
|
|
1501
|
+
"unicorn/prefer-export-from": ["error", { checkUsedVariables: false }],
|
|
1502
|
+
"unicorn/prefer-native-coercion-functions": "error",
|
|
1503
|
+
"unicorn/prefer-regexp-test": "error",
|
|
1504
|
+
"unicorn/prefer-spread": "error",
|
|
1505
|
+
"unicorn/relative-url-style": "error",
|
|
1506
|
+
"unicorn/prefer-node-protocol": "error",
|
|
1507
|
+
"unicorn/prefer-global-this": "error",
|
|
1508
|
+
"unicorn/prefer-optional-catch-binding": "error",
|
|
1509
|
+
"unicorn/prefer-date-now": "error",
|
|
1510
|
+
"unicorn/text-encoding-identifier-case": "error",
|
|
1511
|
+
"unicorn/prefer-import-meta-properties": "error",
|
|
1512
|
+
"unicorn/prefer-single-call": "off"
|
|
1513
|
+
}
|
|
1514
|
+
}]
|
|
1515
|
+
});
|
|
1516
|
+
if (isAi) {
|
|
1517
|
+
builder.overrideSeverity("unicorn/consistent-function-scoping", "error");
|
|
1518
|
+
builder.overrideSeverity("unicorn/no-array-reduce", "error");
|
|
1519
|
+
builder.overrideSeverity("unicorn/no-for-loop", "error");
|
|
1520
|
+
builder.addRule("unicorn/no-for-each", "error");
|
|
1521
|
+
builder.addRule("unicorn/prefer-ternary", ["error", "only-single-line"]);
|
|
1522
|
+
builder.addRule("unicorn/prefer-switch", ["error", { minimumCases: 3 }]);
|
|
1523
|
+
builder.addRule("unicorn/no-useless-switch-case", "error");
|
|
1524
|
+
builder.addRule("unicorn/custom-error-definition", "error");
|
|
1525
|
+
builder.addRule("unicorn/prefer-default-parameters", "error");
|
|
1526
|
+
builder.addRule("unicorn/prefer-logical-operator-over-ternary", "error");
|
|
1527
|
+
builder.addRule("unicorn/prefer-math-min-max", "error");
|
|
1528
|
+
builder.addRule("unicorn/prefer-set-size", "error");
|
|
1529
|
+
builder.addRule("unicorn/explicit-length-check", "error");
|
|
1530
|
+
builder.addRule("unicorn/switch-case-braces", "error");
|
|
1531
|
+
builder.addRule("unicorn/no-array-push-push", "error");
|
|
1532
|
+
}
|
|
1533
|
+
return builder.build();
|
|
1938
1534
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1535
|
+
//#endregion
|
|
1536
|
+
//#region src/overrides/config-files.ts
|
|
1537
|
+
/**
|
|
1538
|
+
* Config file overrides — relaxed rules for tool configuration files.
|
|
1539
|
+
* Config files (vite, vitest, next, tailwind, postcss) have their own
|
|
1540
|
+
* patterns that conflict with strict app-code rules.
|
|
1541
|
+
*
|
|
1542
|
+
* File patterns: *.config.*, vite.config.*, vitest.config.*, etc.
|
|
1543
|
+
*/
|
|
1941
1544
|
function configFilesOverride() {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
"no-console": "off",
|
|
1965
|
-
// Config files often have magic numbers (ports, sizes, timeouts)
|
|
1966
|
-
"no-magic-numbers": "off",
|
|
1967
|
-
"@typescript-eslint/no-magic-numbers": "off"
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
];
|
|
1545
|
+
return [{
|
|
1546
|
+
name: "eslint-config-setup/config-files",
|
|
1547
|
+
files: [
|
|
1548
|
+
"**/*.config.{ts,mts,cts,js,mjs,cjs}",
|
|
1549
|
+
"**/vite.config.*",
|
|
1550
|
+
"**/vitest.config.*",
|
|
1551
|
+
"**/next.config.*",
|
|
1552
|
+
"**/tailwind.config.*",
|
|
1553
|
+
"**/postcss.config.*"
|
|
1554
|
+
],
|
|
1555
|
+
rules: {
|
|
1556
|
+
"import/no-default-export": "off",
|
|
1557
|
+
complexity: "off",
|
|
1558
|
+
"max-lines": "off",
|
|
1559
|
+
"max-lines-per-function": "off",
|
|
1560
|
+
"max-statements": "off",
|
|
1561
|
+
"@typescript-eslint/no-require-imports": "off",
|
|
1562
|
+
"no-console": "off",
|
|
1563
|
+
"no-magic-numbers": "off",
|
|
1564
|
+
"@typescript-eslint/no-magic-numbers": "off"
|
|
1565
|
+
}
|
|
1566
|
+
}];
|
|
1971
1567
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region src/overrides/declarations.ts
|
|
1570
|
+
/**
|
|
1571
|
+
* Declaration file overrides — minimal rules for `.d.ts` files.
|
|
1572
|
+
* Declaration files follow their own patterns (ambient declarations,
|
|
1573
|
+
* interface merging, namespaces) that conflict with app-code rules.
|
|
1574
|
+
*
|
|
1575
|
+
* File pattern: *.d.ts files
|
|
1576
|
+
*/
|
|
1974
1577
|
function declarationsOverride() {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
"import/no-duplicates": "off",
|
|
1994
|
-
// Unused imports are common in re-export declaration files
|
|
1995
|
-
"unused-imports/no-unused-imports": "off",
|
|
1996
|
-
// ── AI mode rules that don't apply to declaration files ────────
|
|
1997
|
-
// Declarations inherit return types from the implementation
|
|
1998
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
1999
|
-
// Third-party naming conventions must be followed as-is
|
|
2000
|
-
"@typescript-eslint/naming-convention": "off",
|
|
2001
|
-
// Abbreviations in third-party types are unavoidable
|
|
2002
|
-
"unicorn/prevent-abbreviations": "off",
|
|
2003
|
-
// .d.ts filename is dictated by the module it declares
|
|
2004
|
-
"unicorn/filename-case": "off"
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
];
|
|
1578
|
+
return [{
|
|
1579
|
+
name: "eslint-config-setup/declarations",
|
|
1580
|
+
files: ["**/*.d.ts"],
|
|
1581
|
+
rules: {
|
|
1582
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
1583
|
+
"@typescript-eslint/no-empty-interface": "off",
|
|
1584
|
+
"@typescript-eslint/no-empty-object-type": "off",
|
|
1585
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
1586
|
+
"@typescript-eslint/consistent-type-definitions": "off",
|
|
1587
|
+
"@typescript-eslint/no-namespace": "off",
|
|
1588
|
+
"import/no-duplicates": "off",
|
|
1589
|
+
"unused-imports/no-unused-imports": "off",
|
|
1590
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
1591
|
+
"@typescript-eslint/naming-convention": "off",
|
|
1592
|
+
"unicorn/prevent-abbreviations": "off",
|
|
1593
|
+
"unicorn/filename-case": "off"
|
|
1594
|
+
}
|
|
1595
|
+
}];
|
|
2008
1596
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1597
|
+
//#endregion
|
|
1598
|
+
//#region src/overrides/e2e.ts
|
|
1599
|
+
/**
|
|
1600
|
+
* E2E test overrides — Playwright rules + relaxed strictness for E2E tests.
|
|
1601
|
+
*
|
|
1602
|
+
* File pattern: *.spec.ts (Playwright convention)
|
|
1603
|
+
*
|
|
1604
|
+
* @see https://github.com/playwright-community/eslint-plugin-playwright#rules
|
|
1605
|
+
*/
|
|
2012
1606
|
function e2eOverride() {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-eval.md
|
|
2042
|
-
"playwright/no-eval": "error",
|
|
2043
|
-
// No test.only — prevents accidentally skipping tests in CI
|
|
2044
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-focused-test.md
|
|
2045
|
-
"playwright/no-focused-test": "error",
|
|
2046
|
-
// Warn on { force: true } — bypasses visibility/actionability checks
|
|
2047
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-force-option.md
|
|
2048
|
-
"playwright/no-force-option": "warn",
|
|
2049
|
-
// No waitUntil: "networkidle" — unreliable, use specific waits
|
|
2050
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-networkidle.md
|
|
2051
|
-
"playwright/no-networkidle": "error",
|
|
2052
|
-
// No page.pause() — debugging artifact, breaks CI
|
|
2053
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-page-pause.md
|
|
2054
|
-
"playwright/no-page-pause": "error",
|
|
2055
|
-
// Warn on test.skip — track disabled tests
|
|
2056
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-skipped-test.md
|
|
2057
|
-
"playwright/no-skipped-test": "warn",
|
|
2058
|
-
// No unnecessary await on non-promise values
|
|
2059
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-useless-await.md
|
|
2060
|
-
"playwright/no-useless-await": "error",
|
|
2061
|
-
// No double-negative assertions: expect(x).not.not...
|
|
2062
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-useless-not.md
|
|
2063
|
-
"playwright/no-useless-not": "error",
|
|
2064
|
-
// Warn on waitForSelector — prefer locators with auto-wait
|
|
2065
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-wait-for-selector.md
|
|
2066
|
-
"playwright/no-wait-for-selector": "warn",
|
|
2067
|
-
// No hardcoded timeouts — use Playwright's built-in waiting
|
|
2068
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-wait-for-timeout.md
|
|
2069
|
-
"playwright/no-wait-for-timeout": "error",
|
|
2070
|
-
// Prefer web-first assertions (toBeVisible, toHaveText) — auto-retry
|
|
2071
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/prefer-web-first-assertions.md
|
|
2072
|
-
"playwright/prefer-web-first-assertions": "error",
|
|
2073
|
-
// Validate expect() usage — proper matcher and argument count
|
|
2074
|
-
// https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/valid-expect.md
|
|
2075
|
-
"playwright/valid-expect": "error",
|
|
2076
|
-
// ── Relaxed rules for E2E tests ───────────────────────────────
|
|
2077
|
-
// E2E tests are long procedural scripts with many page interactions
|
|
2078
|
-
"max-lines": "off",
|
|
2079
|
-
"max-lines-per-function": "off",
|
|
2080
|
-
"max-statements": "off"
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
];
|
|
1607
|
+
return [{
|
|
1608
|
+
name: "eslint-config-setup/e2e",
|
|
1609
|
+
files: ["**/*.spec.ts"],
|
|
1610
|
+
plugins: { playwright: playwrightPlugin },
|
|
1611
|
+
rules: {
|
|
1612
|
+
"playwright/expect-expect": "error",
|
|
1613
|
+
"playwright/max-nested-describe": ["error", { max: 3 }],
|
|
1614
|
+
"playwright/missing-playwright-await": "error",
|
|
1615
|
+
"playwright/no-conditional-expect": "error",
|
|
1616
|
+
"playwright/no-conditional-in-test": "warn",
|
|
1617
|
+
"playwright/no-element-handle": "error",
|
|
1618
|
+
"playwright/no-eval": "error",
|
|
1619
|
+
"playwright/no-focused-test": "error",
|
|
1620
|
+
"playwright/no-force-option": "warn",
|
|
1621
|
+
"playwright/no-networkidle": "error",
|
|
1622
|
+
"playwright/no-page-pause": "error",
|
|
1623
|
+
"playwright/no-skipped-test": "warn",
|
|
1624
|
+
"playwright/no-useless-await": "error",
|
|
1625
|
+
"playwright/no-useless-not": "error",
|
|
1626
|
+
"playwright/no-wait-for-selector": "warn",
|
|
1627
|
+
"playwright/no-wait-for-timeout": "error",
|
|
1628
|
+
"playwright/prefer-web-first-assertions": "error",
|
|
1629
|
+
"playwright/valid-expect": "error",
|
|
1630
|
+
"max-lines": "off",
|
|
1631
|
+
"max-lines-per-function": "off",
|
|
1632
|
+
"max-statements": "off"
|
|
1633
|
+
}
|
|
1634
|
+
}];
|
|
2084
1635
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
1636
|
+
//#endregion
|
|
1637
|
+
//#region src/overrides/scripts.ts
|
|
1638
|
+
/**
|
|
1639
|
+
* Script file overrides — relaxed rules for build/dev scripts.
|
|
1640
|
+
* Scripts are CLI tools that legitimately use console.log and process.exit.
|
|
1641
|
+
*
|
|
1642
|
+
* File pattern: scripts dir (*.ts, *.mts, *.js, *.mjs)
|
|
1643
|
+
*/
|
|
2087
1644
|
function scriptsOverride() {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
"max-lines-per-function": "off",
|
|
2101
|
-
// Scripts are often procedural without explicit return types
|
|
2102
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
2103
|
-
// Scripts legitimately call process.exit()
|
|
2104
|
-
// https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-process-exit.md
|
|
2105
|
-
"unicorn/no-process-exit": "off"
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
];
|
|
1645
|
+
return [{
|
|
1646
|
+
name: "eslint-config-setup/scripts",
|
|
1647
|
+
files: ["**/scripts/**/*.{ts,mts,js,mjs}"],
|
|
1648
|
+
rules: {
|
|
1649
|
+
"no-console": "off",
|
|
1650
|
+
"node/no-process-exit": "off",
|
|
1651
|
+
"max-lines": "off",
|
|
1652
|
+
"max-lines-per-function": "off",
|
|
1653
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
1654
|
+
"unicorn/no-process-exit": "off"
|
|
1655
|
+
}
|
|
1656
|
+
}];
|
|
2109
1657
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
1658
|
+
//#endregion
|
|
1659
|
+
//#region src/overrides/stories.ts
|
|
1660
|
+
/**
|
|
1661
|
+
* Storybook overrides — Storybook best-practice rules for story files.
|
|
1662
|
+
*
|
|
1663
|
+
* File pattern: *.stories.{ts,tsx}
|
|
1664
|
+
*
|
|
1665
|
+
* @see https://github.com/storybookjs/eslint-plugin-storybook#supported-rules
|
|
1666
|
+
*/
|
|
2113
1667
|
function storiesOverride() {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
// No deprecated storiesOf() API — use CSF (Component Story Format)
|
|
2136
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-stories-of.md
|
|
2137
|
-
"storybook/no-stories-of": "error",
|
|
2138
|
-
// Warn if title is in meta — auto-title is preferred in CSF 3
|
|
2139
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-title-property-in-meta.md
|
|
2140
|
-
"storybook/no-title-property-in-meta": "warn",
|
|
2141
|
-
// Story exports should be PascalCase — component naming convention
|
|
2142
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/prefer-pascal-case.md
|
|
2143
|
-
"storybook/prefer-pascal-case": "error",
|
|
2144
|
-
// File must export at least one story — prevents empty story files
|
|
2145
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/story-exports.md
|
|
2146
|
-
"storybook/story-exports": "error",
|
|
2147
|
-
// Use Storybook's expect() instead of Jest/Vitest in play functions
|
|
2148
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/use-storybook-expect.md
|
|
2149
|
-
"storybook/use-storybook-expect": "error",
|
|
2150
|
-
// Use Storybook's Testing Library, not direct import
|
|
2151
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/use-storybook-testing-library.md
|
|
2152
|
-
"storybook/use-storybook-testing-library": "error",
|
|
2153
|
-
// Enforce `satisfies Meta<typeof Component>` on default export — type-safe meta
|
|
2154
|
-
// https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/meta-satisfies-type.md
|
|
2155
|
-
"storybook/meta-satisfies-type": "error",
|
|
2156
|
-
// ── Relaxed rules for stories ─────────────────────────────────
|
|
2157
|
-
// Stories require default exports (meta object)
|
|
2158
|
-
"import/no-default-export": "off",
|
|
2159
|
-
// Stories can be long (many variants of a component)
|
|
2160
|
-
"max-lines": "off",
|
|
2161
|
-
"max-lines-per-function": "off"
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
];
|
|
1668
|
+
return [{
|
|
1669
|
+
name: "eslint-config-setup/stories",
|
|
1670
|
+
files: ["**/*.stories.{ts,tsx}"],
|
|
1671
|
+
plugins: { storybook: storybookPlugin },
|
|
1672
|
+
rules: {
|
|
1673
|
+
"storybook/await-interactions": "error",
|
|
1674
|
+
"storybook/default-exports": "error",
|
|
1675
|
+
"storybook/hierarchy-separator": "error",
|
|
1676
|
+
"storybook/no-redundant-story-name": "error",
|
|
1677
|
+
"storybook/no-stories-of": "error",
|
|
1678
|
+
"storybook/no-title-property-in-meta": "warn",
|
|
1679
|
+
"storybook/prefer-pascal-case": "error",
|
|
1680
|
+
"storybook/story-exports": "error",
|
|
1681
|
+
"storybook/use-storybook-expect": "error",
|
|
1682
|
+
"storybook/use-storybook-testing-library": "error",
|
|
1683
|
+
"storybook/meta-satisfies-type": "error",
|
|
1684
|
+
"import/no-default-export": "off",
|
|
1685
|
+
"max-lines": "off",
|
|
1686
|
+
"max-lines-per-function": "off"
|
|
1687
|
+
}
|
|
1688
|
+
}];
|
|
2165
1689
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
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"
|
|
1690
|
+
//#endregion
|
|
1691
|
+
//#region src/overrides/tests.ts
|
|
1692
|
+
const TEST_FILES = ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}"];
|
|
1693
|
+
const VITEST_RULES = {
|
|
1694
|
+
"vitest/expect-expect": "error",
|
|
1695
|
+
"vitest/no-identical-title": "error",
|
|
1696
|
+
"vitest/no-focused-tests": "error",
|
|
1697
|
+
"vitest/no-disabled-tests": "warn",
|
|
1698
|
+
"vitest/no-duplicate-hooks": "error",
|
|
1699
|
+
"vitest/prefer-to-be": "error",
|
|
1700
|
+
"vitest/prefer-to-have-length": "error",
|
|
1701
|
+
"vitest/valid-expect": "error",
|
|
1702
|
+
"vitest/valid-title": "error",
|
|
1703
|
+
"vitest/no-conditional-in-test": "error",
|
|
1704
|
+
"vitest/no-conditional-expect": "error",
|
|
1705
|
+
"vitest/no-standalone-expect": "error",
|
|
1706
|
+
"vitest/prefer-strict-equal": "error",
|
|
1707
|
+
"vitest/prefer-spy-on": "error",
|
|
1708
|
+
"vitest/require-to-throw-message": "error",
|
|
1709
|
+
"max-lines": "off",
|
|
1710
|
+
"max-lines-per-function": "off",
|
|
1711
|
+
"max-statements": "off",
|
|
1712
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
1713
|
+
"@typescript-eslint/no-non-null-assertion": "off",
|
|
1714
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
1715
|
+
"@typescript-eslint/no-unsafe-member-access": "off"
|
|
2229
1716
|
};
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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"
|
|
1717
|
+
const TESTING_LIBRARY_RULES = {
|
|
1718
|
+
"testing-library/await-async-events": "error",
|
|
1719
|
+
"testing-library/await-async-queries": "error",
|
|
1720
|
+
"testing-library/await-async-utils": "error",
|
|
1721
|
+
"testing-library/no-await-sync-events": "error",
|
|
1722
|
+
"testing-library/no-await-sync-queries": "error",
|
|
1723
|
+
"testing-library/no-container": "error",
|
|
1724
|
+
"testing-library/no-debugging-utils": "warn",
|
|
1725
|
+
"testing-library/no-node-access": "error",
|
|
1726
|
+
"testing-library/no-render-in-lifecycle": "error",
|
|
1727
|
+
"testing-library/no-unnecessary-act": "error",
|
|
1728
|
+
"testing-library/no-wait-for-multiple-assertions": "error",
|
|
1729
|
+
"testing-library/no-wait-for-side-effects": "error",
|
|
1730
|
+
"testing-library/prefer-find-by": "error",
|
|
1731
|
+
"testing-library/prefer-presence-queries": "error",
|
|
1732
|
+
"testing-library/prefer-query-by-disappearance": "error",
|
|
1733
|
+
"testing-library/prefer-screen-queries": "error",
|
|
1734
|
+
"testing-library/render-result-naming-convention": "error"
|
|
2283
1735
|
};
|
|
1736
|
+
/**
|
|
1737
|
+
* Test file overrides — Vitest rules + Testing Library + relaxed strictness.
|
|
1738
|
+
*
|
|
1739
|
+
* File patterns: *.test.{ts,tsx}, __tests__/*.{ts,tsx}
|
|
1740
|
+
*
|
|
1741
|
+
* @see https://github.com/vitest-dev/eslint-plugin-vitest#rules
|
|
1742
|
+
* @see https://github.com/testing-library/eslint-plugin-testing-library#supported-rules
|
|
1743
|
+
*/
|
|
2284
1744
|
function testsOverride() {
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
files: TEST_FILES2,
|
|
2297
|
-
plugins: {
|
|
2298
|
-
"testing-library": testingLibraryPlugin
|
|
2299
|
-
},
|
|
2300
|
-
rules: TESTING_LIBRARY_RULES
|
|
2301
|
-
}
|
|
2302
|
-
];
|
|
1745
|
+
return [{
|
|
1746
|
+
name: "eslint-config-setup/tests",
|
|
1747
|
+
files: TEST_FILES,
|
|
1748
|
+
plugins: { vitest: vitestPlugin },
|
|
1749
|
+
rules: VITEST_RULES
|
|
1750
|
+
}, {
|
|
1751
|
+
name: "eslint-config-setup/tests-testing-library",
|
|
1752
|
+
files: TEST_FILES,
|
|
1753
|
+
plugins: { "testing-library": testingLibraryPlugin },
|
|
1754
|
+
rules: TESTING_LIBRARY_RULES
|
|
1755
|
+
}];
|
|
2303
1756
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
var typedPlugin = oxlintPlugin;
|
|
1757
|
+
//#endregion
|
|
1758
|
+
//#region src/oxlint/integration.ts
|
|
1759
|
+
const typedPlugin = oxlintPlugin;
|
|
2308
1760
|
function addOxlintConfig(configs, configName, blockName) {
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
1761
|
+
const raw = typedPlugin.configs[configName];
|
|
1762
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
1763
|
+
for (const [index, item] of items.entries()) {
|
|
1764
|
+
const suffix = items.length > 1 ? `-${index + 1}` : "";
|
|
1765
|
+
configs.push({
|
|
1766
|
+
...item,
|
|
1767
|
+
name: `eslint-config-setup/${blockName}${suffix}`
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
2315
1770
|
}
|
|
2316
1771
|
function addReactOxlintConfigs(configs, opts) {
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
addOxlintConfig(configs, "flat/react-hooks", "oxlint-react-hooks");
|
|
2323
|
-
if (opts.ai) {
|
|
2324
|
-
addOxlintConfig(configs, "flat/react-perf", "oxlint-react-perf");
|
|
2325
|
-
}
|
|
1772
|
+
if (!opts.react) return;
|
|
1773
|
+
addOxlintConfig(configs, "flat/react", "oxlint-react");
|
|
1774
|
+
addOxlintConfig(configs, "flat/jsx-a11y", "oxlint-jsx-a11y");
|
|
1775
|
+
addOxlintConfig(configs, "flat/react-hooks", "oxlint-react-hooks");
|
|
1776
|
+
if (opts.ai) addOxlintConfig(configs, "flat/react-perf", "oxlint-react-perf");
|
|
2326
1777
|
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Appends eslint-plugin-oxlint configs that disable all ESLint rules
|
|
1780
|
+
* already covered by OxLint. Must be the LAST config in the array.
|
|
1781
|
+
*
|
|
1782
|
+
* This allows running `oxlint && eslint` where OxLint handles the fast
|
|
1783
|
+
* checks and ESLint only runs type-aware and specialty rules.
|
|
1784
|
+
*/
|
|
2327
1785
|
function oxlintIntegration(opts) {
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
addOxlintConfig(configs, "flat/jsdoc", "oxlint-jsdoc");
|
|
2338
|
-
return configs;
|
|
1786
|
+
const configs = [];
|
|
1787
|
+
addOxlintConfig(configs, "flat/recommended", "oxlint");
|
|
1788
|
+
addReactOxlintConfigs(configs, opts);
|
|
1789
|
+
if (opts.node) addOxlintConfig(configs, "flat/node", "oxlint-node");
|
|
1790
|
+
addOxlintConfig(configs, "flat/typescript", "oxlint-typescript");
|
|
1791
|
+
addOxlintConfig(configs, "flat/unicorn", "oxlint-unicorn");
|
|
1792
|
+
addOxlintConfig(configs, "flat/import", "oxlint-import");
|
|
1793
|
+
addOxlintConfig(configs, "flat/jsdoc", "oxlint-jsdoc");
|
|
1794
|
+
return configs;
|
|
2339
1795
|
}
|
|
2340
|
-
|
|
2341
|
-
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region src/presets/standard.ts
|
|
1798
|
+
/**
|
|
1799
|
+
* Complexity preset — limits for production code that encourage small,
|
|
1800
|
+
* focused functions and aggressive extraction of helper functions.
|
|
1801
|
+
* @see https://eslint.org/docs/latest/rules/#suggestions (complexity rules)
|
|
1802
|
+
*/
|
|
2342
1803
|
function standardComplexity() {
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
}
|
|
2378
|
-
},
|
|
2379
|
-
{
|
|
2380
|
-
name: "eslint-config-setup/complexity-tests-relaxed",
|
|
2381
|
-
files: ["**/*.test.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}", "**/*.spec.ts"],
|
|
2382
|
-
rules: {
|
|
2383
|
-
"max-nested-callbacks": "off",
|
|
2384
|
-
"max-lines": "off",
|
|
2385
|
-
"max-lines-per-function": "off",
|
|
2386
|
-
"max-statements": "off"
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
];
|
|
1804
|
+
return [{
|
|
1805
|
+
name: "eslint-config-setup/complexity",
|
|
1806
|
+
rules: {
|
|
1807
|
+
complexity: ["error", 10],
|
|
1808
|
+
"max-depth": ["error", 3],
|
|
1809
|
+
"max-nested-callbacks": ["error", 2],
|
|
1810
|
+
"max-params": ["error", 3],
|
|
1811
|
+
"max-statements": ["error", 15],
|
|
1812
|
+
"max-lines-per-function": ["error", {
|
|
1813
|
+
max: 50,
|
|
1814
|
+
skipBlankLines: true,
|
|
1815
|
+
skipComments: true
|
|
1816
|
+
}],
|
|
1817
|
+
"max-lines": ["error", {
|
|
1818
|
+
max: 300,
|
|
1819
|
+
skipBlankLines: true,
|
|
1820
|
+
skipComments: true
|
|
1821
|
+
}],
|
|
1822
|
+
"sonarjs/cognitive-complexity": ["error", 10]
|
|
1823
|
+
}
|
|
1824
|
+
}, {
|
|
1825
|
+
name: "eslint-config-setup/complexity-tests-relaxed",
|
|
1826
|
+
files: [
|
|
1827
|
+
"**/*.test.{ts,tsx}",
|
|
1828
|
+
"**/__tests__/**/*.{ts,tsx}",
|
|
1829
|
+
"**/*.spec.ts"
|
|
1830
|
+
],
|
|
1831
|
+
rules: {
|
|
1832
|
+
"max-nested-callbacks": "off",
|
|
1833
|
+
"max-lines": "off",
|
|
1834
|
+
"max-lines-per-function": "off",
|
|
1835
|
+
"max-statements": "off"
|
|
1836
|
+
}
|
|
1837
|
+
}];
|
|
2390
1838
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
1839
|
+
//#endregion
|
|
1840
|
+
//#region src/build/compose.ts
|
|
1841
|
+
/**
|
|
1842
|
+
* Composes a full flat config array from the given options.
|
|
1843
|
+
* This is the core logic used by the build system to generate configs.
|
|
1844
|
+
*/
|
|
2393
1845
|
function addCoreConfigs(config, opts) {
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
...perfectionistConfig(),
|
|
2400
|
-
...unicornConfig({ ai }),
|
|
2401
|
-
...regexpConfig({ ai }),
|
|
2402
|
-
...jsdocConfig({ ai }),
|
|
2403
|
-
...cspellConfig(),
|
|
2404
|
-
...sonarjsConfig({ ai }),
|
|
2405
|
-
...securityConfig(),
|
|
2406
|
-
...deMorganConfig()
|
|
2407
|
-
);
|
|
1846
|
+
const ai = opts.ai;
|
|
1847
|
+
config.push(...baseConfig({ ai }), ...typescriptConfig({
|
|
1848
|
+
ai,
|
|
1849
|
+
react: opts.react
|
|
1850
|
+
}), ...importsConfig(), ...perfectionistConfig(), ...unicornConfig({ ai }), ...regexpConfig({ ai }), ...jsdocConfig({ ai }), ...cspellConfig(), ...sonarjsConfig({ ai }), ...securityConfig(), ...deMorganConfig());
|
|
2408
1851
|
}
|
|
2409
1852
|
function addConditionalConfigs(config, opts) {
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
}
|
|
2423
|
-
if (opts.ai) {
|
|
2424
|
-
config.push(...aiConfig());
|
|
2425
|
-
config.push(...perfectionistAiConfig());
|
|
2426
|
-
config.push(...packageJsonAiConfig());
|
|
2427
|
-
}
|
|
1853
|
+
if (!opts.node) config.push(...compatConfig());
|
|
1854
|
+
if (!opts.ai) config.push(...standardComplexity());
|
|
1855
|
+
if (opts.node) config.push(...nodeConfig({ ai: opts.ai }));
|
|
1856
|
+
if (opts.react) {
|
|
1857
|
+
config.push(...reactConfig({ ai: opts.ai }));
|
|
1858
|
+
config.push(...reactEffectConfig());
|
|
1859
|
+
}
|
|
1860
|
+
if (opts.ai) {
|
|
1861
|
+
config.push(...aiConfig());
|
|
1862
|
+
config.push(...perfectionistAiConfig());
|
|
1863
|
+
config.push(...packageJsonAiConfig());
|
|
1864
|
+
}
|
|
2428
1865
|
}
|
|
2429
1866
|
function addFileTypeOverrides(config) {
|
|
2430
|
-
|
|
2431
|
-
...testsOverride(),
|
|
2432
|
-
...e2eOverride(),
|
|
2433
|
-
...storiesOverride(),
|
|
2434
|
-
...configFilesOverride(),
|
|
2435
|
-
...declarationsOverride(),
|
|
2436
|
-
...scriptsOverride()
|
|
2437
|
-
);
|
|
1867
|
+
config.push(...testsOverride(), ...e2eOverride(), ...storiesOverride(), ...configFilesOverride(), ...declarationsOverride(), ...scriptsOverride());
|
|
2438
1868
|
}
|
|
2439
1869
|
function addFinalConfigs(config, opts) {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
config.push(...oxlintIntegration(opts));
|
|
2443
|
-
}
|
|
1870
|
+
config.push(...jsonConfig(), ...packageJsonConfig(), ...markdownConfig(), ...prettierCompatConfig());
|
|
1871
|
+
if (opts.oxlint) config.push(...oxlintIntegration(opts));
|
|
2444
1872
|
}
|
|
2445
1873
|
function composeConfig(opts) {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
1874
|
+
const config = [];
|
|
1875
|
+
addCoreConfigs(config, opts);
|
|
1876
|
+
addConditionalConfigs(config, opts);
|
|
1877
|
+
addFileTypeOverrides(config);
|
|
1878
|
+
addFinalConfigs(config, opts);
|
|
1879
|
+
return config;
|
|
2452
1880
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
composeConfig,
|
|
2457
|
-
configFilesOverride as configFiles,
|
|
2458
|
-
cspellConfig as cspell,
|
|
2459
|
-
deMorganConfig as deMorgan,
|
|
2460
|
-
declarationsOverride as declarations,
|
|
2461
|
-
e2eOverride as e2e,
|
|
2462
|
-
importsConfig as imports,
|
|
2463
|
-
jsdocConfig as jsdoc,
|
|
2464
|
-
jsonConfig as json,
|
|
2465
|
-
markdownConfig as markdown,
|
|
2466
|
-
nodeConfig as node,
|
|
2467
|
-
oxlintIntegration as oxlint,
|
|
2468
|
-
packageJsonConfig as packageJson,
|
|
2469
|
-
packageJsonAiConfig as packageJsonAi,
|
|
2470
|
-
perfectionistConfig as perfectionist,
|
|
2471
|
-
perfectionistAiConfig as perfectionistAi,
|
|
2472
|
-
prettierCompatConfig as prettier,
|
|
2473
|
-
reactConfig as react,
|
|
2474
|
-
reactEffectConfig as reactEffect,
|
|
2475
|
-
regexpConfig as regexp,
|
|
2476
|
-
scriptsOverride as scripts,
|
|
2477
|
-
securityConfig as security,
|
|
2478
|
-
sonarjsConfig as sonarjs,
|
|
2479
|
-
standardComplexity,
|
|
2480
|
-
storiesOverride as stories,
|
|
2481
|
-
testsOverride as tests,
|
|
2482
|
-
typescriptConfig as typescript,
|
|
2483
|
-
unicornConfig as unicorn
|
|
2484
|
-
};
|
|
1881
|
+
//#endregion
|
|
1882
|
+
export { aiConfig as ai, baseConfig as base, composeConfig, configFilesOverride as configFiles, cspellConfig as cspell, deMorganConfig as deMorgan, declarationsOverride as declarations, e2eOverride as e2e, importsConfig as imports, jsdocConfig as jsdoc, jsonConfig as json, markdownConfig as markdown, nodeConfig as node, oxlintIntegration as oxlint, packageJsonConfig as packageJson, packageJsonAiConfig as packageJsonAi, perfectionistConfig as perfectionist, perfectionistAiConfig as perfectionistAi, prettierCompatConfig as prettier, reactConfig as react, reactEffectConfig as reactEffect, regexpConfig as regexp, scriptsOverride as scripts, securityConfig as security, sonarjsConfig as sonarjs, standardComplexity, storiesOverride as stories, testsOverride as tests, typescriptConfig as typescript, unicornConfig as unicorn };
|
|
1883
|
+
|
|
2485
1884
|
//# sourceMappingURL=modules.js.map
|