Skip to content

Commit

Permalink
feat(biome_service): search for, load .editorconfig files, and merge …
Browse files Browse the repository at this point in the history
…settings
  • Loading branch information
dyc3 committed May 18, 2024
1 parent 2226bae commit 5e1b6f8
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 18 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

30 changes: 28 additions & 2 deletions crates/biome_cli/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ use biome_configuration::{
organize_imports::PartialOrganizeImports, PartialConfiguration, PartialFormatterConfiguration,
PartialLinterConfiguration,
};
use biome_console::{markup, ConsoleExt};
use biome_deserialize::Merge;
use biome_service::configuration::PartialConfigurationExt;
use biome_diagnostics::PrintDiagnostic;
use biome_service::configuration::{load_editorconfig, PartialConfigurationExt};
use biome_service::workspace::RegisterProjectFolderParams;
use biome_service::{
configuration::{load_configuration, LoadedConfiguration},
Expand Down Expand Up @@ -74,13 +76,37 @@ pub(crate) fn check(
session.app.console,
cli_options.verbose,
)?;
let (editorconfig, editorconfig_diagnostics) = {
let search_path = loaded_configuration
.directory_path
.clone()
.unwrap_or_else(|| {
let fs = &session.app.fs;
fs.working_directory().unwrap_or_default()
});
load_editorconfig(&session.app.fs, search_path)?
};
for diagnostic in editorconfig_diagnostics {
session.app.console.error(markup! {
{PrintDiagnostic::simple(&diagnostic)}
})
}

resolve_manifest(&session)?;

let LoadedConfiguration {
configuration: mut fs_configuration,
configuration: biome_configuration,
directory_path: configuration_path,
..
} = loaded_configuration;
let mut fs_configuration = if let Some(editorconfig) = editorconfig {
let mut fs_configuration = editorconfig;
// this makes biome configuration take precedence over editorconfig configuration
fs_configuration.merge_with(biome_configuration);
fs_configuration
} else {
biome_configuration
};

let formatter = fs_configuration
.formatter
Expand Down
29 changes: 27 additions & 2 deletions crates/biome_cli/src/commands/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use biome_console::{markup, ConsoleExt};
use biome_deserialize::Merge;
use biome_diagnostics::PrintDiagnostic;
use biome_service::configuration::{
load_configuration, LoadedConfiguration, PartialConfigurationExt,
load_configuration, load_editorconfig, LoadedConfiguration, PartialConfigurationExt,
};
use biome_service::workspace::{RegisterProjectFolderParams, UpdateSettingsParams};
use std::ffi::OsString;
Expand Down Expand Up @@ -65,12 +65,37 @@ pub(crate) fn format(
session.app.console,
cli_options.verbose,
)?;
let (editorconfig, editorconfig_diagnostics) = {
let search_path = loaded_configuration
.directory_path
.clone()
.unwrap_or_else(|| {
let fs = &session.app.fs;
fs.working_directory().unwrap_or_default()
});
load_editorconfig(&session.app.fs, search_path)?
};
for diagnostic in editorconfig_diagnostics {
session.app.console.error(markup! {
{PrintDiagnostic::simple(&diagnostic)}
})
}

resolve_manifest(&session)?;
let LoadedConfiguration {
mut configuration,
configuration: biome_configuration,
directory_path: configuration_path,
..
} = loaded_configuration;
let mut configuration = if let Some(editorconfig) = editorconfig {
let mut configuration = editorconfig;
// this makes biome configuration take precedence over editorconfig configuration
configuration.merge_with(biome_configuration);
configuration
} else {
biome_configuration
};

// TODO: remove in biome 2.0
let console = &mut *session.app.console;
if let Some(config) = formatter_configuration.as_mut() {
Expand Down
95 changes: 95 additions & 0 deletions crates/biome_cli/tests/cases/editorconfig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::run_cli;
use crate::snap_test::assert_file_contents;
use biome_console::BufferConsole;
use biome_fs::MemoryFileSystem;
use biome_service::DynRef;
use bpaf::Args;
use std::path::Path;

#[test]
fn should_use_editorconfig() {
let mut fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

let editorconfig = Path::new(".editorconfig");
fs.insert(
editorconfig.into(),
r#"
[*]
max_line_length = 300
"#,
);

let test_file = Path::new("test.js");
let contents = r#"console.log("really long string that should cause a break if the line width remains at the default 80 characters");
"#;
fs.insert(test_file.into(), contents);

let result = run_cli(
DynRef::Borrowed(&mut fs),
&mut console,
Args::from(
[
("format"),
("--write"),
test_file.as_os_str().to_str().unwrap(),
]
.as_slice(),
),
);

assert!(result.is_ok(), "run_cli returned {result:?}");

assert_file_contents(&fs, test_file, contents);
}

#[test]
fn should_have_biome_override_editorconfig() {
let mut fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

let editorconfig = Path::new(".editorconfig");
fs.insert(
editorconfig.into(),
r#"
[*]
max_line_length = 100
indent_style = tab
"#,
);
let biomeconfig = Path::new("biome.json");
fs.insert(
biomeconfig.into(),
r#"
{
"formatter": {
"lineWidth": 90
}
}
"#,
);

let test_file = Path::new("test.js");
let contents = r#"console.log(
"really long string that should break if the line width is <=90, but not at 100",
);
"#;
fs.insert(test_file.into(), contents);

let result = run_cli(
DynRef::Borrowed(&mut fs),
&mut console,
Args::from(
[
("format"),
("--write"),
test_file.as_os_str().to_str().unwrap(),
]
.as_slice(),
),
);

assert!(result.is_ok(), "run_cli returned {result:?}");

assert_file_contents(&fs, test_file, contents);
}
1 change: 1 addition & 0 deletions crates/biome_cli/tests/cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod config_extends;
mod config_path;
mod cts_files;
mod diagnostics;
mod editorconfig;
mod handle_astro_files;
mod handle_css_files;
mod handle_svelte_files;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: content
---
## `.editorconfig`

```editorconfig
[*]
max_line_length = 300
```

## `test.js`

```js
console.log("really long string that should cause a break if the line width remains at the default 80 characters");

```

# Emitted Messages

```block
Checked 1 file in <TIME>. No fixes needed.
```
41 changes: 28 additions & 13 deletions crates/biome_configuration/src/editorconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,24 @@
use std::{collections::HashMap, str::FromStr};

use biome_deserialize::StringSet;
use biome_diagnostics::Diagnostic;
use biome_diagnostics::{adapters::IniError, Diagnostic};
use biome_formatter::{LineEnding, LineWidth};
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serialize};

use crate::{
OverrideFormatterConfiguration, OverridePattern, Overrides, PartialConfiguration,
PartialFormatterConfiguration, PlainIndentStyle,
};

pub fn parse_str(s: &str) -> serde_ini::de::Result<EditorConfig> {
pub fn parse_str(s: &str) -> Result<EditorConfig, EditorConfigValidationError> {
// TODO: use serde_path_to_error to emit better parse diagnostics
serde_ini::from_str(s)
serde_ini::from_str(s).map_err(|err| EditorConfigValidationError::ParseFailed {
source: IniError {
message: "Failed to parse ini".to_string(),
source: Some(err),
},
})
}

/// Represents a parsed .editorconfig file, containing only the options that are relevant to biome.
Expand Down Expand Up @@ -121,8 +126,8 @@ impl EditorConfigOptions {
// `insert_final_newline = false` results in formatting behavior that is incompatible with biome
if let Some(false) = self.insert_final_newline {
errors.push(EditorConfigValidationError::Incompatible {
key: "insert_final_newline",
message: "Biome always inserts a final newline.",
key: "insert_final_newline".to_string(),
message: "Biome always inserts a final newline.".to_string(),
});
}
errors
Expand Down Expand Up @@ -173,20 +178,20 @@ where
.map(Some)
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EditorConfigValidationError {
/// Failed to parse the .editorconfig file.
ParseFailed { source: IniError },
/// An option is completely incompatible with biome.
Incompatible {
key: &'static str,
message: &'static str,
},
Incompatible { key: String, message: String },
/// A glob pattern that biome doesn't support.
UnknownGlobPattern { pattern: String },
}

impl Diagnostic for EditorConfigValidationError {
fn severity(&self) -> biome_diagnostics::Severity {
match self {
EditorConfigValidationError::ParseFailed { .. } => biome_diagnostics::Severity::Error,
EditorConfigValidationError::Incompatible { .. } => biome_diagnostics::Severity::Error,
EditorConfigValidationError::UnknownGlobPattern { .. } => {
biome_diagnostics::Severity::Warning
Expand All @@ -195,10 +200,13 @@ impl Diagnostic for EditorConfigValidationError {
}

fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "editorconfig validation error: ")?;
write!(fmt, "Editorconfig validation error: ")?;
match self {
EditorConfigValidationError::ParseFailed { source } => {
write!(fmt, "Failed to parse .editorconfig: {:?}", source)?;
}
EditorConfigValidationError::Incompatible { key, message } => {
write!(fmt, "key '{}' is incompatible with biome: {}", key, message)?;
write!(fmt, "Key '{}' is incompatible with biome: {}", key, message)?;
}
EditorConfigValidationError::UnknownGlobPattern { pattern } => {
write!(
Expand All @@ -210,6 +218,13 @@ impl Diagnostic for EditorConfigValidationError {
}
Ok(())
}

fn source(&self) -> Option<&dyn Diagnostic> {
match self {
EditorConfigValidationError::ParseFailed { source } => Some(source),
_ => None,
}
}
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/biome_diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ bpaf = { workspace = true }
oxc_resolver = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_ini = { workspace = true }
serde_json = { workspace = true }
termcolor = { workspace = true }
unicode-width = { workspace = true }
Expand Down
42 changes: 41 additions & 1 deletion crates/biome_diagnostics/src/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

use std::io;

use biome_console::{fmt, markup};
use biome_console::{
fmt::{self, Display},
markup,
};
use serde::{Deserialize, Serialize};

use crate::{category, Category, Diagnostic, DiagnosticTags};

Expand Down Expand Up @@ -156,3 +160,39 @@ impl Diagnostic for SerdeJsonError {
fmt.write_markup(markup!({ AsConsoleDisplay(&self.error) }))
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IniError {
pub message: String,

#[serde(skip)]
pub source: Option<serde_ini::de::Error>,
}

impl Diagnostic for IniError {
fn category(&self) -> Option<&'static Category> {
Some(category!("configuration"))
}

fn severity(&self) -> crate::Severity {
crate::Severity::Error
}

fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", self.source.as_ref().unwrap())
}

fn message(&self, fmt: &mut fmt::Formatter<'_>) -> std::io::Result<()> {
if let Some(source) = &self.source {
fmt.write_markup(markup!({ AsConsoleDisplay(source) }))
} else {
fmt.write_str(&self.message)
}
}
}

impl Display for IniError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> std::io::Result<()> {
write!(fmt, "{:?}", self.source)
}
}
Loading

0 comments on commit 5e1b6f8

Please sign in to comment.