diff --git a/.changeset/css-parse-modules-flag.md b/.changeset/css-parse-modules-flag.md new file mode 100644 index 000000000000..4b4996043911 --- /dev/null +++ b/.changeset/css-parse-modules-flag.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": minor +--- + +Added `--css-parse-css-modules` CLI flag to control whether CSS Modules syntax is enabled. + +You can now enable or disable CSS Modules parsing directly from the command line: + +```shell +biome check --css-parse-css-modules=true file.module.css +biome format --css-parse-css-modules=true file.module.css +biome lint --css-parse-css-modules=true file.module.css +biome ci --css-parse-css-modules=true file.module.css +``` diff --git a/.changeset/css-parse-tailwind-flag.md b/.changeset/css-parse-tailwind-flag.md new file mode 100644 index 000000000000..b2464c9049c9 --- /dev/null +++ b/.changeset/css-parse-tailwind-flag.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": minor +--- + +Added `--css-parse-tailwind-directives` CLI flag to control whether Tailwind CSS 4.0 directives and functions are enabled. + +You can now enable or disable Tailwind CSS 4.0 directive parsing directly from the command line: + +```shell +biome check --css-parse-tailwind-directives=true file.css +biome format --css-parse-tailwind-directives=true file.css +biome lint --css-parse-tailwind-directives=true file.css +biome ci --css-parse-tailwind-directives=true file.css +``` diff --git a/.changeset/json-parse-comments-flag.md b/.changeset/json-parse-comments-flag.md new file mode 100644 index 000000000000..d2f3bf48608a --- /dev/null +++ b/.changeset/json-parse-comments-flag.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": minor +--- + +Added `--json-parse-allow-comments` CLI flag to control whether comments are allowed in JSON files. + +You can now enable or disable comment parsing in JSON files directly from the command line: + +```shell +biome check --json-parse-allow-comments=true file.json +biome format --json-parse-allow-comments=true file.json +biome lint --json-parse-allow-comments=true file.json +biome ci --json-parse-allow-comments=true file.json +``` diff --git a/.changeset/json-parse-trailing-commas-flag.md b/.changeset/json-parse-trailing-commas-flag.md new file mode 100644 index 000000000000..1135b8771ecf --- /dev/null +++ b/.changeset/json-parse-trailing-commas-flag.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": minor +--- + +Added `--json-parse-allow-trailing-commas` CLI flag to control whether trailing commas are allowed in JSON files. + +You can now enable or disable trailing comma parsing in JSON files directly from the command line: + +```shell +biome check --json-parse-allow-trailing-commas=true file.json +biome format --json-parse-allow-trailing-commas=true file.json +biome lint --json-parse-allow-trailing-commas=true file.json +biome ci --json-parse-allow-trailing-commas=true file.json +``` diff --git a/crates/biome_cli/src/commands/check.rs b/crates/biome_cli/src/commands/check.rs index 56e4e0c56530..0b485256ca9d 100644 --- a/crates/biome_cli/src/commands/check.rs +++ b/crates/biome_cli/src/commands/check.rs @@ -4,7 +4,9 @@ use crate::commands::{CommandRunner, get_files_to_process_with_cli_options}; use crate::{CliDiagnostic, Execution, TraversalMode}; use biome_configuration::analyzer::LinterEnabled; use biome_configuration::analyzer::assist::{AssistConfiguration, AssistEnabled}; -use biome_configuration::formatter::FormatterEnabled; +use biome_configuration::css::CssParserConfiguration; +use biome_configuration::formatter::{FormatWithErrorsEnabled, FormatterEnabled}; +use biome_configuration::json::JsonParserConfiguration; use biome_configuration::{Configuration, FormatterConfiguration, LinterConfiguration}; use biome_console::Console; use biome_deserialize::Merge; @@ -26,6 +28,9 @@ pub(crate) struct CheckCommandPayload { pub(crate) staged: bool, pub(crate) changed: bool, pub(crate) since: Option, + pub(crate) format_with_errors: Option, + pub(crate) json_parser: Option, + pub(crate) css_parser: Option, } impl LoadEditorConfig for CheckCommandPayload { @@ -61,6 +66,9 @@ impl CommandRunner for CheckCommandPayload { if self.formatter_enabled.is_some() { formatter.enabled = self.formatter_enabled; } + if self.format_with_errors.is_some() { + formatter.format_with_errors = self.format_with_errors; + } let linter = configuration .linter @@ -78,6 +86,16 @@ impl CommandRunner for CheckCommandPayload { assist.enabled = self.assist_enabled; } + let css = configuration.css.get_or_insert_with(Default::default); + if self.css_parser.is_some() { + css.parser.merge_with(self.css_parser.clone()); + } + + let json = configuration.json.get_or_insert_with(Default::default); + if self.json_parser.is_some() { + json.parser.merge_with(self.json_parser.clone()) + } + if let Some(mut conf) = self.configuration.clone() { if let Some(linter) = conf.linter.as_mut() { // Don't overwrite rules from the CLI configuration. diff --git a/crates/biome_cli/src/commands/ci.rs b/crates/biome_cli/src/commands/ci.rs index 5a791068a78b..41110d46a70e 100644 --- a/crates/biome_cli/src/commands/ci.rs +++ b/crates/biome_cli/src/commands/ci.rs @@ -4,8 +4,12 @@ use crate::commands::{CommandRunner, LoadEditorConfig}; use crate::{CliDiagnostic, Execution}; use biome_configuration::analyzer::LinterEnabled; use biome_configuration::analyzer::assist::{AssistConfiguration, AssistEnabled}; -use biome_configuration::formatter::FormatterEnabled; -use biome_configuration::{Configuration, FormatterConfiguration, LinterConfiguration}; +use biome_configuration::css::CssParserConfiguration; +use biome_configuration::formatter::{FormatWithErrorsEnabled, FormatterEnabled}; +use biome_configuration::json::JsonParserConfiguration; +use biome_configuration::{ + Configuration, CssConfiguration, FormatterConfiguration, JsonConfiguration, LinterConfiguration, +}; use biome_console::Console; use biome_deserialize::Merge; use biome_fs::FileSystem; @@ -22,6 +26,9 @@ pub(crate) struct CiCommandPayload { pub(crate) configuration: Option, pub(crate) changed: bool, pub(crate) since: Option, + pub(crate) format_with_errors: Option, + pub(crate) json_parser: Option, + pub(crate) css_parser: Option, } impl LoadEditorConfig for CiCommandPayload { @@ -56,6 +63,7 @@ impl CommandRunner for CiCommandPayload { if self.formatter_enabled.is_some() { formatter.enabled = self.formatter_enabled; + formatter.format_with_errors = self.format_with_errors; } let linter = configuration @@ -66,6 +74,20 @@ impl CommandRunner for CiCommandPayload { linter.enabled = self.linter_enabled; } + let json = configuration + .json + .get_or_insert_with(JsonConfiguration::default); + if self.json_parser.is_some() { + json.parser.clone_from(&self.json_parser) + } + + let css = configuration + .css + .get_or_insert_with(CssConfiguration::default); + if self.css_parser.is_some() { + css.parser.clone_from(&self.css_parser); + } + let assist = configuration .assist .get_or_insert_with(AssistConfiguration::default); diff --git a/crates/biome_cli/src/commands/format.rs b/crates/biome_cli/src/commands/format.rs index 1ecebbb692ed..51578096a9f5 100644 --- a/crates/biome_cli/src/commands/format.rs +++ b/crates/biome_cli/src/commands/format.rs @@ -1,11 +1,11 @@ use crate::cli_options::CliOptions; use crate::commands::{CommandRunner, LoadEditorConfig, get_files_to_process_with_cli_options}; use crate::{CliDiagnostic, Execution, TraversalMode}; -use biome_configuration::css::CssFormatterConfiguration; +use biome_configuration::css::{CssFormatterConfiguration, CssParserConfiguration}; use biome_configuration::graphql::GraphqlFormatterConfiguration; use biome_configuration::html::HtmlFormatterConfiguration; use biome_configuration::javascript::JsFormatterConfiguration; -use biome_configuration::json::JsonFormatterConfiguration; +use biome_configuration::json::{JsonFormatterConfiguration, JsonParserConfiguration}; use biome_configuration::vcs::VcsConfiguration; use biome_configuration::{Configuration, FilesConfiguration, FormatterConfiguration}; use biome_console::Console; @@ -31,6 +31,8 @@ pub(crate) struct FormatCommandPayload { pub(crate) staged: bool, pub(crate) changed: bool, pub(crate) since: Option, + pub(crate) json_parser: Option, + pub(crate) css_parser: Option, } impl LoadEditorConfig for FormatCommandPayload { @@ -73,10 +75,15 @@ impl CommandRunner for FormatCommandPayload { formatter.enabled = Some(true.into()); } + let css = configuration.css.get_or_insert_with(Default::default); if self.css_formatter.is_some() { - let css = configuration.css.get_or_insert_with(Default::default); css.formatter.merge_with(self.css_formatter.clone()); } + + if self.css_parser.is_some() { + css.parser.merge_with(self.css_parser.clone()); + } + if self.graphql_formatter.is_some() { let graphql = configuration.graphql.get_or_insert_with(Default::default); graphql.formatter.merge_with(self.graphql_formatter.clone()); @@ -94,10 +101,14 @@ impl CommandRunner for FormatCommandPayload { .formatter .merge_with(self.javascript_formatter.clone()); } + let json = configuration.json.get_or_insert_with(Default::default); + if self.json_formatter.is_some() { - let json = configuration.json.get_or_insert_with(Default::default); json.formatter.merge_with(self.json_formatter.clone()); } + if self.json_parser.is_some() { + json.parser.merge_with(self.json_parser.clone()) + } configuration .files diff --git a/crates/biome_cli/src/commands/lint.rs b/crates/biome_cli/src/commands/lint.rs index 1992421aaef7..2526326eeb39 100644 --- a/crates/biome_cli/src/commands/lint.rs +++ b/crates/biome_cli/src/commands/lint.rs @@ -3,10 +3,10 @@ use crate::cli_options::CliOptions; use crate::commands::{CommandRunner, get_files_to_process_with_cli_options}; use crate::{CliDiagnostic, Execution, TraversalMode}; use biome_configuration::analyzer::AnalyzerSelector; -use biome_configuration::css::CssLinterConfiguration; +use biome_configuration::css::{CssLinterConfiguration, CssParserConfiguration}; use biome_configuration::graphql::GraphqlLinterConfiguration; use biome_configuration::javascript::JsLinterConfiguration; -use biome_configuration::json::JsonLinterConfiguration; +use biome_configuration::json::{JsonLinterConfiguration, JsonParserConfiguration}; use biome_configuration::vcs::VcsConfiguration; use biome_configuration::{Configuration, FilesConfiguration, LinterConfiguration}; use biome_console::Console; @@ -36,6 +36,8 @@ pub(crate) struct LintCommandPayload { pub(crate) json_linter: Option, pub(crate) css_linter: Option, pub(crate) graphql_linter: Option, + pub(crate) json_parser: Option, + pub(crate) css_parser: Option, } impl CommandRunner for LintCommandPayload { @@ -71,10 +73,13 @@ impl CommandRunner for LintCommandPayload { ..Default::default() }); + let css = fs_configuration.css.get_or_insert_with(Default::default); if self.css_linter.is_some() { - let css = fs_configuration.css.get_or_insert_with(Default::default); css.linter.merge_with(self.css_linter.clone()); } + if self.css_parser.is_some() { + css.parser.merge_with(self.css_parser.clone()); + } if self.graphql_linter.is_some() { let graphql = fs_configuration @@ -88,10 +93,13 @@ impl CommandRunner for LintCommandPayload { .get_or_insert_with(Default::default); javascript.linter.merge_with(self.javascript_linter.clone()); } + let json = fs_configuration.json.get_or_insert_with(Default::default); if self.json_linter.is_some() { - let json = fs_configuration.json.get_or_insert_with(Default::default); json.linter.merge_with(self.json_linter.clone()); } + if self.json_parser.is_some() { + json.parser.merge_with(self.json_parser.clone()); + } Ok(fs_configuration) } diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index 083c3f3e3f8d..9ebad78f51b3 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -9,21 +9,31 @@ use crate::{ }; use biome_configuration::analyzer::assist::AssistEnabled; use biome_configuration::analyzer::{AnalyzerSelector, LinterEnabled}; -use biome_configuration::css::{CssFormatterConfiguration, CssLinterConfiguration}; -use biome_configuration::formatter::FormatterEnabled; +use biome_configuration::css::{ + CssFormatterConfiguration, CssLinterConfiguration, CssParserConfiguration, +}; +use biome_configuration::formatter::{FormatWithErrorsEnabled, FormatterEnabled}; use biome_configuration::graphql::{GraphqlFormatterConfiguration, GraphqlLinterConfiguration}; use biome_configuration::html::{HtmlFormatterConfiguration, html_formatter_configuration}; use biome_configuration::javascript::{JsFormatterConfiguration, JsLinterConfiguration}; -use biome_configuration::json::{JsonFormatterConfiguration, JsonLinterConfiguration}; +use biome_configuration::json::{ + JsonFormatterConfiguration, JsonLinterConfiguration, JsonParserConfiguration, +}; use biome_configuration::vcs::VcsConfiguration; use biome_configuration::{BiomeDiagnostic, Configuration}; use biome_configuration::{ FilesConfiguration, FormatterConfiguration, LinterConfiguration, configuration, - css::css_formatter_configuration, css::css_linter_configuration, files_configuration, - formatter_configuration, graphql::graphql_formatter_configuration, - graphql::graphql_linter_configuration, javascript::js_formatter_configuration, - javascript::js_linter_configuration, json::json_formatter_configuration, - json::json_linter_configuration, linter_configuration, vcs::vcs_configuration, + css::{css_formatter_configuration, css_linter_configuration, css_parser_configuration}, + files_configuration, formatter_configuration, + graphql::graphql_formatter_configuration, + graphql::graphql_linter_configuration, + javascript::js_formatter_configuration, + javascript::js_linter_configuration, + json::json_formatter_configuration, + json::json_linter_configuration, + json::json_parser_configuration, + linter_configuration, + vcs::vcs_configuration, }; use biome_console::{Console, ConsoleExt, markup}; use biome_deserialize::Merge; @@ -146,6 +156,17 @@ pub enum BiomeCommand { #[bpaf(long("enforce-assist"), argument("true|false"), fallback(true))] enforce_assist: bool, + /// Whether formatting should be allowed to proceed if a given file + /// has syntax errors + #[bpaf(long("format-with-errors"), argument("true|false"))] + format_with_errors: Option, + + #[bpaf(external(json_parser_configuration), optional, hide_usage)] + json_parser: Option, + + #[bpaf(external(css_parser_configuration), optional, hide_usage, hide)] + css_parser: Option, + #[bpaf(external(configuration), hide_usage, optional)] configuration: Option, #[bpaf(external, hide_usage)] @@ -209,6 +230,12 @@ pub enum BiomeCommand { #[bpaf(long("reason"), argument("STRING"))] suppression_reason: Option, + #[bpaf(external(json_parser_configuration), optional, hide_usage)] + json_parser: Option, + + #[bpaf(external(css_parser_configuration), optional, hide_usage, hide)] + css_parser: Option, + #[bpaf(external(linter_configuration), hide_usage, optional)] linter_configuration: Option, @@ -294,12 +321,18 @@ pub enum BiomeCommand { #[bpaf(external(json_formatter_configuration), optional, hide_usage)] json_formatter: Option, - #[bpaf(external(css_formatter_configuration), optional, hide_usage, hide)] - css_formatter: Option, + #[bpaf(external(json_parser_configuration), optional, hide_usage)] + json_parser: Option, + + #[bpaf(external(css_parser_configuration), optional, hide_usage, hide)] + css_parser: Option, #[bpaf(external(graphql_formatter_configuration), optional, hide_usage, hide)] graphql_formatter: Option, + #[bpaf(external(css_formatter_configuration), optional, hide_usage, hide)] + css_formatter: Option, + #[bpaf(external(html_formatter_configuration), optional, hide_usage, hide)] html_formatter: Option, @@ -365,6 +398,17 @@ pub enum BiomeCommand { #[bpaf(long("assist-enabled"), argument("true|false"), optional)] assist_enabled: Option, + /// Whether formatting should be allowed to proceed if a given file + /// has syntax errors + #[bpaf(long("format-with-errors"), argument("true|false"))] + format_with_errors: Option, + + #[bpaf(external(json_parser_configuration), optional, hide_usage)] + json_parser: Option, + + #[bpaf(external(css_parser_configuration), optional, hide_usage, hide)] + css_parser: Option, + /// Allows enforcing assist, and make the CLI fail if some actions aren't applied. Defaults to `true`. #[bpaf(long("enforce-assist"), argument("true|false"), fallback(true))] enforce_assist: bool, diff --git a/crates/biome_cli/src/lib.rs b/crates/biome_cli/src/lib.rs index a1edd8296ef0..393b0bc43213 100644 --- a/crates/biome_cli/src/lib.rs +++ b/crates/biome_cli/src/lib.rs @@ -91,6 +91,9 @@ impl<'app> CliSession<'app> { staged, changed, since, + format_with_errors, + json_parser, + css_parser, } => run_command( self, &cli_options, @@ -108,6 +111,9 @@ impl<'app> CliSession<'app> { staged, changed, since, + format_with_errors, + json_parser, + css_parser, }, ), BiomeCommand::Lint { @@ -131,6 +137,8 @@ impl<'app> CliSession<'app> { javascript_linter, json_linter, graphql_linter, + css_parser, + json_parser, } => run_command( self, &cli_options, @@ -154,6 +162,8 @@ impl<'app> CliSession<'app> { javascript_linter, json_linter, graphql_linter, + css_parser, + json_parser, }, ), BiomeCommand::Ci { @@ -166,6 +176,9 @@ impl<'app> CliSession<'app> { cli_options, changed, since, + format_with_errors, + css_parser, + json_parser, .. } => run_command( self, @@ -179,6 +192,9 @@ impl<'app> CliSession<'app> { paths, changed, since, + format_with_errors, + css_parser, + json_parser, }, ), BiomeCommand::Format { @@ -198,6 +214,8 @@ impl<'app> CliSession<'app> { staged, changed, since, + css_parser, + json_parser, } => run_command( self, &cli_options, @@ -217,6 +235,8 @@ impl<'app> CliSession<'app> { staged, changed, since, + css_parser, + json_parser, }, ), BiomeCommand::Explain { doc } => commands::explain::explain(self, doc), diff --git a/crates/biome_cli/tests/cases/css_parsing.rs b/crates/biome_cli/tests/cases/css_parsing.rs new file mode 100644 index 000000000000..01d751ca2f4e --- /dev/null +++ b/crates/biome_cli/tests/cases/css_parsing.rs @@ -0,0 +1,449 @@ +use crate::snap_test::SnapshotPayload; +use crate::{assert_cli_snapshot, run_cli}; +use biome_console::BufferConsole; +use biome_fs::MemoryFileSystem; +use bpaf::Args; +use camino::Utf8Path; + +// Tests for --css-parse-css-modules flag + +#[test] +fn check_css_parse_css_modules_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules specific syntax + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--css-parse-css-modules=true", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_css_modules_true", + fs, + console, + result, + )); +} + +#[test] +fn check_css_parse_css_modules_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules specific syntax + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--css-parse-css-modules=false", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_css_modules_false", + fs, + console, + result, + )); +} + +#[test] +fn format_css_parse_css_modules_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules specific syntax + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["format", "--css-parse-css-modules=true", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_css_parse_css_modules_true", + fs, + console, + result, + )); +} + +#[test] +fn lint_css_parse_css_modules_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules specific syntax + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--css-parse-css-modules=true", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_css_parse_css_modules_true", + fs, + console, + result, + )); +} + +// Tests for --css-parse-tailwind-directives flag + +#[test] +fn check_css_parse_tailwind_directives_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.css"); + // Tailwind CSS 4.0 directive + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.foo { color: red; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--css-parse-tailwind-directives=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_tailwind_directives_true", + fs, + console, + result, + )); +} + +#[test] +fn check_css_parse_tailwind_directives_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.css"); + // Tailwind CSS 4.0 directive + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.foo { color: red; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--css-parse-tailwind-directives=false", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_tailwind_directives_false", + fs, + console, + result, + )); +} + +#[test] +fn format_css_parse_tailwind_directives_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.css"); + // Tailwind CSS 4.0 directive + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.foo { color: red; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "format", + "--css-parse-tailwind-directives=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_css_parse_tailwind_directives_true", + fs, + console, + result, + )); +} + +#[test] +fn ci_css_parse_tailwind_directives_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.css"); + // Tailwind CSS 4.0 directive + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.foo { color: red; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "ci", + "--css-parse-tailwind-directives=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "ci_css_parse_tailwind_directives_true", + fs, + console, + result, + )); +} + +// Combined tests + +#[test] +fn check_combined_css_parser_flags() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules with Tailwind directives + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.className { composes: other from './other.module.css'; }" + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--css-parse-css-modules=true", + "--css-parse-tailwind-directives=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_combined_css_parser_flags", + fs, + console, + result, + )); +} + +#[test] +fn check_combined_format_with_errors_and_css_modules() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.module.css"); + // CSS Modules with syntax error + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css';".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--format-with-errors=true", + "--css-parse-css-modules=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_combined_format_with_errors_and_css_modules", + fs, + console, + result, + )); +} + +// Config override tests + +#[test] +fn check_css_parser_flags_override_config() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that disables CSS Modules + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "css": { + "parser": { + "cssModules": false + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.module.css"); + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--css-parse-css-modules=true", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parser_flags_override_config", + fs, + console, + result, + )); +} + +#[test] +fn check_css_parse_respects_config_css_modules() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that enables CSS Modules + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "css": { + "parser": { + "cssModules": true + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.module.css"); + fs.insert( + file_path.into(), + ".className { composes: other from './other.module.css'; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_respects_config_css_modules", + fs, + console, + result, + )); +} + +#[test] +fn check_css_parse_respects_config_tailwind_directives() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that enables Tailwind directives + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "css": { + "parser": { + "tailwindDirectives": true + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.css"); + fs.insert( + file_path.into(), + "@import 'tailwindcss';\n.foo { color: red; }".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_css_parse_respects_config_tailwind_directives", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/cases/format_with_errors.rs b/crates/biome_cli/tests/cases/format_with_errors.rs new file mode 100644 index 000000000000..b25d16345adb --- /dev/null +++ b/crates/biome_cli/tests/cases/format_with_errors.rs @@ -0,0 +1,273 @@ +use crate::snap_test::SnapshotPayload; +use crate::{assert_cli_snapshot, run_cli}; +use biome_console::BufferConsole; +use biome_fs::MemoryFileSystem; +use bpaf::Args; +use camino::Utf8Path; + +#[test] +fn check_format_with_errors_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--format-with-errors=true", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_format_with_errors_true", + fs, + console, + result, + )); +} + +#[test] +fn check_format_with_errors_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--format-with-errors=false", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_format_with_errors_false", + fs, + console, + result, + )); +} + +#[test] +fn ci_format_with_errors_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["ci", "--format-with-errors=true", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "ci_format_with_errors_true", + fs, + console, + result, + )); +} + +#[test] +fn ci_format_with_errors_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["ci", "--format-with-errors=false", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "ci_format_with_errors_false", + fs, + console, + result, + )); +} + +#[test] +fn format_format_with_errors_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["format", "--format-with-errors=true", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_format_with_errors_true", + fs, + console, + result, + )); +} + +#[test] +fn format_format_with_errors_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.js"); + // File with syntax error + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["format", "--format-with-errors=false", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_format_with_errors_false", + fs, + console, + result, + )); +} + +#[test] +fn check_format_with_errors_overrides_config() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config with format_with_errors: false + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "formatter": { + "formatWithErrors": false + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.js"); + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--format-with-errors=true", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_format_with_errors_overrides_config", + fs, + console, + result, + )); +} + +#[test] +fn check_format_with_errors_respects_config_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config with format_with_errors: true + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "formatter": { + "formatWithErrors": true + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.js"); + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_format_with_errors_respects_config_true", + fs, + console, + result, + )); +} + +#[test] +fn check_format_with_errors_respects_config_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config with format_with_errors: false + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "formatter": { + "formatWithErrors": false + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.js"); + fs.insert(file_path.into(), "let a = {".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_format_with_errors_respects_config_false", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/cases/json_parsing.rs b/crates/biome_cli/tests/cases/json_parsing.rs new file mode 100644 index 000000000000..8fd69427b5df --- /dev/null +++ b/crates/biome_cli/tests/cases/json_parsing.rs @@ -0,0 +1,433 @@ +use crate::snap_test::SnapshotPayload; +use crate::{assert_cli_snapshot, run_cli}; +use biome_console::BufferConsole; +use biome_fs::MemoryFileSystem; +use bpaf::Args; +use camino::Utf8Path; + +// Tests for --json-parse-allow-comments flag + +#[test] +fn check_json_parse_allow_comments_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with comments + fs.insert( + file_path.into(), + "{\n // This is a comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-comments=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_allow_comments_true", + fs, + console, + result, + )); +} + +#[test] +fn check_json_parse_allow_comments_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with comments + fs.insert( + file_path.into(), + "{\n // This is a comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-comments=false", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_allow_comments_false", + fs, + console, + result, + )); +} + +#[test] +fn format_json_parse_allow_comments_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with comments + fs.insert( + file_path.into(), + "{\n // This is a comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "format", + "--json-parse-allow-comments=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_json_parse_allow_comments_true", + fs, + console, + result, + )); +} + +#[test] +fn lint_json_parse_allow_comments_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with comments + fs.insert( + file_path.into(), + "{\n // This is a comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "lint", + "--json-parse-allow-comments=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "lint_json_parse_allow_comments_true", + fs, + console, + result, + )); +} + +// Tests for --json-parse-allow-trailing-commas flag + +#[test] +fn check_json_parse_allow_trailing_commas_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with trailing comma + fs.insert(file_path.into(), "{\n \"key\": \"value\",\n}".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-trailing-commas=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_allow_trailing_commas_true", + fs, + console, + result, + )); +} + +#[test] +fn check_json_parse_allow_trailing_commas_false() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with trailing comma + fs.insert(file_path.into(), "{\n \"key\": \"value\",\n}".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-trailing-commas=false", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert!(result.is_err(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_allow_trailing_commas_false", + fs, + console, + result, + )); +} + +#[test] +fn format_json_parse_allow_trailing_commas_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with trailing comma + fs.insert(file_path.into(), "{\n \"key\": \"value\",\n}".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "format", + "--json-parse-allow-trailing-commas=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "format_json_parse_allow_trailing_commas_true", + fs, + console, + result, + )); +} + +// Combined tests + +#[test] +fn check_combined_json_parser_flags() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with both comments and trailing comma + fs.insert( + file_path.into(), + "{\n // Comment\n \"key\": \"value\",\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-comments=true", + "--json-parse-allow-trailing-commas=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_combined_json_parser_flags", + fs, + console, + result, + )); +} + +#[test] +fn ci_json_parse_allow_comments_true() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file_path = Utf8Path::new("file.json"); + // JSON with comments + fs.insert( + file_path.into(), + "{\n // This is a comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["ci", "--json-parse-allow-comments=true", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "ci_json_parse_allow_comments_true", + fs, + console, + result, + )); +} + +// Config override tests + +#[test] +fn check_json_parser_flags_override_config() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that disallows comments + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "json": { + "parser": { + "allowComments": false + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.json"); + fs.insert( + file_path.into(), + "{\n // Comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "check", + "--json-parse-allow-comments=true", + file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parser_flags_override_config", + fs, + console, + result, + )); +} + +#[test] +fn check_json_parse_respects_config_allow_comments() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that allows comments + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "json": { + "parser": { + "allowComments": true + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.json"); + fs.insert( + file_path.into(), + "{\n // Comment\n \"key\": \"value\"\n}".as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_respects_config_allow_comments", + fs, + console, + result, + )); +} + +#[test] +fn check_json_parse_respects_config_allow_trailing_commas() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + // Config that allows trailing commas + let config_path = Utf8Path::new("biome.json"); + fs.insert( + config_path.into(), + r#"{ + "json": { + "parser": { + "allowTrailingCommas": true + } + } +}"# + .as_bytes(), + ); + + let file_path = Utf8Path::new("file.json"); + fs.insert(file_path.into(), "{\n \"key\": \"value\",\n}".as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", file_path.as_str()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_json_parse_respects_config_allow_trailing_commas", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/cases/mod.rs b/crates/biome_cli/tests/cases/mod.rs index a4d40d957ae5..6e3155065ff7 100644 --- a/crates/biome_cli/tests/cases/mod.rs +++ b/crates/biome_cli/tests/cases/mod.rs @@ -5,9 +5,11 @@ mod assist; mod biome_json_support; mod config_extends; mod config_path; +mod css_parsing; mod cts_files; mod diagnostics; mod editorconfig; +mod format_with_errors; mod graphql; mod handle_astro_files; mod handle_css_files; @@ -16,6 +18,7 @@ mod handle_vue_files; mod html; mod included_files; mod indent_script_and_style; +mod json_parsing; mod linter_domains; mod linter_groups_plain; mod migrate_v2; diff --git a/crates/biome_cli/tests/snapshots/main_cases_css_parsing/check_combined_css_parser_flags.snap b/crates/biome_cli/tests/snapshots/main_cases_css_parsing/check_combined_css_parser_flags.snap new file mode 100644 index 000000000000..41e1fab027c3 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_cases_css_parsing/check_combined_css_parser_flags.snap @@ -0,0 +1,44 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: redactor(content) +--- +## `file.module.css` + +```css +@import 'tailwindcss'; +.className { composes: other from './other.module.css'; } +``` + +# Termination Message + +```block +check ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Some errors were emitted while running checks. + + + +``` + +# Emitted Messages + +```block +file.module.css format ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Formatter would have printed the following content: + + 1 │ - @import·'tailwindcss'; + 2 │ - .className·{·composes:·other·from·'./other.module.css';·} + 1 │ + @import·"tailwindcss"; + 2 │ + .className·{ + 3 │ + → composes:·other·from·"./other.module.css"; + 4 │ + } + 5 │ + + + +``` + +```block +Checked 1 file in