@oxlint/migrate 1.42.0 → 1.43.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
@@ -24,6 +24,7 @@ When no config file is provided, the script searches for the default ESLint conf
24
24
  | `--type-aware` | Include type aware rules, which are supported with `oxlint --type-aware` and [oxlint-tsgolint](https://github.com/oxc-project/tsgolint) |
25
25
  | `--with-nursery` | Include oxlint rules which are currently under development |
26
26
  | `--js-plugins` | \*\* Include ESLint plugins via `jsPlugins` key. |
27
+ | `--details` | List rules that could not be migrated to oxlint |
27
28
  | `--output-file <file>` | The oxlint configuration file where ESLint v9 rules will be written to, default: `.oxlintrc.json` |
28
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. |
29
30
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as rules_exports, n as preFixForJsPlugins, r as nurseryRules, t as src_default } from "../src-BiX8O61Z.mjs";
2
+ import { a as rules_exports, i as nurseryRules, n as preFixForJsPlugins, r as isOffValue, t as src_default } from "../src-BMWXAQvA.mjs";
3
3
  import { program } from "commander";
4
4
  import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -17,9 +17,9 @@ const FLAT_CONFIG_FILENAMES = [
17
17
  "eslint.config.mts",
18
18
  "eslint.config.cts"
19
19
  ];
20
- const getAutodetectedEslintConfigName = (cwd$1) => {
20
+ const getAutodetectedEslintConfigName = (cwd) => {
21
21
  for (const filename of FLAT_CONFIG_FILENAMES) {
22
- const filePath = path.join(cwd$1, filename);
22
+ const filePath = path.join(cwd, filename);
23
23
  if (existsSync(filePath)) return filePath;
24
24
  }
25
25
  };
@@ -40,7 +40,7 @@ const loadESLintConfig = async (filePath) => {
40
40
 
41
41
  //#endregion
42
42
  //#region package.json
43
- var version = "1.42.0";
43
+ var version = "1.43.0";
44
44
 
45
45
  //#endregion
46
46
  //#region src/walker/comments/replaceRuleDirectiveComment.ts
@@ -211,8 +211,8 @@ function partialAstroSourceTextLoader(sourceText) {
211
211
  pos = frontmatterEndDelimiter + 3;
212
212
  }
213
213
  }
214
- results.push(...extractScriptBlocks(sourceText, pos, Number.MAX_SAFE_INTEGER, false).map((sourceText$1) => {
215
- return Object.assign(sourceText$1, {
214
+ results.push(...extractScriptBlocks(sourceText, pos, Number.MAX_SAFE_INTEGER, false).map((sourceText) => {
215
+ return Object.assign(sourceText, {
216
216
  lang: `ts`,
217
217
  sourceType: `module`
218
218
  });
@@ -227,7 +227,7 @@ const getComments = (absoluteFilePath, partialSourceText, options) => {
227
227
  lang: partialSourceText.lang,
228
228
  sourceType: partialSourceText.sourceType
229
229
  });
230
- if (parserResult.errors.length > 0) options.reporter?.report(`${absoluteFilePath}: failed to parse`);
230
+ if (parserResult.errors.length > 0) options.reporter?.addWarning(`${absoluteFilePath}: failed to parse`);
231
231
  return parserResult.comments;
232
232
  };
233
233
  function replaceCommentsInSourceText(absoluteFilePath, partialSourceText, options) {
@@ -241,7 +241,7 @@ function replaceCommentsInSourceText(absoluteFilePath, partialSourceText, option
241
241
  }
242
242
  } catch (error) {
243
243
  if (error instanceof Error) {
244
- options.reporter?.report(`${absoluteFilePath}, char offset ${comment.start + partialSourceText.offset}: ${error.message}`);
244
+ options.reporter?.addWarning(`${absoluteFilePath}, char offset ${comment.start + partialSourceText.offset}: ${error.message}`);
245
245
  continue;
246
246
  }
247
247
  throw error;
@@ -258,13 +258,13 @@ function replaceCommentsInFile(absoluteFilePath, fileContent, options) {
258
258
 
259
259
  //#endregion
260
260
  //#region src/walker/index.ts
261
- const walkAndReplaceProjectFiles = (projectFiles, readFileSync$1, writeFile$1, options) => {
261
+ const walkAndReplaceProjectFiles = (projectFiles, readFileSync, writeFile, options) => {
262
262
  return Promise.all(projectFiles.map((file) => {
263
- const sourceText = readFileSync$1(file);
263
+ const sourceText = readFileSync(file);
264
264
  if (!sourceText) return Promise.resolve();
265
265
  const newSourceText = replaceCommentsInFile(file, sourceText, options);
266
266
  if (newSourceText === sourceText) return Promise.resolve();
267
- return writeFile$1(file, newSourceText);
267
+ return writeFile(file, newSourceText);
268
268
  }));
269
269
  };
270
270
 
@@ -281,20 +281,18 @@ const getAllProjectFiles = () => {
281
281
  //#endregion
282
282
  //#region src/reporter.ts
283
283
  var DefaultReporter = class {
284
- reports = /* @__PURE__ */ new Set();
284
+ warnings = /* @__PURE__ */ new Set();
285
285
  skippedRules = new Map([
286
286
  ["nursery", /* @__PURE__ */ new Set()],
287
287
  ["type-aware", /* @__PURE__ */ new Set()],
288
- ["unsupported", /* @__PURE__ */ new Set()]
288
+ ["unsupported", /* @__PURE__ */ new Set()],
289
+ ["js-plugins", /* @__PURE__ */ new Set()]
289
290
  ]);
290
- report(message) {
291
- this.reports.add(message);
291
+ addWarning(message) {
292
+ this.warnings.add(message);
292
293
  }
293
- remove(message) {
294
- this.reports.delete(message);
295
- }
296
- getReports() {
297
- return Array.from(this.reports);
294
+ getWarnings() {
295
+ return Array.from(this.warnings);
298
296
  }
299
297
  markSkipped(rule, category) {
300
298
  this.skippedRules.get(category)?.add(rule);
@@ -306,6 +304,7 @@ var DefaultReporter = class {
306
304
  const result = {
307
305
  nursery: [],
308
306
  "type-aware": [],
307
+ "js-plugins": [],
309
308
  unsupported: []
310
309
  };
311
310
  for (const [category, rules] of this.skippedRules) result[category] = Array.from(rules);
@@ -313,6 +312,92 @@ var DefaultReporter = class {
313
312
  }
314
313
  };
315
314
 
315
+ //#endregion
316
+ //#region bin/output-formatter.ts
317
+ const CATEGORY_METADATA = {
318
+ nursery: {
319
+ label: "Nursery",
320
+ description: "Experimental:"
321
+ },
322
+ "type-aware": {
323
+ label: "Type-aware",
324
+ description: "Requires TS info:"
325
+ },
326
+ "js-plugins": {
327
+ label: "JS Plugins",
328
+ description: "Requires JS plugins:"
329
+ },
330
+ unsupported: { label: "Unsupported" }
331
+ };
332
+ const MAX_LABEL_LENGTH = Math.max(...Object.values(CATEGORY_METADATA).map((meta) => meta.label.length));
333
+ /**
334
+ * Formats a category summary as either inline (with example) or vertical list
335
+ */
336
+ function formatCategorySummary(count, category, rules, showAll) {
337
+ const meta = CATEGORY_METADATA[category];
338
+ if (!showAll) {
339
+ const maxRules = 3;
340
+ const exampleList = rules.slice(0, maxRules).join(", ");
341
+ const suffix = count > maxRules ? ", and more" : "";
342
+ const prefix = meta.description ? `${meta.description} ` : "";
343
+ return ` - ${String(count).padStart(3)} ${meta.label.padEnd(MAX_LABEL_LENGTH)} (${prefix}${exampleList}${suffix})\n`;
344
+ }
345
+ let output = ` - ${count} ${meta.label}\n`;
346
+ for (const rule of rules) output += ` - ${rule}\n`;
347
+ return output;
348
+ }
349
+ /**
350
+ * Detects which CLI flags are missing and could enable more rules
351
+ */
352
+ function detectMissingFlags(byCategory, cliOptions) {
353
+ const missingFlags = [];
354
+ if (byCategory.nursery.length > 0 && !cliOptions.withNursery) missingFlags.push("--with-nursery");
355
+ if (byCategory["type-aware"].length > 0 && !cliOptions.typeAware) missingFlags.push("--type-aware");
356
+ if (byCategory["js-plugins"].length > 0 && !cliOptions.jsPlugins) missingFlags.push("--js-plugins");
357
+ return missingFlags;
358
+ }
359
+ /**
360
+ * Formats the complete migration output message
361
+ */
362
+ function formatMigrationOutput(data) {
363
+ let output = "";
364
+ const showAll = data.cliOptions.details || false;
365
+ if (data.enabledRulesCount === 0) output += `\n⚠️ ${data.outputFileName} created with no rules enabled.\n`;
366
+ else output += `\n✨ ${data.outputFileName} created with ${data.enabledRulesCount} rules.\n`;
367
+ const byCategory = data.skippedRulesByCategory;
368
+ const nurseryCount = byCategory.nursery.length;
369
+ const typeAwareCount = byCategory["type-aware"].length;
370
+ const unsupportedCount = byCategory.unsupported.length;
371
+ const jsPluginsCount = byCategory["js-plugins"].length;
372
+ const totalSkipped = nurseryCount + typeAwareCount + unsupportedCount + jsPluginsCount;
373
+ if (totalSkipped > 0) {
374
+ output += `\n Skipped ${totalSkipped} rules:\n`;
375
+ if (nurseryCount > 0) output += formatCategorySummary(nurseryCount, "nursery", byCategory.nursery, showAll);
376
+ if (typeAwareCount > 0) output += formatCategorySummary(typeAwareCount, "type-aware", byCategory["type-aware"], showAll);
377
+ if (jsPluginsCount > 0) output += formatCategorySummary(jsPluginsCount, "js-plugins", byCategory["js-plugins"], showAll);
378
+ if (unsupportedCount > 0) output += formatCategorySummary(unsupportedCount, "unsupported", byCategory.unsupported, showAll);
379
+ if (!showAll) {
380
+ const maxExamples = 3;
381
+ if (nurseryCount > maxExamples || typeAwareCount > maxExamples || unsupportedCount > maxExamples || jsPluginsCount > maxExamples) output += `\n Tip: Use --details to see the full list.\n`;
382
+ }
383
+ const missingFlags = detectMissingFlags(byCategory, data.cliOptions);
384
+ if (missingFlags.length > 0) {
385
+ const eslintConfigArg = data.eslintConfigPath ? ` ${path.basename(data.eslintConfigPath)}` : "";
386
+ output += `\n👉 Re-run with flags to include more:\n`;
387
+ output += ` npx @oxlint/migrate${eslintConfigArg} ${missingFlags.join(" ")}\n`;
388
+ }
389
+ }
390
+ if (data.enabledRulesCount > 0) {
391
+ output += `\n🚀 Next:\n`;
392
+ output += ` npx oxlint .\n`;
393
+ }
394
+ return output;
395
+ }
396
+ function displayMigrationResult(outputMessage, warnings) {
397
+ console.log(outputMessage);
398
+ for (const warning of warnings) console.warn(warning);
399
+ }
400
+
316
401
  //#endregion
317
402
  //#region bin/oxlint-migrate.ts
318
403
  const cwd = process.cwd();
@@ -323,7 +408,22 @@ const getFileContent = (absoluteFilePath) => {
323
408
  return;
324
409
  }
325
410
  };
326
- 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`.").action(async (filePath) => {
411
+ /**
412
+ * Count enabled rules (excluding "off" rules) from both rules and overrides
413
+ */
414
+ const countEnabledRules = (config) => {
415
+ const enabledRules = /* @__PURE__ */ new Set();
416
+ if (config.rules) {
417
+ for (const [ruleName, ruleValue] of Object.entries(config.rules)) if (!isOffValue(ruleValue)) enabledRules.add(ruleName);
418
+ }
419
+ if (config.overrides && Array.isArray(config.overrides)) {
420
+ for (const override of config.overrides) if (override.rules) {
421
+ for (const [ruleName, ruleValue] of Object.entries(override.rules)) if (!isOffValue(ruleValue)) enabledRules.add(ruleName);
422
+ }
423
+ }
424
+ return enabledRules.size;
425
+ };
426
+ 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) => {
327
427
  const cliOptions = program.opts();
328
428
  const oxlintFilePath = path.join(cwd, cliOptions.outputFile);
329
429
  const reporter = new DefaultReporter();
@@ -335,7 +435,7 @@ program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The
335
435
  jsPlugins: !!cliOptions.jsPlugins
336
436
  };
337
437
  if (cliOptions.replaceEslintComments) {
338
- await walkAndReplaceProjectFiles(await getAllProjectFiles(), (filePath$1) => getFileContent(filePath$1), (filePath$1, content) => writeFile(filePath$1, content, "utf-8"), options);
438
+ await walkAndReplaceProjectFiles(await getAllProjectFiles(), (filePath) => getFileContent(filePath), (filePath, content) => writeFile(filePath, content, "utf-8"), options);
339
439
  return;
340
440
  }
341
441
  if (filePath === void 0) filePath = getAutodetectedEslintConfigName(cwd);
@@ -352,7 +452,19 @@ program.name("oxlint-migrate").version(version).argument("[eslint-config]", "The
352
452
  const oxlintConfig = "default" in eslintConfigs ? await src_default(eslintConfigs.default, config, options) : await src_default(eslintConfigs, config, options);
353
453
  if (existsSync(oxlintFilePath)) renameSync(oxlintFilePath, `${oxlintFilePath}.bak`);
354
454
  writeFileSync(oxlintFilePath, JSON.stringify(oxlintConfig, null, 2));
355
- for (const report of reporter.getReports()) console.warn(report);
455
+ const enabledRulesCount = countEnabledRules(oxlintConfig);
456
+ displayMigrationResult(formatMigrationOutput({
457
+ outputFileName: cliOptions.outputFile,
458
+ enabledRulesCount,
459
+ skippedRulesByCategory: reporter.getSkippedRulesByCategory(),
460
+ cliOptions: {
461
+ withNursery: !!cliOptions.withNursery,
462
+ typeAware: !!cliOptions.typeAware,
463
+ details: !!cliOptions.details,
464
+ jsPlugins: !!cliOptions.jsPlugins
465
+ },
466
+ eslintConfigPath: filePath
467
+ }), reporter.getWarnings());
356
468
  });
357
469
  program.parse();
358
470