biome-to-oxc 3.6.1 → 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 +10 -3
- package/dist/bin/biome-to-oxc.mjs +1 -1
- package/dist/src/index.d.mts +12 -3
- package/dist/src/index.mjs +1 -1
- package/dist/{src-FuMDOlMz.mjs → src-JKoQYJ0f.mjs} +173 -33
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -32,7 +32,14 @@ Current capabilities:
|
|
|
32
32
|
✅ **Preserves Representable Setup**
|
|
33
33
|
|
|
34
34
|
- Ignore patterns, override file globs, and override exclusions
|
|
35
|
-
- 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
|
|
36
43
|
- `.biomeignore` patterns migrated into the Oxlint **and** Oxfmt `ignorePatterns`. Biome 2.x does
|
|
37
44
|
not read `.biomeignore` itself, so this deliberately narrows scope; the migration says so
|
|
38
45
|
- Linter configuration overrides
|
|
@@ -435,8 +442,8 @@ npx oxlint --type-aware --type-check
|
|
|
435
442
|
- Overlapping `overrides` are migrated as-is. Biome and Oxfmt both merge matching overrides field
|
|
436
443
|
by field with later entries winning, so the two agree; a conformance test pins this
|
|
437
444
|
- Type-aware linting is stable as of `oxlint-tsgolint` v7 and requires TypeScript 7; 60 of the 62
|
|
438
|
-
targeted typescript-eslint rules are currently implemented
|
|
439
|
-
`
|
|
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
|
|
440
447
|
- CSS, JSON and GraphQL formatter overrides are mapped but may need manual review
|
|
441
448
|
- Prettier plugin support is not available in Oxfmt
|
|
442
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;
|
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,26 +248,46 @@ 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,
|
|
@@ -268,6 +307,91 @@ function withoutMatchAllSelection(selection) {
|
|
|
268
307
|
include: void 0
|
|
269
308
|
} : selection;
|
|
270
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
|
+
}
|
|
271
395
|
function normalizeBiomeConfig(config, reporter) {
|
|
272
396
|
const normalized = { ...config };
|
|
273
397
|
if (normalized.files) {
|
|
@@ -284,7 +408,7 @@ function normalizeBiomeConfig(config, reporter) {
|
|
|
284
408
|
normalized.linter = {
|
|
285
409
|
...normalized.linter,
|
|
286
410
|
include,
|
|
287
|
-
exclude,
|
|
411
|
+
exclude: withoutToolReincludes(exclude, "linter", normalized.files?.exclude, reporter),
|
|
288
412
|
includes: void 0
|
|
289
413
|
};
|
|
290
414
|
}
|
|
@@ -293,19 +417,23 @@ function normalizeBiomeConfig(config, reporter) {
|
|
|
293
417
|
normalized.formatter = {
|
|
294
418
|
...normalized.formatter,
|
|
295
419
|
include,
|
|
296
|
-
exclude,
|
|
420
|
+
exclude: withoutToolReincludes(exclude, "formatter", normalized.files?.exclude, reporter),
|
|
297
421
|
includes: void 0
|
|
298
422
|
};
|
|
299
423
|
}
|
|
300
|
-
if (normalized.overrides)
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
include,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
437
|
return normalized;
|
|
310
438
|
}
|
|
311
439
|
//#endregion
|
|
@@ -1241,12 +1369,11 @@ function mapOrganizeImportsOptions(value, reporter) {
|
|
|
1241
1369
|
}
|
|
1242
1370
|
function mapIgnorePatterns$1(biomeConfig, oxfmtConfig, additionalIgnorePatterns) {
|
|
1243
1371
|
const ignorePatterns = [
|
|
1372
|
+
...joinExclusions(biomeConfig.files?.exclude, biomeConfig.formatter?.exclude),
|
|
1244
1373
|
...oxfmtConfig.ignorePatterns ?? [],
|
|
1245
1374
|
...additionalIgnorePatterns,
|
|
1246
1375
|
...biomeConfig.files?.ignore ?? [],
|
|
1247
|
-
...biomeConfig.formatter?.ignore ?? []
|
|
1248
|
-
...biomeConfig.files?.exclude ?? [],
|
|
1249
|
-
...biomeConfig.formatter?.exclude ?? []
|
|
1376
|
+
...biomeConfig.formatter?.ignore ?? []
|
|
1250
1377
|
];
|
|
1251
1378
|
if (ignorePatterns.length > 0) oxfmtConfig.ignorePatterns = [...new Set(ignorePatterns)];
|
|
1252
1379
|
}
|
|
@@ -1320,7 +1447,7 @@ function validatePatternGroups(configName, groups) {
|
|
|
1320
1447
|
function findPatternProblem(pattern) {
|
|
1321
1448
|
const body = pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
1322
1449
|
if (body.split("/").includes("..")) return "escapes the config directory with \"..\", which Oxlint and Oxfmt reject";
|
|
1323
|
-
if (
|
|
1450
|
+
if (/^[A-Za-z]:[/\\]/u.test(body)) return "is an absolute path, but patterns are resolved within the config directory";
|
|
1324
1451
|
}
|
|
1325
1452
|
//#endregion
|
|
1326
1453
|
//#region src/js-plugin-scaffolder.ts
|
|
@@ -1342,6 +1469,14 @@ const RESERVED_JS_PLUGIN_NAMES = /* @__PURE__ */ new Set([
|
|
|
1342
1469
|
"eslint"
|
|
1343
1470
|
]);
|
|
1344
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
|
+
},
|
|
1345
1480
|
noReExportAll: {
|
|
1346
1481
|
nativeAlternatives: ["import/no-cycle", "import/no-self-import"],
|
|
1347
1482
|
jsPluginSpecifiers: ["eslint-plugin-import"],
|
|
@@ -1558,7 +1693,10 @@ const PARTIAL_RULE_MAPPING_NOTES = {
|
|
|
1558
1693
|
noComponentHookFactories: "Biome rule noComponentHookFactories was mapped to react/no-unstable-nested-components, which reports nested component definitions but not nested custom hook definitions.",
|
|
1559
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`.",
|
|
1560
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`.",
|
|
1561
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.",
|
|
1562
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.",
|
|
1563
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.",
|
|
1564
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.",
|
|
@@ -1775,6 +1913,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1775
1913
|
noLabelVar: "no-label-var",
|
|
1776
1914
|
noLoopFunc: "no-loop-func",
|
|
1777
1915
|
noMagicNumbers: "no-magic-numbers",
|
|
1916
|
+
noMeaninglessVoidOperator: "typescript/no-meaningless-void-operator",
|
|
1778
1917
|
noMisleadingCharacterClass: "no-misleading-character-class",
|
|
1779
1918
|
noMisleadingInstantiator: "typescript/no-misused-new",
|
|
1780
1919
|
noMisrefactoredShorthandAssign: "oxc/misrefactored-assign-op",
|
|
@@ -1807,6 +1946,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1807
1946
|
noProto: "no-proto",
|
|
1808
1947
|
noPrototypeBuiltins: "no-prototype-builtins",
|
|
1809
1948
|
noRedeclare: "no-redeclare",
|
|
1949
|
+
noReactObjectTypeAsDefaultProp: "react/no-object-type-as-default-prop",
|
|
1810
1950
|
noReactPropAssignments: "react/immutability",
|
|
1811
1951
|
noRedundantAlt: "jsx-a11y/img-redundant-alt",
|
|
1812
1952
|
noRedundantRoles: "jsx-a11y/no-redundant-roles",
|
|
@@ -1827,6 +1967,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1827
1967
|
noScriptUrl: "no-script-url",
|
|
1828
1968
|
noSelfCompare: "no-self-compare",
|
|
1829
1969
|
noSelfAssign: "no-self-assign",
|
|
1970
|
+
noSelfImport: "import/no-self-import",
|
|
1830
1971
|
noSetterReturn: "no-setter-return",
|
|
1831
1972
|
noShadow: "no-shadow",
|
|
1832
1973
|
noShadowRestrictedNames: "no-shadow-restricted-names",
|
|
@@ -1996,6 +2137,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
1996
2137
|
useObjectSpread: "prefer-object-spread",
|
|
1997
2138
|
useOptionalChain: "typescript/prefer-optional-chain",
|
|
1998
2139
|
useParseIntRadix: "radix",
|
|
2140
|
+
usePromiseRejectErrors: "prefer-promise-reject-errors",
|
|
1999
2141
|
useReadonlyClassProperties: "typescript/prefer-readonly",
|
|
2000
2142
|
useReactFunctionComponentDefinition: "react/function-component-definition",
|
|
2001
2143
|
useReactCompiler: REACT_COMPILER_RULES,
|
|
@@ -2012,6 +2154,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
|
|
|
2012
2154
|
useSpread: "prefer-spread",
|
|
2013
2155
|
useSpreadOverApply: "prefer-spread",
|
|
2014
2156
|
useStaticResponseMethods: "unicorn/prefer-response-static-json",
|
|
2157
|
+
useStrictBooleanExpressions: "typescript/strict-boolean-expressions",
|
|
2015
2158
|
useStringStartsEndsWith: "typescript/prefer-string-starts-ends-with",
|
|
2016
2159
|
useSymbolDescription: "symbol-description",
|
|
2017
2160
|
useTemplate: "prefer-template",
|
|
@@ -2573,6 +2716,8 @@ function mapTypeAwareOptions(oxlintConfig, typeAware, typeCheck) {
|
|
|
2573
2716
|
function warnAboutUnrepresentableIncludes(biomeConfig, reporter) {
|
|
2574
2717
|
const positiveSelectors = [...biomeConfig.files?.include ?? [], ...biomeConfig.linter?.include ?? []];
|
|
2575
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.`);
|
|
2576
2721
|
}
|
|
2577
2722
|
function addImportGraphRecipe(oxlintConfig, maxDepth) {
|
|
2578
2723
|
oxlintConfig.rules ??= {};
|
|
@@ -2618,11 +2763,10 @@ function sortPlugins(plugins) {
|
|
|
2618
2763
|
}
|
|
2619
2764
|
function mapIgnorePatterns(biomeConfig, oxlintConfig, additionalIgnorePatterns) {
|
|
2620
2765
|
const ignorePatterns = [
|
|
2766
|
+
...joinExclusions(biomeConfig.files?.exclude, biomeConfig.linter?.exclude),
|
|
2621
2767
|
...additionalIgnorePatterns,
|
|
2622
2768
|
...biomeConfig.files?.ignore ?? [],
|
|
2623
|
-
...biomeConfig.linter?.ignore ?? []
|
|
2624
|
-
...biomeConfig.files?.exclude ?? [],
|
|
2625
|
-
...biomeConfig.linter?.exclude ?? []
|
|
2769
|
+
...biomeConfig.linter?.ignore ?? []
|
|
2626
2770
|
];
|
|
2627
2771
|
if (ignorePatterns.length > 0) oxlintConfig.ignorePatterns = [...new Set(ignorePatterns)];
|
|
2628
2772
|
}
|
|
@@ -3579,7 +3723,7 @@ async function buildSuggestions({ biomeIgnorePatterns, detectedIntegrations, opt
|
|
|
3579
3723
|
suggestions.push(" pnpm add -D oxlint-tsgolint@latest");
|
|
3580
3724
|
suggestions.push(` ${typeAwareCommand}`);
|
|
3581
3725
|
suggestions.push("Type-aware mode uses the tsgolint backend.");
|
|
3582
|
-
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.");
|
|
3583
3727
|
suggestions.push("TypeScript compatibility caveat: migrate deprecated tsconfig options if needed (typescript-go / TS7+ behavior).");
|
|
3584
3728
|
suggestions.push(`Resolved type-aware profile: ${typeAwareProfile}`);
|
|
3585
3729
|
}
|
|
@@ -3770,10 +3914,7 @@ function countRebasablePatterns(oxlintConfig, oxfmtConfig) {
|
|
|
3770
3914
|
oxfmtConfig.ignorePatterns,
|
|
3771
3915
|
...(oxlintConfig.overrides ?? []).flatMap((override) => [override.files, override.excludeFiles]),
|
|
3772
3916
|
...(oxfmtConfig.overrides ?? []).flatMap((override) => [override.files, override.excludeFiles])
|
|
3773
|
-
].reduce((total, patterns) => total + (patterns ?? []).
|
|
3774
|
-
}
|
|
3775
|
-
function stripNegation(pattern) {
|
|
3776
|
-
return pattern.startsWith("!") ? pattern.slice(1) : pattern;
|
|
3917
|
+
].reduce((total, patterns) => total + (patterns ?? []).length, 0);
|
|
3777
3918
|
}
|
|
3778
3919
|
function rebasePatterns(patterns, projectFromOutput) {
|
|
3779
3920
|
if (!patterns || !projectFromOutput) return patterns;
|
|
@@ -3781,7 +3922,6 @@ function rebasePatterns(patterns, projectFromOutput) {
|
|
|
3781
3922
|
const negated = pattern.startsWith("!");
|
|
3782
3923
|
const prefix = negated ? "!" : "";
|
|
3783
3924
|
const body = negated ? pattern.slice(1) : pattern;
|
|
3784
|
-
if (isAbsolute(body)) return pattern;
|
|
3785
3925
|
return `${prefix}${posix.normalize(`${projectFromOutput}/${body.replace(/^\//u, "")}`)}`;
|
|
3786
3926
|
});
|
|
3787
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.
|
|
67
|
+
"publint": "^0.3.25",
|
|
68
68
|
"tsdown": "0.23.0",
|
|
69
69
|
"typescript": "next",
|
|
70
|
-
"vitest": "^5.0.
|
|
70
|
+
"vitest": "^5.0.3"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=24.21.0",
|
|
74
|
-
"pnpm": ">=11.28.
|
|
74
|
+
"pnpm": ">=11.28.5"
|
|
75
75
|
},
|
|
76
|
-
"packageManager": "pnpm@11.28.
|
|
76
|
+
"packageManager": "pnpm@11.28.5"
|
|
77
77
|
}
|