Skip to content

Commit

Permalink
refactor(migrate/eslint): improve eslint ignore support (#2897)
Browse files Browse the repository at this point in the history
  • Loading branch information
Conaclos authored May 17, 2024
1 parent b1e7be6 commit beabe1b
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 35 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
+ biome check # You can run the command without the path
```

- `biome migrate eslint` now tries to convert ESLint ignore patterns into Biome ignore patterns.

ESLint uses [gitignore patterns](https://git-scm.com/docs/gitignore#_pattern_format).
Biome now tries to convert these patterns into Biome ignore patterns.

For example, the gitignore pattern `/src` is a relative path to the file in which it appears.
Biome now recognizes this and translates this pattern to `./src`.

Contributed by @Conaclos

- `biome migrate eslint` now supports the `eslintIgnore` field in `package.json`.

ESLint allows the use of `package.json` as an ESLint configuration file.
ESLint supports two fields: `eslintConfig` and `eslintIgnore`.
Biome only supported the former. It now supports both.

Contributed by @Conaclos

### Configuration

#### New features
Expand Down
14 changes: 9 additions & 5 deletions crates/biome_cli/src/execute/migrate/eslint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use biome_json_parser::JsonParserOptions;
use biome_service::DynRef;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::vec;

use crate::diagnostics::MigrationDiagnostic;
use crate::CliDiagnostic;
Expand Down Expand Up @@ -68,11 +67,11 @@ pub(crate) const IGNORE_FILE: &str = ".eslintignore";
/// when no configuration file is found in the working directory.
///
/// Deserialization errors are reported using `console`.
/// Other errors (File Not found, unspported config format, ...) are directly returned.
/// Other errors (File Not found, unsupported config format, ...) are directly returned.
///
/// We extract the ESLint configuration from a JavaScript file, by invoking `node`.
///
/// The `extends` field is recusively resolved.
/// The `extends` field is recursively resolved.
pub(crate) fn read_eslint_config(
fs: &DynRef<'_, dyn FileSystem>,
console: &mut dyn Console,
Expand Down Expand Up @@ -160,14 +159,19 @@ fn load_legacy_config_data(
let mut content = String::new();
file.read_to_string(&mut content)?;
if path.file_name().is_some_and(|name| name == PACKAGE_JSON) {
let (deserialized, _) = deserialize_from_json_str::<eslint_eslint::EslintPackageJson>(
let (deserialized, diagnostics) = deserialize_from_json_str::<eslint_eslint::EslintPackageJson>(
&content,
JsonParserOptions::default()
.with_allow_trailing_commas()
.with_allow_comments(),
"",
).consume();
(deserialized.and_then(|packagejson| packagejson.eslint_config), vec![])
(deserialized.and_then(|mut packagejson| {
if let Some(eslint_config) = packagejson.eslint_config.as_mut() {
eslint_config.ignore_patterns.extend(packagejson.eslint_ignore);
}
packagejson.eslint_config
}), diagnostics)
} else {
deserialize_from_json_str::<eslint_eslint::LegacyConfigData>(
&content,
Expand Down
32 changes: 29 additions & 3 deletions crates/biome_cli/src/execute/migrate/eslint_eslint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::ops::DerefMut;
use std::vec;
use std::{any::TypeId, marker::PhantomData, ops::Deref};

use super::{eslint_jsxa11y, eslint_typescript, eslint_unicorn};
use super::{eslint_jsxa11y, eslint_typescript, eslint_unicorn, ignorefile};

/// This modules includes implementations for deserializing an eslint configuration.
///
Expand Down Expand Up @@ -92,6 +92,7 @@ impl Merge for FlatLanguageOptions {
#[deserializable(unknown_fields = "allow")]
pub(crate) struct EslintPackageJson {
pub(crate) eslint_config: Option<LegacyConfigData>,
pub(crate) eslint_ignore: Vec<IgnorePattern>,
}

#[derive(Debug, Default, Deserializable)]
Expand All @@ -100,7 +101,7 @@ pub(crate) struct LegacyConfigData {
pub(crate) extends: ShorthandVec<String>,
pub(crate) globals: Globals,
/// The glob patterns that ignore to lint.
pub(crate) ignore_patterns: ShorthandVec<String>,
pub(crate) ignore_patterns: ShorthandVec<IgnorePattern>,
/// The parser options.
pub(crate) rules: Rules,
pub(crate) overrides: Vec<OverrideConfigData>,
Expand All @@ -115,6 +116,31 @@ impl Merge for LegacyConfigData {
}
}

#[derive(Debug, Default)]
pub(crate) struct IgnorePattern(pub(crate) String);
impl Deref for IgnorePattern {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl biome_deserialize::Deserializable for IgnorePattern {
fn deserialize(
value: &impl DeserializableValue,
name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<Self> {
let s = biome_deserialize::Text::deserialize(value, name, diagnostics)?;
match ignorefile::convert_pattern(s.text()) {
Ok(pattern) => Some(Self(pattern)),
Err(msg) => {
diagnostics.push(DeserializationDiagnostic::new(msg).with_range(value.range()));
None
}
}
}
}

//? ESLint plugins export metadata in their main export.
/// This includes presets in the `configs` field.
#[derive(Debug, Default, Deserializable)]
Expand Down Expand Up @@ -544,7 +570,7 @@ pub(crate) enum Rule {
TypeScriptArrayType(RuleConf<eslint_typescript::ArrayTypeOptions>),
TypeScriptNamingConvention(RuleConf<Box<eslint_typescript::NamingConventionSelection>>),
UnicornFilenameCase(RuleConf<eslint_unicorn::FilenameCaseOptions>),
// If ypu add new variants, dont forget to update [Rules::deserialize].
// If you add new variants, don't forget to update [Rules::deserialize].
}
impl Rule {
pub(crate) fn name(&self) -> Cow<'static, str> {
Expand Down
6 changes: 5 additions & 1 deletion crates/biome_cli/src/execute/migrate/eslint_to_biome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ impl eslint_eslint::LegacyConfigData {
rules.recommended = Some(false);
linter.rules = Some(rules);
if !self.ignore_patterns.is_empty() {
let ignore = self.ignore_patterns.into_iter().collect::<StringSet>();
let ignore = self
.ignore_patterns
.into_iter()
.map(|p| p.0)
.collect::<StringSet>();
linter.ignore = Some(ignore);
}
if !self.overrides.is_empty() {
Expand Down
46 changes: 29 additions & 17 deletions crates/biome_cli/src/execute/migrate/ignorefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,16 @@ impl IgnorePatterns {
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with('!') {
has_negated_patterns = true;
// Skip negated patterns because we don't support them.
continue;
match convert_pattern(line) {
Ok(pattern) => {
patterns.insert(pattern);
}
Err(_) => {
has_negated_patterns = true;
// Skip negated patterns because we don't support them.
continue;
}
}
let pattern = if let Some(stripped_line) = line.strip_prefix('/') {
// Patterns tha tstarts with `/` are relative to the ignore file
format!("./{}", stripped_line)
} else if line.find('/').is_some_and(|index| index < (line.len() - 1))
|| line == "**"
|| line == "**/"
{
// Patterns that includes at least one `/` in the middle are relatives paths
line.to_string()
} else {
format!("**/{line}")
};
patterns.insert(pattern);
}
IgnorePatterns {
patterns,
Expand All @@ -61,6 +53,26 @@ impl IgnorePatterns {
}
}

pub(crate) fn convert_pattern(line: &str) -> Result<String, &'static str> {
if line.starts_with('!') {
// Skip negated patterns because we don't support them.
return Err("Negated patterns are not supported.");
}
let result = if let Some(stripped_line) = line.strip_prefix('/') {
// Patterns tha tstarts with `/` are relative to the ignore file
format!("./{}", stripped_line)
} else if line.find('/').is_some_and(|index| index < (line.len() - 1))
|| line == "**"
|| line == "**/"
{
// Patterns that includes at least one `/` in the middle are relatives paths
line.to_string()
} else {
format!("**/{line}")
};
Ok(result)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 3 additions & 2 deletions crates/biome_cli/tests/commands/migrate_eslint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ fn migrate_eslint_config_packagejson() {
"rules": {
"eqeqeq": "warn"
}
}
},
"eslintIgnore": ["/dist", "test", "!test/x/**"]
}"#;

let mut fs = MemoryFileSystem::default();
Expand Down Expand Up @@ -450,7 +451,7 @@ test/main.js
fn migrate_eslintignore_and_ignore_patterns() {
let biomejson = r#"{ "linter": { "enabled": true } }"#;
let eslintrc = r#"{
"ignorePatterns": ["**/*.spec.js"],
"ignorePatterns": ["**/*.spec.js", "!x.spec.js", "/dist"],
"rules": { "eqeqeq": "off" }
}"#;
let eslintignore = r#"*.test.js"#;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,27 @@ expression: content
"rules": {
"eqeqeq": "warn"
}
}
},
"eslintIgnore": ["/dist", "test", "!test/x/**"]
}
```

# Emitted Messages

```block
package.json:9:43 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Negated patterns are not supported.
7 │ }
8 │ },
> 9 │ "eslintIgnore": ["/dist", "test", "!test/x/**"]
│ ^^^^^^^^^^^^
10 │ }
```

```block
biome.json migrate ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Expand All @@ -36,10 +51,11 @@ biome.json migrate ━━━━━━━━━━━━━━━━━━━━
4 │ + → → "rules":·{
5 │ + → → → "recommended":·false,
6 │ + → → → "suspicious":·{·"noDoubleEquals":·"warn"·}
7 │ + → → }
8 │ + → }
9 │ + }
10 │ +
7 │ + → → },
8 │ + → → "ignore":·["./dist",·"**/test"]
9 │ + → }
10 │ + }
11 │ +
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,27 @@ expression: content

```json
{
"ignorePatterns": ["**/*.spec.js"],
"ignorePatterns": ["**/*.spec.js", "!x.spec.js", "/dist"],
"rules": { "eqeqeq": "off" }
}
```

# Emitted Messages

```block
.eslintrc.json:2:44 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
× Negated patterns are not supported.
1 │ {
> 2 │ "ignorePatterns": ["**/*.spec.js", "!x.spec.js", "/dist"],
│ ^^^^^^^^^^^^
3 │ "rules": { "eqeqeq": "off" }
4 │ }
```

```block
biome.json migrate ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Expand All @@ -38,7 +52,7 @@ biome.json migrate ━━━━━━━━━━━━━━━━━━━━
5 │ + → → → "recommended":·false,
6 │ + → → → "suspicious":·{·"noDoubleEquals":·"off"·}
7 │ + → → },
8 │ + → → "ignore":·["**/*.spec.js",·"**/*.test.js"]
8 │ + → → "ignore":·["**/*.spec.js",·"./dist",·"**/*.test.js"]
9 │ + → }
10 │ + }
11 │ +
Expand Down

0 comments on commit beabe1b

Please sign in to comment.