From cf837871c68460bf762b4548ff86665648cbdff4 Mon Sep 17 00:00:00 2001 From: zoomdong <1344492820@qq.com> Date: Mon, 28 Oct 2024 14:04:39 +0800 Subject: [PATCH] fix(css_formatter): keep @charset double quote in single quote config --- CHANGELOG.md | 1 + .../css/auxiliary/attribute_matcher_value.rs | 10 +++- .../src/css/value/string.rs | 30 +++++++++-- .../src/utils/string_utils.rs | 50 +++++++++++++------ .../biome_css_formatter/tests/quick_test.rs | 11 ++-- .../css/quote_style/normalize_quotes.css | 6 ++- .../css/quote_style/normalize_quotes.css.snap | 12 +++++ 7 files changed, 92 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f97e3eed9915..0cbadab1fc88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### Bug fixes - Fix [#4121](https://github.com/biomejs/biome/issues/4121). Respect line width when printing multiline strings. Contributed by @ah-yu +- Fix [#4384](https://github.com/biomejs/biome/issues/4384). Keep `@charset` dobule quote under any situation for css syntax rule. Contributed by @fireairforce ### JavaScript APIs diff --git a/crates/biome_css_formatter/src/css/auxiliary/attribute_matcher_value.rs b/crates/biome_css_formatter/src/css/auxiliary/attribute_matcher_value.rs index feb40379c8ad..373e2b840600 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/attribute_matcher_value.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/attribute_matcher_value.rs @@ -1,4 +1,7 @@ -use crate::{prelude::*, utils::string_utils::FormatLiteralStringToken}; +use crate::{ + prelude::*, + utils::string_utils::{FormatLiteralStringToken, StringLiteralParentKind}, +}; use biome_css_syntax::{ AnyCssAttributeMatcherValue, CssAttributeMatcherValue, CssAttributeMatcherValueFields, }; @@ -35,7 +38,10 @@ impl FormatNodeRule for FormatCssAttributeMatcherValue // does not get converted to lowercase. Once it's quoted, it // will be parsed as a CssString on the next pass, at which // point casing is preserved no matter what. - FormatLiteralStringToken::new(&ident.value_token()?), + FormatLiteralStringToken::new( + &ident.value_token()?, + StringLiteralParentKind::Others + ), format_trailing_comments(ident.syntax()), format_dangling_comments(ident.syntax()) ] diff --git a/crates/biome_css_formatter/src/css/value/string.rs b/crates/biome_css_formatter/src/css/value/string.rs index 943cfd533732..f494b4c6700c 100644 --- a/crates/biome_css_formatter/src/css/value/string.rs +++ b/crates/biome_css_formatter/src/css/value/string.rs @@ -1,13 +1,35 @@ -use crate::{prelude::*, utils::string_utils::FormatLiteralStringToken}; -use biome_css_syntax::{CssString, CssStringFields}; +use crate::{ + prelude::*, + utils::string_utils::{FormatLiteralStringToken, StringLiteralParentKind}, +}; +use biome_css_syntax::{CssString, CssStringFields, CssSyntaxKind}; use biome_formatter::write; +use biome_rowan::SyntaxNodeOptionExt; #[derive(Debug, Clone, Default)] pub(crate) struct FormatCssString; impl FormatNodeRule for FormatCssString { fn fmt_fields(&self, node: &CssString, f: &mut CssFormatter) -> FormatResult<()> { let CssStringFields { value_token } = node.as_fields(); - - write!(f, [FormatLiteralStringToken::new(&value_token?)]) + if matches!( + node.syntax().parent().kind(), + Some(CssSyntaxKind::CSS_CHARSET_AT_RULE) + ) { + write!( + f, + [FormatLiteralStringToken::new( + &value_token?, + StringLiteralParentKind::CharsetAtRule + )] + ) + } else { + write!( + f, + [FormatLiteralStringToken::new( + &value_token?, + StringLiteralParentKind::Others + )] + ) + } } } diff --git a/crates/biome_css_formatter/src/utils/string_utils.rs b/crates/biome_css_formatter/src/utils/string_utils.rs index 807262f19d4c..a545f320d92a 100644 --- a/crates/biome_css_formatter/src/utils/string_utils.rs +++ b/crates/biome_css_formatter/src/utils/string_utils.rs @@ -43,17 +43,29 @@ impl Format for FormatTokenAsLowercase { } } +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum StringLiteralParentKind { + /// Variants to track tokens that are inside a CssCharasetRule + /// @charset must always have double quotes: https://www.w3.org/TR/css-syntax-3/#determine-the-fallback-encoding + CharsetAtRule, + /// other types, will add more later + Others, +} + /// Data structure of convenience to format string literals. This is copied /// from the JS formatter, but should eventually have the logic made generic /// and reusable since many languages will have the same needs. pub(crate) struct FormatLiteralStringToken<'token> { /// The current token token: &'token CssSyntaxToken, + + // The parent that holds the token + parent_kind: StringLiteralParentKind, } impl<'token> FormatLiteralStringToken<'token> { - pub fn new(token: &'token CssSyntaxToken) -> Self { - Self { token } + pub fn new(token: &'token CssSyntaxToken, parent_kind: StringLiteralParentKind) -> Self { + Self { token, parent_kind } } fn token(&self) -> &'token CssSyntaxToken { @@ -204,18 +216,28 @@ impl<'token> LiteralStringNormaliser<'token> { } fn normalise_text(&mut self) -> Cow<'token, str> { - let string_information = self - .token - .compute_string_information(self.chosen_quote_style); - - // Normalize string token and non-string token. - // - // Add the chosen quotes to any non-string tokensto normalize them into strings. - // - // CSS has various places where "string-like" tokens can be used without quotes, but the - // semantics aren't affected by whether they are present or not. This function lets those - // tokens become string literals by safely adding quotes around them. - self.normalise_tokens(string_information) + match self.token.parent_kind { + StringLiteralParentKind::CharsetAtRule => { + let string_information = StringInformation { + preferred_quote: QuoteStyle::Double, + }; + self.normalise_tokens(string_information) + } + StringLiteralParentKind::Others => { + let string_information = self + .token + .compute_string_information(self.chosen_quote_style); + + // Normalize string token and non-string token. + // + // Add the chosen quotes to any non-string tokensto normalize them into strings. + // + // CSS has various places where "string-like" tokens can be used without quotes, but the + // semantics aren't affected by whether they are present or not. This function lets those + // tokens become string literals by safely adding quotes around them. + self.normalise_tokens(string_information) + } + } } fn get_token(&self) -> &'token CssSyntaxToken { diff --git a/crates/biome_css_formatter/tests/quick_test.rs b/crates/biome_css_formatter/tests/quick_test.rs index 495e773e2895..8a99a5184d83 100644 --- a/crates/biome_css_formatter/tests/quick_test.rs +++ b/crates/biome_css_formatter/tests/quick_test.rs @@ -1,7 +1,7 @@ use biome_css_formatter::format_node; use biome_css_formatter::{context::CssFormatOptions, CssFormatLanguage}; use biome_css_parser::{parse_css, CssParserOptions}; -use biome_formatter::{IndentStyle, LineWidth}; +use biome_formatter::{IndentStyle, LineWidth, QuoteStyle}; use biome_formatter_test::check_reformat::CheckReformat; mod language { @@ -13,18 +13,15 @@ mod language { // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { let src = r#" -.container { - &:has(.child) { - color: blue; - } -} +@charset "UTF-8"; "#; let parse = parse_css(src, CssParserOptions::default()); println!("{parse:#?}"); let options = CssFormatOptions::default() .with_line_width(LineWidth::try_from(80).unwrap()) - .with_indent_style(IndentStyle::Space); + .with_indent_style(IndentStyle::Space) + .with_quote_style(QuoteStyle::Single); let doc = format_node(options.clone(), &parse.syntax()).unwrap(); let result = doc.print().unwrap(); diff --git a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css index 2e54284ea980..c5ce8454babc 100644 --- a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css +++ b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css @@ -18,4 +18,8 @@ div { width: 0\eestays-unquoted; --\eeunquoted: green; color: var(--\eeunquoted); -} \ No newline at end of file +} + +@charset·"UTF-8"; +@charset "iso-8859-15"; +@charset "any-string-is-okay"; \ No newline at end of file diff --git a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap index 142cf2c7aae9..a9bc7d0633f5 100644 --- a/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap +++ b/crates/biome_css_formatter/tests/specs/css/quote_style/normalize_quotes.css.snap @@ -26,6 +26,10 @@ div { --\eeunquoted: green; color: var(--\eeunquoted); } + +@charset·"UTF-8"; +@charset "iso-8859-15"; +@charset "any-string-is-okay"; ``` @@ -65,6 +69,10 @@ div { --\eeunquoted: green; color: var(--\eeunquoted); } + +@charset· "UTF-8"; +@charset "iso-8859-15"; +@charset "any-string-is-okay"; ``` ## Output 1 @@ -99,4 +107,8 @@ div { --\eeunquoted: green; color: var(--\eeunquoted); } + +@charset· "UTF-8"; +@charset "iso-8859-15"; +@charset "any-string-is-okay"; ```