biome-to-oxc 2.0.0 → 3.0.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
@@ -135,6 +135,12 @@ ignore configuration before removing Biome.
135
135
  - `docs/oxfmt-rules.tsv`: Oxfmt configuration surface inventory
136
136
  - `docs/tsgolint-rules.tsv`: typed linting implementation matrix
137
137
  - `docs/oxlint-vs-tsgolint.tsv`: typed rule availability cross-reference
138
+ - `docs/biome-rules.tsv`: Biome rule inventory, so a mapping cannot key off a rule Biome dropped
139
+ - `docs/biome-formatter-options.tsv`: Biome formatter surface inventory
140
+
141
+ All six are generated from the schemas of the installed `oxlint`, `oxfmt`,
142
+ `oxlint-tsgolint`, and `@biomejs/biome` packages by `pnpm docs:sync`; `pnpm docs:check`
143
+ fails when they drift, and it runs as part of `pnpm check`.
138
144
 
139
145
  ✅ **Script Rewrite Strategy Controls**
140
146
 
@@ -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-CYMpPpfK.mjs";
2
+ import { i as readJsonFile, n as DefaultReporter, r as findClosestPackageJson, t as migrate } from "../src-CUWW9YZ_.mjs";
3
3
  import { realpath } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -19,6 +19,7 @@ const MigrationOptionsSchema = z.object({
19
19
  delete: z.boolean().default(false),
20
20
  noBackup: z.boolean().default(false),
21
21
  updateScripts: z.boolean().default(false),
22
+ dom: z.boolean().default(false),
22
23
  verbose: z.boolean().default(false),
23
24
  typeAware: z.boolean().default(false),
24
25
  typeCheck: z.boolean().default(false),
@@ -106,7 +107,7 @@ function createCommand(packageMetadata, streams) {
106
107
  writeOut: (chunk) => {
107
108
  streams.stdout.write(chunk);
108
109
  }
109
- }).option("-c, --config <path>", "Path to biome.json or biome.jsonc").option("-o, --output-dir <path>", "Output directory for generated configs").option("--dry-run", "Show what would be done without making changes").option("--delete", "Delete legacy Biome files after migration (biome.json/biome.jsonc and .biomeignore)").option("--no-backup", "Skip backup of existing config files").option("--update-scripts", "Update package.json scripts to use oxlint/oxfmt").option("--type-aware", "Include type-aware linting guidance and dependencies").option("--type-check", "Enable strict typed linting mode (implies --type-aware)").addOption(new Option("--type-aware-profile <profile>", "Type-aware profile: standard (--type-aware) or strict (--type-aware --type-check)").choices(["standard", "strict"]).default("standard")).addOption(new Option("--fix-strategy <strategy>", "Fix mode for rewritten scripts: safe | suggestions | dangerous").choices([
110
+ }).option("-c, --config <path>", "Path to biome.json or biome.jsonc").option("-o, --output-dir <path>", "Output directory for generated configs").option("--dry-run", "Show what would be done without making changes").option("--delete", "Delete legacy Biome files after migration (biome.json/biome.jsonc and .biomeignore)").option("--no-backup", "Skip backup of existing config files").option("--update-scripts", "Update package.json scripts to use oxlint/oxfmt").addOption(new Option("--dom").hideHelp()).option("--type-aware", "Include type-aware linting guidance and dependencies").option("--type-check", "Enable strict typed linting mode (implies --type-aware)").addOption(new Option("--type-aware-profile <profile>", "Type-aware profile: standard (--type-aware) or strict (--type-aware --type-check)").choices(["standard", "strict"]).default("standard")).addOption(new Option("--fix-strategy <strategy>", "Fix mode for rewritten scripts: safe | suggestions | dangerous").choices([
110
111
  "safe",
111
112
  "suggestions",
112
113
  "dangerous"
@@ -130,6 +131,7 @@ function normalizeMigrationOptions(options) {
130
131
  delete: options.delete,
131
132
  noBackup: options.backup === false,
132
133
  updateScripts: options.updateScripts,
134
+ dom: options.dom,
133
135
  verbose: options.verbose,
134
136
  typeAware,
135
137
  typeCheck,
@@ -69,6 +69,9 @@ interface BiomeFormatterConfig {
69
69
  lineWidth?: number;
70
70
  attributePosition?: 'auto' | 'multiline';
71
71
  bracketSpacing?: boolean;
72
+ bracketSameLine?: boolean;
73
+ expand?: 'auto' | 'always' | 'never';
74
+ trailingNewline?: boolean;
72
75
  [key: string]: unknown;
73
76
  }
74
77
  interface BiomeJavaScriptConfig {
@@ -86,10 +89,13 @@ interface BiomeJavaScriptConfig {
86
89
  arrowParentheses?: 'always' | 'asNeeded';
87
90
  bracketSameLine?: boolean;
88
91
  bracketSpacing?: boolean;
92
+ expand?: 'auto' | 'always' | 'never';
89
93
  indentStyle?: 'tab' | 'space';
90
94
  indentWidth?: number;
91
95
  lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
92
96
  lineWidth?: number;
97
+ operatorLinebreak?: 'after' | 'before';
98
+ trailingNewline?: boolean;
93
99
  [key: string]: unknown;
94
100
  };
95
101
  linter?: {
@@ -105,11 +111,13 @@ interface BiomeJsonConfig {
105
111
  };
106
112
  formatter?: {
107
113
  enabled?: boolean;
114
+ expand?: 'auto' | 'always' | 'never';
108
115
  indentStyle?: 'tab' | 'space';
109
116
  indentWidth?: number;
110
117
  lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
111
118
  lineWidth?: number;
112
119
  trailingCommas?: 'none' | 'all';
120
+ trailingNewline?: boolean;
113
121
  };
114
122
  linter?: {
115
123
  enabled?: boolean;
@@ -126,6 +134,7 @@ interface BiomeCssConfig {
126
134
  lineEnding?: 'lf' | 'crlf' | 'cr' | 'auto';
127
135
  lineWidth?: number;
128
136
  quoteStyle?: 'single' | 'double';
137
+ trailingNewline?: boolean;
129
138
  };
130
139
  linter?: {
131
140
  enabled?: boolean;
@@ -233,6 +242,7 @@ interface OxfmtConfig {
233
242
  endOfLine?: 'lf' | 'crlf' | 'cr';
234
243
  singleAttributePerLine?: boolean;
235
244
  objectWrap?: 'preserve' | 'collapse';
245
+ experimentalOperatorPosition?: 'start' | 'end';
236
246
  insertFinalNewline?: boolean;
237
247
  embeddedLanguageFormatting?: 'auto' | 'off';
238
248
  htmlWhitespaceSensitivity?: 'css' | 'strict' | 'ignore';
@@ -306,6 +316,7 @@ interface MigrationOptions {
306
316
  delete?: boolean;
307
317
  noBackup?: boolean;
308
318
  updateScripts?: boolean;
319
+ dom?: boolean;
309
320
  verbose?: boolean;
310
321
  typeAware?: boolean;
311
322
  typeCheck?: boolean;
@@ -1,2 +1,2 @@
1
- import { t as migrate } from "../src-CYMpPpfK.mjs";
1
+ import { t as migrate } from "../src-CUWW9YZ_.mjs";
2
2
  export { migrate };
@@ -373,7 +373,14 @@ const BiomeFormatterConfigSchema = IncludeFieldsSchema.extend({
373
373
  ]).optional(),
374
374
  lineWidth: z.number().int().optional(),
375
375
  attributePosition: z.enum(["auto", "multiline"]).optional(),
376
- bracketSpacing: z.boolean().optional()
376
+ bracketSpacing: z.boolean().optional(),
377
+ bracketSameLine: z.boolean().optional(),
378
+ expand: z.enum([
379
+ "auto",
380
+ "always",
381
+ "never"
382
+ ]).optional(),
383
+ trailingNewline: z.boolean().optional()
377
384
  }).passthrough();
378
385
  const BiomeJavaScriptFormatterSchema = z.object({
379
386
  enabled: z.boolean().optional(),
@@ -389,6 +396,11 @@ const BiomeJavaScriptFormatterSchema = z.object({
389
396
  arrowParentheses: z.enum(["always", "asNeeded"]).optional(),
390
397
  bracketSameLine: z.boolean().optional(),
391
398
  bracketSpacing: z.boolean().optional(),
399
+ expand: z.enum([
400
+ "auto",
401
+ "always",
402
+ "never"
403
+ ]).optional(),
392
404
  indentStyle: z.enum(["tab", "space"]).optional(),
393
405
  indentWidth: z.number().int().optional(),
394
406
  lineEnding: z.enum([
@@ -397,7 +409,9 @@ const BiomeJavaScriptFormatterSchema = z.object({
397
409
  "cr",
398
410
  "auto"
399
411
  ]).optional(),
400
- lineWidth: z.number().int().optional()
412
+ lineWidth: z.number().int().optional(),
413
+ operatorLinebreak: z.enum(["after", "before"]).optional(),
414
+ trailingNewline: z.boolean().optional()
401
415
  }).passthrough();
402
416
  const BiomeJavaScriptConfigSchema = z.object({
403
417
  parser: z.object({
@@ -415,6 +429,11 @@ const BiomeJsonConfigSchema = z.object({
415
429
  }).passthrough().optional(),
416
430
  formatter: z.object({
417
431
  enabled: z.boolean().optional(),
432
+ expand: z.enum([
433
+ "auto",
434
+ "always",
435
+ "never"
436
+ ]).optional(),
418
437
  indentStyle: z.enum(["tab", "space"]).optional(),
419
438
  indentWidth: z.number().int().optional(),
420
439
  lineEnding: z.enum([
@@ -424,7 +443,8 @@ const BiomeJsonConfigSchema = z.object({
424
443
  "auto"
425
444
  ]).optional(),
426
445
  lineWidth: z.number().int().optional(),
427
- trailingCommas: z.enum(["none", "all"]).optional()
446
+ trailingCommas: z.enum(["none", "all"]).optional(),
447
+ trailingNewline: z.boolean().optional()
428
448
  }).passthrough().optional(),
429
449
  linter: z.object({ enabled: z.boolean().optional() }).passthrough().optional()
430
450
  }).passthrough();
@@ -441,7 +461,8 @@ const BiomeCssConfigSchema = z.object({
441
461
  "auto"
442
462
  ]).optional(),
443
463
  lineWidth: z.number().int().optional(),
444
- quoteStyle: z.enum(["single", "double"]).optional()
464
+ quoteStyle: z.enum(["single", "double"]).optional(),
465
+ trailingNewline: z.boolean().optional()
445
466
  }).passthrough().optional(),
446
467
  linter: z.object({ enabled: z.boolean().optional() }).passthrough().optional()
447
468
  }).passthrough();
@@ -692,6 +713,7 @@ function generateESLintFormatterBridgeSuggestions() {
692
713
  //#region src/oxfmt-overrides.ts
693
714
  const EXPLICIT_OXFMT_OPTION_KEYS$1 = [
694
715
  "objectWrap",
716
+ "experimentalOperatorPosition",
695
717
  "insertFinalNewline",
696
718
  "embeddedLanguageFormatting",
697
719
  "htmlWhitespaceSensitivity",
@@ -731,11 +753,13 @@ const CSS_EXTENSIONS = [
731
753
  ];
732
754
  const JSON_FORMATTER_KEYS = /* @__PURE__ */ new Set([
733
755
  "enabled",
756
+ "expand",
734
757
  "indentStyle",
735
758
  "indentWidth",
736
759
  "lineEnding",
737
760
  "lineWidth",
738
761
  "trailingCommas",
762
+ "trailingNewline",
739
763
  ...EXPLICIT_OXFMT_OPTION_KEYS$1,
740
764
  ...Object.keys(LEGACY_EXPLICIT_OXFMT_OPTION_ALIASES$1)
741
765
  ]);
@@ -746,9 +770,25 @@ const CSS_FORMATTER_KEYS = /* @__PURE__ */ new Set([
746
770
  "lineEnding",
747
771
  "lineWidth",
748
772
  "quoteStyle",
773
+ "trailingNewline",
749
774
  ...EXPLICIT_OXFMT_OPTION_KEYS$1,
750
775
  ...Object.keys(LEGACY_EXPLICIT_OXFMT_OPTION_ALIASES$1)
751
776
  ]);
777
+ /**
778
+ * Biome's `expand` and Oxfmt's `objectWrap` agree on two of three modes: Biome's `auto`
779
+ * (expand when the literal already has a newline after `{`) is Oxfmt's `preserve`, and
780
+ * Biome's `never` is Oxfmt's `collapse`. Biome's `always` has no Oxfmt counterpart.
781
+ */
782
+ function mapBiomeExpandToObjectWrap(expand, label, reporter) {
783
+ if (expand === "auto") return "preserve";
784
+ if (expand === "never") return "collapse";
785
+ if (expand === "always") reporter.loss(`Biome ${label}.expand "always" has no Oxfmt equivalent; Oxfmt's objectWrap only offers "preserve" and "collapse", so objects and arrays that fit on one line will not be forced to expand.`);
786
+ }
787
+ /** Biome places the operator after or before the break; Oxfmt names the same two positions. */
788
+ function mapBiomeOperatorLinebreakToOxfmt(operatorLinebreak) {
789
+ if (operatorLinebreak === "after") return "end";
790
+ if (operatorLinebreak === "before") return "start";
791
+ }
752
792
  function generateOxfmtOverrides(biomeOverrides, reporter) {
753
793
  if (!biomeOverrides || biomeOverrides.length === 0) return [];
754
794
  const oxfmtOverrides = [];
@@ -792,7 +832,11 @@ function mapBaseFormatterOptions(formatter, reporter) {
792
832
  if (formatter.indentWidth !== void 0) options.tabWidth = formatter.indentWidth;
793
833
  if (formatter.lineEnding !== void 0) options.endOfLine = formatter.lineEnding;
794
834
  if (formatter.bracketSpacing !== void 0) options.bracketSpacing = formatter.bracketSpacing;
835
+ if (formatter.bracketSameLine !== void 0) options.bracketSameLine = formatter.bracketSameLine;
795
836
  if (formatter.attributePosition !== void 0) options.singleAttributePerLine = formatter.attributePosition === "multiline";
837
+ const objectWrap = mapBiomeExpandToObjectWrap(formatter.expand, "formatter", reporter);
838
+ if (objectWrap) options.objectWrap = objectWrap;
839
+ if (formatter.trailingNewline !== void 0) options.insertFinalNewline = formatter.trailingNewline;
796
840
  if (formatter.formatWithErrors) reporter.warn("Biome's formatWithErrors option is not supported in Oxfmt overrides");
797
841
  applyExplicitFormatterOptionPassThrough$1([formatter], options, reporter);
798
842
  return options;
@@ -812,6 +856,12 @@ function mapJavaScriptFormatterOptions(jsFormatter, reporter) {
812
856
  if (jsFormatter.arrowParentheses !== void 0) options.arrowParens = jsFormatter.arrowParentheses === "always" ? "always" : "avoid";
813
857
  if (jsFormatter.bracketSpacing !== void 0) options.bracketSpacing = jsFormatter.bracketSpacing;
814
858
  if (jsFormatter.bracketSameLine !== void 0) options.bracketSameLine = jsFormatter.bracketSameLine;
859
+ if (jsFormatter.attributePosition !== void 0) options.singleAttributePerLine = jsFormatter.attributePosition === "multiline";
860
+ const objectWrap = mapBiomeExpandToObjectWrap(jsFormatter.expand, "javascript.formatter", reporter);
861
+ if (objectWrap) options.objectWrap = objectWrap;
862
+ const operatorPosition = mapBiomeOperatorLinebreakToOxfmt(jsFormatter.operatorLinebreak);
863
+ if (operatorPosition) options.experimentalOperatorPosition = operatorPosition;
864
+ if (jsFormatter.trailingNewline !== void 0) options.insertFinalNewline = jsFormatter.trailingNewline;
815
865
  applyExplicitFormatterOptionPassThrough$1([jsFormatter], options, reporter);
816
866
  return options;
817
867
  }
@@ -824,6 +874,9 @@ function mapJsonFormatterOptions(jsonFormatter, reporter) {
824
874
  if (jsonFormatter.indentWidth !== void 0) options.tabWidth = jsonFormatter.indentWidth;
825
875
  if (jsonFormatter.lineEnding !== void 0) options.endOfLine = jsonFormatter.lineEnding;
826
876
  if (jsonFormatter.trailingCommas !== void 0) options.trailingComma = jsonFormatter.trailingCommas === "none" ? "none" : "all";
877
+ const objectWrap = mapBiomeExpandToObjectWrap(jsonFormatter.expand, "json.formatter", reporter);
878
+ if (objectWrap) options.objectWrap = objectWrap;
879
+ if (jsonFormatter.trailingNewline !== void 0) options.insertFinalNewline = jsonFormatter.trailingNewline;
827
880
  applyExplicitFormatterOptionPassThrough$1([jsonFormatter], options, reporter);
828
881
  return options;
829
882
  }
@@ -836,6 +889,7 @@ function mapCssFormatterOptions(cssFormatter, reporter) {
836
889
  if (cssFormatter.indentWidth !== void 0) options.tabWidth = cssFormatter.indentWidth;
837
890
  if (cssFormatter.lineEnding !== void 0) options.endOfLine = cssFormatter.lineEnding;
838
891
  if (cssFormatter.quoteStyle !== void 0) options.singleQuote = cssFormatter.quoteStyle === "single";
892
+ if (cssFormatter.trailingNewline !== void 0) options.insertFinalNewline = cssFormatter.trailingNewline;
839
893
  applyExplicitFormatterOptionPassThrough$1([cssFormatter], options, reporter);
840
894
  return options;
841
895
  }
@@ -878,6 +932,7 @@ function patternTargetsExtension(pattern, extensions) {
878
932
  //#region src/formatter-mapper.ts
879
933
  const EXPLICIT_OXFMT_OPTION_KEYS = [
880
934
  "objectWrap",
935
+ "experimentalOperatorPosition",
881
936
  "insertFinalNewline",
882
937
  "embeddedLanguageFormatting",
883
938
  "htmlWhitespaceSensitivity",
@@ -906,6 +961,9 @@ const GLOBAL_FORMATTER_KEYS = /* @__PURE__ */ new Set([
906
961
  "lineWidth",
907
962
  "attributePosition",
908
963
  "bracketSpacing",
964
+ "bracketSameLine",
965
+ "expand",
966
+ "trailingNewline",
909
967
  ...EXPLICIT_OXFMT_OPTION_KEYS,
910
968
  ...Object.keys(LEGACY_EXPLICIT_OXFMT_OPTION_ALIASES)
911
969
  ]);
@@ -919,10 +977,13 @@ const JAVASCRIPT_FORMATTER_KEYS = /* @__PURE__ */ new Set([
919
977
  "arrowParentheses",
920
978
  "bracketSameLine",
921
979
  "bracketSpacing",
980
+ "expand",
922
981
  "indentStyle",
923
982
  "indentWidth",
924
983
  "lineEnding",
925
984
  "lineWidth",
985
+ "operatorLinebreak",
986
+ "trailingNewline",
926
987
  ...EXPLICIT_OXFMT_OPTION_KEYS,
927
988
  ...Object.keys(LEGACY_EXPLICIT_OXFMT_OPTION_ALIASES)
928
989
  ]);
@@ -1010,9 +1071,13 @@ function mapFormatterOptions(formatter, oxfmtConfig, reporter) {
1010
1071
  oxfmtConfig.arrowParens = "always";
1011
1072
  const bracketSpacing = formatter?.bracketSpacing;
1012
1073
  if (bracketSpacing !== void 0) oxfmtConfig.bracketSpacing = bracketSpacing;
1074
+ if (formatter?.bracketSameLine !== void 0) oxfmtConfig.bracketSameLine = formatter.bracketSameLine;
1013
1075
  const attributePosition = formatter?.attributePosition;
1014
1076
  if (attributePosition === "multiline") oxfmtConfig.singleAttributePerLine = true;
1015
1077
  else if (attributePosition === "auto") oxfmtConfig.singleAttributePerLine = false;
1078
+ const objectWrap = mapBiomeExpandToObjectWrap(formatter?.expand, "formatter", reporter);
1079
+ if (objectWrap) oxfmtConfig.objectWrap = objectWrap;
1080
+ if (formatter?.trailingNewline !== void 0) oxfmtConfig.insertFinalNewline = formatter.trailingNewline;
1016
1081
  if (formatter?.formatWithErrors) reporter.warn("Biome's formatWithErrors option is not supported in Oxfmt");
1017
1082
  }
1018
1083
  function mapIgnorePatterns$1(biomeConfig, oxfmtConfig, additionalIgnorePatterns) {
@@ -1290,6 +1355,40 @@ const OXLINT_CATEGORIES = [
1290
1355
  "style",
1291
1356
  "suspicious"
1292
1357
  ];
1358
+ /**
1359
+ * The React Compiler rules Oxlint activates for ESLint's `react-hooks/recommended` preset.
1360
+ *
1361
+ * Oxlint 1.79 replaced the single `react/react-compiler` rule with 22 per-diagnostic rules;
1362
+ * Biome's `useReactCompiler` runs the compiler and reports every bailout it finds, so the
1363
+ * `recommended` slice is the closest equivalent. The remaining rules default to off upstream
1364
+ * and would fire on code Biome never flagged.
1365
+ *
1366
+ * @see https://oxc.rs/blog/2026-08-18-react-compiler-support
1367
+ */
1368
+ const REACT_COMPILER_RECOMMENDED_RULES = [
1369
+ "react/error-boundaries",
1370
+ "react/globals",
1371
+ "react/immutability",
1372
+ "react/incompatible-library",
1373
+ "react/preserve-manual-memoization",
1374
+ "react/purity",
1375
+ "react/refs",
1376
+ "react/set-state-in-effect",
1377
+ "react/set-state-in-render",
1378
+ "react/static-components",
1379
+ "react/unsupported-syntax",
1380
+ "react/use-memo"
1381
+ ];
1382
+ /**
1383
+ * Mappings where the Oxlint rule covers only part of what the Biome rule checked. The note
1384
+ * is reported once per migration so the narrowing is visible rather than silent.
1385
+ */
1386
+ const PARTIAL_RULE_MAPPING_NOTES = {
1387
+ noComponentHookFactories: "Biome rule noComponentHookFactories was mapped to react/no-unstable-nested-components, which reports nested component definitions but not nested custom hook definitions.",
1388
+ 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.",
1389
+ 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.",
1390
+ useReactCompiler: `Biome rule useReactCompiler was mapped to Oxlint's React Compiler rules for ESLint's recommended preset (${REACT_COMPILER_RECOMMENDED_RULES.join(", ")}); the compiler rules that are off by default upstream were not enabled. See https://oxc.rs/blog/2026-08-18-react-compiler-support`
1391
+ };
1293
1392
  const BIOME_TO_OXLINT_RULE_MAP = {
1294
1393
  noAccessKey: "jsx-a11y/no-access-key",
1295
1394
  noAdjacentSpacesInRegex: "no-regex-spaces",
@@ -1311,6 +1410,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1311
1410
  noBaseToString: "typescript/no-base-to-string",
1312
1411
  noBeforeInteractiveScriptOutsideDocument: "nextjs/no-before-interactive-script-outside-document",
1313
1412
  noBitwiseOperators: "no-bitwise",
1413
+ noBlankTarget: "react/jsx-no-target-blank",
1314
1414
  noCatchAssign: "no-ex-assign",
1315
1415
  noChildrenProp: "react/no-children-prop",
1316
1416
  noClassAssign: "no-class-assign",
@@ -1321,6 +1421,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1321
1421
  "typescript/no-require-imports",
1322
1422
  "typescript/no-var-requires"
1323
1423
  ],
1424
+ noComponentHookFactories: "react/no-unstable-nested-components",
1324
1425
  noCompareNegZero: "no-compare-neg-zero",
1325
1426
  noConfusingLabels: "no-labels",
1326
1427
  noConfusingVoidType: "typescript/no-invalid-void-type",
@@ -1358,6 +1459,8 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1358
1459
  noEqualsToNull: "no-eq-null",
1359
1460
  noExcessiveClassesPerFile: "max-classes-per-file",
1360
1461
  noExcessiveCognitiveComplexity: "complexity",
1462
+ noExcessiveLinesPerFile: "max-lines",
1463
+ noExcessiveLinesPerFunction: "max-lines-per-function",
1361
1464
  noExcessiveNestedCallbacks: "max-nested-callbacks",
1362
1465
  noExplicitAny: "typescript/no-explicit-any",
1363
1466
  noExtendNative: "no-extend-native",
@@ -1369,7 +1472,10 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1369
1472
  noForEach: "unicorn/no-array-for-each",
1370
1473
  noFunctionAssign: "no-func-assign",
1371
1474
  noGlobalAssign: "no-global-assign",
1475
+ noGlobalDirnameFilename: "unicorn/prefer-module",
1372
1476
  noGlobalEval: "no-eval",
1477
+ noGlobalIsFinite: "unicorn/prefer-number-properties",
1478
+ noGlobalIsNan: "unicorn/prefer-number-properties",
1373
1479
  noGlobalObjectCalls: "no-obj-calls",
1374
1480
  noHeaderScope: "jsx-a11y/scope",
1375
1481
  noHeadElement: "nextjs/no-head-element",
@@ -1389,6 +1495,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1389
1495
  noIrregularWhitespace: "no-irregular-whitespace",
1390
1496
  noInteractiveElementToNoninteractiveRole: "jsx-a11y/no-interactive-element-to-noninteractive-role",
1391
1497
  noInvalidNewBuiltin: "no-new-native-nonconstructor",
1498
+ noJsRestrictedProperties: "no-restricted-properties",
1392
1499
  noJsxLiterals: "react/jsx-no-literals",
1393
1500
  noJsxNamespace: "react/no-namespace",
1394
1501
  noLabelWithoutControl: "jsx-a11y/label-has-associated-control",
@@ -1403,6 +1510,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1403
1510
  noMultilineString: "no-multi-str",
1404
1511
  noMultiStr: "no-multi-str",
1405
1512
  noNegationElse: "no-negated-condition",
1513
+ noNegationInEqualityCheck: "unicorn/no-negation-in-equality-check",
1406
1514
  noNewSymbol: "no-new-native-nonconstructor",
1407
1515
  noNodejsModules: "import/no-nodejs-modules",
1408
1516
  noNamespace: "typescript/no-namespace",
@@ -1426,12 +1534,14 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1426
1534
  noProto: "no-proto",
1427
1535
  noPrototypeBuiltins: "no-prototype-builtins",
1428
1536
  noRedeclare: "no-redeclare",
1537
+ noReactPropAssignments: "react/immutability",
1429
1538
  noRedundantAlt: "jsx-a11y/img-redundant-alt",
1430
1539
  noRedundantRoles: "jsx-a11y/no-redundant-roles",
1431
1540
  noReactStringRefs: "react/no-string-refs",
1432
1541
  noDuplicateTestHooks: ["jest/no-duplicate-hooks", "vitest/no-duplicate-hooks"],
1433
1542
  noConditionalExpect: ["jest/no-conditional-expect", "vitest/no-conditional-expect"],
1434
1543
  noExcessiveNestedTestSuites: ["jest/max-nested-describe", "vitest/max-nested-describe"],
1544
+ noExportsInTest: "jest/no-export",
1435
1545
  noIdenticalTestTitle: ["jest/no-identical-title", "vitest/no-identical-title"],
1436
1546
  noMisplacedAssertion: ["jest/no-standalone-expect", "vitest/no-standalone-expect"],
1437
1547
  noRenderReturnValue: "react/no-render-return-value",
@@ -1469,6 +1579,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1469
1579
  noUnreachable: "no-unreachable",
1470
1580
  noUnreachableSuper: "constructor-super",
1471
1581
  noUnsafeDeclarationMerging: "typescript/no-unsafe-declaration-merging",
1582
+ noUnsafeTypeAssertion: "typescript/no-unsafe-type-assertion",
1472
1583
  noUnsafeFinally: "no-unsafe-finally",
1473
1584
  noUnsafeNegation: "no-unsafe-negation",
1474
1585
  noUnsafeOptionalChaining: "no-unsafe-optional-chaining",
@@ -1479,8 +1590,10 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1479
1590
  noUnusedVariables: "no-unused-vars",
1480
1591
  noUnwantedPolyfillio: "nextjs/no-unwanted-polyfillio",
1481
1592
  noUselessCatch: "no-useless-catch",
1593
+ noUselessContinue: "no-continue",
1482
1594
  noUselessCatchBinding: "unicorn/prefer-optional-catch-binding",
1483
1595
  noUselessConstructor: "no-useless-constructor",
1596
+ noUselessElse: "no-else-return",
1484
1597
  noUselessEmptyExport: "typescript/no-useless-empty-export",
1485
1598
  noUselessEscapeInRegex: "no-useless-escape",
1486
1599
  noUselessEscapeInString: "no-useless-escape",
@@ -1497,6 +1610,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1497
1610
  noUselessTypeConstraint: "typescript/no-unnecessary-type-constraint",
1498
1611
  noUselessTypeConversion: "typescript/no-unnecessary-type-conversion",
1499
1612
  noUselessUndefined: "unicorn/no-useless-undefined",
1613
+ noUselessUndefinedInitialization: "unicorn/no-useless-undefined",
1500
1614
  noVoid: "no-void",
1501
1615
  noVar: "no-var",
1502
1616
  noVoidElementsWithChildren: "react/void-dom-elements-no-children",
@@ -1537,6 +1651,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1537
1651
  useConsistentMethodSignatures: "typescript/method-signature-style",
1538
1652
  useConsistentTypeDefinitions: "typescript/consistent-type-definitions",
1539
1653
  useConsistentMemberAccessibility: "typescript/explicit-member-accessibility",
1654
+ useConsistentObjectDefinitions: "object-shorthand",
1540
1655
  useConsistentTestIt: ["jest/consistent-test-it", "vitest/consistent-test-it"],
1541
1656
  useConst: "prefer-const",
1542
1657
  useDateNow: "unicorn/prefer-date-now",
@@ -1551,6 +1666,8 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1551
1666
  useErrorMessage: "unicorn/error-message",
1552
1667
  useExpect: ["jest/expect-expect", "vitest/expect-expect"],
1553
1668
  useExplicitLengthCheck: "unicorn/explicit-length-check",
1669
+ useExplicitReturnType: "typescript/explicit-function-return-type",
1670
+ useExplicitType: "typescript/explicit-function-return-type",
1554
1671
  useExponentiationOperator: "prefer-exponentiation-operator",
1555
1672
  useExportsLast: "import/exports-last",
1556
1673
  useExportType: "typescript/consistent-type-exports",
@@ -1573,6 +1690,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1573
1690
  useHookAtTopLevel: "react/rules-of-hooks",
1574
1691
  useIframeSandbox: "react/iframe-missing-sandbox",
1575
1692
  useIframeTitle: "jsx-a11y/iframe-has-title",
1693
+ useImportExtensions: "import/extensions",
1576
1694
  useImportsFirst: "import/first",
1577
1695
  useImportType: "typescript/consistent-type-imports",
1578
1696
  useIncludes: "typescript/prefer-includes",
@@ -1602,6 +1720,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1602
1720
  useParseIntRadix: "radix",
1603
1721
  useReadonlyClassProperties: "typescript/prefer-readonly",
1604
1722
  useReactFunctionComponentDefinition: "react/function-component-definition",
1723
+ useReactCompiler: REACT_COMPILER_RECOMMENDED_RULES,
1605
1724
  useReactFunctionComponents: "react/prefer-function-component",
1606
1725
  useReduceTypeParameter: "typescript/prefer-reduce-type-parameter",
1607
1726
  useRegexLiterals: "prefer-regex-literals",
@@ -1611,6 +1730,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1611
1730
  useSelfClosingElements: "react/self-closing-comp",
1612
1731
  useShorthandAssign: "operator-assignment",
1613
1732
  useShorthandFunctionType: "typescript/prefer-function-type",
1733
+ useSingleVarDeclarator: "one-var",
1614
1734
  useSimplifiedLogicExpression: "unicorn/prefer-logical-operator-over-ternary",
1615
1735
  useSpread: "prefer-spread",
1616
1736
  useSpreadOverApply: "prefer-spread",
@@ -1639,6 +1759,7 @@ const BIOME_TO_OXLINT_RULE_MAP = {
1639
1759
  useYield: "require-yield"
1640
1760
  };
1641
1761
  const WARNED_UNMAPPED_RULES_BY_REPORTER = /* @__PURE__ */ new WeakMap();
1762
+ const WARNED_PARTIAL_MAPPINGS_BY_REPORTER = /* @__PURE__ */ new WeakMap();
1642
1763
  const WARNED_UNSUPPORTED_SEVERITIES_BY_REPORTER = /* @__PURE__ */ new WeakMap();
1643
1764
  const OXLINT_SEVERITY_NORMALIZATION = {
1644
1765
  allow: "off",
@@ -1663,6 +1784,20 @@ function mapBiomeRuleOptionsToOxlintSeverity(biomeName, severity, biomeSeverity,
1663
1784
  if (biomeName === "noExcessiveNestedCallbacks") return [severity, { max: options && isNonNegativeInteger(options.max) ? options.max : 5 }];
1664
1785
  if (biomeName === "useErrorCause") return [severity, { requireCatchParameter: options && typeof options.requireCatchParameter === "boolean" ? options.requireCatchParameter : true }];
1665
1786
  if (biomeName === "useMaxParams") return [severity, { max: options && isNonNegativeInteger(options.max) ? options.max : 4 }];
1787
+ if (biomeName === "noExcessiveLinesPerFile") return [severity, {
1788
+ max: options && isNonNegativeInteger(options.maxLines) ? options.maxLines : 300,
1789
+ skipBlankLines: options?.skipBlankLines === true
1790
+ }];
1791
+ if (biomeName === "noExcessiveLinesPerFunction") return [severity, {
1792
+ max: options && isNonNegativeInteger(options.maxLines) ? options.maxLines : 50,
1793
+ skipBlankLines: options?.skipBlankLines === true,
1794
+ IIFEs: options?.skipIifes !== true
1795
+ }];
1796
+ if (biomeName === "useSingleVarDeclarator") return [severity, "never"];
1797
+ if (biomeName === "useImportExtensions") {
1798
+ 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.");
1799
+ return [severity, "ignorePackages"];
1800
+ }
1666
1801
  if (biomeName === "noTsIgnore") return [severity, {
1667
1802
  "ts-ignore": true,
1668
1803
  "ts-expect-error": false,
@@ -1714,6 +1849,22 @@ function mapBiomeRuleOptionsToOxlintSeverity(biomeName, severity, biomeSeverity,
1714
1849
  { requireReturnForObjectLiteral: options.requireForObjectLiteral }
1715
1850
  ] : [severity, mode];
1716
1851
  }
1852
+ if (biomeName === "noJsRestrictedProperties") {
1853
+ const { entries } = options;
1854
+ if (!Array.isArray(entries) || !entries.every(isRecord)) return severity;
1855
+ return [severity, ...entries];
1856
+ }
1857
+ if (biomeName === "useConsistentObjectDefinitions") {
1858
+ const { syntax } = options;
1859
+ return syntax === "shorthand" ? [severity, "always"] : syntax === "explicit" ? [severity, "never"] : severity;
1860
+ }
1861
+ if (biomeName === "useExplicitReturnType") {
1862
+ const oxlintOptions = {};
1863
+ if (typeof options.allowExpressions === "boolean") oxlintOptions.allowExpressions = options.allowExpressions;
1864
+ if (typeof options.allowIifes === "boolean") oxlintOptions.allowIIFEs = options.allowIifes;
1865
+ if (Array.isArray(options.allowedNames) && options.allowedNames.every((value) => typeof value === "string")) oxlintOptions.allowedNames = options.allowedNames;
1866
+ return Object.keys(oxlintOptions).length > 0 ? [severity, oxlintOptions] : severity;
1867
+ }
1717
1868
  if (biomeName === "useConsistentMethodSignatures") {
1718
1869
  const { style } = options;
1719
1870
  return style === "method" || style === "property" ? [severity, style] : severity;
@@ -1780,10 +1931,24 @@ function mapBiomeRuleSeverity(severity, reporter, biomeRuleName) {
1780
1931
  }
1781
1932
  function mapBiomeRuleToOxlintRules(biomeName, reporter) {
1782
1933
  const mapped = BIOME_TO_OXLINT_RULE_MAP[biomeName];
1783
- if (mapped) return [...new Set((Array.isArray(mapped) ? mapped : [mapped]).map((ruleName) => normalizeOxlintRuleName(ruleName)))];
1934
+ if (mapped) {
1935
+ const partialMappingNote = PARTIAL_RULE_MAPPING_NOTES[biomeName];
1936
+ if (partialMappingNote) warnPartialMappingOnce(biomeName, partialMappingNote, reporter);
1937
+ return [...new Set((Array.isArray(mapped) ? mapped : [mapped]).map((ruleName) => normalizeOxlintRuleName(ruleName)))];
1938
+ }
1784
1939
  warnUnmappedRuleOnce(biomeName, reporter);
1785
1940
  return [];
1786
1941
  }
1942
+ function warnPartialMappingOnce(biomeName, note, reporter) {
1943
+ let warnedRules = WARNED_PARTIAL_MAPPINGS_BY_REPORTER.get(reporter);
1944
+ if (!warnedRules) {
1945
+ warnedRules = /* @__PURE__ */ new Set();
1946
+ WARNED_PARTIAL_MAPPINGS_BY_REPORTER.set(reporter, warnedRules);
1947
+ }
1948
+ if (warnedRules.has(biomeName)) return;
1949
+ warnedRules.add(biomeName);
1950
+ reporter.loss(note);
1951
+ }
1787
1952
  function warnUnmappedRuleOnce(biomeName, reporter) {
1788
1953
  let warnedRules = WARNED_UNMAPPED_RULES_BY_REPORTER.get(reporter);
1789
1954
  if (!warnedRules) {
@@ -2121,15 +2286,19 @@ async function updatePackageJson(projectDir, reporter, dryRun, options = {}) {
2121
2286
  summary.found = true;
2122
2287
  let modified = false;
2123
2288
  const updateScriptsEnabled = options.updateScripts ?? false;
2289
+ const domModeEnabled = options.dom ?? false;
2124
2290
  const removeBiomeAllowed = options.removeBiome ?? false;
2125
2291
  const typeAwareProfile = options.typeAwareProfile ?? "standard";
2126
2292
  const typeCheckEnabled = options.typeCheck ?? typeAwareProfile === "strict";
2127
2293
  const typeAwareEnabled = options.typeAware ?? (typeCheckEnabled || typeAwareProfile === "strict");
2128
- const needsTypeAwareDependency = typeAwareEnabled || typeCheckEnabled;
2294
+ const needsTypeAwareDependency = typeAwareEnabled || typeCheckEnabled || domModeEnabled;
2129
2295
  const fixStrategy = options.fixStrategy ?? "safe";
2130
2296
  const oxlintCommand = buildConfiguredCommand("oxlint", packageJsonPath, options.oxlintConfigPath);
2131
2297
  const oxfmtCommand = buildConfiguredCommand("oxfmt", packageJsonPath, options.oxfmtConfigPath);
2132
- if (updateScriptsEnabled && packageJson.scripts) modified = updateScripts(packageJson.scripts, reporter, summary.scriptsUpdated, {
2298
+ if (domModeEnabled) {
2299
+ packageJson.scripts ??= {};
2300
+ modified = applyDomScriptPreset(packageJson.scripts, summary.scriptsUpdated, oxlintCommand, oxfmtCommand) || modified;
2301
+ } else if (updateScriptsEnabled && packageJson.scripts) modified = updateScripts(packageJson.scripts, reporter, summary.scriptsUpdated, {
2133
2302
  typeAwareEnabled,
2134
2303
  typeCheckEnabled,
2135
2304
  fixStrategy,
@@ -2204,6 +2373,32 @@ async function detectLockfile(packageRoot, dependenciesChanged) {
2204
2373
  };
2205
2374
  }
2206
2375
  }
2376
+ function applyDomScriptPreset(scripts, updates, oxlintCommand, oxfmtCommand) {
2377
+ let modified = false;
2378
+ const preset = {
2379
+ check: `${oxlintCommand} . && ${oxfmtCommand} --check .`,
2380
+ "check:fix": `${oxlintCommand} --fix . && ${oxfmtCommand} --write .`,
2381
+ format: `${oxfmtCommand} --write .`,
2382
+ "format:check": `${oxfmtCommand} --check .`,
2383
+ lint: `${oxlintCommand} -f github . > lint.md 2>&1`,
2384
+ "lint:fix": `${oxlintCommand} -f stylish --fix .`,
2385
+ "lint:fix-unsafe": `${oxlintCommand} -f stylish --react-plugin --import-plugin --react-perf-plugin --nextjs-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions --fix-dangerously .`,
2386
+ "check:fix-suggestions": `${oxlintCommand} -f stylish --react-plugin --import-plugin --react-perf-plugin --nextjs-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions . && ${oxfmtCommand} --write .`,
2387
+ "type-check": "tsc --noEmit"
2388
+ };
2389
+ for (const [scriptName, scriptValue] of Object.entries(preset)) {
2390
+ const before = scripts[scriptName];
2391
+ if (before === scriptValue) continue;
2392
+ scripts[scriptName] = scriptValue;
2393
+ updates.push({
2394
+ name: scriptName,
2395
+ before: before ?? "<missing>",
2396
+ after: scriptValue
2397
+ });
2398
+ modified = true;
2399
+ }
2400
+ return modified;
2401
+ }
2207
2402
  function updateScripts(scripts, reporter, updates, options) {
2208
2403
  let modified = false;
2209
2404
  const lintBase = buildLintBaseCommand(options.oxlintCommand, options.typeAwareEnabled, options.typeCheckEnabled);
@@ -2810,6 +3005,7 @@ async function runMigration(options, reporter, biomeConfigPath) {
2810
3005
  const packageJsonErrorCount = reporter.getErrors().length;
2811
3006
  packageJsonSummary = await updatePackageJson(projectDir, reporter, false, {
2812
3007
  updateScripts: options.updateScripts,
3008
+ dom: options.dom,
2813
3009
  removeBiome: cleanupAllowed,
2814
3010
  typeAware: typeAwareEnabled,
2815
3011
  typeCheck: typeCheckEnabled,
@@ -2838,6 +3034,7 @@ async function runMigration(options, reporter, biomeConfigPath) {
2838
3034
  const packageJsonErrorCount = reporter.getErrors().length;
2839
3035
  packageJsonSummary = await updatePackageJson(projectDir, reporter, true, {
2840
3036
  updateScripts: options.updateScripts,
3037
+ dom: options.dom,
2841
3038
  removeBiome: cleanupAllowed,
2842
3039
  typeAware: typeAwareEnabled,
2843
3040
  typeCheck: typeCheckEnabled,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biome-to-oxc",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Migrate from Biome to Oxc ecosystem (oxlint + oxfmt + oxlint-tsgolint)",
5
5
  "keywords": [
6
6
  "biome",
@@ -32,6 +32,24 @@
32
32
  "types": "./dist/src/index.d.mts",
33
33
  "import": "./dist/src/index.mjs"
34
34
  },
35
+ "scripts": {
36
+ "test": "vitest",
37
+ "test:run": "vitest --run",
38
+ "test:coverage": "vitest --run --coverage",
39
+ "format": "oxfmt --write .",
40
+ "format:check": "oxfmt --check .",
41
+ "lint": "oxlint .",
42
+ "lint:fix": "oxlint --fix .",
43
+ "lint:fix-unsafe": "oxlint --import-plugin --node-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions --fix-dangerously .",
44
+ "check:fix-suggestions": "oxlint --import-plugin --node-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions . && oxfmt --write .",
45
+ "typecheck": "tsc --noEmit",
46
+ "docs:sync": "node scripts/sync-rule-inventories.ts",
47
+ "docs:check": "node scripts/sync-rule-inventories.ts --check",
48
+ "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm docs:check && pnpm test:run",
49
+ "build": "tsdown",
50
+ "dev": "tsdown --watch",
51
+ "prepublishOnly": "pnpm check && pnpm build"
52
+ },
35
53
  "dependencies": {
36
54
  "commander": "15.0.0",
37
55
  "jsonc-parser": "next",
@@ -40,34 +58,20 @@
40
58
  },
41
59
  "devDependencies": {
42
60
  "@arethetypeswrong/core": "^0.18.5",
43
- "@biomejs/biome": "^2.5.7",
61
+ "@biomejs/biome": "^2.5.9",
44
62
  "@types/node": "^26.2.0",
45
- "@vitest/coverage-v8": "^4.1.10",
46
- "oxfmt": "^0.63.0",
47
- "oxlint": "^1.78.0",
63
+ "@vitest/coverage-v8": "^4.1.11",
64
+ "oxfmt": "^0.64.0",
65
+ "oxlint": "^1.79.0",
48
66
  "oxlint-tsgolint": "^7.0.2001",
49
- "publint": "^0.3.23",
67
+ "publint": "^0.3.24",
50
68
  "tsdown": "^0.22.14",
51
- "typescript": "^7.0.2",
52
- "vitest": "^4.1.10"
69
+ "typescript": "next",
70
+ "vitest": "^4.1.11"
53
71
  },
54
72
  "engines": {
55
73
  "node": ">=24.19.0",
56
- "pnpm": ">=11.21.0"
74
+ "pnpm": ">=11.22.0"
57
75
  },
58
- "scripts": {
59
- "test": "vitest",
60
- "test:run": "vitest --run",
61
- "test:coverage": "vitest --run --coverage",
62
- "format": "oxfmt --write .",
63
- "format:check": "oxfmt --check .",
64
- "lint": "oxlint .",
65
- "lint:fix": "oxlint --fix .",
66
- "lint:fix-unsafe": "oxlint --import-plugin --node-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions --fix-dangerously .",
67
- "check:fix-suggestions": "oxlint --import-plugin --node-plugin --type-aware --type-check --vitest-plugin --fix --fix-suggestions . && oxfmt --write .",
68
- "typecheck": "tsc --noEmit",
69
- "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test:run",
70
- "build": "tsdown",
71
- "dev": "tsdown --watch"
72
- }
73
- }
76
+ "packageManager": "pnpm@11.22.0"
77
+ }