From 658ffe97deadb202701121df5e59caa5d34ec074 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 31 Dec 2023 12:33:52 -0800 Subject: [PATCH] feat(css_formatter): Add `quoteStyle` for css formatting (#1384) --- crates/biome_cli/src/commands/mod.rs | 2 + .../tests/cases/overrides_formatter.rs | 19 +- crates/biome_cli/tests/commands/format.rs | 45 +++ ...include_file_with_different_languages.snap | 16 +- ...le_with_different_languages_and_files.snap | 14 - .../main_commands_check/check_help.snap | 2 +- .../snapshots/main_commands_ci/ci_help.snap | 2 +- .../applies_custom_css_quote_style.snap | 20 ++ .../main_commands_format/format_help.snap | 2 +- crates/biome_css_formatter/src/context.rs | 20 +- .../src/css/any/dimension.rs | 2 +- .../css/auxiliary/attribute_matcher_value.rs | 26 +- .../src/css/value/string.rs | 4 +- .../src/utils/string_utils.rs | 290 ++++++++++++++++++ crates/biome_css_formatter/tests/language.rs | 58 ++-- .../tests/specs/css/atrule/charset.css.snap | 1 + .../specs/css/atrule/color_profile.css.snap | 1 + .../tests/specs/css/atrule/container.css.snap | 1 + .../specs/css/atrule/counter_style.css.snap | 1 + .../tests/specs/css/atrule/font_face.css.snap | 1 + .../tests/specs/css/atrule/keyframes.css.snap | 1 + .../tests/specs/css/atrule/layer.css.snap | 1 + .../tests/specs/css/atrule/media.css.snap | 1 + .../tests/specs/css/atrule/page.css.snap | 1 + .../specs/css/atrule/page_complex.css.snap | 1 + .../tests/specs/css/atrule/scope.css.snap | 1 + .../tests/specs/css/atrule/supports.css.snap | 1 + .../css/atrule/supports_complex.css.snap | 1 + .../tests/specs/css/block.css.snap | 1 + .../tests/specs/css/casing.css.snap | 1 + .../css/color/functional_colors.css.snap | 1 + .../tests/specs/css/color/hex_colors.css.snap | 1 + .../tests/specs/css/declaration_list.css.snap | 1 + .../tests/specs/css/dimensions.css.snap | 1 + .../tests/specs/css/empty.css.snap | 1 + .../tests/specs/css/functions.css.snap | 1 + .../tests/specs/css/important.css.snap | 1 + .../tests/specs/css/namespace.css.snap | 1 + .../tests/specs/css/pseudo/is.css.snap | 1 + .../tests/specs/css/pseudo/not.css.snap | 1 + ..._class_function_compound_selector.css.snap | 1 + ...s_function_compound_selector_list.css.snap | 1 + .../pseudo/pseudo_class_function_nth.css.snap | 1 + ...s_function_relative_selector_list.css.snap | 1 + .../pseudo_class_function_selector.css.snap | 1 + .../pseudo_class_function_value_list.css.snap | 1 + .../pseudo/pseudo_class_identifier.css.snap | 1 + .../pseudo/pseudo_element_selector.css.snap | 1 + .../tests/specs/css/pseudo/where.css.snap | 1 + .../css/quote_style/normalize_quotes.css | 21 ++ .../css/quote_style/normalize_quotes.css.snap | 105 +++++++ .../tests/specs/css/quote_style/options.json | 7 + .../specs/css/range/between_rules.css.snap | 1 + .../tests/specs/css/range/keyframes.css.snap | 1 + .../tests/specs/css/range/mid_value.css.snap | 1 + .../specs/css/range/selector_list.css.snap | 1 + .../css/range/single_declaration.css.snap | 1 + .../specs/css/range/single_rule.css.snap | 1 + .../specs/css/range/single_value.css.snap | 1 + .../css/selectors/attribute_selector.css.snap | 53 ++-- .../css/selectors/class_selector.css.snap | 1 + .../css/selectors/complex_selector.css.snap | 1 + .../specs/css/selectors/id_selector.css.snap | 1 + .../css/selectors/selector_lists.css.snap | 3 +- .../css/selectors/type_selector.css.snap | 1 + .../tests/specs/css/simple.css.snap | 1 + .../tests/specs/css/units.css.snap | 1 + .../tests/specs/css/url.css.snap | 1 + .../tests/specs/css/value_fill.css.snap | 1 + .../tests/specs/css/variables.css.snap | 1 + crates/biome_formatter/src/lib.rs | 110 +++++++ crates/biome_js_formatter/src/context.rs | 113 +------ crates/biome_js_formatter/src/utils/jsx.rs | 3 +- .../src/utils/string_utils.rs | 5 +- crates/biome_js_formatter/tests/language.rs | 4 +- crates/biome_js_formatter/tests/quick_test.rs | 4 +- crates/biome_service/src/configuration/css.rs | 9 +- .../src/configuration/javascript/formatter.rs | 15 +- .../src/configuration/overrides.rs | 42 ++- .../src/configuration/parse/json/css/mod.rs | 5 + crates/biome_service/src/file_handlers/css.rs | 8 +- .../src/file_handlers/javascript.rs | 7 +- crates/biome_service/src/settings.rs | 19 +- .../invalid/css_formatter_quote_style.json | 7 + .../css_formatter_quote_style.json.snap | 22 ++ .../tests/invalid/formatter_quote_style.json | 5 + .../invalid/formatter_quote_style.json.snap | 29 ++ .../tests/valid/base_options_inside_css.json | 3 +- .../valid/base_options_inside_javascript.json | 5 +- .../@biomejs/backend-jsonrpc/src/workspace.ts | 3 +- .../@biomejs/biome/configuration_schema.json | 3 + 91 files changed, 937 insertions(+), 245 deletions(-) create mode 100644 crates/biome_cli/tests/snapshots/main_commands_format/applies_custom_css_quote_style.snap create mode 100644 crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css create mode 100644 crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap create mode 100644 crates/biome_css_formatter/tests/specs/css/quote_style/options.json create mode 100644 crates/biome_service/tests/invalid/css_formatter_quote_style.json create mode 100644 crates/biome_service/tests/invalid/css_formatter_quote_style.json.snap create mode 100644 crates/biome_service/tests/invalid/formatter_quote_style.json create mode 100644 crates/biome_service/tests/invalid/formatter_quote_style.json.snap diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index 3e069bacb6fc..f680bf669592 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -408,7 +408,9 @@ pub(crate) fn validate_configuration_diagnostics( {if verbose { PrintDiagnostic::verbose(diagnostic) } else { PrintDiagnostic::simple(diagnostic) }} }); } + if loaded_configuration.has_errors() { + println!("{:#?}", loaded_configuration); return Err(CliDiagnostic::workspace_error( WorkspaceError::Configuration(ConfigurationDiagnostic::invalid_configuration( "Biome exited because the configuration resulted in errors. Please fix them.", diff --git a/crates/biome_cli/tests/cases/overrides_formatter.rs b/crates/biome_cli/tests/cases/overrides_formatter.rs index b112bf563db6..e6151a71b34c 100644 --- a/crates/biome_cli/tests/cases/overrides_formatter.rs +++ b/crates/biome_cli/tests/cases/overrides_formatter.rs @@ -24,6 +24,11 @@ const FORMATTED_LINE_WIDTH: &str = "const a = [\"loreum\", \"ipsum\"];\n"; const FORMATTED_WITH_SINGLE_QUOTES: &str = "const a = ['loreum', 'ipsum'];\n"; const FORMATTED_WITH_NO_SEMICOLONS: &str = "const a = [\"loreum\", \"ipsum\"]\n"; +const CSS_UNFORMATTED_QUOTES: &str = + r#"[class='foo'] { background-image: url("/path/to/file.jpg")}"#; +const CSS_FORMATTED_SINGLE_QUOTES_AND_SPACES: &str = + "[class='foo'] {\n background-image: url('/path/to/file.jpg');\n}\n"; + #[test] fn does_handle_included_file_and_disable_formatter() { let mut console = BufferConsole::default(); @@ -239,10 +244,10 @@ fn does_include_file_with_different_languages() { r#"{ "overrides": [ { "include": ["test.js"], "formatter": { "lineWidth": 120 }, "javascript": { "formatter": { "quoteStyle": "single" } } }, - { "include": ["test2.js"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, "javascript": { "formatter": { "semicolons": "asNeeded" } } } + { "include": ["test2.js"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, "javascript": { "formatter": { "semicolons": "asNeeded" } } }, + { "include": ["test.css"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, "css": { "formatter": { "quoteStyle": "single" } } } ] } - "# .as_bytes(), ); @@ -252,6 +257,8 @@ fn does_include_file_with_different_languages() { let test2 = Path::new("test2.js"); fs.insert(test2.into(), UNFORMATTED_LINE_WIDTH.as_bytes()); + let test_css = Path::new("test.css"); + fs.insert(test_css.into(), CSS_UNFORMATTED_QUOTES.as_bytes()); let result = run_cli( DynRef::Borrowed(&mut fs), @@ -262,6 +269,7 @@ fn does_include_file_with_different_languages() { ("--write"), test.as_os_str().to_str().unwrap(), test2.as_os_str().to_str().unwrap(), + test_css.as_os_str().to_str().unwrap(), ] .as_slice(), ), @@ -271,6 +279,7 @@ fn does_include_file_with_different_languages() { assert_file_contents(&fs, test, FORMATTED_WITH_SINGLE_QUOTES); assert_file_contents(&fs, test2, FORMATTED_WITH_NO_SEMICOLONS); + assert_file_contents(&fs, test_css, CSS_FORMATTED_SINGLE_QUOTES_AND_SPACES); assert_cli_snapshot(SnapshotPayload::new( module_path!(), @@ -295,14 +304,12 @@ fn does_include_file_with_different_languages_and_files() { "include": ["test2.js"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, "javascript": { "formatter": { "semicolons": "asNeeded" } }, - "json": { "formatter": { "indentStyle": "space", "lineWidth": 20, "indentWidth": 4 } }, - "css": { "formatter": { "indentStyle": "space", "lineWidth": 30, "indentWidth": 3 } } + "json": { "formatter": { "indentStyle": "space", "lineWidth": 20, "indentWidth": 4 } } }, { "include": ["test3.json"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, - "json": { "formatter": { "indentStyle": "space", "lineWidth": 20, "indentWidth": 4 } }, - "css": { "formatter": { "indentStyle": "space", "lineWidth": 30, "indentWidth": 3 } } + "json": { "formatter": { "indentStyle": "space", "lineWidth": 20, "indentWidth": 4 } } } ] } diff --git a/crates/biome_cli/tests/commands/format.rs b/crates/biome_cli/tests/commands/format.rs index 6c0b7227284e..8a532cb02536 100644 --- a/crates/biome_cli/tests/commands/format.rs +++ b/crates/biome_cli/tests/commands/format.rs @@ -37,6 +37,12 @@ let b = { const APPLY_QUOTE_STYLE_AFTER: &str = "let a = 'something'; let b = {\n\t'hey': 'hello',\n};\n"; +const APPLY_CSS_QUOTE_STYLE_BEFORE: &str = + r#"[class='foo'] { background-image: url("/path/to/file.jpg")}"#; + +const APPLY_CSS_QUOTE_STYLE_AFTER: &str = + "[class='foo'] {\n\tbackground-image: url('/path/to/file.jpg');\n}\n"; + const APPLY_TRAILING_COMMA_BEFORE: &str = r#" const a = [ longlonglonglongItem1longlonglonglongItem1, @@ -616,6 +622,45 @@ fn applies_custom_quote_style() { )); } +#[test] +fn applies_custom_css_quote_style() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let css_file_path = Path::new("file.css"); + fs.insert( + css_file_path.into(), + APPLY_CSS_QUOTE_STYLE_BEFORE.as_bytes(), + ); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from( + [ + ("format"), + ("--css-formatter-quote-style"), + ("single"), + ("--write"), + css_file_path.as_os_str().to_str().unwrap(), + ] + .as_slice(), + ), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_file_contents(&fs, css_file_path, APPLY_CSS_QUOTE_STYLE_AFTER); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "applies_custom_css_quote_style", + fs, + console, + result, + )); +} + #[test] fn applies_custom_trailing_comma() { let mut fs = MemoryFileSystem::default(); diff --git a/crates/biome_cli/tests/snapshots/main_cases_overrides_formatter/does_include_file_with_different_languages.snap b/crates/biome_cli/tests/snapshots/main_cases_overrides_formatter/does_include_file_with_different_languages.snap index 8ae59cc282db..cfef3d31aec1 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_overrides_formatter/does_include_file_with_different_languages.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_overrides_formatter/does_include_file_with_different_languages.snap @@ -16,11 +16,25 @@ expression: content "include": ["test2.js"], "formatter": { "lineWidth": 120, "indentStyle": "space" }, "javascript": { "formatter": { "semicolons": "asNeeded" } } + }, + { + "include": ["test.css"], + "formatter": { "lineWidth": 120, "indentStyle": "space" }, + "css": { "formatter": { "quoteStyle": "single" } } } ] } ``` +## `test.css` + +```css +[class='foo'] { + background-image: url('/path/to/file.jpg'); +} + +``` + ## `test.js` ```js @@ -38,7 +52,7 @@ const a = ["loreum", "ipsum"] # Emitted Messages ```block -Formatted 2 file(s) in