Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement loading biome.config.js #1825

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
818 changes: 787 additions & 31 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -123,6 +123,7 @@ tests_macros = { path = "./crates/tests_macros" }

# Crates needed in the workspace
bitflags = "2.3.1"
boa_engine = { git = "https://github.com/boa-dev/boa.git", branch = "main" }
bpaf = { version = "0.9.5", features = ["derive"] }
convert_case = "0.6.0"
countme = "3.0.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/cli_options.rs
Expand Up @@ -82,7 +82,7 @@ pub struct CliOptions {

impl CliOptions {
/// Computes the [ConfigurationBasePath] based on the options passed by the user
pub(crate) fn as_configuration_base_path(&self) -> ConfigurationBasePath {
pub(crate) fn to_configuration_base_path(&self) -> ConfigurationBasePath {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the distraction, just renamed this to follow Rust's naming conventions: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv

match self.config_path.as_ref() {
None => ConfigurationBasePath::default(),
Some(path) => ConfigurationBasePath::FromUser(PathBuf::from(path)),
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/commands/check.rs
Expand Up @@ -62,7 +62,7 @@ pub(crate) fn check(
};

let loaded_configuration =
load_configuration(&session.app.fs, cli_options.as_configuration_base_path())?;
load_configuration(&session.app.fs, cli_options.to_configuration_base_path())?;
validate_configuration_diagnostics(
&loaded_configuration,
session.app.console,
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/commands/ci.rs
Expand Up @@ -38,7 +38,7 @@ pub(crate) fn ci(session: CliSession, payload: CiCommandPayload) -> Result<(), C
setup_cli_subscriber(cli_options.log_level.clone(), cli_options.log_kind.clone());

let loaded_configuration =
load_configuration(&session.app.fs, cli_options.as_configuration_base_path())?;
load_configuration(&session.app.fs, cli_options.to_configuration_base_path())?;

validate_configuration_diagnostics(
&loaded_configuration,
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/commands/format.rs
Expand Up @@ -54,7 +54,7 @@ pub(crate) fn format(
setup_cli_subscriber(cli_options.log_level.clone(), cli_options.log_kind.clone());

let loaded_configuration =
load_configuration(&session.app.fs, cli_options.as_configuration_base_path())?;
load_configuration(&session.app.fs, cli_options.to_configuration_base_path())?;
validate_configuration_diagnostics(
&loaded_configuration,
session.app.console,
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_cli/src/commands/lint.rs
Expand Up @@ -56,7 +56,7 @@ pub(crate) fn lint(session: CliSession, payload: LintCommandPayload) -> Result<(
};

let loaded_configuration =
load_configuration(&session.app.fs, cli_options.as_configuration_base_path())?;
load_configuration(&session.app.fs, cli_options.to_configuration_base_path())?;
validate_configuration_diagnostics(
&loaded_configuration,
session.app.console,
Expand Down
34 changes: 33 additions & 1 deletion crates/biome_cli/tests/commands/format.rs
@@ -1,7 +1,8 @@
use crate::configs::{
CONFIG_DISABLED_FORMATTER, CONFIG_FILE_SIZE_LIMIT, CONFIG_FORMAT,
CONFIG_FORMATTER_AND_FILES_IGNORE, CONFIG_FORMATTER_IGNORED_DIRECTORIES,
CONFIG_FORMATTER_IGNORED_FILES, CONFIG_FORMAT_JSONC, CONFIG_ISSUE_3175_1, CONFIG_ISSUE_3175_2,
CONFIG_FORMATTER_IGNORED_FILES, CONFIG_FORMAT_JS, CONFIG_FORMAT_JSONC, CONFIG_ISSUE_3175_1,
CONFIG_ISSUE_3175_2,
};
use crate::snap_test::{assert_file_contents, markup_to_string, SnapshotPayload};
use crate::{
Expand Down Expand Up @@ -1177,6 +1178,37 @@ fn format_with_configuration() {
));
}

#[test]
fn format_with_js_configuration() {
let mut console = BufferConsole::default();
let mut fs = MemoryFileSystem::default();
fs.set_working_directory("/");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boa's default module loader didn't like it if there's no working directory, so I had to add one to the memory filesystem. Note the main module is currently loaded from a string that is read from disk by our config finder, so I expect this might need a little bit more before loading external modules will work through the memory file system.


let file_path = Path::new("/biome.config.js");
fs.insert(file_path.into(), CONFIG_FORMAT_JS.as_bytes());

let file_path = Path::new("/file.js");
fs.insert(file_path.into(), CUSTOM_FORMAT_BEFORE.as_bytes());

let result = run_cli(
DynRef::Borrowed(&mut fs),
&mut console,
Args::from(&["format", "/file.js", "--write"]),
);

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

assert_file_contents(&fs, file_path, CUSTOM_FORMAT_AFTER);

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"format_with_js_configuration",
fs,
console,
result,
));
}

#[test]
fn format_is_disabled() {
let mut fs = MemoryFileSystem::default();
Expand Down
10 changes: 10 additions & 0 deletions crates/biome_cli/tests/configs.rs
Expand Up @@ -8,6 +8,16 @@ pub const CONFIG_FORMAT: &str = r#"{
}
"#;

pub const CONFIG_FORMAT_JS: &str = r#"export const config = {
formatter: {
enabled: true,
lineWidth: 160,
indentStyle: "space",
indentSize: 6,
}
};
"#;

pub const CONFIG_FORMAT_JSONC: &str = r#"{
// Formatting options
"formatter": {
Expand Down
@@ -0,0 +1,34 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: content
---
## `/biome.config.js`

```js
export const config = {
formatter: {
enabled: true,
lineWidth: 160,
indentStyle: "space",
indentSize: 6,
}
};

```

## `/file.js`

```js
function f() {
return { something };
}

```

# Emitted Messages

```block
Formatted 1 file(s) in <TIME>
```


6 changes: 6 additions & 0 deletions crates/biome_console/src/markup.rs
Expand Up @@ -268,3 +268,9 @@ impl Debug for MarkupBuf {
Ok(())
}
}

impl From<Markup<'_>> for MarkupBuf {
fn from(value: Markup<'_>) -> Self {
value.to_owned()
}
}
1 change: 1 addition & 0 deletions crates/biome_deserialize/Cargo.toml
Expand Up @@ -18,6 +18,7 @@ biome_json_parser = { workspace = true }
biome_json_syntax = { workspace = true }
biome_rowan = { workspace = true }
bitflags = { workspace = true }
boa_engine = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
schemars = { workspace = true, optional = true }
serde = { workspace = true }
Expand Down
68 changes: 68 additions & 0 deletions crates/biome_deserialize/src/js.rs
@@ -0,0 +1,68 @@
//! Implementation of [DeserializableValue] for the JS engine.
use crate::{DeserializationDiagnostic, Deserialized};
use biome_console::markup;
use biome_diagnostics::Error;
use boa_engine::{Context, JsValue};
use serde::de::DeserializeOwned;

/// Attempts to interpret a value from the JS runtime and deserialize it following the semantics
/// defined by JSON. As such, JS types that cannot be serialized to JSON, such as functions or
/// Promises are not supported by this deserializer.
///
/// The data structures that need to be deserialized have to implement the [Deserializable] trait.
/// For most data structures, this can be achieved using the
/// [biome_deserialize_macros::Deserializable] derive.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this comment is not entirely correct, since the function uses Serde traits for now, instead of our Deserializable. If we agree on picking Boa, I would optimize this function to use Deserializable again so we can go straight from boa_engine::JsValue to our own types, instead of using serde_json::Value as intermediary.

///
/// `name` corresponds to the name used in a diagnostic to designate the deserialized value.
///
/// ## Examples
///
/// ```
/// use biome_deserialize::js::deserialize_from_js_value;
/// use boa_engine::{Context, Source};
/// use serde::Deserialize;
///
/// #[derive(Debug, Default, Deserialize, Eq, PartialEq)]
/// struct NewConfiguration {
/// lorem: String
/// }
///
/// let source = r#"({ lorem: "ipsum" })"#;
///
/// let mut context = Context::default();
/// let value = context.eval(Source::from_bytes(source)).unwrap();
///
/// let deserialized = deserialize_from_js_value::<NewConfiguration>(&mut context, value);
/// assert!(!deserialized.has_errors());
/// assert_eq!(deserialized.into_deserialized().unwrap(), NewConfiguration { lorem: "ipsum".to_string() });
/// ```
pub fn deserialize_from_js_value<Output: DeserializeOwned>(
context: &mut Context,
value: JsValue,
) -> Deserialized<Output> {
let mut diagnostics: Vec<DeserializationDiagnostic> = Vec::new();

// TODO: We probably want to improve our diagnostics somewhat.
let deserialized = match value.to_json(context) {
Ok(json) => match serde_json::from_value(json) {
Ok(value) => Some(value),
Err(err) => {
diagnostics.push(DeserializationDiagnostic::new(
markup!("Value could not be exported to JSON: "{err.to_string()}),
));
None
}
},
Err(err) => {
diagnostics.push(DeserializationDiagnostic::new(
markup!("Value could not be parsed as JSON: "{err.to_string()}),
));
None
}
};

Deserialized {
diagnostics: diagnostics.into_iter().map(Error::from).collect::<Vec<_>>(),
deserialized,
}
}
1 change: 1 addition & 0 deletions crates/biome_deserialize/src/lib.rs
Expand Up @@ -31,6 +31,7 @@
//!
mod diagnostics;
mod impls;
pub mod js;
pub mod json;
mod merge;
pub mod string_set;
Expand Down
20 changes: 15 additions & 5 deletions crates/biome_fs/src/fs.rs
Expand Up @@ -20,18 +20,28 @@ pub const ROME_JSON: &str = "rome.json";
pub struct ConfigName;

impl ConfigName {
const BIOME_JSON: [&'static str; 2] = ["biome.json", "biome.jsonc"];
const BIOME_JSON: &'static str = "biome.json";
const BIOME_JSONC: &'static str = "biome.jsonc";
const BIOME_CONFIG_JS: &'static str = "biome.config.js";

pub const fn biome_json() -> &'static str {
Self::BIOME_JSON[0]
Self::BIOME_JSON
}

pub const fn biome_jsonc() -> &'static str {
Self::BIOME_JSON[1]
Self::BIOME_JSONC
}

pub const fn file_names() -> [&'static str; 2] {
Self::BIOME_JSON
pub const fn biome_config_js() -> &'static str {
Self::BIOME_CONFIG_JS
}

pub const fn file_names() -> [&'static str; 3] {
[
&Self::BIOME_JSON,
&Self::BIOME_JSONC,
&Self::BIOME_CONFIG_JS,
]
}
}

Expand Down
8 changes: 7 additions & 1 deletion crates/biome_fs/src/fs/memory.rs
Expand Up @@ -29,6 +29,7 @@ pub struct MemoryFileSystem {
errors: FxHashMap<PathBuf, ErrorEntry>,
allow_write: bool,
on_get_changed_files: OnGetChangedFiles,
working_directory: Option<PathBuf>,
}

impl Default for MemoryFileSystem {
Expand All @@ -38,6 +39,7 @@ impl Default for MemoryFileSystem {
errors: Default::default(),
allow_write: true,
on_get_changed_files: None,
working_directory: None,
}
}
}
Expand Down Expand Up @@ -108,6 +110,10 @@ impl MemoryFileSystem {
) {
self.on_get_changed_files = Some(Arc::new(AssertUnwindSafe(Mutex::new(Some(cfn)))));
}

pub fn set_working_directory(&mut self, path: impl Into<PathBuf>) {
self.working_directory = Some(path.into());
}
}

impl FileSystem for MemoryFileSystem {
Expand Down Expand Up @@ -177,7 +183,7 @@ impl FileSystem for MemoryFileSystem {
}

fn working_directory(&self) -> Option<PathBuf> {
None
self.working_directory.clone()
}

fn path_exists(&self, path: &Path) -> bool {
Expand Down
1 change: 1 addition & 0 deletions crates/biome_service/Cargo.toml
Expand Up @@ -39,6 +39,7 @@ biome_parser = { workspace = true }
biome_project = { workspace = true }
biome_rowan = { workspace = true, features = ["serde"] }
biome_text_edit = { workspace = true }
boa_engine = { workspace = true }
bpaf = { workspace = true }
dashmap = { workspace = true }
ignore = { workspace = true }
Expand Down