@oxlint/migrate 0.16.0 → 0.16.2

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
@@ -16,6 +16,18 @@ npx @oxlint/migrate <optional-eslint-flat-config-path>
16
16
 
17
17
  When no config file provided, the script searches for the default eslint config filenames in the current directory.
18
18
 
19
+ ### Options
20
+
21
+ | Options | Description |
22
+ | ---------------------- | ---------------------------------------------------------------------------------------------------- |
23
+ | `--merge` | \* merge eslint configuration with an existing .oxlintrc.json configuration |
24
+ | `--with-nursery` | Include oxlint rules which are currently under development |
25
+ | `--output-file <file>` | The oxlint configuration file where to eslint v9 rules will be written to, default: `.oxlintrc.json` |
26
+
27
+ \* WARNING: When some `categories` are enabled, this tools will enable more rules with the combination of `plugins`.
28
+ Else we need to disable each rule `plugin/categories` combination, which is not covered by your eslint configuration.
29
+ This behavior can change in the future.
30
+
19
31
  ### User Flow
20
32
 
21
33
  - Upgrade `oxlint` and `@oxlint/migrate` to the same version.
@@ -33,7 +45,7 @@ pnpm generate
33
45
  pnpm format
34
46
  ```
35
47
 
36
- ### Vitest + Integration Test
48
+ ### Unit + Integration Test
37
49
 
38
50
  ```shell
39
51
  pnpm vitest
@@ -1,13 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from "commander";
3
3
  import { getAutodetectedEslintConfigName } from "./autoDetectConfigFile.mjs";
4
- import { existsSync, renameSync, writeFileSync } from "fs";
4
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "fs";
5
5
  import main from "../src/index.mjs";
6
6
  import path from "path";
7
7
  import packageJson from "../package.json.mjs";
8
8
  import { pathToFileURL } from "node:url";
9
- program.name("oxlint-migrate").version(packageJson.version).argument("[eslint-config]", "The path to the eslint v9 config file").action(async (filePath) => {
10
- let cwd = process.cwd();
9
+ const cwd = process.cwd();
10
+ program.name("oxlint-migrate").version(packageJson.version).argument("[eslint-config]", "The path to the eslint v9 config file").option(
11
+ "--output-file <file>",
12
+ "The oxlint configuration file where to eslint v9 rules will be written to",
13
+ ".oxlintrc.json"
14
+ ).option(
15
+ "--merge",
16
+ "Merge eslint configuration with an existing .oxlintrc.json configuration",
17
+ false
18
+ ).option(
19
+ "--with-nursery",
20
+ "Include oxlint rules which are currently under development",
21
+ false
22
+ ).action(async (filePath) => {
23
+ const cliOptions = program.opts();
24
+ const oxlintFilePath = path.join(cwd, cliOptions.outputFile);
11
25
  if (filePath === void 0) {
12
26
  filePath = getAutodetectedEslintConfigName(cwd);
13
27
  if (filePath === void 0) {
@@ -20,8 +34,18 @@ program.name("oxlint-migrate").version(packageJson.version).argument("[eslint-co
20
34
  program.error(`eslint config file not found: ${filePath}`);
21
35
  } else {
22
36
  const eslintConfigs = await import(pathToFileURL(filePath).toString());
23
- const oxlintConfig = "default" in eslintConfigs ? await main(eslintConfigs.default, console.warn) : await main(eslintConfigs, console.warn);
24
- const oxlintFilePath = path.join(cwd, ".oxlintrc.json");
37
+ const options = {
38
+ reporter: console.warn,
39
+ merge: !!cliOptions.merge,
40
+ withNursery: !!cliOptions.withNursery
41
+ };
42
+ let config;
43
+ if (options.merge && existsSync(oxlintFilePath)) {
44
+ config = JSON.parse(
45
+ readFileSync(oxlintFilePath, { encoding: "utf8", flag: "r" })
46
+ );
47
+ }
48
+ const oxlintConfig = "default" in eslintConfigs ? await main(eslintConfigs.default, config, options) : await main(eslintConfigs, config, options);
25
49
  if (existsSync(oxlintFilePath)) {
26
50
  renameSync(oxlintFilePath, `${oxlintFilePath}.bak`);
27
51
  }
@@ -1,4 +1,4 @@
1
- const version = "0.16.0";
1
+ const version = "0.16.2";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -1,14 +1,6 @@
1
1
  import { removeGlobalsWithAreCoveredByEnv, transformBoolGlobalToString, ES_VERSIONS, cleanUpUselessOverridesEnv } from "./env_globals.mjs";
2
- import { replaceTypescriptAliasRules, replaceNodePluginName, cleanUpUselessOverridesRules, cleanUpUselessOverridesPlugins } from "./plugins_rules.mjs";
3
- const isEqualDeep = (a, b) => {
4
- if (a === b) {
5
- return true;
6
- }
7
- const bothAreObjects = a && b && typeof a === "object" && typeof b === "object";
8
- return Boolean(
9
- bothAreObjects && Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([k, v]) => isEqualDeep(v, b[k]))
10
- );
11
- };
2
+ import { replaceTypescriptAliasRules, replaceNodePluginName, cleanUpRulesWhichAreCoveredByCategory, cleanUpUselessOverridesRules, cleanUpUselessOverridesPlugins } from "./plugins_rules.mjs";
3
+ import { isEqualDeep } from "./utilities.mjs";
12
4
  const TS_ESLINT_DEFAULT_OVERRIDE = {
13
5
  files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
14
6
  rules: {
@@ -73,6 +65,7 @@ const cleanUpOxlintConfig = (config) => {
73
65
  transformBoolGlobalToString(config);
74
66
  replaceTypescriptAliasRules(config);
75
67
  replaceNodePluginName(config);
68
+ cleanUpRulesWhichAreCoveredByCategory(config);
76
69
  if (config.globals !== void 0 && Object.keys(config.globals).length === 0) {
77
70
  delete config.globals;
78
71
  }
@@ -81,7 +74,7 @@ const cleanUpOxlintConfig = (config) => {
81
74
  delete config.env.es5;
82
75
  delete config.env.es2015;
83
76
  let detected = false;
84
- for (const esVersion of ES_VERSIONS.reverse()) {
77
+ for (const esVersion of [...ES_VERSIONS].reverse()) {
85
78
  if (detected) {
86
79
  delete config.env[`es${esVersion}`];
87
80
  } else if (config.env[`es${esVersion}`] === true) {
@@ -1,8 +1,8 @@
1
- import { OxlintConfig, OxlintConfigOrOverride, Reporter } from './types.js';
1
+ import { Options, OxlintConfig, OxlintConfigOrOverride } from './types.js';
2
2
  import { Linter } from 'eslint';
3
3
  export declare const ES_VERSIONS: number[];
4
4
  export declare const removeGlobalsWithAreCoveredByEnv: (config: OxlintConfigOrOverride) => void;
5
5
  export declare const transformBoolGlobalToString: (config: OxlintConfigOrOverride) => void;
6
6
  export declare const detectEnvironmentByGlobals: (config: OxlintConfigOrOverride) => void;
7
- export declare const transformEnvAndGlobals: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, reporter?: Reporter) => void;
7
+ export declare const transformEnvAndGlobals: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, options?: Options) => void;
8
8
  export declare const cleanUpUselessOverridesEnv: (config: OxlintConfig) => void;
@@ -101,11 +101,11 @@ const detectEnvironmentByGlobals = (config) => {
101
101
  }
102
102
  }
103
103
  };
104
- const transformEnvAndGlobals = (eslintConfig, targetConfig, reporter) => {
104
+ const transformEnvAndGlobals = (eslintConfig, targetConfig, options) => {
105
105
  if (eslintConfig.languageOptions?.parser !== void 0 && !SUPPORTED_ESLINT_PARSERS.includes(
106
106
  eslintConfig.languageOptions.parser.meta?.name
107
107
  )) {
108
- reporter !== void 0 && reporter(
108
+ options?.reporter !== void 0 && options.reporter(
109
109
  "special parser detected: " + eslintConfig.languageOptions.parser.meta?.name
110
110
  );
111
111
  }
@@ -113,22 +113,35 @@ const transformEnvAndGlobals = (eslintConfig, targetConfig, reporter) => {
113
113
  if (targetConfig.globals === void 0) {
114
114
  targetConfig.globals = {};
115
115
  }
116
- Object.assign(targetConfig.globals, eslintConfig.languageOptions.globals);
116
+ if (options?.merge) {
117
+ for (const [global, globalSetting] of Object.entries(
118
+ eslintConfig.languageOptions.globals
119
+ )) {
120
+ if (!(global in targetConfig.globals)) {
121
+ targetConfig.globals[global] = globalSetting;
122
+ }
123
+ }
124
+ } else {
125
+ Object.assign(targetConfig.globals, eslintConfig.languageOptions.globals);
126
+ }
117
127
  }
118
128
  if (eslintConfig.languageOptions?.ecmaVersion !== void 0) {
119
- if (targetConfig.globals === void 0) {
120
- targetConfig.globals = {};
121
- }
122
129
  if (eslintConfig.languageOptions?.ecmaVersion === "latest") {
123
130
  if (targetConfig.env === void 0) {
124
131
  targetConfig.env = {};
125
132
  }
126
- targetConfig.env[`es${ES_VERSIONS[ES_VERSIONS.length - 1]}`] = true;
133
+ const latestVersion = `es${ES_VERSIONS[ES_VERSIONS.length - 1]}`;
134
+ if (!(latestVersion in targetConfig.env)) {
135
+ targetConfig.env[latestVersion] = true;
136
+ }
127
137
  } else if (ES_VERSIONS.includes(eslintConfig.languageOptions?.ecmaVersion)) {
128
138
  if (targetConfig.env === void 0) {
129
139
  targetConfig.env = {};
130
140
  }
131
- targetConfig.env[`es${eslintConfig.languageOptions?.ecmaVersion}`] = true;
141
+ const targetVersion = `es${eslintConfig.languageOptions?.ecmaVersion}`;
142
+ if (!(targetVersion in targetConfig.env)) {
143
+ targetConfig.env[targetVersion] = true;
144
+ }
132
145
  }
133
146
  }
134
147
  };
@@ -1,7 +1,7 @@
1
1
  export declare const pedanticRules: string[];
2
- export declare const nurseryRules: string[];
3
2
  export declare const styleRules: string[];
4
3
  export declare const restrictionRules: string[];
5
4
  export declare const correctnessRules: string[];
5
+ export declare const nurseryRules: string[];
6
6
  export declare const perfRules: string[];
7
7
  export declare const suspiciousRules: string[];
@@ -84,21 +84,6 @@ const pedanticRules = [
84
84
  "import-x/max-dependencies",
85
85
  "vitest/no-conditional-in-test"
86
86
  ];
87
- const nurseryRules = [
88
- "constructor-super",
89
- "getter-return",
90
- "no-undef",
91
- "no-unreachable",
92
- "import/export",
93
- "import/named",
94
- "promise/no-return-in-finally",
95
- "react-hooks/exhaustive-deps",
96
- "react/require-render-return",
97
- "@typescript-eslint/consistent-type-imports",
98
- "@typescript-eslint/no-unnecessary-parameter-property-assignment",
99
- "import-x/export",
100
- "import-x/named"
101
- ];
102
87
  const styleRules = [
103
88
  "curly",
104
89
  "default-case-last",
@@ -324,6 +309,7 @@ const restrictionRules = [
324
309
  "unicorn/no-anonymous-default-export",
325
310
  "unicorn/no-array-for-each",
326
311
  "unicorn/no-array-reduce",
312
+ "unicorn/no-document-cookie",
327
313
  "unicorn/no-length-as-slice-end",
328
314
  "unicorn/no-magic-array-flat-depth",
329
315
  "unicorn/no-nested-ternary",
@@ -482,6 +468,7 @@ const correctnessRules = [
482
468
  "@typescript-eslint/no-misused-new",
483
469
  "@typescript-eslint/no-non-null-asserted-optional-chain",
484
470
  "@typescript-eslint/no-this-alias",
471
+ "@typescript-eslint/no-unnecessary-parameter-property-assignment",
485
472
  "@typescript-eslint/no-unsafe-declaration-merging",
486
473
  "@typescript-eslint/no-useless-empty-export",
487
474
  "@typescript-eslint/no-wrapper-object-types",
@@ -489,7 +476,6 @@ const correctnessRules = [
489
476
  "@typescript-eslint/triple-slash-reference",
490
477
  "unicorn/no-invalid-fetch-options",
491
478
  "unicorn/no-await-in-promise-methods",
492
- "unicorn/no-document-cookie",
493
479
  "unicorn/no-empty-file",
494
480
  "unicorn/no-invalid-remove-event-listener",
495
481
  "unicorn/no-new-array",
@@ -517,6 +503,19 @@ const correctnessRules = [
517
503
  "vitest/valid-describe-callback",
518
504
  "vitest/valid-expect"
519
505
  ];
506
+ const nurseryRules = [
507
+ "getter-return",
508
+ "no-undef",
509
+ "no-unreachable",
510
+ "import/export",
511
+ "import/named",
512
+ "promise/no-return-in-finally",
513
+ "react-hooks/exhaustive-deps",
514
+ "react/require-render-return",
515
+ "@typescript-eslint/consistent-type-imports",
516
+ "import-x/export",
517
+ "import-x/named"
518
+ ];
520
519
  const perfRules = [
521
520
  "no-useless-call",
522
521
  "no-await-in-loop",
@@ -534,6 +533,7 @@ const suspiciousRules = [
534
533
  "no-unexpected-multiline",
535
534
  "no-useless-concat",
536
535
  "no-useless-constructor",
536
+ "import/no-empty-named-blocks",
537
537
  "import/no-absolute-path",
538
538
  "import/no-duplicates",
539
539
  "import/no-named-as-default",
@@ -554,6 +554,7 @@ const suspiciousRules = [
554
554
  "unicorn/prefer-add-event-listener",
555
555
  "unicorn/require-post-message-target-origin",
556
556
  "@typescript-eslint/no-useless-constructor",
557
+ "import-x/no-empty-named-blocks",
557
558
  "import-x/no-absolute-path",
558
559
  "import-x/no-duplicates",
559
560
  "import-x/no-named-as-default",
@@ -1,3 +1,3 @@
1
1
  import { Linter } from 'eslint';
2
- import { OxlintConfigOrOverride, Reporter } from './types.js';
3
- export declare const transformIgnorePatterns: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, reporter?: Reporter) => void;
2
+ import { Options, OxlintConfigOrOverride } from './types.js';
3
+ export declare const transformIgnorePatterns: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, options?: Options) => void;
@@ -1,17 +1,23 @@
1
- const transformIgnorePatterns = (eslintConfig, targetConfig, reporter) => {
1
+ const transformIgnorePatterns = (eslintConfig, targetConfig, options) => {
2
2
  if (eslintConfig.ignores === void 0) {
3
3
  return;
4
4
  }
5
5
  if ("files" in targetConfig) {
6
- reporter !== void 0 && reporter("ignore list inside overrides is not supported");
6
+ options?.reporter !== void 0 && options.reporter("ignore list inside overrides is not supported");
7
7
  return;
8
8
  }
9
9
  if (targetConfig.ignorePatterns === void 0) {
10
10
  targetConfig.ignorePatterns = [];
11
11
  }
12
- targetConfig.ignorePatterns.push(...eslintConfig.ignores);
12
+ for (const ignores of eslintConfig.ignores) {
13
+ if (!targetConfig.ignorePatterns.includes(ignores)) {
14
+ targetConfig.ignorePatterns.push(ignores);
15
+ }
16
+ }
13
17
  eslintConfig.ignores.filter((ignore) => ignore.startsWith("!")).forEach(
14
- (ignore) => reporter !== void 0 && reporter(`ignore allow list is currently not supported: ${ignore}`)
18
+ (ignore) => options?.reporter !== void 0 && options.reporter(
19
+ `ignore allow list is currently not supported: ${ignore}`
20
+ )
15
21
  );
16
22
  };
17
23
  export {
@@ -1,4 +1,4 @@
1
1
  import { Linter } from 'eslint';
2
- import { OxlintConfig, Reporter } from './types.js';
3
- declare const main: (configs: Linter.Config | Linter.Config[] | Promise<Linter.Config> | Promise<Linter.Config[]>, reporter?: Reporter) => Promise<OxlintConfig>;
2
+ import { Options, OxlintConfig } from './types.js';
3
+ declare const main: (configs: Linter.Config | Linter.Config[] | Promise<Linter.Config> | Promise<Linter.Config[]>, oxlintConfig?: OxlintConfig, options?: Options) => Promise<OxlintConfig>;
4
4
  export default main;
@@ -2,20 +2,41 @@ import { transformEnvAndGlobals, detectEnvironmentByGlobals } from "./env_global
2
2
  import { cleanUpOxlintConfig } from "./cleanup.mjs";
3
3
  import { transformIgnorePatterns } from "./ignorePatterns.mjs";
4
4
  import { transformRuleEntry, detectNeededRulesPlugins } from "./plugins_rules.mjs";
5
- const buildConfig = (configs, reporter) => {
6
- const oxlintConfig = {
7
- $schema: "./node_modules/oxlint/configuration_schema.json",
8
- // disable all plugins and check later
9
- plugins: [],
10
- env: {
11
- builtin: true
12
- },
13
- categories: {
14
- // default category
15
- correctness: "off"
5
+ import { detectSameOverride } from "./overrides.mjs";
6
+ const buildConfig = (configs, oxlintConfig, options) => {
7
+ if (oxlintConfig === void 0) {
8
+ if (options?.merge) {
9
+ oxlintConfig = {
10
+ // disable all plugins and check later
11
+ plugins: ["oxc", "typescript", "unicorn", "react"],
12
+ categories: {
13
+ correctness: "warn"
14
+ }
15
+ };
16
+ } else {
17
+ oxlintConfig = {
18
+ $schema: "./node_modules/oxlint/configuration_schema.json",
19
+ // disable all plugins and check later
20
+ plugins: [],
21
+ categories: {
22
+ // ToDo: for merge set the default category manuel when it is not found
23
+ // ToDo: later we can remove it again
24
+ // default category
25
+ correctness: "off"
26
+ }
27
+ };
16
28
  }
17
- };
18
- const overrides = [];
29
+ }
30
+ if (oxlintConfig.$schema === void 0 && options?.merge) {
31
+ oxlintConfig.$schema = "./node_modules/oxlint/configuration_schema.json";
32
+ }
33
+ if (oxlintConfig.env?.builtin === void 0) {
34
+ if (oxlintConfig.env === void 0) {
35
+ oxlintConfig.env = {};
36
+ }
37
+ oxlintConfig.env.builtin = true;
38
+ }
39
+ const overrides = options?.merge ? oxlintConfig.overrides ?? [] : [];
19
40
  for (const config of configs) {
20
41
  if (config.name?.startsWith("oxlint/")) {
21
42
  continue;
@@ -27,28 +48,30 @@ const buildConfig = (configs, reporter) => {
27
48
  targetConfig = {
28
49
  files: Array.isArray(config.files) ? config.files : [config.files]
29
50
  };
30
- overrides.push(targetConfig);
51
+ const [push, result] = detectSameOverride(oxlintConfig, targetConfig);
52
+ if (push) {
53
+ overrides.push(result);
54
+ }
31
55
  }
32
- if (config.plugins !== void 0) ;
33
56
  if (config.settings !== void 0) ;
34
- transformIgnorePatterns(config, targetConfig, reporter);
35
- transformRuleEntry(config, targetConfig, reporter);
36
- transformEnvAndGlobals(config, targetConfig, reporter);
57
+ transformIgnorePatterns(config, targetConfig, options);
58
+ transformRuleEntry(config, targetConfig, options);
59
+ transformEnvAndGlobals(config, targetConfig, options);
37
60
  if ("files" in targetConfig) {
38
- detectNeededRulesPlugins(targetConfig, reporter);
61
+ detectNeededRulesPlugins(targetConfig, options);
39
62
  detectEnvironmentByGlobals(targetConfig);
40
63
  cleanUpOxlintConfig(targetConfig);
41
64
  }
42
65
  }
43
66
  oxlintConfig.overrides = overrides;
44
- detectNeededRulesPlugins(oxlintConfig, reporter);
67
+ detectNeededRulesPlugins(oxlintConfig, options);
45
68
  detectEnvironmentByGlobals(oxlintConfig);
46
69
  cleanUpOxlintConfig(oxlintConfig);
47
70
  return oxlintConfig;
48
71
  };
49
- const main = async (configs, reporter) => {
72
+ const main = async (configs, oxlintConfig, options) => {
50
73
  const resolved = await Promise.resolve(configs);
51
- return Array.isArray(resolved) ? buildConfig(resolved, reporter) : buildConfig([resolved], reporter);
74
+ return Array.isArray(resolved) ? buildConfig(resolved, oxlintConfig, options) : buildConfig([resolved], oxlintConfig, options);
52
75
  };
53
76
  export {
54
77
  main as default
@@ -0,0 +1,2 @@
1
+ import { OxlintConfig, OxlintConfigOverride } from './types.js';
2
+ export declare const detectSameOverride: (config: OxlintConfig, override: OxlintConfigOverride) => [boolean, OxlintConfigOverride];
@@ -0,0 +1,16 @@
1
+ import { isEqualDeep } from "./utilities.mjs";
2
+ const detectSameOverride = (config, override) => {
3
+ if (config.overrides === void 0) {
4
+ return [true, override];
5
+ }
6
+ const matchedOverride = config.overrides.find(({ files, categories }) => {
7
+ return categories === void 0 && isEqualDeep(files, override.files);
8
+ });
9
+ if (matchedOverride !== void 0) {
10
+ return [false, matchedOverride];
11
+ }
12
+ return [true, override];
13
+ };
14
+ export {
15
+ detectSameOverride
16
+ };
@@ -1,9 +1,10 @@
1
1
  import { Linter } from 'eslint';
2
- import { OxlintConfig, OxlintConfigOrOverride, Reporter } from './types.js';
3
- export declare const transformRuleEntry: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, reporter?: Reporter) => void;
4
- export declare const detectNeededRulesPlugins: (targetConfig: OxlintConfigOrOverride, reporter?: Reporter) => void;
2
+ import { Options, OxlintConfig, OxlintConfigOrOverride } from './types.js';
3
+ export declare const transformRuleEntry: (eslintConfig: Linter.Config, targetConfig: OxlintConfigOrOverride, options?: Options) => void;
4
+ export declare const detectNeededRulesPlugins: (targetConfig: OxlintConfigOrOverride, options?: Options) => void;
5
5
  export declare const cleanUpUselessOverridesPlugins: (config: OxlintConfig) => void;
6
6
  export declare const cleanUpUselessOverridesRules: (config: OxlintConfig) => void;
7
+ export declare const cleanUpRulesWhichAreCoveredByCategory: (config: OxlintConfigOrOverride) => void;
7
8
  export declare const replaceTypescriptAliasRules: (config: OxlintConfigOrOverride) => void;
8
9
  /**
9
10
  * Oxlint support them only under the node plugin name
@@ -33,7 +33,7 @@ const normalizeSeverityValue = (value) => {
33
33
  }
34
34
  return void 0;
35
35
  };
36
- const transformRuleEntry = (eslintConfig, targetConfig, reporter) => {
36
+ const transformRuleEntry = (eslintConfig, targetConfig, options) => {
37
37
  if (eslintConfig.rules === void 0) {
38
38
  return;
39
39
  }
@@ -42,19 +42,25 @@ const transformRuleEntry = (eslintConfig, targetConfig, reporter) => {
42
42
  }
43
43
  for (const [rule, config] of Object.entries(eslintConfig.rules)) {
44
44
  if (allRules.includes(rule)) {
45
- if (nurseryRules.includes(rule)) {
46
- reporter !== void 0 && reporter(`unsupported rule, but in development: ${rule}`);
45
+ if (!options?.withNursery && nurseryRules.includes(rule)) {
46
+ options?.reporter !== void 0 && options.reporter(`unsupported rule, but in development: ${rule}`);
47
47
  continue;
48
48
  }
49
- targetConfig.rules[rule] = normalizeSeverityValue(config);
49
+ if (options?.merge) {
50
+ if (!(rule in targetConfig.rules)) {
51
+ targetConfig.rules[rule] = normalizeSeverityValue(config);
52
+ }
53
+ } else {
54
+ targetConfig.rules[rule] = normalizeSeverityValue(config);
55
+ }
50
56
  } else {
51
57
  if (isActiveValue(config)) {
52
- reporter !== void 0 && reporter(`unsupported rule: ${rule}`);
58
+ options?.reporter !== void 0 && options.reporter(`unsupported rule: ${rule}`);
53
59
  }
54
60
  }
55
61
  }
56
62
  };
57
- const detectNeededRulesPlugins = (targetConfig, reporter) => {
63
+ const detectNeededRulesPlugins = (targetConfig, options) => {
58
64
  if (targetConfig.rules === void 0) {
59
65
  return;
60
66
  }
@@ -75,7 +81,7 @@ const detectNeededRulesPlugins = (targetConfig, reporter) => {
75
81
  }
76
82
  }
77
83
  if (!found) {
78
- reporter !== void 0 && reporter(`unsupported plugin for rule: ${rule}`);
84
+ options?.reporter !== void 0 && options.reporter(`unsupported plugin for rule: ${rule}`);
79
85
  }
80
86
  }
81
87
  if ("files" in targetConfig && targetConfig.plugins.length === 0) {
@@ -118,6 +124,22 @@ const cleanUpUselessOverridesRules = (config) => {
118
124
  }
119
125
  }
120
126
  };
127
+ const cleanUpRulesWhichAreCoveredByCategory = (config) => {
128
+ if (config.rules === void 0 || config.categories === void 0) {
129
+ return;
130
+ }
131
+ const enabledCategories = Object.entries(config.categories).filter(([, severity]) => severity === "warn" || severity === "error").map(([category]) => category);
132
+ for (const [rule, settings] of Object.entries(config.rules)) {
133
+ for (const category of enabledCategories) {
134
+ if (`${category}Rules` in rules && // @ts-expect-error -- ts can not resolve the type
135
+ rules[`${category}Rules`].includes(rule)) {
136
+ if (settings === config.categories[category] || Array.isArray(settings) && settings.length === 1 && settings[0] === config.categories[category]) {
137
+ delete config.rules[rule];
138
+ }
139
+ }
140
+ }
141
+ }
142
+ };
121
143
  const replaceTypescriptAliasRules = (config) => {
122
144
  if (config.rules === void 0) {
123
145
  return;
@@ -153,6 +175,7 @@ const replaceNodePluginName = (config) => {
153
175
  }
154
176
  };
155
177
  export {
178
+ cleanUpRulesWhichAreCoveredByCategory,
156
179
  cleanUpUselessOverridesPlugins,
157
180
  cleanUpUselessOverridesRules,
158
181
  detectNeededRulesPlugins,
@@ -8,6 +8,7 @@ export type OxlintConfigOverride = {
8
8
  env?: OxlintConfigEnv;
9
9
  globals?: Linter.Globals;
10
10
  plugins?: OxlintConfigPlugins;
11
+ categories?: OxlintConfigCategories;
11
12
  rules?: Partial<Linter.RulesRecord>;
12
13
  };
13
14
  export type OxlintConfig = {
@@ -21,5 +22,10 @@ export type OxlintConfig = {
21
22
  ignorePatterns?: OxlintConfigIgnorePatterns;
22
23
  };
23
24
  export type OxlintConfigOrOverride = OxlintConfig | OxlintConfigOverride;
24
- export type Reporter = (warning: string) => void;
25
+ type Reporter = (warning: string) => void;
26
+ export type Options = {
27
+ reporter?: Reporter;
28
+ merge?: boolean;
29
+ withNursery?: boolean;
30
+ };
25
31
  export {};
@@ -0,0 +1 @@
1
+ export declare const isEqualDeep: <T>(a: T, b: T) => boolean;
@@ -0,0 +1,12 @@
1
+ const isEqualDeep = (a, b) => {
2
+ if (a === b) {
3
+ return true;
4
+ }
5
+ const bothAreObjects = a && b && typeof a === "object" && typeof b === "object";
6
+ return Boolean(
7
+ bothAreObjects && Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([k, v]) => isEqualDeep(v, b[k]))
8
+ );
9
+ };
10
+ export {
11
+ isEqualDeep
12
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxlint/migrate",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "Generates a `.oxlintrc.json` from a existing eslint flat config",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "@eslint/eslintrc": "^3.3.0",
38
38
  "@eslint/js": "^9.20.0",
39
39
  "@logux/eslint-config": "^55.0.0",
40
- "@oxc-node/core": "^0.0.20",
40
+ "@oxc-node/core": "^0.0.21",
41
41
  "@stylistic/eslint-plugin": "^4.0.1",
42
42
  "@stylistic/eslint-plugin-ts": "^4.0.0",
43
43
  "@types/eslint-config-prettier": "^6.11.3",
@@ -52,12 +52,12 @@
52
52
  "eslint-plugin-import-x": "^4.6.1",
53
53
  "eslint-plugin-jsdoc": "^50.6.3",
54
54
  "eslint-plugin-local": "^6.0.0",
55
- "eslint-plugin-oxlint": "^0.15.10",
55
+ "eslint-plugin-oxlint": "^0.16.0",
56
56
  "eslint-plugin-regexp": "^2.7.0",
57
57
  "eslint-plugin-unicorn": "^57.0.0",
58
58
  "husky": "^9.1.7",
59
59
  "lint-staged": "^15.4.3",
60
- "oxlint": "^0.16.0",
60
+ "oxlint": "^0.16.2",
61
61
  "prettier": "^3.5.1",
62
62
  "typescript": "^5.7.3",
63
63
  "typescript-eslint": "^8.24.0",
@@ -72,5 +72,5 @@
72
72
  "commander": "^13.1.0",
73
73
  "globals": "^16.0.0"
74
74
  },
75
- "packageManager": "pnpm@10.6.1"
75
+ "packageManager": "pnpm@10.6.3"
76
76
  }