diff --git a/CHANGELOG.md b/CHANGELOG.md index 09de48b76e0d..0699e0761291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,24 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### Linter +#### Bug Fixes + +- The CSS parser now accepts more emoji in identifiers ([#3627](https://github.com/biomejs/biome/issues/3627#issuecomment-2392388022)). + + Browsers accept more emoji than the standard allows. + Biome now accepts these additional emoji. + + The following code is now correctly parsed: + + ```css + p { + --✨-color: red; + color: var(--✨-color); + } + ``` + + Contributed by @Conaclos + ### Parser ## v1.9.3 (2024-10-01) diff --git a/crates/biome_css_analyze/tests/specs/nursery/noIrregularWhitespace/invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noIrregularWhitespace/invalid.css.snap index 77f568bd3877..262373476f9c 100644 --- a/crates/biome_css_analyze/tests/specs/nursery/noIrregularWhitespace/invalid.css.snap +++ b/crates/biome_css_analyze/tests/specs/nursery/noIrregularWhitespace/invalid.css.snap @@ -394,14 +394,14 @@ invalid.css:85:11 lint/nursery/noIrregularWhitespace ━━━━━━━━━ ``` ``` -invalid.css:95:11 lint/nursery/noIrregularWhitespace ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.css:95:2 lint/nursery/noIrregularWhitespace ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Irregular whitespace found. 93 │ } 94 │ /* \u{3000} */ > 95 │ @container␠(width < 15rem) { - │ ^ + │ ^^^^^^^^^^ 96 │ color: blue; 97 │ } diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/character-escaping/character_escaping.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/character-escaping/character_escaping.css.snap index ecdbf573379a..dd6f0f1dbe22 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/character-escaping/character_escaping.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/character-escaping/character_escaping.css.snap @@ -72,29 +72,24 @@ info: css/character-escaping/character_escaping.css ```diff --- Prettier +++ Biome -@@ -1,15 +1,9 @@ --#♥ { --} +@@ -1,13 +1,10 @@ + #♥ { + } -#© { -} -#“‘’” { -} --#☺☃ { --} --#⌘⌥ { --} --#𝄞♪♩♫♬ { --} -+#♥ {} +#© {} +#“‘’” {} -+#☺☃ {} + #☺☃ { + } +-#⌘⌥ { +-} +#⌘⌥ {} -+#𝄞♪♩♫♬ {} - #💩 { + #𝄞♪♩♫♬ { } - #\? { -@@ -50,7 +44,7 @@ + #💩 { +@@ -50,7 +47,7 @@ } #\3A hover { } @@ -108,12 +103,15 @@ info: css/character-escaping/character_escaping.css # Output ```css -#♥ {} +#♥ { +} #© {} #“‘’” {} -#☺☃ {} +#☺☃ { +} #⌘⌥ {} -#𝄞♪♩♫♬ {} +#𝄞♪♩♫♬ { +} #💩 { } #\? { @@ -204,15 +202,6 @@ info: css/character-escaping/character_escaping.css # Errors ``` -character_escaping.css:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `♥` - - > 1 │ #♥ {} - │ ^ - 2 │ #© {} - 3 │ #“‘’” {} - character_escaping.css:2:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × unexpected character `©` @@ -267,39 +256,6 @@ character_escaping.css:3:5 parse ━━━━━━━━━━━━━━━ 4 │ #☺☃ {} 5 │ #⌘⌥ {} -character_escaping.css:4:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `☺` - - 2 │ #© {} - 3 │ #“‘’” {} - > 4 │ #☺☃ {} - │ ^ - 5 │ #⌘⌥ {} - 6 │ #𝄞♪♩♫♬ {} - -character_escaping.css:4:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `☃` - - 2 │ #© {} - 3 │ #“‘’” {} - > 4 │ #☺☃ {} - │ ^ - 5 │ #⌘⌥ {} - 6 │ #𝄞♪♩♫♬ {} - -character_escaping.css:5:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `⌘` - - 3 │ #“‘’” {} - 4 │ #☺☃ {} - > 5 │ #⌘⌥ {} - │ ^ - 6 │ #𝄞♪♩♫♬ {} - 7 │ #💩 {} - character_escaping.css:5:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × unexpected character `⌥` @@ -311,54 +267,10 @@ character_escaping.css:5:3 parse ━━━━━━━━━━━━━━━ 6 │ #𝄞♪♩♫♬ {} 7 │ #💩 {} -character_escaping.css:6:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `♪` - - 4 │ #☺☃ {} - 5 │ #⌘⌥ {} - > 6 │ #𝄞♪♩♫♬ {} - │ ^ - 7 │ #💩 {} - 8 │ #\? {} - -character_escaping.css:6:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `♩` - - 4 │ #☺☃ {} - 5 │ #⌘⌥ {} - > 6 │ #𝄞♪♩♫♬ {} - │ ^ - 7 │ #💩 {} - 8 │ #\? {} - -character_escaping.css:6:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `♫` - - 4 │ #☺☃ {} - 5 │ #⌘⌥ {} - > 6 │ #𝄞♪♩♫♬ {} - │ ^ - 7 │ #💩 {} - 8 │ #\? {} - -character_escaping.css:6:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × unexpected character `♬` - - 4 │ #☺☃ {} - 5 │ #⌘⌥ {} - > 6 │ #𝄞♪♩♫♬ {} - │ ^ - 7 │ #💩 {} - 8 │ #\? {} - ``` # Lines exceeding max width of 80 characters ``` - 27: #\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\. { + 30: #\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\. { ``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css b/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css index 8a970615c189..16d8eee95e14 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css @@ -1,4 +1,6 @@ p { --🥔-color: red; + --☂-color: red; + --✨-color: red; color: var(--🥔-color); } \ No newline at end of file diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css.snap index 0b438105b320..669d534ad0e6 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/property-with-emoji.css.snap @@ -7,6 +7,8 @@ expression: snapshot ```css p { --🥔-color: red; + --☂-color: red; + --✨-color: red; color: var(--🥔-color); } ``` @@ -51,54 +53,88 @@ CssRoot { }, semicolon_token: SEMICOLON@23..24 ";" [] [], }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssDashedIdentifier { + value_token: IDENT@24..38 "--☂-color" [Newline("\n"), Whitespace(" ")] [], + }, + colon_token: COLON@38..40 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@40..43 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@43..44 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssDashedIdentifier { + value_token: IDENT@44..58 "--✨-color" [Newline("\n"), Whitespace(" ")] [], + }, + colon_token: COLON@58..60 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@60..63 "red" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@63..64 ";" [] [], + }, CssDeclarationWithSemicolon { declaration: CssDeclaration { property: CssGenericProperty { name: CssIdentifier { - value_token: IDENT@24..32 "color" [Newline("\n"), Whitespace(" ")] [], + value_token: IDENT@64..72 "color" [Newline("\n"), Whitespace(" ")] [], }, - colon_token: COLON@32..34 ":" [] [Whitespace(" ")], + colon_token: COLON@72..74 ":" [] [Whitespace(" ")], value: CssGenericComponentValueList [ CssFunction { name: CssIdentifier { - value_token: IDENT@34..37 "var" [] [], + value_token: IDENT@74..77 "var" [] [], }, - l_paren_token: L_PAREN@37..38 "(" [] [], + l_paren_token: L_PAREN@77..78 "(" [] [], items: CssParameterList [ CssParameter { any_css_expression: CssListOfComponentValuesExpression { css_component_value_list: CssComponentValueList [ CssDashedIdentifier { - value_token: IDENT@38..50 "--🥔-color" [] [], + value_token: IDENT@78..90 "--🥔-color" [] [], }, ], }, }, ], - r_paren_token: R_PAREN@50..51 ")" [] [], + r_paren_token: R_PAREN@90..91 ")" [] [], }, ], }, important: missing (optional), }, - semicolon_token: SEMICOLON@51..52 ";" [] [], + semicolon_token: SEMICOLON@91..92 ";" [] [], }, ], - r_curly_token: R_CURLY@52..54 "}" [Newline("\n")] [], + r_curly_token: R_CURLY@92..94 "}" [Newline("\n")] [], }, }, ], - eof_token: EOF@54..54 "" [] [], + eof_token: EOF@94..94 "" [] [], } ``` ## CST ``` -0: CSS_ROOT@0..54 +0: CSS_ROOT@0..94 0: (empty) - 1: CSS_RULE_LIST@0..54 - 0: CSS_QUALIFIED_RULE@0..54 + 1: CSS_RULE_LIST@0..94 + 0: CSS_QUALIFIED_RULE@0..94 0: CSS_SELECTOR_LIST@0..2 0: CSS_COMPOUND_SELECTOR@0..2 0: CSS_NESTED_SELECTOR_LIST@0..0 @@ -107,9 +143,9 @@ CssRoot { 1: CSS_IDENTIFIER@0..2 0: IDENT@0..2 "p" [] [Whitespace(" ")] 2: CSS_SUB_SELECTOR_LIST@2..2 - 1: CSS_DECLARATION_OR_RULE_BLOCK@2..54 + 1: CSS_DECLARATION_OR_RULE_BLOCK@2..94 0: L_CURLY@2..3 "{" [] [] - 1: CSS_DECLARATION_OR_RULE_LIST@3..52 + 1: CSS_DECLARATION_OR_RULE_LIST@3..92 0: CSS_DECLARATION_WITH_SEMICOLON@3..24 0: CSS_DECLARATION@3..23 0: CSS_GENERIC_PROPERTY@3..23 @@ -121,27 +157,49 @@ CssRoot { 0: IDENT@20..23 "red" [] [] 1: (empty) 1: SEMICOLON@23..24 ";" [] [] - 1: CSS_DECLARATION_WITH_SEMICOLON@24..52 - 0: CSS_DECLARATION@24..51 - 0: CSS_GENERIC_PROPERTY@24..51 - 0: CSS_IDENTIFIER@24..32 - 0: IDENT@24..32 "color" [Newline("\n"), Whitespace(" ")] [] - 1: COLON@32..34 ":" [] [Whitespace(" ")] - 2: CSS_GENERIC_COMPONENT_VALUE_LIST@34..51 - 0: CSS_FUNCTION@34..51 - 0: CSS_IDENTIFIER@34..37 - 0: IDENT@34..37 "var" [] [] - 1: L_PAREN@37..38 "(" [] [] - 2: CSS_PARAMETER_LIST@38..50 - 0: CSS_PARAMETER@38..50 - 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@38..50 - 0: CSS_COMPONENT_VALUE_LIST@38..50 - 0: CSS_DASHED_IDENTIFIER@38..50 - 0: IDENT@38..50 "--🥔-color" [] [] - 3: R_PAREN@50..51 ")" [] [] + 1: CSS_DECLARATION_WITH_SEMICOLON@24..44 + 0: CSS_DECLARATION@24..43 + 0: CSS_GENERIC_PROPERTY@24..43 + 0: CSS_DASHED_IDENTIFIER@24..38 + 0: IDENT@24..38 "--☂-color" [Newline("\n"), Whitespace(" ")] [] + 1: COLON@38..40 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@40..43 + 0: CSS_IDENTIFIER@40..43 + 0: IDENT@40..43 "red" [] [] + 1: (empty) + 1: SEMICOLON@43..44 ";" [] [] + 2: CSS_DECLARATION_WITH_SEMICOLON@44..64 + 0: CSS_DECLARATION@44..63 + 0: CSS_GENERIC_PROPERTY@44..63 + 0: CSS_DASHED_IDENTIFIER@44..58 + 0: IDENT@44..58 "--✨-color" [Newline("\n"), Whitespace(" ")] [] + 1: COLON@58..60 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@60..63 + 0: CSS_IDENTIFIER@60..63 + 0: IDENT@60..63 "red" [] [] + 1: (empty) + 1: SEMICOLON@63..64 ";" [] [] + 3: CSS_DECLARATION_WITH_SEMICOLON@64..92 + 0: CSS_DECLARATION@64..91 + 0: CSS_GENERIC_PROPERTY@64..91 + 0: CSS_IDENTIFIER@64..72 + 0: IDENT@64..72 "color" [Newline("\n"), Whitespace(" ")] [] + 1: COLON@72..74 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@74..91 + 0: CSS_FUNCTION@74..91 + 0: CSS_IDENTIFIER@74..77 + 0: IDENT@74..77 "var" [] [] + 1: L_PAREN@77..78 "(" [] [] + 2: CSS_PARAMETER_LIST@78..90 + 0: CSS_PARAMETER@78..90 + 0: CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION@78..90 + 0: CSS_COMPONENT_VALUE_LIST@78..90 + 0: CSS_DASHED_IDENTIFIER@78..90 + 0: IDENT@78..90 "--🥔-color" [] [] + 3: R_PAREN@90..91 ")" [] [] 1: (empty) - 1: SEMICOLON@51..52 ";" [] [] - 2: R_CURLY@52..54 "}" [Newline("\n")] [] - 2: EOF@54..54 "" [] [] + 1: SEMICOLON@91..92 ";" [] [] + 2: R_CURLY@92..94 "}" [Newline("\n")] [] + 2: EOF@94..94 "" [] [] ``` diff --git a/crates/biome_unicode_table/src/lib.rs b/crates/biome_unicode_table/src/lib.rs index 0077e68f8893..a96d1e615e46 100644 --- a/crates/biome_unicode_table/src/lib.rs +++ b/crates/biome_unicode_table/src/lib.rs @@ -14,6 +14,11 @@ pub fn is_html_id_start(c: char) -> bool { /// Is `c` a CSS non-ascii character. /// See https://drafts.csswg.org/css-syntax-3/#ident-token-diagram +/// +/// In contrast to the standard we also accept all characters from: +/// - the Miscellaneous Symbols Unicode block +/// - the Dingbats Unicode block +/// and some of the Miscellaneous Technical Unicode block. #[inline] pub fn is_css_non_ascii(c: char) -> bool { matches!( @@ -28,6 +33,13 @@ pub fn is_css_non_ascii(c: char) -> bool { | 0x203F | 0x2040 | 0x2070..=0x218F + // https://en.wikipedia.org/wiki/List_of_Unicode_characters#Miscellaneous_Technical + | 0x2318 | 0x231A | 0x231B | 0x2328 | 0x2399 + | 0x23E9..=0x23F3 + | 0x23F9..=0x23F3E + // https://en.wikipedia.org/wiki/List_of_Unicode_characters#Miscellaneous_Symbols + // https://en.wikipedia.org/wiki/Dingbats_(Unicode_block) + | 0x2600..=0x27BF | 0x2C00..=0x2FEF | 0x3001..=0xD7FF | 0xF900..=0xFDCF