Skip to content

Commit

Permalink
feat(migrate): add migrate eslint (#2103)
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos authored Apr 5, 2024
1 parent 8c92a63 commit 3adb1d9
Show file tree
Hide file tree
Showing 63 changed files with 5,808 additions and 534 deletions.
80 changes: 78 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,84 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b

#### New features

- Add a command to migrate from ESLint

`@biomejs/biome migrate eslint` allows you to migrate an ESLint configuration to Biome.
The command supports [legacy ESLint configurations](https://eslint.org/docs/latest/use/configure/configuration-files) and [new flat ESLint configurations](https://eslint.org/docs/latest/use/configure/configuration-files-new).
Legacy ESLint configurations using the YAML format are not supported.

When loading a legacy ESLint configuration, Biome resolves the `extends` field.
It resolves both shared configurations and plugin presets!
To do this, it invokes NodeJS.

Biome relies on the metadata of its rules to determine the [equivalent rule of an ESLint rule](https://biomejs.dev/linter/rules-sources/).
A Biome rule is either inspired or roughly identical to an ESLint rules.
By default, inspired and nursery rules are excluded from the migration.
You can use the CLI flags `--include-inspired` and `--include-nursery` to migrate them as well.

Note that this is a best-effort approach.
You are not guaranteed to get the same behavior as ESLint.

Given the following ESLint configuration:

```json
{
"ignore_patterns": ["**/*.test.js"],
"globals": { "var2": "readonly" },
"rules": {
"eqeqeq": "error"
},
"overrides": [{
"files": ["lib/*.js"],
"rules": {
"default-param-last": "off"
}
}]
}
```

`@biomejs/biome migrate eslint --write` changes the Biome configuration as follows:

```json
{
"linter": {
"rules": {
"recommended": false,
"suspicious": {
"noDoubleEquals": "error"
}
}
},
"javascript": { "globals": ["var2"] },
"overrides": [{
"include": ["lib/*.js"],
"linter": {
"rules": {
"style": {
"useDefaultParameterLast": "off"
}
}
}
}]
}
```

Also, if the working diretcory contains `.eslintignore`, then Biome migrates the glob patterns.
Nested `.eslintignore` in subdirectories and negated glob patterns are not supported.

If you find any issue, please don't hesitate to report them.

Contributed by @Conaclos

#### Enhancements

- Improve support of `.prettierignore` when migrating from Prettier

Now, Biome translates most of the glob patterns in `.prettierignore` to the equivalent Biome ignore pattern.
Only negated glob patterns are not supported.

Contributed by @Conaclos

### Configuration

#### Enhancements
Expand Down Expand Up @@ -714,7 +790,7 @@ Additionally, the following rules are now recommended:

```diff
- <div class="px-2 foo p-4 bar" />
+ <div class="foo·bar·p-4·px-2" />
+ <div class="foo bar p-4 px-2" />
```
Contributed by @DaniGuardiola

Expand Down Expand Up @@ -936,7 +1012,7 @@ Additionally, the following rules are now recommended:
- Fix [#1656](https://github.com/biomejs/biome/issues/1656). [useOptionalChain](https://biomejs.dev/linter/rules/use-optional-chain/) code action now correctly handles logical and chains where methods with the same name are invoked with different arguments:
```diff
- tags·&&·tags.includes('a')·&&·tags.includes('b')
- tags && tags.includes('a') && tags.includes('b')
+ tags?.includes('a') && tags.includes('b')
```
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 19 additions & 21 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@ impl RuleSource {
}
}

pub fn to_namespaced_rule_name(&self) -> String {
match self {
Self::Clippy(rule_name) | Self::Eslint(rule_name) => (*rule_name).to_string(),
Self::EslintImport(rule_name) => format!("import/{rule_name}"),
Self::EslintImportAccess(rule_name) => format!("import-access/{rule_name}"),
Self::EslintJest(rule_name) => format!("jest/{rule_name}"),
Self::EslintJsxA11y(rule_name) => format!("jsx-a11y/{rule_name}"),
Self::EslintReact(rule_name) => format!("react/{rule_name}"),
Self::EslintReactHooks(rule_name) => format!("react-hooks/{rule_name}"),
Self::EslintTypeScript(rule_name) => format!("@typescript-eslint/{rule_name}"),
Self::EslintSonarJs(rule_name) => format!("sonarjs/{rule_name}"),
Self::EslintStylistic(rule_name) => format!("@stylistic/{rule_name}"),
Self::EslintUnicorn(rule_name) => format!("unicorn/{rule_name}"),
Self::EslintMysticatea(rule_name) => format!("@mysticatea/{rule_name}"),
Self::EslintBarrelFiles(rule_name) => format!("barrel-files/{rule_name}"),
}
}

pub fn to_rule_url(&self) -> String {
match self {
Self::Clippy(rule_name) => format!("https://rust-lang.github.io/rust-clippy/master/#/{rule_name}"),
Expand Down Expand Up @@ -186,29 +204,9 @@ impl RuleSource {
matches!(self, Self::Eslint(_))
}

/// TypeScript plugin
pub const fn is_eslint_typescript(&self) -> bool {
matches!(self, Self::EslintTypeScript(_))
}

/// All ESLint plugins, exception for the TypeScript one
pub const fn is_eslint_plugin(&self) -> bool {
matches!(
self,
Self::EslintImport(_)
| Self::EslintImportAccess(_)
| Self::EslintJest(_)
| Self::EslintStylistic(_)
| Self::EslintJsxA11y(_)
| Self::EslintReact(_)
| Self::EslintReactHooks(_)
| Self::EslintSonarJs(_)
| Self::EslintUnicorn(_)
)
}

pub const fn is_clippy(&self) -> bool {
matches!(self, Self::Clippy(_))
!matches!(self, Self::Clippy(_) | Self::Eslint(_))
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/biome_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ biome_deserialize_macros = { workspace = true }
biome_diagnostics = { workspace = true }
biome_formatter = { workspace = true }
biome_fs = { workspace = true }
biome_js_analyze = { workspace = true }
biome_js_formatter = { workspace = true }
biome_json_formatter = { workspace = true }
biome_json_parser = { workspace = true }
Expand All @@ -46,6 +47,7 @@ rayon = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
smallvec = { workspace = true }
tokio = { workspace = true, features = ["io-std", "io-util", "net", "time", "rt", "sync", "rt-multi-thread", "macros"] }
tracing = { workspace = true }
tracing-appender = "0.2"
Expand Down
20 changes: 11 additions & 9 deletions crates/biome_cli/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ use crate::cli_options::CliOptions;
use crate::diagnostics::MigrationDiagnostic;
use crate::execute::{execute_mode, Execution, TraversalMode};
use crate::{setup_cli_subscriber, CliDiagnostic, CliSession};
use biome_configuration::ConfigurationBasePath;
use biome_console::{markup, ConsoleExt};
use biome_service::configuration::{load_configuration, LoadedConfiguration};
use std::path::PathBuf;

use super::MigrateSubCommand;

/// Handler for the "check" command of the Biome CLI
pub(crate) fn migrate(
session: CliSession,
cli_options: CliOptions,
write: bool,
prettier: bool,
sub_command: Option<MigrateSubCommand>,
) -> Result<(), CliDiagnostic> {
let base_path = match cli_options.config_path.as_ref() {
None => ConfigurationBasePath::default(),
Some(path) => ConfigurationBasePath::FromUser(PathBuf::from(path)),
};
let base_path = cli_options.as_configuration_base_path();
let LoadedConfiguration {
configuration: _,
diagnostics: _,
Expand All @@ -31,15 +29,19 @@ pub(crate) fn migrate(
write,
configuration_file_path: path,
configuration_directory_path: directory_path,
prettier,
sub_command,
}),
session,
&cli_options,
vec![],
)
} else {
let console = session.app.console;
console.log(markup! {
<Info>"If this project has not yet been set up with Biome yet, please follow the "<Hyperlink href="https://biomejs.dev/guides/getting-started/">"Getting Started guide"</Hyperlink>" first."</Info>
});
Err(CliDiagnostic::MigrateError(MigrationDiagnostic {
reason: "Biome couldn't find the configuration file".to_string(),
reason: "Biome couldn't find the Biome configuration file.".to_string(),
}))
}
}
10 changes: 10 additions & 0 deletions crates/biome_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,16 @@ pub enum MigrateSubCommand {
/// It attempts to find the files `.prettierrc`/`prettier.json` and `.prettierignore`, and map the Prettier's configuration into Biome's configuration file.
#[bpaf(command)]
Prettier,
/// It attempts to find the ESLint configuration file in the working directory, and update the Biome's configuration file as a result.
#[bpaf(command)]
Eslint {
/// Includes rules inspired from an eslint rule in the migration
#[bpaf(long("include-inspired"))]
include_inspired: bool,
/// Includes nursery rules in the migration
#[bpaf(long("include-nursery"))]
include_nursery: bool,
},
}

impl MigrateSubCommand {
Expand Down
Loading

0 comments on commit 3adb1d9

Please sign in to comment.