@oxlint/migrate 1.49.0 → 1.51.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
@@ -1,8 +1,8 @@
1
1
  # @oxlint/migrate
2
2
 
3
3
  ![test](https://github.com/oxc-project/oxlint-migrate/actions/workflows/test.yml/badge.svg)
4
- [![NPM Version](https://img.shields.io/npm/v/%40oxlint%2Fmigrate)](https://www.npmjs.com/package/@oxlint/migrate)
5
- [![NPM Downloads](https://img.shields.io/npm/dm/%40oxlint%2Fmigrate)](https://www.npmjs.com/package/@oxlint/migrate)
4
+ [![NPM Version](https://img.shields.io/npm/v/%40oxlint%2Fmigrate)](https://npmx.dev/package/@oxlint/migrate)
5
+ [![NPM Downloads](https://img.shields.io/npm/dm/%40oxlint%2Fmigrate)](https://npmx.dev/package/@oxlint/migrate)
6
6
 
7
7
  Generates a `.oxlintrc.json` from an existing ESLint flat config.
8
8
 
@@ -18,15 +18,15 @@ When no config file is provided, the script searches for the default ESLint conf
18
18
 
19
19
  ### Options
20
20
 
21
- | Options | Description |
22
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
23
- | `--merge` | \* merge ESLint configuration with an existing .oxlintrc.json configuration |
24
- | `--type-aware` | Include type aware rules, which are supported with `oxlint --type-aware` and [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) |
25
- | `--with-nursery` | Include oxlint rules which are currently under development |
26
- | `--js-plugins` | \*\* Include ESLint plugins via `jsPlugins` key. |
27
- | `--details` | List rules that could not be migrated to oxlint |
28
- | `--output-file <file>` | The oxlint configuration file where ESLint v9 rules will be written to, default: `.oxlintrc.json` |
29
- | `--replace-eslint-comments` | Search in the project files for ESLint comments and replaces them with oxlint. Some ESLint comments are not supported and will be reported. |
21
+ | Options | Description |
22
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `--merge` | \* merge ESLint configuration with an existing .oxlintrc.json configuration |
24
+ | `--type-aware` | Include type aware rules. These rules are supported with `oxlint --type-aware` and [oxlint-tsgolint](https://github.com/oxc-project/tsgolint). This will also enable the `typeAware` option in the generated configuration. |
25
+ | `--with-nursery` | Include oxlint rules which are currently under development |
26
+ | `--js-plugins [bool]` | \*\* Include ESLint plugins via `jsPlugins` key (enabled by default). Use `--js-plugins=false` to disable. |
27
+ | `--details` | List rules that could not be migrated to oxlint |
28
+ | `--output-file <file>` | The oxlint configuration file where ESLint v9 rules will be written to, default: `.oxlintrc.json` |
29
+ | `--replace-eslint-comments` | Search in the project files for ESLint comments and replaces them with oxlint. Some ESLint comments are not supported and will be reported. |
30
30
 
31
31
  \* WARNING: When some `categories` are enabled, this tools will enable more rules with the combination of `plugins`.
32
32
  Else we need to disable each rule `plugin/categories` combination, which is not covered by your ESLint configuration.
@@ -49,7 +49,7 @@ TypeScript configuration files, like `eslint.config.mts`, are supported in the f
49
49
  - **Deno and Bun**: TypeScript configuration files are natively supported.
50
50
  - **Node.js >=22.18.0**: TypeScript configuration files are supported natively with built-in type-stripping enabled by default.
51
51
  - **Node.js >=22.6.0**: TypeScript configuration files can be used by setting `NODE_OPTIONS=--experimental-strip-types`.
52
- - **Node.js <22.6.0**: TypeScript configuration files can be used by setting `NODE_OPTIONS=--import @oxc-node/core/register` and installing [@oxc-node/core](https://www.npmjs.com/package/@oxc-node/core) as a dev dependency.
52
+ - **Node.js <22.6.0**: TypeScript configuration files can be used by setting `NODE_OPTIONS=--import @oxc-node/core/register` and installing [@oxc-node/core](https://npmx.dev/package/@oxc-node/core) as a dev dependency.
53
53
 
54
54
  If you attempt to use a TypeScript configuration file without the proper setup for your Node.js version, Node.js will throw an error when trying to import the file.
55
55
 
@@ -65,7 +65,7 @@ Here are some known caveats to be aware of:
65
65
 
66
66
  **`settings` field migration**
67
67
 
68
- The `settings` field (e.g. for setting the React version) is migrated for known oxlint-supported plugins: `jsx-a11y`, `next`, `react`, `jsdoc`, and `vitest`. By default, other settings keys are skipped since they aren't supported by oxlint. If using the `--js-plugins` flag, other settings keys will also be migrated in order to support JS Plugins.
68
+ The `settings` field (e.g. for setting the React version) is migrated for known oxlint-supported plugins: `jsx-a11y`, `next`, `react`, `jsdoc`, and `vitest`. By default, other settings keys are also migrated to support JS Plugins. Use `--js-plugins=false` to skip migrating unknown settings keys.
69
69
 
70
70
  Note: Oxlint does not support `settings` in override configs. If your ESLint config has settings in configs with `files` patterns, those settings will be skipped and a warning will be shown.
71
71
 
@@ -73,7 +73,7 @@ Not all `settings` options are supported by oxlint, and so rule behavior in cert
73
73
 
74
74
  **Local ESLint Plugins imported via path are not migrated**
75
75
 
76
- The `--js-plugins` flag cannot migrate ESLint plugins from file paths in the same repo currently (e.g. if you have `../eslint-plugin-myplugin` in your `eslint.config.mjs`). You will need to copy them over into the `jsPlugins` manually. See [the JS Plugins docs]() for more info.
76
+ JS plugin migration cannot migrate ESLint plugins from file paths in the same repo currently (e.g. if you have `../eslint-plugin-myplugin` in your `eslint.config.mjs`). You will need to copy them over into the `jsPlugins` manually. See [the JS Plugins docs]() for more info.
77
77
 
78
78
  **`globals` field with large number of values**
79
79
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as preFixForJsPlugins, f as buildUnsupportedRuleExplanations, m as rules_exports, p as nurseryRules, u as isOffValue } from "../settings-Bb6227Gq.mjs";
2
+ import { a as preFixForJsPlugins, f as buildUnsupportedRuleExplanations, m as rules_exports, p as nurseryRules, u as isOffValue } from "../settings-R7dCJ7Y3.mjs";
3
3
  import main from "../src/index.mjs";
4
4
  import { program } from "commander";
5
5
  import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -33,7 +33,7 @@ const loadESLintConfig = async (filePath) => {
33
33
 
34
34
  //#endregion
35
35
  //#region package.json
36
- var version = "1.49.0";
36
+ var version = "1.51.0";
37
37
 
38
38
  //#endregion
39
39
  //#region src/walker/comments/replaceRuleDirectiveComment.ts
@@ -357,7 +357,7 @@ function detectMissingFlags(byCategory, cliOptions) {
357
357
  const missingFlags = [];
358
358
  if (byCategory.nursery.length > 0 && !cliOptions.withNursery) missingFlags.push("--with-nursery");
359
359
  if (byCategory["type-aware"].length > 0 && !cliOptions.typeAware) missingFlags.push("--type-aware");
360
- if (byCategory["js-plugins"].length > 0 && !cliOptions.jsPlugins) missingFlags.push("--js-plugins");
360
+ if (byCategory["js-plugins"].length > 0 && !cliOptions.jsPlugins) missingFlags.push("--js-plugins=true");
361
361
  return missingFlags;
362
362
  }
363
363
  /**
@@ -399,14 +399,25 @@ function formatMigrationOutput(data) {
399
399
  }
400
400
  return output;
401
401
  }
402
+ function formatWarningsOutput(warnings) {
403
+ if (warnings.length === 0) return "";
404
+ let output = `⚠️ Warnings (${warnings.length}):\n`;
405
+ for (const warning of warnings) {
406
+ const [message, ...details] = warning.split("\n");
407
+ output += ` * ${message}\n`;
408
+ for (const detail of details.filter((line) => line.trim().length)) output += ` * ${detail}\n`;
409
+ }
410
+ return output.trimEnd();
411
+ }
402
412
  function displayMigrationResult(outputMessage, warnings) {
403
413
  console.log(outputMessage);
404
- for (const warning of warnings) console.warn(warning);
414
+ if (warnings.length > 0) console.warn(formatWarningsOutput(warnings));
405
415
  }
406
416
 
407
417
  //#endregion
408
418
  //#region bin/oxlint-migrate.ts
409
419
  const cwd = process.cwd();
420
+ const parseCliBoolean = (value) => value === "false" ? false : !!value;
410
421
  const getFileContent = (absoluteFilePath) => {
411
422
  try {
412
423
  return readFileSync(absoluteFilePath, "utf-8");
@@ -429,8 +440,9 @@ const countEnabledRules = (config) => {
429
440
  }
430
441
  return enabledRules.size;
431
442
  };
432
- program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The path to the eslint v9 config file").option("--output-file <file>", "The oxlint configuration file where to eslint v9 rules will be written to", ".oxlintrc.json").option("--merge", "Merge eslint configuration with an existing .oxlintrc.json configuration", false).option("--with-nursery", "Include oxlint rules which are currently under development", false).option("--replace-eslint-comments", "Search in the project files for eslint comments and replaces them with oxlint. Some eslint comments are not supported and will be reported.").option("--type-aware", "Includes supported type-aware rules. Needs the same flag in `oxlint` to enable it.").option("--js-plugins", "Tries to convert unsupported oxlint plugins with `jsPlugins`.").option("--details", "List rules that could not be migrated to oxlint.", false).action(async (filePath) => {
443
+ program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The path to the eslint v9 config file").option("--output-file <file>", "The oxlint configuration file where to eslint v9 rules will be written to", ".oxlintrc.json").option("--merge", "Merge eslint configuration with an existing .oxlintrc.json configuration", false).option("--with-nursery", "Include oxlint rules which are currently under development", false).option("--replace-eslint-comments", "Search in the project files for eslint comments and replaces them with oxlint. Some eslint comments are not supported and will be reported.").option("--type-aware", "Includes supported type-aware rules. Needs the same flag in `oxlint` or the `typeAware` config option to enable it.").option("--js-plugins [bool]", "Tries to convert unsupported oxlint plugins with `jsPlugins`. Enabled by default; pass `--js-plugins=false` to disable.", true).option("--details", "List rules that could not be migrated to oxlint.", false).action(async (filePath) => {
433
444
  const cliOptions = program.opts();
445
+ const jsPlugins = parseCliBoolean(cliOptions.jsPlugins);
434
446
  const oxlintFilePath = path.join(cwd, cliOptions.outputFile);
435
447
  const reporter = new DefaultReporter();
436
448
  const options = {
@@ -438,7 +450,7 @@ program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The
438
450
  merge: !!cliOptions.merge,
439
451
  withNursery: !!cliOptions.withNursery,
440
452
  typeAware: !!cliOptions.typeAware,
441
- jsPlugins: !!cliOptions.jsPlugins
453
+ jsPlugins
442
454
  };
443
455
  if (cliOptions.replaceEslintComments) {
444
456
  await walkAndReplaceProjectFiles(await getAllProjectFiles(), (filePath) => getFileContent(filePath), (filePath, content) => writeFile(filePath, content, "utf-8"), options);
@@ -467,7 +479,7 @@ program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The
467
479
  withNursery: !!cliOptions.withNursery,
468
480
  typeAware: !!cliOptions.typeAware,
469
481
  details: !!cliOptions.details,
470
- jsPlugins: !!cliOptions.jsPlugins
482
+ jsPlugins
471
483
  },
472
484
  eslintConfigPath: filePath
473
485
  }), reporter.getWarnings());
@@ -476,6 +476,7 @@ const styleRules = [
476
476
  "unicorn/prefer-string-raw",
477
477
  "unicorn/prefer-string-trim-start-end",
478
478
  "unicorn/prefer-structured-clone",
479
+ "unicorn/prefer-ternary",
479
480
  "unicorn/relative-url-style",
480
481
  "unicorn/require-array-join-separator",
481
482
  "unicorn/require-module-attributes",
@@ -682,6 +683,7 @@ const restrictionRules = [
682
683
  "unicorn/no-process-exit",
683
684
  "unicorn/no-useless-error-capture-stack-trace",
684
685
  "unicorn/prefer-modern-math-apis",
686
+ "unicorn/prefer-module",
685
687
  "unicorn/prefer-node-protocol",
686
688
  "unicorn/prefer-number-properties",
687
689
  "vue/max-props",
@@ -1069,13 +1071,47 @@ const ignorePlugins = new Set([
1069
1071
  ...Object.values(rulesPrefixesForPlugins),
1070
1072
  "local"
1071
1073
  ]);
1072
- const guessEslintPluginName = (pluginName) => {
1074
+ const tryResolvePackage = (packageName) => {
1075
+ try {
1076
+ import.meta.resolve(packageName);
1077
+ return true;
1078
+ } catch {
1079
+ return false;
1080
+ }
1081
+ };
1082
+ const pluginNameCache = /* @__PURE__ */ new Map();
1083
+ /**
1084
+ * Resolves the npm package name for an ESLint plugin given its scope name.
1085
+ *
1086
+ * For scoped plugin names (starting with `@`), the mapping is unambiguous:
1087
+ * - `@scope` -> `@scope/eslint-plugin`
1088
+ * - `@scope/sub` -> `@scope/eslint-plugin-sub`
1089
+ *
1090
+ * For non-scoped names, the npm package could follow either convention:
1091
+ * - `eslint-plugin-{name}` (e.g. `eslint-plugin-mocha`)
1092
+ * - `@{name}/eslint-plugin` (e.g. `@e18e/eslint-plugin`)
1093
+ *
1094
+ * We try to resolve both candidates against the installed packages and
1095
+ * use the one that is actually present, falling back to the standard
1096
+ * `eslint-plugin-{name}` convention when neither can be resolved.
1097
+ */
1098
+ const resolveEslintPluginName = (pluginName) => {
1099
+ const cached = pluginNameCache.get(pluginName);
1100
+ if (cached !== void 0) return cached;
1101
+ let result;
1073
1102
  if (pluginName.startsWith("@")) {
1074
1103
  const [scope, maybeSub] = pluginName.split("/");
1075
- if (maybeSub) return `${scope}/eslint-plugin-${maybeSub}`;
1076
- return `${scope}/eslint-plugin`;
1104
+ if (maybeSub) result = `${scope}/eslint-plugin-${maybeSub}`;
1105
+ else result = `${scope}/eslint-plugin`;
1106
+ } else {
1107
+ const standardName = `eslint-plugin-${pluginName}`;
1108
+ const scopedName = `@${pluginName}/eslint-plugin`;
1109
+ if (tryResolvePackage(standardName)) result = standardName;
1110
+ else if (tryResolvePackage(scopedName)) result = scopedName;
1111
+ else result = standardName;
1077
1112
  }
1078
- return `eslint-plugin-${pluginName}`;
1113
+ pluginNameCache.set(pluginName, result);
1114
+ return result;
1079
1115
  };
1080
1116
  const extractPluginId = (ruleId) => {
1081
1117
  const firstSlash = ruleId.indexOf("/");
@@ -1091,15 +1127,68 @@ const isIgnoredPluginRule = (ruleId) => {
1091
1127
  if (pluginName === void 0) return true;
1092
1128
  return ignorePlugins.has(pluginName);
1093
1129
  };
1094
- const enableJsPluginRule = (targetConfig, rule, ruleEntry) => {
1130
+ /**
1131
+ * Derives the npm package name for a plugin from its `meta.name` field.
1132
+ *
1133
+ * If `meta.name` already looks like a full npm package name (contains
1134
+ * "eslint-plugin"), it is returned as-is. Otherwise it is fed through
1135
+ * {@link resolveEslintPluginName} for the usual heuristic resolution.
1136
+ */
1137
+ const resolveFromMetaName = (metaName) => {
1138
+ if (metaName.includes("eslint-plugin")) return metaName;
1139
+ return resolveEslintPluginName(metaName);
1140
+ };
1141
+ /**
1142
+ * Derives the rule-ID prefix that an npm package exposes.
1143
+ *
1144
+ * Examples:
1145
+ * `eslint-plugin-react-dom` -> `react-dom`
1146
+ * `eslint-plugin-mocha` -> `mocha`
1147
+ * `@stylistic/eslint-plugin` -> `@stylistic`
1148
+ * `@stylistic/eslint-plugin-ts` -> `@stylistic/ts`
1149
+ */
1150
+ const deriveRulePrefix = (packageName) => {
1151
+ if (packageName.startsWith("@")) {
1152
+ const slashIdx = packageName.indexOf("/");
1153
+ const scope = packageName.substring(0, slashIdx);
1154
+ const rest = packageName.substring(slashIdx + 1);
1155
+ if (rest === "eslint-plugin") return scope;
1156
+ if (rest.startsWith("eslint-plugin-")) return `${scope}/${rest.substring(14)}`;
1157
+ return packageName;
1158
+ }
1159
+ if (packageName.startsWith("eslint-plugin-")) return packageName.substring(14);
1160
+ return packageName;
1161
+ };
1162
+ /**
1163
+ * Resolves the canonical rule name for a jsPlugin rule.
1164
+ *
1165
+ * When a plugin is registered under an alias (e.g. `@eslint-react/dom`) but
1166
+ * its `meta.name` reveals a different canonical package (`eslint-plugin-react-dom`),
1167
+ * the rule must be rewritten so that oxlint can match it to the loaded plugin.
1168
+ *
1169
+ * For example:
1170
+ * `@eslint-react/dom/no-find-dom-node` -> `react-dom/no-find-dom-node`
1171
+ */
1172
+ const resolveJsPluginRuleName = (rule, plugins) => {
1173
+ const pluginName = extractPluginId(rule);
1174
+ if (pluginName === void 0) return rule;
1175
+ const metaName = plugins?.[pluginName]?.meta?.name;
1176
+ if (!metaName || !metaName.includes("eslint-plugin")) return rule;
1177
+ const canonicalPrefix = deriveRulePrefix(metaName);
1178
+ if (canonicalPrefix === pluginName) return rule;
1179
+ return `${canonicalPrefix}/${rule.substring(pluginName.length + 1)}`;
1180
+ };
1181
+ const enableJsPluginRule = (targetConfig, rule, ruleEntry, plugins) => {
1095
1182
  const pluginName = extractPluginId(rule);
1096
1183
  if (pluginName === void 0) return false;
1097
1184
  if (ignorePlugins.has(pluginName)) return false;
1098
1185
  if (targetConfig.jsPlugins === void 0) targetConfig.jsPlugins = [];
1099
- const eslintPluginName = guessEslintPluginName(pluginName);
1186
+ const metaName = plugins?.[pluginName]?.meta?.name;
1187
+ const eslintPluginName = metaName ? resolveFromMetaName(metaName) : resolveEslintPluginName(pluginName);
1100
1188
  if (!targetConfig.jsPlugins.includes(eslintPluginName)) targetConfig.jsPlugins.push(eslintPluginName);
1189
+ const resolvedRule = resolveJsPluginRuleName(rule, plugins);
1101
1190
  targetConfig.rules = targetConfig.rules || {};
1102
- targetConfig.rules[rule] = ruleEntry;
1191
+ targetConfig.rules[resolvedRule] = ruleEntry;
1103
1192
  return true;
1104
1193
  };
1105
1194
 
@@ -1144,6 +1233,7 @@ var unsupportedRules = {
1144
1233
  "import/no-deprecated": "No need to implement, already implemented by `typescript/no-deprecated` via tsgolint.",
1145
1234
  "n/no-restricted-import": "No need to implement, already implemented by `no-restricted-imports` rule.",
1146
1235
  "n/no-restricted-require": "No need to implement, already implemented by `no-restricted-imports` rule.",
1236
+ "import/order": "Not implementing this in Oxlint as its behavior is covered very well by [Oxfmt's import sorting](https://oxc.rs/docs/guide/usage/formatter/sorting.html).",
1147
1237
  "jsdoc/type-formatting": "Experimental rule in the original plugin, may reconsider once stable.",
1148
1238
  "jsdoc/convert-to-jsdoc-comments": "Experimental rule in the original plugin, may reconsider once stable.",
1149
1239
  "jsdoc/check-examples": "Deprecated.",
@@ -1569,12 +1659,22 @@ const mergeRuleConfig = (existingConfig, newConfig) => {
1569
1659
  if (Array.isArray(newConfig) && newConfig.length === 1 && existingIsArray && existingConfig.length > 1) return [newConfig[0], ...existingConfig.slice(1)];
1570
1660
  return newConfig;
1571
1661
  };
1572
- const transformRuleEntry = (eslintConfig, targetConfig, baseConfig, options, overrides) => {
1662
+ const transformRuleEntry = (eslintConfig, targetConfig, baseConfig, options, overrides, globalPlugins) => {
1573
1663
  if (eslintConfig.rules === void 0) return;
1574
1664
  if (targetConfig.rules === void 0) targetConfig.rules = {};
1665
+ const effectivePlugins = globalPlugins ? {
1666
+ ...globalPlugins,
1667
+ ...eslintConfig.plugins
1668
+ } : eslintConfig.plugins;
1575
1669
  for (const [rule, config] of Object.entries(eslintConfig.rules)) {
1576
1670
  const normalizedConfig = normalizeSeverityValue(config);
1577
- if (!options?.merge) removePreviousOverrideRule(rule, eslintConfig, overrides);
1671
+ if (!options?.merge) {
1672
+ removePreviousOverrideRule(rule, eslintConfig, overrides);
1673
+ if (options?.jsPlugins) {
1674
+ const resolved = resolveJsPluginRuleName(rule, effectivePlugins);
1675
+ if (resolved !== rule) removePreviousOverrideRule(resolved, eslintConfig, overrides);
1676
+ }
1677
+ }
1578
1678
  if (allRules.includes(rule)) {
1579
1679
  if (!options?.withNursery && nurseryRules.includes(rule)) {
1580
1680
  options?.reporter?.markSkipped(rule, "nursery");
@@ -1593,8 +1693,9 @@ const transformRuleEntry = (eslintConfig, targetConfig, baseConfig, options, ove
1593
1693
  } else {
1594
1694
  if (options?.jsPlugins) {
1595
1695
  if (isOffValue(normalizedConfig)) {
1596
- if (eslintConfig.files === void 0) delete targetConfig.rules[rule];
1597
- else if (!isIgnoredPluginRule(rule)) targetConfig.rules[rule] = normalizedConfig;
1696
+ const resolvedRule = resolveJsPluginRuleName(rule, effectivePlugins);
1697
+ if (eslintConfig.files === void 0) delete targetConfig.rules[resolvedRule];
1698
+ else if (!isIgnoredPluginRule(rule)) targetConfig.rules[resolvedRule] = normalizedConfig;
1598
1699
  if (eslintConfig.files === void 0) {
1599
1700
  options?.reporter?.removeSkipped(rule, "js-plugins");
1600
1701
  options?.reporter?.removeSkipped(rule, "not-implemented");
@@ -1602,7 +1703,7 @@ const transformRuleEntry = (eslintConfig, targetConfig, baseConfig, options, ove
1602
1703
  }
1603
1704
  continue;
1604
1705
  }
1605
- if (!enableJsPluginRule(targetConfig, rule, normalizedConfig)) {
1706
+ if (!enableJsPluginRule(targetConfig, rule, normalizedConfig, effectivePlugins)) {
1606
1707
  const category = unsupportedRuleExplanations[rule] ? "unsupported" : "not-implemented";
1607
1708
  options?.reporter?.markSkipped(rule, category);
1608
1709
  }
@@ -1989,6 +2090,13 @@ const isSupportedSettingsKey = (key) => {
1989
2090
  return OXLINT_SUPPORTED_SETTINGS_KEYS.includes(key);
1990
2091
  };
1991
2092
  /**
2093
+ * Format a list of settings keys into a newline-separated, backtick-quoted
2094
+ * string for warning output.
2095
+ */
2096
+ const formatSettingsKeyList = (keys) => {
2097
+ return keys.map((key) => `\`${key}\``).join("\n");
2098
+ };
2099
+ /**
1992
2100
  * Transform ESLint settings to oxlint settings.
1993
2101
  *
1994
2102
  * Only processes settings for the base config (not overrides) since oxlint
@@ -2025,24 +2133,22 @@ const transformSettings = (eslintConfig, targetConfig, options) => {
2025
2133
  }
2026
2134
  let settingsValue = value;
2027
2135
  if (key === "react" && settingsValue.version === "detect") {
2028
- options?.reporter?.addWarning("react.version \"detect\" is not supported by oxlint. Please specify an explicit version (e.g., \"18.2.0\") in your oxlint config.");
2136
+ options?.reporter?.addWarning("react.version \"detect\" is not supported. Specify an explicit version (e.g., \"18.2.0\") in your oxlint config.");
2029
2137
  const { version: _, ...restReactSettings } = settingsValue;
2030
2138
  settingsValue = restReactSettings;
2031
2139
  }
2032
2140
  const unsupportedSubKeys = UNSUPPORTED_SETTINGS_SUB_KEYS[key];
2033
2141
  if (unsupportedSubKeys !== void 0) {
2034
- const strippedKeys = [];
2035
2142
  for (const subKey of unsupportedSubKeys) if (subKey in settingsValue) {
2036
- strippedKeys.push(`${key}.${subKey}`);
2143
+ skippedKeys.push(`${key}.${subKey}`);
2037
2144
  const { [subKey]: _, ...rest } = settingsValue;
2038
2145
  settingsValue = rest;
2039
2146
  }
2040
- if (strippedKeys.length > 0) options?.reporter?.addWarning(`Settings not migrated (not supported by oxlint): ${strippedKeys.join(", ")}.`);
2041
2147
  }
2042
2148
  if (Object.keys(settingsValue).length === 0) continue;
2043
2149
  filteredSettings[key] = settingsValue;
2044
2150
  }
2045
- if (skippedKeys.length > 0) options?.reporter?.addWarning(`Settings not migrated (not supported by oxlint): ${skippedKeys.join(", ")}.`);
2151
+ if (skippedKeys.length > 0) options?.reporter?.addWarning(`Settings not migrated (not supported by oxlint):\n${formatSettingsKeyList(skippedKeys)}`);
2046
2152
  if (Object.keys(filteredSettings).length === 0) return;
2047
2153
  if (targetConfig.settings === void 0) targetConfig.settings = {};
2048
2154
  if (options?.merge) deepMerge(targetConfig.settings, filteredSettings);
@@ -2057,10 +2163,10 @@ const transformSettings = (eslintConfig, targetConfig, options) => {
2057
2163
  */
2058
2164
  const warnSettingsInOverride = (eslintConfig, options) => {
2059
2165
  if (eslintConfig.settings !== void 0 && eslintConfig.settings !== null && Object.keys(eslintConfig.settings).length > 0) {
2060
- const settingsKeys = Object.keys(eslintConfig.settings).join(", ");
2061
- options?.reporter?.addWarning(`Settings found in config with 'files' pattern (${settingsKeys}). Oxlint does not support settings in overrides, these settings will be skipped.`);
2166
+ const settingsKeys = Object.keys(eslintConfig.settings);
2167
+ options?.reporter?.addWarning("Settings found under a 'files' pattern oxlint does not support settings in overrides and they will be skipped:\n" + formatSettingsKeyList(settingsKeys));
2062
2168
  }
2063
2169
  };
2064
2170
 
2065
2171
  //#endregion
2066
- export { preFixForJsPlugins as a, cleanUpOxlintConfig as c, transformRuleEntry as d, buildUnsupportedRuleExplanations as f, transformEnvAndGlobals as g, detectEnvironmentByGlobals as h, fixForJsPlugins as i, detectNeededRulesPlugins as l, rules_exports as m, warnSettingsInOverride as n, detectSameOverride as o, nurseryRules as p, processConfigFiles as r, transformIgnorePatterns as s, transformSettings as t, isOffValue as u };
2172
+ export { transformEnvAndGlobals as _, preFixForJsPlugins as a, cleanUpOxlintConfig as c, transformRuleEntry as d, buildUnsupportedRuleExplanations as f, detectEnvironmentByGlobals as g, typeAwareRules as h, fixForJsPlugins as i, detectNeededRulesPlugins as l, rules_exports as m, warnSettingsInOverride as n, detectSameOverride as o, nurseryRules as p, processConfigFiles as r, transformIgnorePatterns as s, transformSettings as t, isOffValue as u };