biome-to-oxc 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -11
- package/dist/bin/biome-to-oxc.mjs +1 -1
- package/dist/src/index.d.mts +14 -3
- package/dist/src/index.mjs +1 -1
- package/dist/{src-BN3SXk7r.mjs → src-JKoQYJ0f.mjs} +468 -104
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -22,20 +22,31 @@ Current capabilities:
|
|
|
22
22
|
|
|
23
23
|
- Converts `biome.json`/`biome.jsonc` to `.oxlintrc.json` and `.oxfmtrc.jsonc`
|
|
24
24
|
- Maps 80+ Biome linter rules to Oxlint equivalents (native `typescript/` rules)
|
|
25
|
-
- Substitutes a non-type-aware Oxlint rule for the
|
|
26
|
-
only Oxlint counterpart is type-aware, so a migration without tsgolint keeps their coverage
|
|
25
|
+
- Substitutes a non-type-aware Oxlint rule for the four Biome rules that need no types but whose
|
|
26
|
+
only Oxlint counterpart is type-aware, so a migration without tsgolint keeps their coverage, and
|
|
27
|
+
reports every other rule that maps only to a type-aware Oxlint rule as a semantic loss when
|
|
28
|
+
type-aware linting is off
|
|
27
29
|
- Transforms Biome formatter options to Oxfmt (Prettier-compatible)
|
|
28
30
|
- Normalizes `include`/`includes` field variations automatically
|
|
29
31
|
|
|
30
32
|
✅ **Preserves Representable Setup**
|
|
31
33
|
|
|
32
34
|
- Ignore patterns, override file globs, and override exclusions
|
|
33
|
-
- Negated `includes` exceptions (`!pattern`), translated into `ignorePatterns` for both tools
|
|
35
|
+
- Negated `includes` exceptions (`!pattern`), translated into `ignorePatterns` for both tools, and
|
|
36
|
+
patterns that re-include files after an exception, carried over as `!` entries (or, in an
|
|
37
|
+
override, as a second override for the re-included files)
|
|
38
|
+
- Root anchoring of `includes` patterns: Biome matches `!dist` and `*.gen.ts` at the config root
|
|
39
|
+
only, so they are written as `/dist` in `ignorePatterns` and `./*.gen.ts` in an override, where
|
|
40
|
+
Oxlint and Oxfmt would otherwise match them at any depth
|
|
41
|
+
- `includes` patterns Biome matches against no path — ending in `/`, starting with `/`, or an
|
|
42
|
+
exception starting with `./` — are left out with a warning instead of excluding the path
|
|
34
43
|
- `.biomeignore` patterns migrated into the Oxlint **and** Oxfmt `ignorePatterns`. Biome 2.x does
|
|
35
44
|
not read `.biomeignore` itself, so this deliberately narrows scope; the migration says so
|
|
36
45
|
- Linter configuration overrides
|
|
37
46
|
- Formatter configuration overrides when they can be represented as Oxfmt file-glob overrides
|
|
38
|
-
- JavaScript globals
|
|
47
|
+
- JavaScript globals, plus `env: { browser: true, node: true }`, which gives Oxlint the browser and
|
|
48
|
+
Node.js globals Biome resolves without configuration (`no-undef`, `no-global-assign` and
|
|
49
|
+
`no-implied-eval` depend on it)
|
|
39
50
|
- Rule severities (`error`/`warn`/`off`; `info` and `on` are accepted with explicit lossy-mapping warnings)
|
|
40
51
|
- Explicit `printWidth` handling (no silent changes)
|
|
41
52
|
- The set of formatted files: Oxfmt-only formats (YAML, TOML, Markdown) are excluded, and
|
|
@@ -308,7 +319,7 @@ Oxlint's broader `restriction` category.
|
|
|
308
319
|
|
|
309
320
|
### Type-Aware Rule Fallbacks
|
|
310
321
|
|
|
311
|
-
|
|
322
|
+
Four Biome rules need no type information, but their only Oxlint counterpart is a type-aware
|
|
312
323
|
(tsgolint) rule. Type-aware linting is off unless the migration enables it, so mapping straight to
|
|
313
324
|
the tsgolint rule would drop diagnostics the Biome config did produce. When type-aware linting is
|
|
314
325
|
off, a non-type-aware Oxlint rule takes its place instead:
|
|
@@ -318,15 +329,18 @@ off, a non-type-aware Oxlint rule takes its place instead:
|
|
|
318
329
|
| `useArrayFind` | `typescript/prefer-find` | `unicorn/prefer-array-find` |
|
|
319
330
|
| `useIncludes` | `typescript/prefer-includes` | `unicorn/prefer-includes` |
|
|
320
331
|
| `useStringStartsEndsWith` | `typescript/prefer-string-starts-ends-with` | `unicorn/prefer-string-starts-ends-with` |
|
|
332
|
+
| `useThrowOnlyError` | `typescript/only-throw-error` | `no-throw-literal` |
|
|
321
333
|
|
|
322
334
|
The fallback replaces the type-aware rule rather than joining it, so turning type-aware linting on
|
|
323
|
-
later can never report the same line twice.
|
|
324
|
-
reports; `unicorn/prefer-string-starts-ends-with` covers only the anchored-regex form of the four
|
|
335
|
+
later can never report the same line twice. `useArrayFind`, `useIncludes` and `useThrowOnlyError`
|
|
336
|
+
report exactly what the Biome rule reports; `unicorn/prefer-string-starts-ends-with` covers only the anchored-regex form of the four
|
|
325
337
|
Biome flags, so that substitution is reported as a semantic loss.
|
|
326
338
|
|
|
327
339
|
Every other Biome rule that maps only to a type-aware Oxlint rule — `noFloatingPromises`,
|
|
328
|
-
`noMisusedPromises`, `useOptionalChain` and the rest —
|
|
329
|
-
|
|
340
|
+
`noMisusedPromises`, `useOptionalChain`, `useLiteralKeys` and the rest — has no non-type-aware
|
|
341
|
+
Oxlint counterpart. Oxlint skips type-aware rules unless type-aware linting is on, so a migration
|
|
342
|
+
without `--type-aware` reports each of them as a semantic loss instead of emitting a rule that
|
|
343
|
+
never runs.
|
|
330
344
|
|
|
331
345
|
### Assist Actions
|
|
332
346
|
|
|
@@ -428,8 +442,8 @@ npx oxlint --type-aware --type-check
|
|
|
428
442
|
- Overlapping `overrides` are migrated as-is. Biome and Oxfmt both merge matching overrides field
|
|
429
443
|
by field with later entries winning, so the two agree; a conformance test pins this
|
|
430
444
|
- Type-aware linting is stable as of `oxlint-tsgolint` v7 and requires TypeScript 7; 60 of the 62
|
|
431
|
-
targeted typescript-eslint rules are currently implemented
|
|
432
|
-
`
|
|
445
|
+
targeted typescript-eslint rules are currently implemented (`naming-convention` and
|
|
446
|
+
`prefer-destructuring` are not), and Oxlint 1.86 and later can enable all 60
|
|
433
447
|
- CSS, JSON and GraphQL formatter overrides are mapped but may need manual review
|
|
434
448
|
- Prettier plugin support is not available in Oxfmt
|
|
435
449
|
- Assist actions are migrated only when the Biome config names them explicitly; actions enabled
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as readJsonFile, n as DefaultReporter, r as findClosestPackageJson, t as migrate } from "../src-
|
|
2
|
+
import { i as readJsonFile, n as DefaultReporter, r as findClosestPackageJson, t as migrate } from "../src-JKoQYJ0f.mjs";
|
|
3
3
|
import { realpath } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/dist/src/index.d.mts
CHANGED
|
@@ -18,7 +18,10 @@ export interface BiomeConfig {
|
|
|
18
18
|
export interface BiomeFilesConfig {
|
|
19
19
|
include?: string[];
|
|
20
20
|
includes?: string[];
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Exclusions split out of `includes` during normalization, in source order: an exception
|
|
23
|
+
* (`!pattern`) as `pattern`, and a pattern re-included after one as `!pattern`.
|
|
24
|
+
*/
|
|
22
25
|
exclude?: string[];
|
|
23
26
|
ignore?: string[];
|
|
24
27
|
ignoreUnknown?: boolean;
|
|
@@ -35,7 +38,10 @@ export interface BiomeLinterConfig {
|
|
|
35
38
|
enabled?: boolean;
|
|
36
39
|
include?: string[];
|
|
37
40
|
includes?: string[];
|
|
38
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Exclusions split out of `includes` during normalization, in source order: an exception
|
|
43
|
+
* (`!pattern`) as `pattern`, and a pattern re-included after one as `!pattern`.
|
|
44
|
+
*/
|
|
39
45
|
exclude?: string[];
|
|
40
46
|
ignore?: string[];
|
|
41
47
|
rules?: BiomeLinterRules;
|
|
@@ -84,7 +90,10 @@ export interface BiomeFormatterConfig {
|
|
|
84
90
|
enabled?: boolean;
|
|
85
91
|
include?: string[];
|
|
86
92
|
includes?: string[];
|
|
87
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Exclusions split out of `includes` during normalization, in source order: an exception
|
|
95
|
+
* (`!pattern`) as `pattern`, and a pattern re-included after one as `!pattern`.
|
|
96
|
+
*/
|
|
88
97
|
exclude?: string[];
|
|
89
98
|
ignore?: string[];
|
|
90
99
|
formatWithErrors?: boolean;
|
|
@@ -397,6 +406,8 @@ export interface PackageUpdateSummary {
|
|
|
397
406
|
scriptsUpdated: PackageScriptUpdate[];
|
|
398
407
|
dependenciesRemoved: PackageDependencyRemoval[];
|
|
399
408
|
devDependencies: PackageDevDependencyChange[];
|
|
409
|
+
/** Package scripts that still invoke Biome after the update, which keep Biome in use. */
|
|
410
|
+
scriptsStillUsingBiome: string[];
|
|
400
411
|
changed: boolean;
|
|
401
412
|
/** Lockfile that dependency changes invalidated, when one was detected. */
|
|
402
413
|
lockfile?: LockfileStatus;
|
package/dist/src/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as migrate } from "../src-
|
|
1
|
+
import { t as migrate } from "../src-JKoQYJ0f.mjs";
|
|
2
2
|
export { migrate };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { access, copyFile, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname,
|
|
3
|
+
import { basename, dirname, join, posix, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import pc from "picocolors";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -215,13 +215,32 @@ async function loadBiomeIgnorePatterns(projectDir, reporter) {
|
|
|
215
215
|
//#endregion
|
|
216
216
|
//#region src/schema-normalizer.ts
|
|
217
217
|
/**
|
|
218
|
+
* Biome 2 anchors every `includes` pattern at the config root, so `dist` and `*.gen.ts` only
|
|
219
|
+
* match there. Oxlint and Oxfmt match a pattern without a `/` at any depth, and anchor one
|
|
220
|
+
* with a leading `/` in `ignorePatterns` but with `./` in an override's `files` and
|
|
221
|
+
* `excludeFiles`, where a leading `/` matches nothing.
|
|
222
|
+
*/
|
|
223
|
+
function anchorAtRoot(pattern, target) {
|
|
224
|
+
if (pattern.includes("/") || pattern === "**") return pattern;
|
|
225
|
+
return `${target === "ignorePatterns" ? "/" : "./"}${pattern}`;
|
|
226
|
+
}
|
|
227
|
+
/** Why Biome 2 matches `pattern` against no path, if it does. */
|
|
228
|
+
function findUnmatchableReason(pattern) {
|
|
229
|
+
const body = pattern.replace(/^!{1,2}/u, "");
|
|
230
|
+
if (body.endsWith("/")) return "ends with \"/\"";
|
|
231
|
+
if (body.startsWith("/")) return "starts with \"/\"";
|
|
232
|
+
return body !== pattern && body.startsWith("./") ? "starts with \"./\"" : void 0;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
218
235
|
* Splits a Biome selection into positive selectors and negated exceptions.
|
|
219
236
|
*
|
|
220
237
|
* Biome 2.x `includes` mixes both: a bare pattern selects files, `!pattern` excludes
|
|
221
|
-
* them again, and `!!pattern` force-ignores a path at the scanner level. The
|
|
222
|
-
*
|
|
238
|
+
* them again, and `!!pattern` force-ignores a path at the scanner level. The patterns
|
|
239
|
+
* apply in order, so a bare pattern after an exception re-includes what it matches, and a
|
|
240
|
+
* pattern ending in `/` matches nothing. The legacy `include` field carries positive
|
|
241
|
+
* selectors only.
|
|
223
242
|
*/
|
|
224
|
-
function normalizeIncludeFields(obj, fieldName, reporter) {
|
|
243
|
+
function normalizeIncludeFields(obj, fieldName, reporter, target = "ignorePatterns") {
|
|
225
244
|
if (obj.include && obj.includes) {
|
|
226
245
|
reporter.warn(`Both 'include' and 'includes' found in ${fieldName}. Using 'include' and ignoring 'includes'.`);
|
|
227
246
|
return {
|
|
@@ -229,36 +248,154 @@ function normalizeIncludeFields(obj, fieldName, reporter) {
|
|
|
229
248
|
exclude: void 0
|
|
230
249
|
};
|
|
231
250
|
}
|
|
232
|
-
if (obj.includes) return splitIncludes(obj.includes, fieldName, reporter);
|
|
251
|
+
if (obj.includes) return splitIncludes(obj.includes, fieldName, reporter, target);
|
|
233
252
|
return {
|
|
234
253
|
include: obj.include,
|
|
235
254
|
exclude: void 0
|
|
236
255
|
};
|
|
237
256
|
}
|
|
238
|
-
function splitIncludes(includes, fieldName, reporter) {
|
|
257
|
+
function splitIncludes(includes, fieldName, reporter, target) {
|
|
239
258
|
const include = [];
|
|
240
259
|
const exclude = [];
|
|
241
260
|
for (const pattern of includes) {
|
|
261
|
+
const unmatchable = findUnmatchableReason(pattern);
|
|
262
|
+
if (unmatchable && pattern.startsWith("!")) {
|
|
263
|
+
const body = pattern.replace(/^!{1,2}/u, "");
|
|
264
|
+
const intended = anchorAtRoot(`${body.replace(/^\.?\//u, "")}${body.endsWith("/") ? "**" : ""}`, target);
|
|
265
|
+
const destination = target === "ignorePatterns" ? "the Oxlint and Oxfmt ignorePatterns" : "the excludeFiles of the migrated override";
|
|
266
|
+
reporter.warn(`Biome exception "${pattern}" in ${fieldName} ${unmatchable} and matches no file in Biome, so it excludes nothing and nothing was migrated for it. If the path was meant to be excluded, add "${intended}" to ${destination}.`);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (unmatchable) reporter.warn(`Biome pattern "${pattern}" in ${fieldName} ${unmatchable} and matches no file in Biome, so it selects nothing.`);
|
|
242
270
|
if (pattern.startsWith("!!")) {
|
|
243
|
-
exclude.push(pattern.slice(2));
|
|
271
|
+
exclude.push(anchorAtRoot(pattern.slice(2), target));
|
|
244
272
|
reporter.loss(`Biome force-ignore pattern "${pattern}" in ${fieldName} was migrated as a plain ignore; Oxc has no scanner-level force-ignore, so files reachable through other tooling paths may still be processed.`);
|
|
245
273
|
continue;
|
|
246
274
|
}
|
|
247
275
|
if (pattern.startsWith("!")) {
|
|
248
|
-
exclude.push(pattern.slice(1));
|
|
276
|
+
exclude.push(anchorAtRoot(pattern.slice(1), target));
|
|
249
277
|
continue;
|
|
250
278
|
}
|
|
251
|
-
|
|
279
|
+
const anchored = unmatchable ? pattern : anchorAtRoot(pattern.replace(/^\.\//u, ""), target);
|
|
280
|
+
if (exclude.length === 0) {
|
|
281
|
+
include.push(target === "override" ? anchored : pattern);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (unmatchable) continue;
|
|
285
|
+
const blockedBy = target === "ignorePatterns" ? findExcludedAncestor(anchored, exclude) : null;
|
|
286
|
+
if (blockedBy) {
|
|
287
|
+
reporter.warn(`Biome pattern "${pattern}" in ${fieldName} re-includes nothing: an earlier exception still excludes the directory "${blockedBy}", and Biome does not look inside an excluded directory, so nothing was migrated for it. Re-include "${blockedBy}" first if its files were meant to be processed.`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
exclude.push(`!${anchored}`);
|
|
252
291
|
}
|
|
253
292
|
return {
|
|
254
293
|
include: include.length > 0 ? include : void 0,
|
|
255
294
|
exclude: exclude.length > 0 ? exclude : void 0
|
|
256
295
|
};
|
|
257
296
|
}
|
|
297
|
+
/** Patterns that select every file, which Biome 2.x configs list before their `!` exceptions. */
|
|
298
|
+
const MATCH_ALL_PATTERNS = /* @__PURE__ */ new Set(["**", "**/*"]);
|
|
299
|
+
/**
|
|
300
|
+
* Drops a positive selection that already selects every file. `["**", "!dist"]` is Biome's
|
|
301
|
+
* canonical way to exclude a directory, and the positive part narrows nothing, so keeping
|
|
302
|
+
* it would report an unrepresentable include selection for a config that has none.
|
|
303
|
+
*/
|
|
304
|
+
function withoutMatchAllSelection(selection) {
|
|
305
|
+
return selection.include?.some((pattern) => MATCH_ALL_PATTERNS.has(pattern)) ? {
|
|
306
|
+
...selection,
|
|
307
|
+
include: void 0
|
|
308
|
+
} : selection;
|
|
309
|
+
}
|
|
310
|
+
function isReinclude(pattern) {
|
|
311
|
+
return pattern.startsWith("!");
|
|
312
|
+
}
|
|
313
|
+
const GLOB_SEGMENT = /[*?[{]/u;
|
|
314
|
+
/** Whether a glob can match files at more than one directory depth or in several directories. */
|
|
315
|
+
function hasDirectoryGlob(pattern) {
|
|
316
|
+
return pattern.replace(/^!/u, "").split("/").slice(0, -1).some((segment) => GLOB_SEGMENT.test(segment));
|
|
317
|
+
}
|
|
318
|
+
/** A pattern as a project-relative glob, without the `!` or the root anchor it was given. */
|
|
319
|
+
function globBody(pattern) {
|
|
320
|
+
return pattern.replace(/^!/u, "").replace(/^\.?\//u, "");
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The directory that stops Biome from reaching anything `pattern` names, if there is one.
|
|
324
|
+
*
|
|
325
|
+
* Biome walks the project and skips a directory whose last matching `includes` pattern is
|
|
326
|
+
* an exception, so a re-include of a file below one has no effect. Only the directories in
|
|
327
|
+
* the pattern's literal prefix are checked: those the pattern itself needs to be walked.
|
|
328
|
+
*/
|
|
329
|
+
function findExcludedAncestor(pattern, ordered) {
|
|
330
|
+
const segments = globBody(pattern).split("/").slice(0, -1);
|
|
331
|
+
const literalDepth = segments.findIndex((segment) => GLOB_SEGMENT.test(segment));
|
|
332
|
+
const directories = segments.slice(0, literalDepth === -1 ? segments.length : literalDepth).map((_, index, literal) => literal.slice(0, index + 1).join("/"));
|
|
333
|
+
for (const directory of directories) {
|
|
334
|
+
const lastMatch = ordered.findLast((entry) => posix.matchesGlob(directory, globBody(entry)));
|
|
335
|
+
if (lastMatch !== void 0 && !isReinclude(lastMatch)) return directory;
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Joins the `files` exclusions with a tool's own for one `ignorePatterns` list. A `!` entry
|
|
341
|
+
* lifts every earlier pattern it matches, so the list carrying one goes first and cannot lift
|
|
342
|
+
* the other list's exclusions. `normalizeBiomeConfig` leaves at most one of the two with any.
|
|
343
|
+
*/
|
|
344
|
+
function joinExclusions(filesExclude = [], toolExclude = []) {
|
|
345
|
+
return toolExclude.some(isReinclude) ? [...toolExclude, ...filesExclude] : [...filesExclude, ...toolExclude];
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* `files.includes` bounds what a tool's own `includes` can select, but both end up in one
|
|
349
|
+
* `ignorePatterns` list, where a re-include from either would lift the other's exclusions.
|
|
350
|
+
* When both carry re-includes only those of `files` are kept.
|
|
351
|
+
*/
|
|
352
|
+
function withoutToolReincludes(exclude, fieldName, filesExclude, reporter) {
|
|
353
|
+
const reincludes = exclude?.filter(isReinclude) ?? [];
|
|
354
|
+
if (!exclude || reincludes.length === 0 || !filesExclude?.some(isReinclude)) return exclude;
|
|
355
|
+
reporter.loss(`Biome ${fieldName}.includes re-includes ${reincludes.map((pattern) => pattern.slice(1)).join(", ")} after a negated pattern, and files.includes re-includes files too; one ignorePatterns list cannot keep the two sets of exceptions apart, so the files ${fieldName}.includes re-includes stay excluded.`);
|
|
356
|
+
return exclude.filter((pattern) => !isReinclude(pattern));
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Splits an override selection that re-includes files into selections that need no
|
|
360
|
+
* re-include. An override's `excludeFiles` cannot carry an exception to an exclusion, but
|
|
361
|
+
* Biome selects a file when the last pattern matching it is a positive one, which is the
|
|
362
|
+
* union of every positive pattern less the exceptions listed after it. So the patterns
|
|
363
|
+
* ahead of the first exception keep every exception, and each run of re-includes keeps
|
|
364
|
+
* only the exceptions that follow it.
|
|
365
|
+
*/
|
|
366
|
+
function splitReincludes(selection) {
|
|
367
|
+
const ordered = selection.exclude ?? [];
|
|
368
|
+
if (!ordered.some(isReinclude)) return [selection];
|
|
369
|
+
const exceptionsFrom = (index) => {
|
|
370
|
+
const exceptions = ordered.slice(index).filter((pattern) => !isReinclude(pattern));
|
|
371
|
+
return exceptions.length > 0 ? exceptions : void 0;
|
|
372
|
+
};
|
|
373
|
+
const leading = selection.include ?? [];
|
|
374
|
+
const selections = leading.length > 0 ? [{
|
|
375
|
+
include: leading,
|
|
376
|
+
exclude: exceptionsFrom(0)
|
|
377
|
+
}] : [];
|
|
378
|
+
let run;
|
|
379
|
+
for (const [index, pattern] of ordered.entries()) {
|
|
380
|
+
if (!isReinclude(pattern)) {
|
|
381
|
+
run = void 0;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!run) {
|
|
385
|
+
run = [];
|
|
386
|
+
selections.push({
|
|
387
|
+
include: run,
|
|
388
|
+
exclude: exceptionsFrom(index)
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
run.push(pattern.slice(1));
|
|
392
|
+
}
|
|
393
|
+
return selections;
|
|
394
|
+
}
|
|
258
395
|
function normalizeBiomeConfig(config, reporter) {
|
|
259
396
|
const normalized = { ...config };
|
|
260
397
|
if (normalized.files) {
|
|
261
|
-
const { include, exclude } = normalizeIncludeFields(normalized.files, "files", reporter);
|
|
398
|
+
const { include, exclude } = withoutMatchAllSelection(normalizeIncludeFields(normalized.files, "files", reporter));
|
|
262
399
|
normalized.files = {
|
|
263
400
|
...normalized.files,
|
|
264
401
|
include,
|
|
@@ -267,32 +404,36 @@ function normalizeBiomeConfig(config, reporter) {
|
|
|
267
404
|
};
|
|
268
405
|
}
|
|
269
406
|
if (normalized.linter) {
|
|
270
|
-
const { include, exclude } = normalizeIncludeFields(normalized.linter, "linter", reporter);
|
|
407
|
+
const { include, exclude } = withoutMatchAllSelection(normalizeIncludeFields(normalized.linter, "linter", reporter));
|
|
271
408
|
normalized.linter = {
|
|
272
409
|
...normalized.linter,
|
|
273
410
|
include,
|
|
274
|
-
exclude,
|
|
411
|
+
exclude: withoutToolReincludes(exclude, "linter", normalized.files?.exclude, reporter),
|
|
275
412
|
includes: void 0
|
|
276
413
|
};
|
|
277
414
|
}
|
|
278
415
|
if (normalized.formatter) {
|
|
279
|
-
const { include, exclude } = normalizeIncludeFields(normalized.formatter, "formatter", reporter);
|
|
416
|
+
const { include, exclude } = withoutMatchAllSelection(normalizeIncludeFields(normalized.formatter, "formatter", reporter));
|
|
280
417
|
normalized.formatter = {
|
|
281
418
|
...normalized.formatter,
|
|
282
419
|
include,
|
|
283
|
-
exclude,
|
|
420
|
+
exclude: withoutToolReincludes(exclude, "formatter", normalized.files?.exclude, reporter),
|
|
284
421
|
includes: void 0
|
|
285
422
|
};
|
|
286
423
|
}
|
|
287
|
-
if (normalized.overrides)
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
include,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
424
|
+
if (normalized.overrides) {
|
|
425
|
+
const overrides = [];
|
|
426
|
+
for (const [index, override] of normalized.overrides.entries()) {
|
|
427
|
+
const selection = normalizeIncludeFields(override, `overrides[${index}]`, reporter, "override");
|
|
428
|
+
for (const { include, exclude } of splitReincludes(selection)) overrides.push({
|
|
429
|
+
...override,
|
|
430
|
+
include,
|
|
431
|
+
exclude,
|
|
432
|
+
includes: void 0
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
normalized.overrides = overrides;
|
|
436
|
+
}
|
|
296
437
|
return normalized;
|
|
297
438
|
}
|
|
298
439
|
//#endregion
|
|
@@ -317,7 +458,7 @@ const SUPPORTED_TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
|
317
458
|
const IncludeFieldsSchema = z.object({
|
|
318
459
|
include: z.array(z.string()).optional(),
|
|
319
460
|
includes: z.array(z.string()).optional()
|
|
320
|
-
}).
|
|
461
|
+
}).loose();
|
|
321
462
|
const BiomeRuleSeveritySchema = z.union([z.enum([
|
|
322
463
|
"off",
|
|
323
464
|
"on",
|
|
@@ -333,7 +474,7 @@ const BiomeRuleSeveritySchema = z.union([z.enum([
|
|
|
333
474
|
"error"
|
|
334
475
|
]),
|
|
335
476
|
options: z.unknown().optional()
|
|
336
|
-
}).
|
|
477
|
+
}).loose()]);
|
|
337
478
|
const BiomeRuleGroupSchema = z.object({
|
|
338
479
|
recommended: z.boolean().optional(),
|
|
339
480
|
all: z.boolean().optional(),
|
|
@@ -383,7 +524,7 @@ const BiomeFormatterConfigSchema = IncludeFieldsSchema.extend({
|
|
|
383
524
|
"never"
|
|
384
525
|
]).optional(),
|
|
385
526
|
trailingNewline: z.boolean().optional()
|
|
386
|
-
}).
|
|
527
|
+
}).loose();
|
|
387
528
|
const BiomeJavaScriptFormatterSchema = z.object({
|
|
388
529
|
enabled: z.boolean().optional(),
|
|
389
530
|
quoteStyle: z.enum(["single", "double"]).optional(),
|
|
@@ -414,21 +555,21 @@ const BiomeJavaScriptFormatterSchema = z.object({
|
|
|
414
555
|
lineWidth: z.number().int().optional(),
|
|
415
556
|
operatorLinebreak: z.enum(["after", "before"]).optional(),
|
|
416
557
|
trailingNewline: z.boolean().optional()
|
|
417
|
-
}).
|
|
558
|
+
}).loose();
|
|
418
559
|
const BiomeJavaScriptConfigSchema = z.object({
|
|
419
560
|
parser: z.object({
|
|
420
561
|
unsafeParameterDecoratorsEnabled: z.boolean().optional(),
|
|
421
562
|
jsxEverywhere: z.boolean().optional()
|
|
422
|
-
}).
|
|
563
|
+
}).loose().optional(),
|
|
423
564
|
formatter: BiomeJavaScriptFormatterSchema.optional(),
|
|
424
|
-
linter: z.object({ enabled: z.boolean().optional() }).
|
|
565
|
+
linter: z.object({ enabled: z.boolean().optional() }).loose().optional(),
|
|
425
566
|
globals: z.array(z.string()).optional()
|
|
426
|
-
}).
|
|
567
|
+
}).loose();
|
|
427
568
|
const BiomeJsonConfigSchema = z.object({
|
|
428
569
|
parser: z.object({
|
|
429
570
|
allowComments: z.boolean().optional(),
|
|
430
571
|
allowTrailingCommas: z.boolean().optional()
|
|
431
|
-
}).
|
|
572
|
+
}).loose().optional(),
|
|
432
573
|
formatter: z.object({
|
|
433
574
|
enabled: z.boolean().optional(),
|
|
434
575
|
expand: z.enum([
|
|
@@ -447,11 +588,11 @@ const BiomeJsonConfigSchema = z.object({
|
|
|
447
588
|
lineWidth: z.number().int().optional(),
|
|
448
589
|
trailingCommas: z.enum(["none", "all"]).optional(),
|
|
449
590
|
trailingNewline: z.boolean().optional()
|
|
450
|
-
}).
|
|
451
|
-
linter: z.object({ enabled: z.boolean().optional() }).
|
|
452
|
-
}).
|
|
591
|
+
}).loose().optional(),
|
|
592
|
+
linter: z.object({ enabled: z.boolean().optional() }).loose().optional()
|
|
593
|
+
}).loose();
|
|
453
594
|
const BiomeCssConfigSchema = z.object({
|
|
454
|
-
parser: z.object({ cssModules: z.boolean().optional() }).
|
|
595
|
+
parser: z.object({ cssModules: z.boolean().optional() }).loose().optional(),
|
|
455
596
|
formatter: z.object({
|
|
456
597
|
enabled: z.boolean().optional(),
|
|
457
598
|
indentStyle: z.enum(["tab", "space"]).optional(),
|
|
@@ -465,9 +606,9 @@ const BiomeCssConfigSchema = z.object({
|
|
|
465
606
|
lineWidth: z.number().int().optional(),
|
|
466
607
|
quoteStyle: z.enum(["single", "double"]).optional(),
|
|
467
608
|
trailingNewline: z.boolean().optional()
|
|
468
|
-
}).
|
|
469
|
-
linter: z.object({ enabled: z.boolean().optional() }).
|
|
470
|
-
}).
|
|
609
|
+
}).loose().optional(),
|
|
610
|
+
linter: z.object({ enabled: z.boolean().optional() }).loose().optional()
|
|
611
|
+
}).loose();
|
|
471
612
|
const BiomeGraphqlConfigSchema = z.object({
|
|
472
613
|
formatter: z.object({
|
|
473
614
|
enabled: z.boolean().optional(),
|
|
@@ -483,18 +624,18 @@ const BiomeGraphqlConfigSchema = z.object({
|
|
|
483
624
|
lineWidth: z.number().int().optional(),
|
|
484
625
|
quoteStyle: z.enum(["single", "double"]).optional(),
|
|
485
626
|
trailingNewline: z.boolean().optional()
|
|
486
|
-
}).
|
|
487
|
-
linter: z.object({ enabled: z.boolean().optional() }).
|
|
488
|
-
}).
|
|
627
|
+
}).loose().optional(),
|
|
628
|
+
linter: z.object({ enabled: z.boolean().optional() }).loose().optional()
|
|
629
|
+
}).loose();
|
|
489
630
|
const BiomeLinterConfigSchema = IncludeFieldsSchema.extend({
|
|
490
631
|
enabled: z.boolean().optional(),
|
|
491
632
|
ignore: z.array(z.string()).optional(),
|
|
492
633
|
rules: BiomeLinterRulesSchema.optional()
|
|
493
|
-
}).
|
|
634
|
+
}).loose();
|
|
494
635
|
const BiomeAssistActionSchema = z.union([z.enum(["off", "on"]), z.object({
|
|
495
636
|
level: z.enum(["off", "on"]),
|
|
496
637
|
options: z.unknown().optional()
|
|
497
|
-
}).
|
|
638
|
+
}).loose()]);
|
|
498
639
|
const BiomeAssistConfigSchema = z.object({
|
|
499
640
|
enabled: z.boolean().optional(),
|
|
500
641
|
includes: z.array(z.string()).optional(),
|
|
@@ -508,8 +649,8 @@ const BiomeAssistConfigSchema = z.object({
|
|
|
508
649
|
"none"
|
|
509
650
|
]).optional()
|
|
510
651
|
}).catchall(z.union([BiomeAssistActionSchema, z.boolean()])).optional()
|
|
511
|
-
}).
|
|
512
|
-
}).
|
|
652
|
+
}).loose().optional()
|
|
653
|
+
}).loose();
|
|
513
654
|
const BiomeOverrideSchema = z.object({
|
|
514
655
|
include: z.array(z.string()).optional(),
|
|
515
656
|
includes: z.array(z.string()).optional(),
|
|
@@ -521,7 +662,7 @@ const BiomeOverrideSchema = z.object({
|
|
|
521
662
|
json: BiomeJsonConfigSchema.optional(),
|
|
522
663
|
css: BiomeCssConfigSchema.optional(),
|
|
523
664
|
graphql: BiomeGraphqlConfigSchema.optional()
|
|
524
|
-
}).
|
|
665
|
+
}).loose();
|
|
525
666
|
const BiomeConfigSchema = z.object({
|
|
526
667
|
$schema: z.string().optional(),
|
|
527
668
|
extends: z.union([z.string(), z.array(z.string())]).optional(),
|
|
@@ -532,14 +673,14 @@ const BiomeConfigSchema = z.object({
|
|
|
532
673
|
ignore: z.array(z.string()).optional(),
|
|
533
674
|
ignoreUnknown: z.boolean().optional(),
|
|
534
675
|
maxSize: z.number().int().optional()
|
|
535
|
-
}).
|
|
676
|
+
}).loose().optional(),
|
|
536
677
|
vcs: z.object({
|
|
537
678
|
enabled: z.boolean().optional(),
|
|
538
679
|
clientKind: z.literal("git").optional(),
|
|
539
680
|
useIgnoreFile: z.boolean().optional(),
|
|
540
681
|
root: z.string().optional(),
|
|
541
682
|
defaultBranch: z.string().optional()
|
|
542
|
-
}).
|
|
683
|
+
}).loose().optional(),
|
|
543
684
|
linter: BiomeLinterConfigSchema.optional(),
|
|
544
685
|
formatter: BiomeFormatterConfigSchema.optional(),
|
|
545
686
|
assist: BiomeAssistConfigSchema.optional(),
|
|
@@ -549,8 +690,8 @@ const BiomeConfigSchema = z.object({
|
|
|
549
690
|
graphql: BiomeGraphqlConfigSchema.optional(),
|
|
550
691
|
html: z.record(z.string(), z.unknown()).optional(),
|
|
551
692
|
overrides: z.array(BiomeOverrideSchema).optional()
|
|
552
|
-
}).
|
|
553
|
-
const WorkspacePackageJsonSchema$1 = z.object({ workspaces: z.union([z.array(z.string()), z.record(z.string(), z.unknown())]).optional() }).
|
|
693
|
+
}).loose();
|
|
694
|
+
const WorkspacePackageJsonSchema$1 = z.object({ workspaces: z.union([z.array(z.string()), z.record(z.string(), z.unknown())]).optional() }).loose();
|
|
554
695
|
async function findBiomeConfig(startDir) {
|
|
555
696
|
for (const name of BIOME_CONFIG_NAMES) {
|
|
556
697
|
const configPath = resolve(startDir, name);
|
|
@@ -1024,6 +1165,7 @@ const GLOBAL_FORMATTER_KEYS = /* @__PURE__ */ new Set([
|
|
|
1024
1165
|
"enabled",
|
|
1025
1166
|
"include",
|
|
1026
1167
|
"includes",
|
|
1168
|
+
"exclude",
|
|
1027
1169
|
"ignore",
|
|
1028
1170
|
"formatWithErrors",
|
|
1029
1171
|
"indentStyle",
|
|
@@ -1227,12 +1369,11 @@ function mapOrganizeImportsOptions(value, reporter) {
|
|
|
1227
1369
|
}
|
|
1228
1370
|
function mapIgnorePatterns$1(biomeConfig, oxfmtConfig, additionalIgnorePatterns) {
|
|
1229
1371
|
const ignorePatterns = [
|
|
1372
|
+
...joinExclusions(biomeConfig.files?.exclude, biomeConfig.formatter?.exclude),
|
|
1230
1373
|
...oxfmtConfig.ignorePatterns ?? [],
|
|
1231
1374
|
...additionalIgnorePatterns,
|
|
1232
1375
|
...biomeConfig.files?.ignore ?? [],
|
|
1233
|
-
...biomeConfig.formatter?.ignore ?? []
|
|
1234
|
-
...biomeConfig.files?.exclude ?? [],
|
|
1235
|
-
...biomeConfig.formatter?.exclude ?? []
|
|
1376
|
+
...biomeConfig.formatter?.ignore ?? []
|
|
1236
1377
|
];
|
|
1237
1378
|
if (ignorePatterns.length > 0) oxfmtConfig.ignorePatterns = [...new Set(ignorePatterns)];
|
|
1238
1379
|
}
|
|
@@ -1306,7 +1447,7 @@ function validatePatternGroups(configName, groups) {
|
|
|
1306
1447
|
function findPatternProblem(pattern) {
|
|
1307
1448
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
1308
1449
|
if (body.split("/").includes("..")) return "escapes the config directory with \"..\", which Oxlint and Oxfmt reject";
|
|
1309
|
-
if (
|
|
1450
|
+
if (/^[A-Za-z]:[/\\]/u.test(body)) return "is an absolute path, but patterns are resolved within the config directory";
|
|
1310
1451
|
}
|
|
1311
1452
|
//#endregion
|
|
1312
1453
|
//#region src/js-plugin-scaffolder.ts
|
|
@@ -1328,6 +1469,14 @@ const RESERVED_JS_PLUGIN_NAMES = /* @__PURE__ */ new Set([
|
|
|
1328
1469
|
"eslint"
|
|
1329
1470
|
]);
|
|
1330
1471
|
const UNSUPPORTED_BIOME_RULE_GUIDANCE = {
|
|
1472
|
+
noDeprecatedImports: {
|
|
1473
|
+
nativeAlternatives: ["typescript/no-deprecated"],
|
|
1474
|
+
notes: "Not a drop-in replacement: typescript/no-deprecated is type-aware and reports each use of a deprecated symbol, wherever it was declared, rather than the import statement, so an imported but unused deprecated symbol is not reported."
|
|
1475
|
+
},
|
|
1476
|
+
noForIn: {
|
|
1477
|
+
nativeAlternatives: ["guard-for-in", "typescript/no-for-in-array"],
|
|
1478
|
+
notes: "Neither forbids every for-in loop: guard-for-in reports the ones whose body is not wrapped in an own-property check, and the type-aware typescript/no-for-in-array reports only loops over arrays."
|
|
1479
|
+
},
|
|
1331
1480
|
noReExportAll: {
|
|
1332
1481
|
nativeAlternatives: ["import/no-cycle", "import/no-self-import"],
|
|
1333
1482
|
jsPluginSpecifiers: ["eslint-plugin-import"],
|
|
@@ -1540,12 +1689,21 @@ const REACT_COMPILER_RULES = [
|
|
|
1540
1689
|
* is reported once per migration so the narrowing is visible rather than silent.
|
|
1541
1690
|
*/
|
|
1542
1691
|
const PARTIAL_RULE_MAPPING_NOTES = {
|
|
1692
|
+
noAssignInExpressions: "Biome rule noAssignInExpressions was mapped to no-cond-assign in `always` mode, which reports assignments inside conditions but not assignments nested in other expressions, such as `const a = (b = 1)` or a call argument.",
|
|
1543
1693
|
noComponentHookFactories: "Biome rule noComponentHookFactories was mapped to react/no-unstable-nested-components, which reports nested component definitions but not nested custom hook definitions.",
|
|
1694
|
+
noBarrelFile: "Biome rule noBarrelFile was mapped to oxc/no-barrel-file, which only counts `export *` re-exports and reports a file once it re-exports more modules than its threshold (100 by default); Biome reports any file that re-exports, including through named `export { ... } from`.",
|
|
1695
|
+
noExcessiveCognitiveComplexity: "Biome rule noExcessiveCognitiveComplexity was mapped to complexity, which scores cyclomatic complexity rather than cognitive complexity; the threshold carries over, but the two scores differ, so a different set of functions is reported.",
|
|
1696
|
+
noMeaninglessVoidOperator: "Biome rule noMeaninglessVoidOperator was mapped to typescript/no-meaningless-void-operator, which reports `void` applied to a value whose type is `void` or `undefined` but not `void` applied to any other non-call operand, such as `void 1` or `void value`.",
|
|
1544
1697
|
noInlineStyles: "Biome rule noInlineStyles was mapped to react/forbid-dom-props, which reports the `style` prop on JSX DOM elements but not `style` passed through React.createElement() or written in HTML, Vue, Svelte or Astro templates.",
|
|
1698
|
+
noSelfImport: "Biome rule noSelfImport was mapped to import/no-self-import, which reports static `import` and `export ... from` statements that resolve to their own file but not `require()` calls or dynamic `import()` expressions that do.",
|
|
1699
|
+
noReactObjectTypeAsDefaultProp: "Biome rule noReactObjectTypeAsDefaultProp was mapped to react/no-object-type-as-default-prop, which reports array, object, function, class, regex, `new` and JSX default prop values but not a `Symbol()` default.",
|
|
1545
1700
|
noReactPropAssignments: "Biome rule noReactPropAssignments was mapped to the React Compiler rule react/immutability, which reports prop mutation alongside other mutations of values React treats as immutable.",
|
|
1546
1701
|
noUnresolvedImports: "Biome rule noUnresolvedImports was mapped to import/named, which reports named imports a JavaScript module does not export but skips TypeScript files entirely and does not report import paths that cannot be resolved.",
|
|
1547
1702
|
noUnsafeIframeSandbox: "Biome rule noUnsafeIframeSandbox was mapped to react/iframe-missing-sandbox, which reports `allow-scripts` combined with `allow-same-origin` only when the sandbox value is a plain string attribute, not a `{\"...\"}` or template-literal expression; it also reports iframes with no or an invalid sandbox attribute, which Biome leaves to useIframeSandbox.",
|
|
1703
|
+
useConsistentCurlyBraces: "Biome rule useConsistentCurlyBraces was mapped to react/jsx-curly-brace-presence, which reports unnecessary braces around string literals in JSX props and children but not a JSX element passed as a prop value without braces (`foo=<Bar />`).",
|
|
1704
|
+
useArrowFunction: "Biome rule useArrowFunction was mapped to prefer-arrow-callback, which reports function expressions passed as callbacks but not ones assigned to variables or properties, such as `const f = function () {}`.",
|
|
1548
1705
|
useExplicitType: "Biome rule useExplicitType was mapped to typescript/explicit-function-return-type, which requires return types on functions and methods but not types on variables or parameters.",
|
|
1706
|
+
noUselessSwitchCase: "Biome rule noUselessSwitchCase was mapped to unicorn/no-useless-switch-case, which reports a case that falls through into a trailing `default` but not one next to a `default` placed earlier in the switch.",
|
|
1549
1707
|
useStaticResponseMethods: "Biome rule useStaticResponseMethods was mapped to unicorn/prefer-response-static-json, which reports `new Response(JSON.stringify(...))` but not the `new Response(null, { status, headers: { Location } })` form Biome rewrites to Response.redirect()."
|
|
1550
1708
|
};
|
|
1551
1709
|
/**
|
|
@@ -1562,8 +1720,77 @@ const TYPE_AWARE_RULE_FALLBACKS = {
|
|
|
1562
1720
|
useArrayFind: "unicorn/prefer-array-find",
|
|
1563
1721
|
useFind: "unicorn/prefer-array-find",
|
|
1564
1722
|
useIncludes: "unicorn/prefer-includes",
|
|
1565
|
-
useStringStartsEndsWith: "unicorn/prefer-string-starts-ends-with"
|
|
1723
|
+
useStringStartsEndsWith: "unicorn/prefer-string-starts-ends-with",
|
|
1724
|
+
useThrowOnlyError: "no-throw-literal"
|
|
1566
1725
|
};
|
|
1726
|
+
/**
|
|
1727
|
+
* Oxlint rules implemented by tsgolint, which only run when type-aware linting is on. A Biome
|
|
1728
|
+
* rule that maps onto one of them with no fallback above loses its diagnostics in a migration
|
|
1729
|
+
* that leaves type-aware linting off, so that is reported rather than emitted silently.
|
|
1730
|
+
* `rule-inventory.test.ts` keeps this list equal to `docs/tsgolint-rules.tsv`.
|
|
1731
|
+
*/
|
|
1732
|
+
const TYPE_AWARE_OXLINT_RULES = /* @__PURE__ */ new Set([
|
|
1733
|
+
"typescript/await-thenable",
|
|
1734
|
+
"typescript/consistent-return",
|
|
1735
|
+
"typescript/consistent-type-exports",
|
|
1736
|
+
"typescript/dot-notation",
|
|
1737
|
+
"typescript/no-array-delete",
|
|
1738
|
+
"typescript/no-base-to-string",
|
|
1739
|
+
"typescript/no-confusing-void-expression",
|
|
1740
|
+
"typescript/no-deprecated",
|
|
1741
|
+
"typescript/no-duplicate-type-constituents",
|
|
1742
|
+
"typescript/no-floating-promises",
|
|
1743
|
+
"typescript/no-for-in-array",
|
|
1744
|
+
"typescript/no-generated-empty-object-type",
|
|
1745
|
+
"typescript/no-implied-eval",
|
|
1746
|
+
"typescript/no-meaningless-void-operator",
|
|
1747
|
+
"typescript/no-misused-promises",
|
|
1748
|
+
"typescript/no-misused-spread",
|
|
1749
|
+
"typescript/no-mixed-enums",
|
|
1750
|
+
"typescript/no-redundant-type-constituents",
|
|
1751
|
+
"typescript/no-unnecessary-boolean-literal-compare",
|
|
1752
|
+
"typescript/no-unnecessary-condition",
|
|
1753
|
+
"typescript/no-unnecessary-qualifier",
|
|
1754
|
+
"typescript/no-unnecessary-template-expression",
|
|
1755
|
+
"typescript/no-unnecessary-type-arguments",
|
|
1756
|
+
"typescript/no-unnecessary-type-assertion",
|
|
1757
|
+
"typescript/no-unnecessary-type-conversion",
|
|
1758
|
+
"typescript/no-unnecessary-type-parameters",
|
|
1759
|
+
"typescript/no-unsafe-argument",
|
|
1760
|
+
"typescript/no-unsafe-assignment",
|
|
1761
|
+
"typescript/no-unsafe-call",
|
|
1762
|
+
"typescript/no-unsafe-enum-comparison",
|
|
1763
|
+
"typescript/no-unsafe-member-access",
|
|
1764
|
+
"typescript/no-unsafe-return",
|
|
1765
|
+
"typescript/no-unsafe-type-assertion",
|
|
1766
|
+
"typescript/no-unsafe-unary-minus",
|
|
1767
|
+
"typescript/no-useless-default-assignment",
|
|
1768
|
+
"typescript/non-nullable-type-assertion-style",
|
|
1769
|
+
"typescript/only-throw-error",
|
|
1770
|
+
"typescript/prefer-find",
|
|
1771
|
+
"typescript/prefer-includes",
|
|
1772
|
+
"typescript/prefer-nullish-coalescing",
|
|
1773
|
+
"typescript/prefer-optional-chain",
|
|
1774
|
+
"typescript/prefer-promise-reject-errors",
|
|
1775
|
+
"typescript/prefer-readonly",
|
|
1776
|
+
"typescript/prefer-readonly-parameter-types",
|
|
1777
|
+
"typescript/prefer-reduce-type-parameter",
|
|
1778
|
+
"typescript/prefer-regexp-exec",
|
|
1779
|
+
"typescript/prefer-return-this-type",
|
|
1780
|
+
"typescript/prefer-string-starts-ends-with",
|
|
1781
|
+
"typescript/promise-function-async",
|
|
1782
|
+
"typescript/related-getter-setter-pairs",
|
|
1783
|
+
"typescript/require-array-sort-compare",
|
|
1784
|
+
"typescript/require-await",
|
|
1785
|
+
"typescript/restrict-plus-operands",
|
|
1786
|
+
"typescript/restrict-template-expressions",
|
|
1787
|
+
"typescript/return-await",
|
|
1788
|
+
"typescript/strict-boolean-expressions",
|
|
1789
|
+
"typescript/strict-void-return",
|
|
1790
|
+
"typescript/switch-exhaustiveness-check",
|
|
1791
|
+
"typescript/unbound-method",
|
|
1792
|
+
"typescript/use-unknown-in-catch-callback-variable"
|
|
1793
|
+
]);
|
|
1567
1794
|
/** Fallbacks that cover less than the type-aware rule they stand in for. */
|
|
1568
1795
|
const TYPE_AWARE_FALLBACK_NOTES = { useStringStartsEndsWith: "Biome rule useStringStartsEndsWith was mapped to unicorn/prefer-string-starts-ends-with because type-aware linting is off; it reports anchored RegExp#test() prefix checks but not the indexOf(), slice() and charAt() comparisons Biome also reports. Enabling type-aware linting maps it to typescript/prefer-string-starts-ends-with instead, which covers all four." };
|
|
1569
1796
|
const BIOME_TO_OXLINT_RULE_MAP = {
|
|
@@ -1628,7 +1855,11 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1628
1855
|
noDocumentImportInPage: "nextjs/no-document-import-in-page",
|
|
1629
1856
|
noDoneCallback: "jest/no-done-callback",
|
|
1630
1857
|
noDivRegex: "no-div-regex",
|
|
1631
|
-
noEmptyBlockStatements:
|
|
1858
|
+
noEmptyBlockStatements: [
|
|
1859
|
+
"no-empty",
|
|
1860
|
+
"no-empty-function",
|
|
1861
|
+
"no-empty-static-block"
|
|
1862
|
+
],
|
|
1632
1863
|
noEmptyCharacterClassInRegex: "no-empty-character-class",
|
|
1633
1864
|
noEmptyInterface: "typescript/no-empty-interface",
|
|
1634
1865
|
noEmptyPattern: "no-empty-pattern",
|
|
@@ -1664,7 +1895,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1664
1895
|
noImplicitCoercions: "no-implicit-coercion",
|
|
1665
1896
|
noInnerDeclarations: "no-inner-declarations",
|
|
1666
1897
|
noInlineStyles: "react/forbid-dom-props",
|
|
1667
|
-
noImplicitBoolean: "
|
|
1898
|
+
noImplicitBoolean: "react/jsx-boolean-value",
|
|
1668
1899
|
noImpliedEval: "no-implied-eval",
|
|
1669
1900
|
noIncrementDecrement: "no-plusplus",
|
|
1670
1901
|
noInferrableTypes: "typescript/no-inferrable-types",
|
|
@@ -1682,6 +1913,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1682
1913
|
noLabelVar: "no-label-var",
|
|
1683
1914
|
noLoopFunc: "no-loop-func",
|
|
1684
1915
|
noMagicNumbers: "no-magic-numbers",
|
|
1916
|
+
noMeaninglessVoidOperator: "typescript/no-meaningless-void-operator",
|
|
1685
1917
|
noMisleadingCharacterClass: "no-misleading-character-class",
|
|
1686
1918
|
noMisleadingInstantiator: "typescript/no-misused-new",
|
|
1687
1919
|
noMisrefactoredShorthandAssign: "oxc/misrefactored-assign-op",
|
|
@@ -1714,6 +1946,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1714
1946
|
noProto: "no-proto",
|
|
1715
1947
|
noPrototypeBuiltins: "no-prototype-builtins",
|
|
1716
1948
|
noRedeclare: "no-redeclare",
|
|
1949
|
+
noReactObjectTypeAsDefaultProp: "react/no-object-type-as-default-prop",
|
|
1717
1950
|
noReactPropAssignments: "react/immutability",
|
|
1718
1951
|
noRedundantAlt: "jsx-a11y/img-redundant-alt",
|
|
1719
1952
|
noRedundantRoles: "jsx-a11y/no-redundant-roles",
|
|
@@ -1734,6 +1967,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1734
1967
|
noScriptUrl: "no-script-url",
|
|
1735
1968
|
noSelfCompare: "no-self-compare",
|
|
1736
1969
|
noSelfAssign: "no-self-assign",
|
|
1970
|
+
noSelfImport: "import/no-self-import",
|
|
1737
1971
|
noSetterReturn: "no-setter-return",
|
|
1738
1972
|
noShadow: "no-shadow",
|
|
1739
1973
|
noShadowRestrictedNames: "no-shadow-restricted-names",
|
|
@@ -1754,7 +1988,6 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1754
1988
|
noUnusedExpressions: "no-unused-expressions",
|
|
1755
1989
|
noUnusedFunctionParameters: "no-unused-vars",
|
|
1756
1990
|
noUnusedImports: "no-unused-vars",
|
|
1757
|
-
noUnnecessaryContinue: "no-continue",
|
|
1758
1991
|
noUnmodifiedLoopCondition: "no-unmodified-loop-condition",
|
|
1759
1992
|
noUnnecessaryConditions: "typescript/no-unnecessary-condition",
|
|
1760
1993
|
noUnnecessaryTemplateExpression: "typescript/no-unnecessary-template-expression",
|
|
@@ -1774,7 +2007,6 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1774
2007
|
noUnusedVariables: "no-unused-vars",
|
|
1775
2008
|
noUnwantedPolyfillio: "nextjs/no-unwanted-polyfillio",
|
|
1776
2009
|
noUselessCatch: "no-useless-catch",
|
|
1777
|
-
noUselessContinue: "no-continue",
|
|
1778
2010
|
noUselessCatchBinding: "unicorn/prefer-optional-catch-binding",
|
|
1779
2011
|
noUselessConstructor: "no-useless-constructor",
|
|
1780
2012
|
noUselessElse: "no-else-return",
|
|
@@ -1830,7 +2062,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1830
2062
|
useConsistentArrayType: "typescript/array-type",
|
|
1831
2063
|
useConsistentArrowReturn: "arrow-body-style",
|
|
1832
2064
|
useConsistentBuiltinInstantiation: "no-new-wrappers",
|
|
1833
|
-
useConsistentCurlyBraces: "curly",
|
|
2065
|
+
useConsistentCurlyBraces: "react/jsx-curly-brace-presence",
|
|
1834
2066
|
useConsistentEnumValueType: "typescript/no-mixed-enums",
|
|
1835
2067
|
useConsistentFunctionStyle: "func-style",
|
|
1836
2068
|
useConsistentMethodSignatures: "typescript/method-signature-style",
|
|
@@ -1905,6 +2137,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1905
2137
|
useObjectSpread: "prefer-object-spread",
|
|
1906
2138
|
useOptionalChain: "typescript/prefer-optional-chain",
|
|
1907
2139
|
useParseIntRadix: "radix",
|
|
2140
|
+
usePromiseRejectErrors: "prefer-promise-reject-errors",
|
|
1908
2141
|
useReadonlyClassProperties: "typescript/prefer-readonly",
|
|
1909
2142
|
useReactFunctionComponentDefinition: "react/function-component-definition",
|
|
1910
2143
|
useReactCompiler: REACT_COMPILER_RULES,
|
|
@@ -1918,10 +2151,10 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1918
2151
|
useShorthandAssign: "operator-assignment",
|
|
1919
2152
|
useShorthandFunctionType: "typescript/prefer-function-type",
|
|
1920
2153
|
useSingleVarDeclarator: "one-var",
|
|
1921
|
-
useSimplifiedLogicExpression: "unicorn/prefer-logical-operator-over-ternary",
|
|
1922
2154
|
useSpread: "prefer-spread",
|
|
1923
2155
|
useSpreadOverApply: "prefer-spread",
|
|
1924
2156
|
useStaticResponseMethods: "unicorn/prefer-response-static-json",
|
|
2157
|
+
useStrictBooleanExpressions: "typescript/strict-boolean-expressions",
|
|
1925
2158
|
useStringStartsEndsWith: "typescript/prefer-string-starts-ends-with",
|
|
1926
2159
|
useSymbolDescription: "symbol-description",
|
|
1927
2160
|
useTemplate: "prefer-template",
|
|
@@ -1968,12 +2201,13 @@ function normalizeOxlintRuleName(ruleName) {
|
|
|
1968
2201
|
return ruleName;
|
|
1969
2202
|
}
|
|
1970
2203
|
function mapBiomeRuleOptionsToOxlintSeverity(biomeName, severity, biomeSeverity, reporter) {
|
|
1971
|
-
if (typeof severity !== "string") return severity;
|
|
2204
|
+
if (typeof severity !== "string" || severity === "off") return severity;
|
|
1972
2205
|
if (biomeName === "useVueNextTickPromise") return [severity, "promise"];
|
|
1973
2206
|
const options = getBiomeRuleOptions(biomeSeverity);
|
|
1974
2207
|
if (biomeName === "noExcessiveNestedCallbacks") return [severity, { max: options && isNonNegativeInteger(options.max) ? options.max : 5 }];
|
|
1975
2208
|
if (biomeName === "useErrorCause") return [severity, { requireCatchParameter: options && typeof options.requireCatchParameter === "boolean" ? options.requireCatchParameter : true }];
|
|
1976
2209
|
if (biomeName === "useMaxParams") return [severity, { max: options && isNonNegativeInteger(options.max) ? options.max : 4 }];
|
|
2210
|
+
if (biomeName === "noExcessiveCognitiveComplexity") return [severity, { max: options && isNonNegativeInteger(options.maxAllowedComplexity) ? options.maxAllowedComplexity : 15 }];
|
|
1977
2211
|
if (biomeName === "noExcessiveLinesPerFile") return [severity, {
|
|
1978
2212
|
max: options && isNonNegativeInteger(options.maxLines) ? options.maxLines : 300,
|
|
1979
2213
|
skipBlankLines: options?.skipBlankLines === true
|
|
@@ -1984,6 +2218,34 @@ function mapBiomeRuleOptionsToOxlintSeverity(biomeName, severity, biomeSeverity,
|
|
|
1984
2218
|
IIFEs: options?.skipIifes !== true
|
|
1985
2219
|
}];
|
|
1986
2220
|
if (biomeName === "useSingleVarDeclarator") return [severity, "never"];
|
|
2221
|
+
if (biomeName === "noAssignInExpressions") return [severity, "always"];
|
|
2222
|
+
if (biomeName === "noConfusingLabels") {
|
|
2223
|
+
if (options?.allowedLabels !== void 0) reporter.loss("Biome rule noConfusingLabels option \"allowedLabels\" is not supported by Oxlint no-labels; those labels are reported again unless they label a loop.");
|
|
2224
|
+
return [severity, { allowLoop: true }];
|
|
2225
|
+
}
|
|
2226
|
+
if (biomeName === "noDoubleEquals") return options?.ignoreNull === false ? [severity, "always"] : [
|
|
2227
|
+
severity,
|
|
2228
|
+
"always",
|
|
2229
|
+
{ null: "ignore" }
|
|
2230
|
+
];
|
|
2231
|
+
if (biomeName === "noRestrictedGlobals") {
|
|
2232
|
+
const denied = options?.deniedGlobals;
|
|
2233
|
+
return [
|
|
2234
|
+
severity,
|
|
2235
|
+
"event",
|
|
2236
|
+
"error",
|
|
2237
|
+
...Array.isArray(denied) ? denied.filter((name) => typeof name === "string") : isRecord(denied) ? Object.entries(denied).map(([name, message]) => typeof message === "string" ? {
|
|
2238
|
+
name,
|
|
2239
|
+
message
|
|
2240
|
+
} : name) : []
|
|
2241
|
+
];
|
|
2242
|
+
}
|
|
2243
|
+
if (biomeName === "useConsistentMemberAccessibility") {
|
|
2244
|
+
if (options?.accessibility === "explicit") return [severity, { accessibility: "explicit" }];
|
|
2245
|
+
if (options?.accessibility === "none") reporter.loss("Biome rule useConsistentMemberAccessibility option \"accessibility\": \"none\" is not supported by Oxlint explicit-member-accessibility, which can forbid `public` but not `protected` or `private`; it was migrated as `no-public`.");
|
|
2246
|
+
return [severity, { accessibility: "no-public" }];
|
|
2247
|
+
}
|
|
2248
|
+
if (biomeName === "noImplicitBoolean") return [severity, "always"];
|
|
1987
2249
|
if (biomeName === "noInlineStyles") return [severity, { forbid: ["style"] }];
|
|
1988
2250
|
if (biomeName === "useImportExtensions") {
|
|
1989
2251
|
if (options?.extensionMappings !== void 0 || options?.forceJsExtensions !== void 0) reporter.loss("Biome rule useImportExtensions options \"extensionMappings\" and \"forceJsExtensions\" are not supported by Oxlint import/extensions; the rule requires an extension without rewriting it.");
|
|
@@ -2146,30 +2408,37 @@ function mapBiomeRuleSeverity(severity, reporter, biomeRuleName) {
|
|
|
2146
2408
|
warnUnsupportedSeverityOnce(rawSeverity, reporter, biomeRuleName);
|
|
2147
2409
|
return "warn";
|
|
2148
2410
|
}
|
|
2149
|
-
|
|
2411
|
+
/**
|
|
2412
|
+
* Maps a Biome rule to its Oxlint rules. Coverage gaps are only reported for an `enabled`
|
|
2413
|
+
* rule: a rule the Biome config turns off produced no diagnostics, so nothing is lost when
|
|
2414
|
+
* the Oxlint side covers less of it, or none of it.
|
|
2415
|
+
*/
|
|
2416
|
+
function mapBiomeRuleToOxlintRules(biomeName, reporter, typeAware, enabled = true) {
|
|
2150
2417
|
const fallback = typeAware ? void 0 : TYPE_AWARE_RULE_FALLBACKS[biomeName];
|
|
2151
2418
|
if (fallback) {
|
|
2152
2419
|
const fallbackNote = TYPE_AWARE_FALLBACK_NOTES[biomeName];
|
|
2153
|
-
if (fallbackNote) warnPartialMappingOnce(biomeName, fallbackNote, reporter);
|
|
2420
|
+
if (fallbackNote && enabled) warnPartialMappingOnce(biomeName, fallbackNote, reporter);
|
|
2154
2421
|
return [normalizeOxlintRuleName(fallback)];
|
|
2155
2422
|
}
|
|
2156
2423
|
const mapped = BIOME_TO_OXLINT_RULE_MAP[biomeName];
|
|
2157
2424
|
if (mapped) {
|
|
2158
2425
|
const partialMappingNote = PARTIAL_RULE_MAPPING_NOTES[biomeName];
|
|
2159
|
-
if (partialMappingNote) warnPartialMappingOnce(biomeName, partialMappingNote, reporter);
|
|
2160
|
-
|
|
2426
|
+
if (partialMappingNote && enabled) warnPartialMappingOnce(biomeName, partialMappingNote, reporter);
|
|
2427
|
+
const ruleNames = [...new Set((Array.isArray(mapped) ? mapped : [mapped]).map(normalizeOxlintRuleName))];
|
|
2428
|
+
if (!typeAware && enabled) for (const ruleName of ruleNames.filter((name) => TYPE_AWARE_OXLINT_RULES.has(name))) warnPartialMappingOnce(`${biomeName}::${ruleName}`, `Biome rule ${biomeName} was mapped to ${ruleName}, a type-aware Oxlint rule that only runs with type-aware linting, so its diagnostics are not reported. Re-run the migration with --type-aware to keep them.`, reporter);
|
|
2429
|
+
return ruleNames;
|
|
2161
2430
|
}
|
|
2162
|
-
warnUnmappedRuleOnce(biomeName, reporter);
|
|
2431
|
+
if (enabled) warnUnmappedRuleOnce(biomeName, reporter);
|
|
2163
2432
|
return [];
|
|
2164
2433
|
}
|
|
2165
|
-
function warnPartialMappingOnce(
|
|
2434
|
+
function warnPartialMappingOnce(key, note, reporter) {
|
|
2166
2435
|
let warnedRules = WARNED_PARTIAL_MAPPINGS_BY_REPORTER.get(reporter);
|
|
2167
2436
|
if (!warnedRules) {
|
|
2168
2437
|
warnedRules = /* @__PURE__ */ new Set();
|
|
2169
2438
|
WARNED_PARTIAL_MAPPINGS_BY_REPORTER.set(reporter, warnedRules);
|
|
2170
2439
|
}
|
|
2171
|
-
if (warnedRules.has(
|
|
2172
|
-
warnedRules.add(
|
|
2440
|
+
if (warnedRules.has(key)) return;
|
|
2441
|
+
warnedRules.add(key);
|
|
2173
2442
|
reporter.loss(note);
|
|
2174
2443
|
}
|
|
2175
2444
|
function warnUnmappedRuleOnce(biomeName, reporter) {
|
|
@@ -2215,6 +2484,7 @@ function extractRulesFromBiomeConfig(linterRules, reporter, options = {}) {
|
|
|
2215
2484
|
sourceRulesSkipped
|
|
2216
2485
|
};
|
|
2217
2486
|
applyTopLevelPreset(linterRules, categories, reporter, applyImplicitRecommended);
|
|
2487
|
+
const sourcesByOxlintRule = /* @__PURE__ */ new Map();
|
|
2218
2488
|
for (const [key, value] of Object.entries(linterRules)) {
|
|
2219
2489
|
if (key === "recommended" || key === "all" || key === "preset") continue;
|
|
2220
2490
|
if (typeof value === "boolean") continue;
|
|
@@ -2233,11 +2503,18 @@ function extractRulesFromBiomeConfig(linterRules, reporter, options = {}) {
|
|
|
2233
2503
|
if (ruleName === "recommended" || ruleName === "all" || ruleName === "preset") continue;
|
|
2234
2504
|
if (typeof ruleSeverity === "boolean" || ruleSeverity === void 0) continue;
|
|
2235
2505
|
if (!isBiomeRuleSeverity(ruleSeverity)) continue;
|
|
2236
|
-
const oxlintRuleNames = mapBiomeRuleToOxlintRules(ruleName, reporter, typeAware);
|
|
2506
|
+
const oxlintRuleNames = mapBiomeRuleToOxlintRules(ruleName, reporter, typeAware, (typeof ruleSeverity === "string" ? ruleSeverity : ruleSeverity.level) !== "off");
|
|
2237
2507
|
if (oxlintRuleNames.length > 0) {
|
|
2238
2508
|
sourceRulesConverted.add(ruleName);
|
|
2239
2509
|
const oxlintSeverity = mapBiomeRuleOptionsToOxlintSeverity(ruleName, mapBiomeRuleSeverity(ruleSeverity, reporter, ruleName), ruleSeverity, reporter);
|
|
2240
|
-
for (const oxlintRuleName of oxlintRuleNames)
|
|
2510
|
+
for (const oxlintRuleName of oxlintRuleNames) {
|
|
2511
|
+
const sources = sourcesByOxlintRule.get(oxlintRuleName) ?? [];
|
|
2512
|
+
sources.push({
|
|
2513
|
+
biomeRule: ruleName,
|
|
2514
|
+
severity: oxlintSeverity
|
|
2515
|
+
});
|
|
2516
|
+
sourcesByOxlintRule.set(oxlintRuleName, sources);
|
|
2517
|
+
}
|
|
2241
2518
|
} else sourceRulesSkipped.add(ruleName);
|
|
2242
2519
|
}
|
|
2243
2520
|
const groupPreset = value.preset;
|
|
@@ -2245,6 +2522,7 @@ function extractRulesFromBiomeConfig(linterRules, reporter, options = {}) {
|
|
|
2245
2522
|
if (oxlintCategory && groupEnabled !== void 0) categories[oxlintCategory] = groupEnabled ? "warn" : "off";
|
|
2246
2523
|
}
|
|
2247
2524
|
}
|
|
2525
|
+
for (const [oxlintRuleName, sources] of sourcesByOxlintRule) rules[oxlintRuleName] = resolveSharedRuleSeverity(oxlintRuleName, sources, reporter);
|
|
2248
2526
|
return {
|
|
2249
2527
|
rules,
|
|
2250
2528
|
categories,
|
|
@@ -2252,6 +2530,48 @@ function extractRulesFromBiomeConfig(linterRules, reporter, options = {}) {
|
|
|
2252
2530
|
sourceRulesSkipped
|
|
2253
2531
|
};
|
|
2254
2532
|
}
|
|
2533
|
+
const SEVERITY_RANK = {
|
|
2534
|
+
off: 0,
|
|
2535
|
+
warn: 1,
|
|
2536
|
+
error: 2
|
|
2537
|
+
};
|
|
2538
|
+
function severityLevel(severity) {
|
|
2539
|
+
return typeof severity === "string" ? severity : severity[0];
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Resolves one Oxlint rule that several configured Biome rules map onto.
|
|
2543
|
+
*
|
|
2544
|
+
* Writing each source in turn would let whichever Biome rule the config lists last decide,
|
|
2545
|
+
* so `noUnusedFunctionParameters: "off"` listed after `noUnusedVariables: "error"` would turn
|
|
2546
|
+
* off `no-unused-vars` and drop every unused-variable diagnostic. Instead the strongest
|
|
2547
|
+
* severity wins, because an enabled Biome rule's diagnostics are behaviour to keep, while a
|
|
2548
|
+
* disabled one only means that Biome rule stayed quiet. What the shared rule then reports
|
|
2549
|
+
* beyond the enabled sources is surfaced rather than hidden.
|
|
2550
|
+
*/
|
|
2551
|
+
function resolveSharedRuleSeverity(oxlintRuleName, sources, reporter) {
|
|
2552
|
+
const [first] = sources;
|
|
2553
|
+
const enabled = sources.filter(({ severity }) => severityLevel(severity) !== "off");
|
|
2554
|
+
if (!first || sources.length === 1 || enabled.length === 0) return first?.severity ?? "off";
|
|
2555
|
+
const strongest = enabled.reduce((current, candidate) => SEVERITY_RANK[severityLevel(candidate.severity)] > SEVERITY_RANK[severityLevel(current.severity)] ? candidate : current);
|
|
2556
|
+
const optionSets = /* @__PURE__ */ new Map();
|
|
2557
|
+
for (const { severity } of enabled) if (typeof severity !== "string") optionSets.set(JSON.stringify(severity.slice(1)), severity.slice(1));
|
|
2558
|
+
if (optionSets.size > 1) reporter.loss(`Biome rules ${formatRuleList(enabled)} all map to Oxlint ${oxlintRuleName} with different options; Oxlint accepts one set, so the options of ${strongest.biomeRule} were kept and the others were not migrated.`);
|
|
2559
|
+
const level = severityLevel(strongest.severity);
|
|
2560
|
+
const options = typeof strongest.severity === "string" ? [...optionSets.values()][0] ?? [] : strongest.severity.slice(1);
|
|
2561
|
+
const disabled = sources.filter(({ severity }) => severityLevel(severity) === "off");
|
|
2562
|
+
if (oxlintRuleName === "no-unused-vars" && disabled.length === 1 && disabled[0]?.biomeRule === "noUnusedFunctionParameters") {
|
|
2563
|
+
const [currentOptions] = options;
|
|
2564
|
+
return [level, {
|
|
2565
|
+
...isRecord(currentOptions) ? currentOptions : {},
|
|
2566
|
+
args: "none"
|
|
2567
|
+
}];
|
|
2568
|
+
}
|
|
2569
|
+
if (disabled.length > 0) reporter.warn(`Biome ${formatRuleList(disabled)} ${disabled.length === 1 ? "is" : "are"} off, but Oxlint ${oxlintRuleName} also implements ${formatRuleList(enabled)}, which ${enabled.length === 1 ? "is" : "are"} on; Oxlint will also report what ${disabled.length === 1 ? "that rule" : "those rules"} would have reported.`);
|
|
2570
|
+
return options.length > 0 ? [level, ...options] : level;
|
|
2571
|
+
}
|
|
2572
|
+
function formatRuleList(sources) {
|
|
2573
|
+
return sources.map(({ biomeRule }) => biomeRule).join(", ");
|
|
2574
|
+
}
|
|
2255
2575
|
function applyTopLevelPreset(linterRules, categories, reporter, applyImplicitRecommended) {
|
|
2256
2576
|
const preset = linterRules.preset ?? (linterRules.all === true ? "all" : linterRules.all === false || linterRules.recommended === false ? "none" : linterRules.recommended === true ? "recommended" : applyImplicitRecommended ? "recommended" : void 0);
|
|
2257
2577
|
if (!preset) return;
|
|
@@ -2377,7 +2697,7 @@ function generateOxlintConfig(biomeConfig, reporter, options = {}) {
|
|
|
2377
2697
|
if (options.enableImportGraph && !linterDisabled) addImportGraphRecipe(oxlintConfig, options.importCycleMaxDepth ?? 3);
|
|
2378
2698
|
determinePlugins(oxlintConfig);
|
|
2379
2699
|
mapIgnorePatterns(biomeConfig, oxlintConfig, options.biomeIgnorePatterns ?? []);
|
|
2380
|
-
mapEnvironment(biomeConfig, oxlintConfig);
|
|
2700
|
+
mapEnvironment(biomeConfig, oxlintConfig, linterDisabled);
|
|
2381
2701
|
mapTypeAwareOptions(oxlintConfig, options.typeAware ?? false, options.typeCheck ?? false);
|
|
2382
2702
|
mapSettings(biomeConfig, oxlintConfig, options.typeAwareProfile ?? "standard");
|
|
2383
2703
|
return {
|
|
@@ -2396,6 +2716,8 @@ function mapTypeAwareOptions(oxlintConfig, typeAware, typeCheck) {
|
|
|
2396
2716
|
function warnAboutUnrepresentableIncludes(biomeConfig, reporter) {
|
|
2397
2717
|
const positiveSelectors = [...biomeConfig.files?.include ?? [], ...biomeConfig.linter?.include ?? []];
|
|
2398
2718
|
if (positiveSelectors.length > 0) reporter.loss(`Biome files/linter positive include selection (${positiveSelectors.join(", ")}) cannot be represented in an Oxlint config; Oxlint will lint every file not covered by ignorePatterns, which is a wider set. Pass equivalent paths to the Oxlint CLI before replacing Biome.`);
|
|
2719
|
+
const reincludes = joinExclusions(biomeConfig.files?.exclude, biomeConfig.linter?.exclude).filter((pattern) => pattern.startsWith("!") && hasDirectoryGlob(pattern)).map((pattern) => pattern.slice(1));
|
|
2720
|
+
if (reincludes.length > 0) reporter.warn(`Biome re-includes ${reincludes.join(", ")} after a negated pattern, which was migrated as "!" entries in the Oxlint ignorePatterns. Oxlint lifts the exclusion for every file such an entry matches, while Biome does not look inside a directory an earlier exception excluded, so Oxlint may lint files below one that Biome skipped.`);
|
|
2399
2721
|
}
|
|
2400
2722
|
function addImportGraphRecipe(oxlintConfig, maxDepth) {
|
|
2401
2723
|
oxlintConfig.rules ??= {};
|
|
@@ -2441,15 +2763,25 @@ function sortPlugins(plugins) {
|
|
|
2441
2763
|
}
|
|
2442
2764
|
function mapIgnorePatterns(biomeConfig, oxlintConfig, additionalIgnorePatterns) {
|
|
2443
2765
|
const ignorePatterns = [
|
|
2766
|
+
...joinExclusions(biomeConfig.files?.exclude, biomeConfig.linter?.exclude),
|
|
2444
2767
|
...additionalIgnorePatterns,
|
|
2445
2768
|
...biomeConfig.files?.ignore ?? [],
|
|
2446
|
-
...biomeConfig.linter?.ignore ?? []
|
|
2447
|
-
...biomeConfig.files?.exclude ?? [],
|
|
2448
|
-
...biomeConfig.linter?.exclude ?? []
|
|
2769
|
+
...biomeConfig.linter?.ignore ?? []
|
|
2449
2770
|
];
|
|
2450
2771
|
if (ignorePatterns.length > 0) oxlintConfig.ignorePatterns = [...new Set(ignorePatterns)];
|
|
2451
2772
|
}
|
|
2452
|
-
|
|
2773
|
+
/**
|
|
2774
|
+
* Biome resolves browser and Node.js globals without any configuration, while Oxlint only
|
|
2775
|
+
* knows ECMAScript builtins until an `env` names more. Without these two environments
|
|
2776
|
+
* `no-undef` reports `window`, `console` and `process`, and `no-global-assign` and
|
|
2777
|
+
* `no-implied-eval` stop recognising the globals they guard, so each would diverge from the
|
|
2778
|
+
* Biome rule it replaces. Both environments together match Biome's set of known globals.
|
|
2779
|
+
*/
|
|
2780
|
+
function mapEnvironment(biomeConfig, oxlintConfig, linterDisabled) {
|
|
2781
|
+
if (!linterDisabled) oxlintConfig.env = {
|
|
2782
|
+
browser: true,
|
|
2783
|
+
node: true
|
|
2784
|
+
};
|
|
2453
2785
|
if (biomeConfig.javascript?.globals) {
|
|
2454
2786
|
const globals = {};
|
|
2455
2787
|
for (const globalVar of biomeConfig.javascript.globals) globals[globalVar] = "readonly";
|
|
@@ -2494,7 +2826,7 @@ const PackageJsonSchema = z.object({
|
|
|
2494
2826
|
const ToolVersionManifestSchema = z.object({
|
|
2495
2827
|
dependencies: z.record(z.string(), z.string()).optional(),
|
|
2496
2828
|
devDependencies: z.record(z.string(), z.string()).optional()
|
|
2497
|
-
}).
|
|
2829
|
+
}).loose();
|
|
2498
2830
|
let recommendedToolVersionsPromise;
|
|
2499
2831
|
async function updatePackageJson(projectDir, reporter, dryRun, options = {}) {
|
|
2500
2832
|
const packageJsonPath = await findClosestPackageJson(projectDir) ?? resolve(projectDir, "package.json");
|
|
@@ -2505,6 +2837,7 @@ async function updatePackageJson(projectDir, reporter, dryRun, options = {}) {
|
|
|
2505
2837
|
scriptsUpdated: [],
|
|
2506
2838
|
dependenciesRemoved: [],
|
|
2507
2839
|
devDependencies: [],
|
|
2840
|
+
scriptsStillUsingBiome: [],
|
|
2508
2841
|
changed: false
|
|
2509
2842
|
};
|
|
2510
2843
|
try {
|
|
@@ -2533,16 +2866,16 @@ async function updatePackageJson(projectDir, reporter, dryRun, options = {}) {
|
|
|
2533
2866
|
oxfmtCommand
|
|
2534
2867
|
}) || modified;
|
|
2535
2868
|
const scriptsStillUsingBiome = Object.entries(packageJson.scripts ?? {}).filter(([, script]) => containsBiomeExecutable(script)).map(([name]) => name);
|
|
2869
|
+
summary.scriptsStillUsingBiome = scriptsStillUsingBiome;
|
|
2536
2870
|
if (!removeBiomeAllowed) reporter.info("Keeping @biomejs/biome: removing it requires --delete and a migration with no semantic losses.");
|
|
2537
|
-
else if (scriptsStillUsingBiome.length > 0) reporter.warn(`Keeping @biomejs/biome because these package scripts still invoke Biome: ${scriptsStillUsingBiome.join(", ")}`);
|
|
2871
|
+
else if (scriptsStillUsingBiome.length > 0) reporter.warn(`Keeping @biomejs/biome and the Biome config because these package scripts still invoke Biome: ${scriptsStillUsingBiome.join(", ")}`);
|
|
2538
2872
|
else {
|
|
2539
2873
|
if (packageJson.devDependencies) modified = removeBiomeDependency(packageJson.devDependencies, "devDependencies", summary.dependenciesRemoved, reporter) || modified;
|
|
2540
2874
|
if (packageJson.dependencies) modified = removeBiomeDependency(packageJson.dependencies, "dependencies", summary.dependenciesRemoved, reporter) || modified;
|
|
2541
2875
|
}
|
|
2542
|
-
packageJson.devDependencies
|
|
2543
|
-
modified = ensureDevDependency(packageJson
|
|
2544
|
-
modified = ensureDevDependency(packageJson
|
|
2545
|
-
if (needsTypeAwareDependency) modified = ensureDevDependency(packageJson.devDependencies, "oxlint-tsgolint", recommendedVersions["oxlint-tsgolint"], summary.devDependencies) || modified;
|
|
2876
|
+
modified = ensureDevDependency(packageJson, "oxlint", recommendedVersions.oxlint, summary.devDependencies) || modified;
|
|
2877
|
+
modified = ensureDevDependency(packageJson, "oxfmt", recommendedVersions.oxfmt, summary.devDependencies) || modified;
|
|
2878
|
+
if (needsTypeAwareDependency) modified = ensureDevDependency(packageJson, "oxlint-tsgolint", recommendedVersions["oxlint-tsgolint"], summary.devDependencies) || modified;
|
|
2546
2879
|
summary.changed = modified;
|
|
2547
2880
|
const dependenciesChanged = summary.dependenciesRemoved.length > 0 || summary.devDependencies.some((change) => change.action !== "already-present");
|
|
2548
2881
|
summary.lockfile = await detectLockfile(dirname(packageJsonPath), dependenciesChanged);
|
|
@@ -2769,7 +3102,7 @@ function replaceBiomeCommand(script, command, checkReplacement, fixByStrategy, d
|
|
|
2769
3102
|
const cleanedArgs = args.replace(/\s--(write|fix|unsafe)\b/gu, "").trim();
|
|
2770
3103
|
const suffix = cleanedArgs.length > 0 ? ` ${cleanedArgs}` : "";
|
|
2771
3104
|
const effectiveFixStrategy = hasUnsafe ? escalateFixStrategy(defaultFixStrategy, "dangerous") : defaultFixStrategy;
|
|
2772
|
-
return `${leading}${applySuffixToCommands(hasFix
|
|
3105
|
+
return `${leading}${applySuffixToCommands(hasFix ? fixByStrategy[effectiveFixStrategy] : checkReplacement, suffix)}`;
|
|
2773
3106
|
}),
|
|
2774
3107
|
didReplace
|
|
2775
3108
|
};
|
|
@@ -2815,9 +3148,18 @@ function removeBiomeDependency(dependencies, dependencyType, removals, reporter)
|
|
|
2815
3148
|
}
|
|
2816
3149
|
return modified;
|
|
2817
3150
|
}
|
|
2818
|
-
|
|
3151
|
+
/**
|
|
3152
|
+
* Makes sure an Oxc tool is installed at least at the version this migration targets.
|
|
3153
|
+
*
|
|
3154
|
+
* An existing entry is only raised when it is a plain semver range whose minimum is older
|
|
3155
|
+
* than the target. A newer range, a `catalog:`/`workspace:` protocol or a dist-tag is the
|
|
3156
|
+
* project's own choice, and an entry under `dependencies` already installs the tool, so
|
|
3157
|
+
* those are left alone rather than downgraded, broken, or duplicated.
|
|
3158
|
+
*/
|
|
3159
|
+
function ensureDevDependency(packageJson, name, version, changes) {
|
|
3160
|
+
const dependencies = [packageJson.devDependencies, packageJson.dependencies].find((candidate) => candidate?.[name] !== void 0) ?? (packageJson.devDependencies ??= {});
|
|
2819
3161
|
const existing = dependencies[name];
|
|
2820
|
-
if (
|
|
3162
|
+
if (existing === void 0) {
|
|
2821
3163
|
dependencies[name] = version;
|
|
2822
3164
|
changes.push({
|
|
2823
3165
|
name,
|
|
@@ -2826,21 +3168,32 @@ function ensureDevDependency(dependencies, name, version, changes) {
|
|
|
2826
3168
|
});
|
|
2827
3169
|
return true;
|
|
2828
3170
|
}
|
|
2829
|
-
if (existing
|
|
2830
|
-
dependencies[name] = version;
|
|
3171
|
+
if (!isOlderSemverRange(existing, version)) {
|
|
2831
3172
|
changes.push({
|
|
2832
3173
|
name,
|
|
2833
|
-
action: "
|
|
2834
|
-
|
|
2835
|
-
to: version
|
|
3174
|
+
action: "already-present",
|
|
3175
|
+
to: existing
|
|
2836
3176
|
});
|
|
2837
|
-
return
|
|
3177
|
+
return false;
|
|
2838
3178
|
}
|
|
3179
|
+
dependencies[name] = version;
|
|
2839
3180
|
changes.push({
|
|
2840
3181
|
name,
|
|
2841
|
-
action: "
|
|
2842
|
-
|
|
3182
|
+
action: "updated",
|
|
3183
|
+
from: existing,
|
|
3184
|
+
to: version
|
|
2843
3185
|
});
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
const SEMVER_RANGE_PATTERN = /^(?:\^|~|>=)?(\d+)\.(\d+)\.(\d+)/u;
|
|
3189
|
+
function isOlderSemverRange(existing, target) {
|
|
3190
|
+
const existingVersion = SEMVER_RANGE_PATTERN.exec(existing.trim());
|
|
3191
|
+
const targetVersion = SEMVER_RANGE_PATTERN.exec(target.trim());
|
|
3192
|
+
if (!existingVersion || !targetVersion) return false;
|
|
3193
|
+
for (let index = 1; index <= 3; index++) {
|
|
3194
|
+
const difference = Number(existingVersion[index]) - Number(targetVersion[index]);
|
|
3195
|
+
if (difference !== 0) return difference < 0;
|
|
3196
|
+
}
|
|
2844
3197
|
return false;
|
|
2845
3198
|
}
|
|
2846
3199
|
async function getRecommendedToolVersions() {
|
|
@@ -3075,7 +3428,7 @@ const LEGACY_BIOME_CONFIG_NAMES = [
|
|
|
3075
3428
|
".biome.jsonc"
|
|
3076
3429
|
];
|
|
3077
3430
|
var MigrationStepFailedError = class extends Error {};
|
|
3078
|
-
const WorkspacePackageJsonSchema = z.object({ workspaces: z.union([z.array(z.string()), z.record(z.string(), z.unknown())]).optional() }).
|
|
3431
|
+
const WorkspacePackageJsonSchema = z.object({ workspaces: z.union([z.array(z.string()), z.record(z.string(), z.unknown())]).optional() }).loose();
|
|
3079
3432
|
async function migrate(options = {}, reporter = new CollectingReporter()) {
|
|
3080
3433
|
const cwd = process.cwd();
|
|
3081
3434
|
const biomeConfigPath = options.configPath ? resolve(cwd, options.configPath) : await findBiomeConfig(cwd);
|
|
@@ -3248,7 +3601,7 @@ async function runMigration(options, reporter, biomeConfigPath) {
|
|
|
3248
3601
|
await updateTurboConfig(projectDir, reporter, false, options.signal);
|
|
3249
3602
|
throwIfMigrationErrorsIncreased(turboErrorCount, reporter);
|
|
3250
3603
|
}
|
|
3251
|
-
if (cleanupAllowed) {
|
|
3604
|
+
if (cleanupAllowed && !biomeStillInvoked(packageJsonSummary)) {
|
|
3252
3605
|
deleteWasAttempted = true;
|
|
3253
3606
|
deletedLegacyFiles = await cleanupLegacyBiomeFiles(projectDir, biomeConfigPath, false, reporter, options.signal);
|
|
3254
3607
|
}
|
|
@@ -3277,7 +3630,7 @@ async function runMigration(options, reporter, biomeConfigPath) {
|
|
|
3277
3630
|
await updateTurboConfig(projectDir, reporter, true, options.signal);
|
|
3278
3631
|
throwIfMigrationErrorsIncreased(turboErrorCount, reporter);
|
|
3279
3632
|
}
|
|
3280
|
-
if (cleanupAllowed) {
|
|
3633
|
+
if (cleanupAllowed && !biomeStillInvoked(packageJsonSummary)) {
|
|
3281
3634
|
deleteWasAttempted = true;
|
|
3282
3635
|
deletedLegacyFiles = await cleanupLegacyBiomeFiles(projectDir, biomeConfigPath, true, reporter, options.signal);
|
|
3283
3636
|
}
|
|
@@ -3300,6 +3653,7 @@ async function runMigration(options, reporter, biomeConfigPath) {
|
|
|
3300
3653
|
deletedLegacyFiles,
|
|
3301
3654
|
dryRun: options.dryRun ?? false,
|
|
3302
3655
|
semanticLosses: reporter.getLosses(),
|
|
3656
|
+
scriptsStillUsingBiome: packageJsonSummary?.scriptsStillUsingBiome ?? [],
|
|
3303
3657
|
suggestions
|
|
3304
3658
|
});
|
|
3305
3659
|
if (packageJsonSummary?.lockfile?.stale) suggestions.push(`Dependencies changed: run \`${packageJsonSummary.lockfile.installCommand}\` to refresh ${packageJsonSummary.lockfile.path} before any frozen-lockfile install.`);
|
|
@@ -3369,7 +3723,7 @@ async function buildSuggestions({ biomeIgnorePatterns, detectedIntegrations, opt
|
|
|
3369
3723
|
suggestions.push(" pnpm add -D oxlint-tsgolint@latest");
|
|
3370
3724
|
suggestions.push(` ${typeAwareCommand}`);
|
|
3371
3725
|
suggestions.push("Type-aware mode uses the tsgolint backend.");
|
|
3372
|
-
suggestions.push("Type-aware linting is stable as of tsgolint v7; it implements 60 of typescript-eslint’s 62 type-aware rules,
|
|
3726
|
+
suggestions.push("Type-aware linting is stable as of tsgolint v7; it implements 60 of typescript-eslint’s 62 type-aware rules, so check naming-convention and prefer-destructuring if you relied on them.");
|
|
3373
3727
|
suggestions.push("TypeScript compatibility caveat: migrate deprecated tsconfig options if needed (typescript-go / TS7+ behavior).");
|
|
3374
3728
|
suggestions.push(`Resolved type-aware profile: ${typeAwareProfile}`);
|
|
3375
3729
|
}
|
|
@@ -3415,7 +3769,7 @@ function reconcileVcsIgnoreSettings(biomeConfig, reporter) {
|
|
|
3415
3769
|
reporter.warn("Biome vcs is enabled without useIgnoreFile, so Biome ignored .gitignore. Oxfmt reads .gitignore by default and will therefore skip files Biome formatted; pass --ignore-path to override.");
|
|
3416
3770
|
if (vcs.root !== void 0 || vcs.defaultBranch !== void 0) reporter.loss("Biome vcs.root/vcs.defaultBranch scoping (used by Biome’s changed-files filtering) has no Oxlint or Oxfmt equivalent and was not migrated.");
|
|
3417
3771
|
}
|
|
3418
|
-
function buildCleanupOutcome({ requested, cleanupAllowed, deleteWasAttempted, deletedLegacyFiles, dryRun, semanticLosses, suggestions }) {
|
|
3772
|
+
function buildCleanupOutcome({ requested, cleanupAllowed, deleteWasAttempted, deletedLegacyFiles, dryRun, semanticLosses, scriptsStillUsingBiome, suggestions }) {
|
|
3419
3773
|
if (!requested) return {
|
|
3420
3774
|
requested: false,
|
|
3421
3775
|
performed: false,
|
|
@@ -3433,6 +3787,16 @@ function buildCleanupOutcome({ requested, cleanupAllowed, deleteWasAttempted, de
|
|
|
3433
3787
|
files: []
|
|
3434
3788
|
};
|
|
3435
3789
|
}
|
|
3790
|
+
if (scriptsStillUsingBiome.length > 0) {
|
|
3791
|
+
const blockedReason = `package scripts still invoke Biome: ${scriptsStillUsingBiome.join(", ")}`;
|
|
3792
|
+
suggestions.push(`--delete was skipped because ${blockedReason}. Deleting the Biome config would leave them running with Biome's defaults; update them (or re-run with --update-scripts), then re-run with --delete.`);
|
|
3793
|
+
return {
|
|
3794
|
+
requested: true,
|
|
3795
|
+
performed: false,
|
|
3796
|
+
blockedReason,
|
|
3797
|
+
files: []
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3436
3800
|
if (!deleteWasAttempted) {
|
|
3437
3801
|
const blockedReason = "the migration did not complete successfully";
|
|
3438
3802
|
suggestions.push(`--delete skipped because ${blockedReason}.`);
|
|
@@ -3460,6 +3824,10 @@ function buildCleanupOutcome({ requested, cleanupAllowed, deleteWasAttempted, de
|
|
|
3460
3824
|
files: deletedLegacyFiles
|
|
3461
3825
|
};
|
|
3462
3826
|
}
|
|
3827
|
+
/** Scripts that still run Biome need its config, so it cannot be deleted from under them. */
|
|
3828
|
+
function biomeStillInvoked(summary) {
|
|
3829
|
+
return (summary?.scriptsStillUsingBiome.length ?? 0) > 0;
|
|
3830
|
+
}
|
|
3463
3831
|
async function detectWorkspaceMonorepo(projectDir) {
|
|
3464
3832
|
const packageJsonPath = resolve(projectDir, "package.json");
|
|
3465
3833
|
if (!await pathExists(packageJsonPath)) return false;
|
|
@@ -3546,10 +3914,7 @@ function countRebasablePatterns(oxlintConfig, oxfmtConfig) {
|
|
|
3546
3914
|
oxfmtConfig.ignorePatterns,
|
|
3547
3915
|
...(oxlintConfig.overrides ?? []).flatMap((override) => [override.files, override.excludeFiles]),
|
|
3548
3916
|
...(oxfmtConfig.overrides ?? []).flatMap((override) => [override.files, override.excludeFiles])
|
|
3549
|
-
].reduce((total, patterns) => total + (patterns ?? []).
|
|
3550
|
-
}
|
|
3551
|
-
function stripNegation(pattern) {
|
|
3552
|
-
return pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
3917
|
+
].reduce((total, patterns) => total + (patterns ?? []).length, 0);
|
|
3553
3918
|
}
|
|
3554
3919
|
function rebasePatterns(patterns, projectFromOutput) {
|
|
3555
3920
|
if (!patterns || !projectFromOutput) return patterns;
|
|
@@ -3557,7 +3922,6 @@ function rebasePatterns(patterns, projectFromOutput) {
|
|
|
3557
3922
|
const negated = pattern.startsWith("!");
|
|
3558
3923
|
const prefix = negated ? "!" : "";
|
|
3559
3924
|
const body = negated ? pattern.slice(1) : pattern;
|
|
3560
|
-
if (isAbsolute(body)) return pattern;
|
|
3561
3925
|
return `${prefix}${posix.normalize(`${projectFromOutput}/${body.replace(/^\//u, "")}`)}`;
|
|
3562
3926
|
});
|
|
3563
3927
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "biome-to-oxc",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Migrate from Biome to Oxc ecosystem (oxlint + oxfmt + oxlint-tsgolint)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"biome",
|
|
@@ -58,20 +58,20 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@arethetypeswrong/core": "^0.18.5",
|
|
61
|
-
"@biomejs/biome": "^2.5.
|
|
62
|
-
"@types/node": "^26.6.
|
|
63
|
-
"@vitest/coverage-v8": "^5.0.
|
|
64
|
-
"oxfmt": "^0.
|
|
65
|
-
"oxlint": "^1.
|
|
61
|
+
"@biomejs/biome": "^2.5.15",
|
|
62
|
+
"@types/node": "^26.6.4",
|
|
63
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
64
|
+
"oxfmt": "^0.72.0",
|
|
65
|
+
"oxlint": "^1.87.0",
|
|
66
66
|
"oxlint-tsgolint": "^7.0.2003",
|
|
67
|
-
"publint": "^0.3.
|
|
68
|
-
"tsdown": "
|
|
67
|
+
"publint": "^0.3.25",
|
|
68
|
+
"tsdown": "0.23.0",
|
|
69
69
|
"typescript": "next",
|
|
70
|
-
"vitest": "^5.0.
|
|
70
|
+
"vitest": "^5.0.3"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
|
-
"node": ">=24.
|
|
74
|
-
"pnpm": ">=11.
|
|
73
|
+
"node": ">=24.21.0",
|
|
74
|
+
"pnpm": ">=11.28.5"
|
|
75
75
|
},
|
|
76
|
-
"packageManager": "pnpm@11.
|
|
76
|
+
"packageManager": "pnpm@11.28.5"
|
|
77
77
|
}
|