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 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, and Oxlint 1.85 does not yet expose
439
- `no-generated-empty-object-type`, so 59 can be enabled
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-FuMDOlMz.mjs";
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";
@@ -18,7 +18,10 @@ export interface BiomeConfig {
18
18
  export interface BiomeFilesConfig {
19
19
  include?: string[];
20
20
  includes?: string[];
21
- /** Negated `includes` entries (`!pattern`), split out during normalization. */
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
- /** Negated `includes` entries (`!pattern`), split out during normalization. */
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
- /** Negated `includes` entries (`!pattern`), split out during normalization. */
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;
@@ -1,2 +1,2 @@
1
- import { t as migrate } from "../src-FuMDOlMz.mjs";
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, isAbsolute, join, posix, relative, resolve } from "node:path";
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 legacy
222
- * `include` field carries positive selectors only.
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
- include.push(pattern);
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) normalized.overrides = normalized.overrides.map((override, index) => {
301
- const { include, exclude } = normalizeIncludeFields(override, `overrides[${index}]`, reporter);
302
- return {
303
- ...override,
304
- include,
305
- exclude,
306
- includes: void 0
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 (body.startsWith("/") || /^[A-Za-z]:[/\\]/u.test(body)) return "is an absolute path, but patterns are resolved within the config directory";
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, and Oxlint does not yet expose no-generated-empty-object-type, so check those three if you relied on them.");
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 ?? []).filter((pattern) => !isAbsolute(stripNegation(pattern))).length, 0);
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.6.1",
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.14",
62
- "@types/node": "^26.6.3",
63
- "@vitest/coverage-v8": "^5.0.2",
64
- "oxfmt": "^0.70.0",
65
- "oxlint": "^1.85.0",
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.24",
67
+ "publint": "^0.3.25",
68
68
  "tsdown": "0.23.0",
69
69
  "typescript": "next",
70
- "vitest": "^5.0.2"
70
+ "vitest": "^5.0.3"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=24.21.0",
74
- "pnpm": ">=11.28.0"
74
+ "pnpm": ">=11.28.5"
75
75
  },
76
- "packageManager": "pnpm@11.28.0"
76
+ "packageManager": "pnpm@11.28.5"
77
77
  }