@mkrz/oxlint-config 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/LICENSE.anti-slop +21 -0
- package/LICENSE.stylistic +22 -0
- package/README.md +283 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +624 -0
- package/dist/plugin/index.d.ts +4 -0
- package/dist/plugin/index.js +1957 -0
- package/docs/performance.md +29 -0
- package/docs/rules.md +190 -0
- package/package.json +85 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import { defineConfig as defineConfig$1 } from "oxlint";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/configs/ignore-patterns.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build output, caches, and lockfiles that should never be linted.
|
|
8
|
+
*
|
|
9
|
+
* Oxlint only honors `ignorePatterns` on the root config; patterns inside an
|
|
10
|
+
* extended config are dropped. Spread this list into the root config.
|
|
11
|
+
*/
|
|
12
|
+
const ignorePatterns = [
|
|
13
|
+
"**/__snapshots__/**",
|
|
14
|
+
"**/.next/**",
|
|
15
|
+
"**/.turbo/**",
|
|
16
|
+
"**/*.d.ts",
|
|
17
|
+
"**/*.min.*",
|
|
18
|
+
"**/CHANGELOG.md",
|
|
19
|
+
"**/coverage/**",
|
|
20
|
+
"**/dist/**",
|
|
21
|
+
"**/node_modules/**",
|
|
22
|
+
"**/out/**",
|
|
23
|
+
"**/output/**",
|
|
24
|
+
"**/package-lock.json",
|
|
25
|
+
"**/pnpm-lock.yaml",
|
|
26
|
+
"**/yarn.lock"
|
|
27
|
+
];
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/configs/base.ts
|
|
30
|
+
/**
|
|
31
|
+
* Fast defaults for TypeScript projects.
|
|
32
|
+
*
|
|
33
|
+
* Rule policy: the `correctness` and `suspicious` categories are enabled at
|
|
34
|
+
* error severity for every enabled plugin, so rules in those categories are
|
|
35
|
+
* not repeated here. The explicit list below only holds rules outside those
|
|
36
|
+
* categories, rules that need options, and rules turned off for TypeScript
|
|
37
|
+
* because the compiler already reports them.
|
|
38
|
+
*/
|
|
39
|
+
const base = {
|
|
40
|
+
plugins: [
|
|
41
|
+
"import",
|
|
42
|
+
"oxc",
|
|
43
|
+
"promise",
|
|
44
|
+
"typescript",
|
|
45
|
+
"unicorn"
|
|
46
|
+
],
|
|
47
|
+
categories: {
|
|
48
|
+
correctness: "error",
|
|
49
|
+
suspicious: "error"
|
|
50
|
+
},
|
|
51
|
+
env: {
|
|
52
|
+
builtin: true,
|
|
53
|
+
node: true
|
|
54
|
+
},
|
|
55
|
+
options: { reportUnusedDisableDirectives: "error" },
|
|
56
|
+
rules: {
|
|
57
|
+
"mkrz/no-chained-type-assertions": "error",
|
|
58
|
+
"mkrz/no-known-value-widening": "error",
|
|
59
|
+
"mkrz/no-let": "error",
|
|
60
|
+
"mkrz/no-module-mocking": "error",
|
|
61
|
+
"mkrz/no-reflect-apply": "error",
|
|
62
|
+
"mkrz/no-reflect-get": "error",
|
|
63
|
+
"mkrz/no-type-assertion": "error",
|
|
64
|
+
"mkrz/no-unknown-parameters": "error",
|
|
65
|
+
"mkrz/no-unknown-returns": "error",
|
|
66
|
+
"mkrz/no-unknown-type-aliases": "error",
|
|
67
|
+
"mkrz/no-unsafe-dictionary-type": "error",
|
|
68
|
+
"mkrz/no-widen-then-assert": "error",
|
|
69
|
+
"mkrz/padding-line-between-statements": [
|
|
70
|
+
"error",
|
|
71
|
+
{
|
|
72
|
+
blankLine: "always",
|
|
73
|
+
prev: {
|
|
74
|
+
selector: "*",
|
|
75
|
+
lineMode: "multiline"
|
|
76
|
+
},
|
|
77
|
+
next: "*"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
blankLine: "always",
|
|
81
|
+
prev: "block-like",
|
|
82
|
+
next: "*"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
blankLine: "always",
|
|
86
|
+
prev: "*",
|
|
87
|
+
next: "return"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
blankLine: "any",
|
|
91
|
+
prev: "import",
|
|
92
|
+
next: "import"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"array-callback-return": "error",
|
|
96
|
+
complexity: ["error", { max: 10 }],
|
|
97
|
+
curly: ["error", "all"],
|
|
98
|
+
"default-case-last": "error",
|
|
99
|
+
eqeqeq: [
|
|
100
|
+
"error",
|
|
101
|
+
"always",
|
|
102
|
+
{ null: "ignore" }
|
|
103
|
+
],
|
|
104
|
+
"import/consistent-type-specifier-style": ["error", "prefer-inline"],
|
|
105
|
+
"import/no-duplicates": "error",
|
|
106
|
+
"import/no-relative-parent-imports": "error",
|
|
107
|
+
"no-alert": "error",
|
|
108
|
+
"no-array-constructor": "error",
|
|
109
|
+
"no-case-declarations": "error",
|
|
110
|
+
"no-empty": "error",
|
|
111
|
+
"no-empty-function": "error",
|
|
112
|
+
"no-fallthrough": "error",
|
|
113
|
+
"no-new-wrappers": "error",
|
|
114
|
+
"no-promise-executor-return": "error",
|
|
115
|
+
"no-prototype-builtins": "error",
|
|
116
|
+
"no-self-compare": "error",
|
|
117
|
+
"no-template-curly-in-string": "error",
|
|
118
|
+
"no-unreachable-loop": "error",
|
|
119
|
+
"no-unused-vars": ["error", {
|
|
120
|
+
argsIgnorePattern: "^_",
|
|
121
|
+
caughtErrorsIgnorePattern: "^_",
|
|
122
|
+
varsIgnorePattern: "^_"
|
|
123
|
+
}],
|
|
124
|
+
"no-var": "error",
|
|
125
|
+
"no-warning-comments": ["error", { terms: ["@nocommit"] }],
|
|
126
|
+
"oxc/bad-bitwise-operator": "error",
|
|
127
|
+
"oxc/no-accumulating-spread": "error",
|
|
128
|
+
"prefer-const": "error",
|
|
129
|
+
"prefer-object-has-own": "error",
|
|
130
|
+
"promise/no-return-wrap": "error",
|
|
131
|
+
"typescript/adjacent-overload-signatures": "error",
|
|
132
|
+
"typescript/array-type": "error",
|
|
133
|
+
"typescript/ban-ts-comment": "error",
|
|
134
|
+
"typescript/ban-tslint-comment": "error",
|
|
135
|
+
"typescript/class-literal-property-style": "error",
|
|
136
|
+
"typescript/consistent-generic-constructors": "error",
|
|
137
|
+
"typescript/consistent-indexed-object-style": "error",
|
|
138
|
+
"typescript/consistent-type-definitions": ["error", "type"],
|
|
139
|
+
"typescript/consistent-type-imports": ["error", {
|
|
140
|
+
fixStyle: "inline-type-imports",
|
|
141
|
+
prefer: "type-imports"
|
|
142
|
+
}],
|
|
143
|
+
"typescript/no-empty-object-type": "error",
|
|
144
|
+
"typescript/no-explicit-any": "error",
|
|
145
|
+
"typescript/no-inferrable-types": "error",
|
|
146
|
+
"typescript/no-namespace": "error",
|
|
147
|
+
"typescript/no-non-null-asserted-nullish-coalescing": "error",
|
|
148
|
+
"typescript/no-non-null-assertion": "error",
|
|
149
|
+
"typescript/no-require-imports": "error",
|
|
150
|
+
"typescript/no-unnecessary-type-assertion": "off",
|
|
151
|
+
"typescript/no-unsafe-function-type": "error",
|
|
152
|
+
"typescript/no-unsafe-type-assertion": "off",
|
|
153
|
+
"typescript/prefer-for-of": "error",
|
|
154
|
+
"typescript/prefer-function-type": "error",
|
|
155
|
+
"unicorn/no-abusive-eslint-disable": "error",
|
|
156
|
+
"unicorn/no-magic-array-flat-depth": "error",
|
|
157
|
+
"unicorn/no-unnecessary-slice-end": "error",
|
|
158
|
+
"unicorn/no-useless-promise-resolve-reject": "error",
|
|
159
|
+
"unicorn/prefer-array-flat-map": "error",
|
|
160
|
+
"unicorn/prefer-array-some": "error",
|
|
161
|
+
"unicorn/prefer-date-now": "error",
|
|
162
|
+
"unicorn/prefer-node-protocol": "error",
|
|
163
|
+
"unicorn/prefer-number-properties": "error",
|
|
164
|
+
"unicorn/throw-new-error": "error"
|
|
165
|
+
},
|
|
166
|
+
overrides: [{
|
|
167
|
+
files: [
|
|
168
|
+
"**/*.cts",
|
|
169
|
+
"**/*.mts",
|
|
170
|
+
"**/*.ts",
|
|
171
|
+
"**/*.tsx"
|
|
172
|
+
],
|
|
173
|
+
rules: {
|
|
174
|
+
"constructor-super": "off",
|
|
175
|
+
"getter-return": "off",
|
|
176
|
+
"no-class-assign": "off",
|
|
177
|
+
"no-const-assign": "off",
|
|
178
|
+
"no-dupe-class-members": "off",
|
|
179
|
+
"no-dupe-keys": "off",
|
|
180
|
+
"no-func-assign": "off",
|
|
181
|
+
"no-import-assign": "off",
|
|
182
|
+
"no-new-native-nonconstructor": "off",
|
|
183
|
+
"no-obj-calls": "off",
|
|
184
|
+
"no-setter-return": "off",
|
|
185
|
+
"no-this-before-super": "off",
|
|
186
|
+
"no-unreachable": "off",
|
|
187
|
+
"no-unsafe-negation": "off",
|
|
188
|
+
"no-with": "off",
|
|
189
|
+
"prefer-rest-params": "error",
|
|
190
|
+
"prefer-spread": "error"
|
|
191
|
+
}
|
|
192
|
+
}, {
|
|
193
|
+
files: ["**/*.cjs"],
|
|
194
|
+
rules: { "typescript/no-require-imports": "off" }
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/configs/monorepo.ts
|
|
199
|
+
const message = "Move shared code into a package and import it from there instead.";
|
|
200
|
+
/**
|
|
201
|
+
* Overrides for the apps/packages layout.
|
|
202
|
+
*
|
|
203
|
+
* Relative imports that climb out of a package are already rejected by
|
|
204
|
+
* `import/no-relative-parent-imports` in `base`, so only workspace package
|
|
205
|
+
* names need a rule here.
|
|
206
|
+
*/
|
|
207
|
+
function monorepo({ appsPath = ["apps/**"], packagesPath = ["packages/**"], appPackages = [] } = {}) {
|
|
208
|
+
const packageRules = { "mkrz/package-disable-policy": "error" };
|
|
209
|
+
if (appPackages.length > 0) {
|
|
210
|
+
const patterns = appPackages.flatMap((name) => [name, `${name}/**`]);
|
|
211
|
+
packageRules["mkrz/no-app-requires"] = ["error", patterns];
|
|
212
|
+
packageRules["no-restricted-imports"] = ["error", { patterns: [{
|
|
213
|
+
group: patterns,
|
|
214
|
+
message
|
|
215
|
+
}] }];
|
|
216
|
+
}
|
|
217
|
+
return { overrides: [{
|
|
218
|
+
files: appsPath,
|
|
219
|
+
rules: { "mkrz/no-oxlint-disable": "error" }
|
|
220
|
+
}, {
|
|
221
|
+
files: packagesPath,
|
|
222
|
+
rules: packageRules
|
|
223
|
+
}] };
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/configs/plugin-specifiers.ts
|
|
227
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
228
|
+
const moduleExtension = path.extname(modulePath);
|
|
229
|
+
const pluginCandidates = [`../plugin/index${moduleExtension}`, `./plugin/index${moduleExtension}`].map((candidate) => fileURLToPath(new URL(candidate, import.meta.url)));
|
|
230
|
+
const mkrzPluginSpecifier = pluginCandidates.find((candidate) => fs.existsSync(candidate));
|
|
231
|
+
if (mkrzPluginSpecifier === void 0) throw new Error(`@mkrz/oxlint-config cannot locate its plugin module; looked at ${pluginCandidates.join(" and ")}.`);
|
|
232
|
+
/** Absolute specifier so `extends` of this config still loads the plugin. */
|
|
233
|
+
const mkrzPlugin = {
|
|
234
|
+
name: "mkrz",
|
|
235
|
+
specifier: mkrzPluginSpecifier
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/configs/react.ts
|
|
239
|
+
/**
|
|
240
|
+
* React, accessibility, compiler, query, and store rules.
|
|
241
|
+
*
|
|
242
|
+
* React and jsx-a11y rules in the `correctness` and `suspicious` categories
|
|
243
|
+
* are already on through `base`; only rules outside those categories, rules
|
|
244
|
+
* with options, and rules turned off are listed.
|
|
245
|
+
*/
|
|
246
|
+
const react = {
|
|
247
|
+
plugins: ["jsx-a11y", "react"],
|
|
248
|
+
env: { browser: true },
|
|
249
|
+
rules: {
|
|
250
|
+
"import/no-unassigned-import": ["error", { allow: [
|
|
251
|
+
"**/*.css",
|
|
252
|
+
"**/*.less",
|
|
253
|
+
"**/*.sass",
|
|
254
|
+
"**/*.scss"
|
|
255
|
+
] }],
|
|
256
|
+
"jsx-a11y/interactive-supports-focus": ["error", { tabbable: [
|
|
257
|
+
"button",
|
|
258
|
+
"checkbox",
|
|
259
|
+
"link",
|
|
260
|
+
"searchbox",
|
|
261
|
+
"spinbutton",
|
|
262
|
+
"switch",
|
|
263
|
+
"textbox"
|
|
264
|
+
] }],
|
|
265
|
+
"jsx-a11y/label-has-associated-control": ["error", {
|
|
266
|
+
controlComponents: [
|
|
267
|
+
"Checkbox",
|
|
268
|
+
"Input",
|
|
269
|
+
"Select",
|
|
270
|
+
"Switch"
|
|
271
|
+
],
|
|
272
|
+
depth: 3
|
|
273
|
+
}],
|
|
274
|
+
"jsx-a11y/no-interactive-element-to-noninteractive-role": ["error", {
|
|
275
|
+
canvas: ["img"],
|
|
276
|
+
tr: ["none", "presentation"]
|
|
277
|
+
}],
|
|
278
|
+
"jsx-a11y/no-noninteractive-element-interactions": ["error", {
|
|
279
|
+
alert: [
|
|
280
|
+
"onKeyDown",
|
|
281
|
+
"onKeyPress",
|
|
282
|
+
"onKeyUp"
|
|
283
|
+
],
|
|
284
|
+
body: ["onError", "onLoad"],
|
|
285
|
+
dialog: [
|
|
286
|
+
"onKeyDown",
|
|
287
|
+
"onKeyPress",
|
|
288
|
+
"onKeyUp"
|
|
289
|
+
],
|
|
290
|
+
handlers: [
|
|
291
|
+
"onClick",
|
|
292
|
+
"onError",
|
|
293
|
+
"onKeyDown",
|
|
294
|
+
"onKeyPress",
|
|
295
|
+
"onKeyUp",
|
|
296
|
+
"onLoad",
|
|
297
|
+
"onMouseDown",
|
|
298
|
+
"onMouseUp"
|
|
299
|
+
],
|
|
300
|
+
iframe: ["onError", "onLoad"],
|
|
301
|
+
img: ["onError", "onLoad"]
|
|
302
|
+
}],
|
|
303
|
+
"jsx-a11y/no-noninteractive-element-to-interactive-role": ["error", {
|
|
304
|
+
fieldset: ["presentation", "radiogroup"],
|
|
305
|
+
li: [
|
|
306
|
+
"menuitem",
|
|
307
|
+
"menuitemcheckbox",
|
|
308
|
+
"menuitemradio",
|
|
309
|
+
"option",
|
|
310
|
+
"row",
|
|
311
|
+
"tab",
|
|
312
|
+
"treeitem"
|
|
313
|
+
],
|
|
314
|
+
ol: [
|
|
315
|
+
"listbox",
|
|
316
|
+
"menu",
|
|
317
|
+
"menubar",
|
|
318
|
+
"radiogroup",
|
|
319
|
+
"tablist",
|
|
320
|
+
"tree",
|
|
321
|
+
"treegrid"
|
|
322
|
+
],
|
|
323
|
+
table: ["grid"],
|
|
324
|
+
td: ["gridcell"],
|
|
325
|
+
ul: [
|
|
326
|
+
"listbox",
|
|
327
|
+
"menu",
|
|
328
|
+
"menubar",
|
|
329
|
+
"radiogroup",
|
|
330
|
+
"tablist",
|
|
331
|
+
"tree",
|
|
332
|
+
"treegrid"
|
|
333
|
+
]
|
|
334
|
+
}],
|
|
335
|
+
"jsx-a11y/no-noninteractive-tabindex": ["error", {
|
|
336
|
+
allowExpressionValues: true,
|
|
337
|
+
roles: ["tabpanel"],
|
|
338
|
+
tags: []
|
|
339
|
+
}],
|
|
340
|
+
"jsx-a11y/no-static-element-interactions": ["error", {
|
|
341
|
+
allowExpressionValues: true,
|
|
342
|
+
handlers: [
|
|
343
|
+
"onClick",
|
|
344
|
+
"onKeyDown",
|
|
345
|
+
"onKeyPress",
|
|
346
|
+
"onKeyUp",
|
|
347
|
+
"onMouseDown",
|
|
348
|
+
"onMouseUp"
|
|
349
|
+
]
|
|
350
|
+
}],
|
|
351
|
+
"mkrz/no-query-result-destructuring": "error",
|
|
352
|
+
"mkrz/no-restricted-react-hooks": "error",
|
|
353
|
+
"mkrz/require-store-selector": "error",
|
|
354
|
+
"react/button-has-type": "error",
|
|
355
|
+
"react/checked-requires-onchange-or-readonly": "error",
|
|
356
|
+
"react/display-name": "error",
|
|
357
|
+
"react/exhaustive-effect-dependencies": "off",
|
|
358
|
+
"react/jsx-no-constructed-context-values": "error",
|
|
359
|
+
"react/jsx-no-target-blank": "error",
|
|
360
|
+
"react/jsx-no-useless-fragment": "error",
|
|
361
|
+
"react/no-array-index-key": "error",
|
|
362
|
+
"react/no-unescaped-entities": "error",
|
|
363
|
+
"react/no-unknown-property": "error",
|
|
364
|
+
"react/react-in-jsx-scope": "off",
|
|
365
|
+
"react/require-render-return": "error",
|
|
366
|
+
"react/rules-of-hooks": "error",
|
|
367
|
+
"react/unsupported-syntax": "error"
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/configs/repository.ts
|
|
372
|
+
const monorepoDirectories = ["apps", "packages"];
|
|
373
|
+
/**
|
|
374
|
+
* A `pnpm-workspace.yaml` only marks a monorepo when it declares `packages`.
|
|
375
|
+
* Single-package repositories use the file for the catalog alone.
|
|
376
|
+
*/
|
|
377
|
+
function declaresWorkspacePackages(root) {
|
|
378
|
+
const file = path.join(root, "pnpm-workspace.yaml");
|
|
379
|
+
return fs.existsSync(file) && /^packages:/mu.test(fs.readFileSync(file, "utf8"));
|
|
380
|
+
}
|
|
381
|
+
function readManifest(root) {
|
|
382
|
+
const file = path.join(root, "package.json");
|
|
383
|
+
if (!fs.existsSync(file)) return null;
|
|
384
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
385
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
386
|
+
return {
|
|
387
|
+
private: "private" in parsed && parsed.private === true,
|
|
388
|
+
workspaces: "workspaces" in parsed && parsed.workspaces != null
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Detect the repository type from the directory Oxlint runs in.
|
|
393
|
+
*
|
|
394
|
+
* A `pnpm-workspace.yaml` with `packages`, a `workspaces` field, or an `apps/`
|
|
395
|
+
* or `packages/` directory means `monorepo`. Otherwise `package.json` decides:
|
|
396
|
+
* `"private": true` means `app`, anything else `library`. Without a
|
|
397
|
+
* `package.json` the result is `app`, with a warning.
|
|
398
|
+
*
|
|
399
|
+
* A workspace without the default `apps/` and `packages/` directories also
|
|
400
|
+
* warns, because the monorepo overrides would then match no file at all.
|
|
401
|
+
*/
|
|
402
|
+
function detectRepositoryType(root, warn) {
|
|
403
|
+
const manifest = readManifest(root);
|
|
404
|
+
if (monorepoDirectories.some((directory) => fs.existsSync(path.join(root, directory)))) return "monorepo";
|
|
405
|
+
if (declaresWorkspacePackages(root) || manifest?.workspaces === true) {
|
|
406
|
+
warn(`@mkrz/oxlint-config: ${root} is a workspace but has no apps/ or packages/ directory, so the disable-directive policy applies to no file. Pass repositoryType: 'monorepo' with appsPath and packagesPath to mkrz().`);
|
|
407
|
+
return "monorepo";
|
|
408
|
+
}
|
|
409
|
+
if (manifest === null) {
|
|
410
|
+
warn(`@mkrz/oxlint-config: no package.json in ${root}; treating the repository as an application. Pass repositoryType to mkrz() to silence this.`);
|
|
411
|
+
return "app";
|
|
412
|
+
}
|
|
413
|
+
return manifest.private ? "app" : "library";
|
|
414
|
+
}
|
|
415
|
+
const app = { overrides: [{
|
|
416
|
+
files: ["**/*"],
|
|
417
|
+
rules: { "mkrz/no-oxlint-disable": "error" }
|
|
418
|
+
}] };
|
|
419
|
+
const library = { overrides: [{
|
|
420
|
+
files: ["**/*"],
|
|
421
|
+
rules: { "mkrz/package-disable-policy": "error" }
|
|
422
|
+
}] };
|
|
423
|
+
/** The overrides that carry the disable-directive and import policy. */
|
|
424
|
+
function repository(type, layout = {}) {
|
|
425
|
+
switch (type) {
|
|
426
|
+
case "app": return app;
|
|
427
|
+
case "library": return library;
|
|
428
|
+
default: return monorepo(layout);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Test-file rules: vitest checks, plus permission to use type assertions and
|
|
433
|
+
* non-null assertions.
|
|
434
|
+
*/
|
|
435
|
+
const tests = { overrides: [{
|
|
436
|
+
files: [
|
|
437
|
+
"**/*.{test,spec}.*",
|
|
438
|
+
"**/*.test-d.*",
|
|
439
|
+
"**/__tests__/**"
|
|
440
|
+
],
|
|
441
|
+
plugins: ["vitest"],
|
|
442
|
+
rules: {
|
|
443
|
+
"mkrz/no-chained-type-assertions": "off",
|
|
444
|
+
"mkrz/no-known-value-widening": "off",
|
|
445
|
+
"mkrz/no-type-assertion": "off",
|
|
446
|
+
"mkrz/no-widen-then-assert": "off",
|
|
447
|
+
"typescript/no-non-null-assertion": "off",
|
|
448
|
+
"vitest/expect-expect": "error",
|
|
449
|
+
"vitest/no-disabled-tests": "error",
|
|
450
|
+
"vitest/no-focused-tests": "error"
|
|
451
|
+
}
|
|
452
|
+
}] };
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/configs/type-aware.ts
|
|
455
|
+
function* ancestors(directory) {
|
|
456
|
+
const parent = path.dirname(directory);
|
|
457
|
+
yield directory;
|
|
458
|
+
if (parent !== directory) yield* ancestors(parent);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Whether Oxlint will find the `tsgolint` binary when run from `root`.
|
|
462
|
+
*
|
|
463
|
+
* Oxlint reads `OXLINT_TSGOLINT_PATH` first and otherwise looks for the
|
|
464
|
+
* `.bin/tsgolint` shim in the `node_modules` directories above the working
|
|
465
|
+
* directory. Resolving the package beside this config is insufficient: a
|
|
466
|
+
* linked config may live outside the executable search path.
|
|
467
|
+
*/
|
|
468
|
+
function hasTsgolint(root) {
|
|
469
|
+
if (process.env.OXLINT_TSGOLINT_PATH !== void 0) return true;
|
|
470
|
+
return [...ancestors(root)].some((directory) => fs.existsSync(path.join(directory, "node_modules", ".bin", "tsgolint")));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Type-checked rules for consumers that can pay the extra lint time.
|
|
474
|
+
*
|
|
475
|
+
* Type-aware rules in the `correctness` and `suspicious` categories are
|
|
476
|
+
* already on through `base`; only rules outside those categories are listed.
|
|
477
|
+
*/
|
|
478
|
+
const typeAware = {
|
|
479
|
+
options: { typeAware: true },
|
|
480
|
+
plugins: ["typescript"],
|
|
481
|
+
rules: {
|
|
482
|
+
"typescript/dot-notation": "error",
|
|
483
|
+
"typescript/no-misused-promises": "error",
|
|
484
|
+
"typescript/no-mixed-enums": "error",
|
|
485
|
+
"typescript/no-unsafe-argument": "error",
|
|
486
|
+
"typescript/no-unsafe-assignment": "error",
|
|
487
|
+
"typescript/no-unsafe-call": "error",
|
|
488
|
+
"typescript/no-unsafe-member-access": "error",
|
|
489
|
+
"typescript/no-unsafe-return": "error",
|
|
490
|
+
"typescript/only-throw-error": "error",
|
|
491
|
+
"typescript/prefer-find": "error",
|
|
492
|
+
"typescript/prefer-includes": "error",
|
|
493
|
+
"typescript/prefer-nullish-coalescing": "error",
|
|
494
|
+
"typescript/prefer-optional-chain": "error",
|
|
495
|
+
"typescript/prefer-promise-reject-errors": "error",
|
|
496
|
+
"typescript/prefer-regexp-exec": "error",
|
|
497
|
+
"typescript/prefer-string-starts-ends-with": "error",
|
|
498
|
+
"typescript/related-getter-setter-pairs": "error",
|
|
499
|
+
"typescript/require-await": "error",
|
|
500
|
+
"typescript/restrict-plus-operands": "error",
|
|
501
|
+
"typescript/return-await": "error",
|
|
502
|
+
"typescript/switch-exhaustiveness-check": ["error", { considerDefaultExhaustiveForUnions: true }]
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/mkrz.ts
|
|
507
|
+
/** Shallow-merge two optional record fields, the second winning. */
|
|
508
|
+
function merged(first, second) {
|
|
509
|
+
return first === void 0 && second === void 0 ? void 0 : Object.assign({}, first, second);
|
|
510
|
+
}
|
|
511
|
+
/** Concatenate two optional array fields. */
|
|
512
|
+
function joined(first, second) {
|
|
513
|
+
return first === void 0 && second === void 0 ? void 0 : [...first ?? [], ...second ?? []];
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Lay `top` over `bottom` the way `extends` should: object fields merge with
|
|
517
|
+
* `top` winning, list fields concatenate, and nothing is dropped.
|
|
518
|
+
*/
|
|
519
|
+
function layer(bottom, top) {
|
|
520
|
+
const plugins = joined(bottom.plugins, top.plugins);
|
|
521
|
+
return {
|
|
522
|
+
...bottom,
|
|
523
|
+
...top,
|
|
524
|
+
categories: merged(bottom.categories, top.categories),
|
|
525
|
+
env: merged(bottom.env, top.env),
|
|
526
|
+
globals: merged(bottom.globals, top.globals),
|
|
527
|
+
ignorePatterns: joined(bottom.ignorePatterns, top.ignorePatterns),
|
|
528
|
+
jsPlugins: joined(bottom.jsPlugins ?? void 0, top.jsPlugins ?? void 0),
|
|
529
|
+
options: merged(bottom.options, top.options),
|
|
530
|
+
overrides: joined(bottom.overrides, top.overrides),
|
|
531
|
+
plugins: plugins === void 0 ? void 0 : [...new Set(plugins)],
|
|
532
|
+
rules: merged(bottom.rules, top.rules),
|
|
533
|
+
settings: merged(bottom.settings, top.settings)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* `defineConfig` from Oxlint for consumers who compose with `extends`.
|
|
538
|
+
*
|
|
539
|
+
* Oxlint only honors `ignorePatterns`, `env`, `globals`, and `settings` on
|
|
540
|
+
* the root config and drops them from extended configs. This wrapper folds
|
|
541
|
+
* those fields from every entry of `extends` into the root, and adds this
|
|
542
|
+
* package's ignore list. Prefer the second argument of `mkrz` when you only
|
|
543
|
+
* need to layer settings on this ruleset.
|
|
544
|
+
*/
|
|
545
|
+
function defineConfig(config) {
|
|
546
|
+
const inherited = inheritedRootFields(config);
|
|
547
|
+
return defineConfig$1({
|
|
548
|
+
...config,
|
|
549
|
+
...inherited,
|
|
550
|
+
ignorePatterns: [...ignorePatterns, ...inherited.ignorePatterns ?? []]
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/** Fold root-only fields without changing Oxlint's rule/override precedence. */
|
|
554
|
+
function inheritedRootFields(config) {
|
|
555
|
+
const inherited = (config.extends ?? []).reduce((accumulated, entry) => layer(accumulated, inheritedRootFields(entry)), {});
|
|
556
|
+
return {
|
|
557
|
+
env: merged(inherited.env, config.env),
|
|
558
|
+
globals: merged(inherited.globals, config.globals),
|
|
559
|
+
settings: merged(inherited.settings, config.settings),
|
|
560
|
+
ignorePatterns: joined(inherited.ignorePatterns, config.ignorePatterns)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
const empty = {};
|
|
564
|
+
function typeAwareEnabled(requested, root, warn) {
|
|
565
|
+
if (requested === false || hasTsgolint(root)) return requested ?? true;
|
|
566
|
+
if (requested === true) throw new Error("@mkrz/oxlint-config: typeAware is on but oxlint-tsgolint is not installed. Add it with `pnpm add -D oxlint-tsgolint` or pass typeAware: false.");
|
|
567
|
+
warn("@mkrz/oxlint-config: oxlint-tsgolint is not installed; type-aware rules are off until it is. Add it with `pnpm add -D oxlint-tsgolint` or pass typeAware: false to silence this.");
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
function resolveRepositoryType(options, root, warn) {
|
|
571
|
+
return options.repositoryType === void 0 || options.repositoryType === "auto" ? detectRepositoryType(root, warn) : options.repositoryType;
|
|
572
|
+
}
|
|
573
|
+
function build(options, config, warn) {
|
|
574
|
+
const root = process.cwd();
|
|
575
|
+
const { react: withReact = true, typeAware: typeAwareOption } = options;
|
|
576
|
+
const typeAwarePiece = typeAwareEnabled(typeAwareOption, root, warn) ? typeAware : empty;
|
|
577
|
+
const reactPiece = withReact ? react : empty;
|
|
578
|
+
const repositoryPiece = options.repositoryType === "monorepo" ? repository("monorepo", options) : repository(resolveRepositoryType(options, root, warn));
|
|
579
|
+
const ruleset = {
|
|
580
|
+
jsPlugins: [mkrzPlugin],
|
|
581
|
+
plugins: [...new Set([
|
|
582
|
+
base,
|
|
583
|
+
typeAwarePiece,
|
|
584
|
+
reactPiece
|
|
585
|
+
].flatMap((piece) => piece.plugins ?? []))],
|
|
586
|
+
categories: base.categories,
|
|
587
|
+
env: {
|
|
588
|
+
...base.env,
|
|
589
|
+
...reactPiece.env
|
|
590
|
+
},
|
|
591
|
+
options: {
|
|
592
|
+
...base.options,
|
|
593
|
+
...typeAwarePiece.options
|
|
594
|
+
},
|
|
595
|
+
rules: {
|
|
596
|
+
...base.rules,
|
|
597
|
+
...typeAwarePiece.rules,
|
|
598
|
+
...reactPiece.rules
|
|
599
|
+
},
|
|
600
|
+
overrides: [
|
|
601
|
+
base,
|
|
602
|
+
tests,
|
|
603
|
+
repositoryPiece
|
|
604
|
+
].flatMap((piece) => piece.overrides ?? []),
|
|
605
|
+
ignorePatterns
|
|
606
|
+
};
|
|
607
|
+
return defineConfig$1(layer(ruleset, config));
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* The complete ruleset as one flat root config.
|
|
611
|
+
*
|
|
612
|
+
* Everything is built as a single object rather than a chain of `extends`
|
|
613
|
+
* pieces: the plugin is declared once, the rule categories are declared once,
|
|
614
|
+
* and no subset can be handed to Oxlint that fails to load or runs at the
|
|
615
|
+
* wrong severity. `config` is the consumer's own root config and is layered
|
|
616
|
+
* on top: its root `rules` win over our root rules, its `overrides` run last,
|
|
617
|
+
* and its `ignorePatterns` are added to this package's list. Matching file
|
|
618
|
+
* overrides still take precedence over root rules.
|
|
619
|
+
*/
|
|
620
|
+
function mkrz(options = {}, config = {}) {
|
|
621
|
+
return build(options, config, console.warn);
|
|
622
|
+
}
|
|
623
|
+
//#endregion
|
|
624
|
+
export { defineConfig, ignorePatterns, mkrz };
|