diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7c3c4ecf73..b38ab24a1029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,13 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features - Implement [CSS unicode range](https://github.com/biomejs/biome/pull/3251). Contributed by @denbezrukov -- + ### Formatter #### Bug fixes - Fix [#3184](https://github.com/biomejs/biome/issues/3184) CSS formatter converts custom identifiers to lowercase. Contributed by @denbezrukov +- Fix [#3256](https://github.com/biomejs/biome/issues/3256) constant crashes when editing css files #3256. Contributed by @denbezrukov ## v1.8.2 (2024-06-20) diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 31950ed63b53..dab4e9945035 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -11,6 +11,7 @@ use crate::parser::CssParser; use crate::syntax::at_rule::{is_at_at_rule, parse_at_rule}; use crate::syntax::block::parse_declaration_or_rule_list_block; use crate::syntax::parse_error::{expected_any_rule, expected_non_css_wide_keyword_identifier}; +use crate::syntax::property::color::{is_at_color, parse_color}; use crate::syntax::property::unicode_range::{is_at_unicode_range, parse_unicode_range}; use crate::syntax::property::{is_at_any_property, parse_any_property}; use crate::syntax::selector::is_nth_at_selector; @@ -287,21 +288,6 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { } } -#[inline] -pub(crate) fn is_at_color(p: &mut CssParser) -> bool { - p.at(T![#]) -} -#[inline] -pub(crate) fn parse_color(p: &mut CssParser) -> ParsedSyntax { - if !is_at_color(p) { - return Absent; - } - let m = p.start(); - p.bump_with_context(T![#], CssLexContext::Color); - p.expect(CSS_COLOR_LITERAL); - Present(m.complete(p, CSS_COLOR)) -} - struct CssComponentValueList; impl ParseNodeList for CssComponentValueList { type Kind = CssSyntaxKind; diff --git a/crates/biome_css_parser/src/syntax/property/color.rs b/crates/biome_css_parser/src/syntax/property/color.rs new file mode 100644 index 000000000000..d2070eabaaa6 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/property/color.rs @@ -0,0 +1,35 @@ +use crate::lexer::CssLexContext; +use crate::parser::CssParser; +use biome_css_syntax::CssSyntaxKind::{CSS_COLOR, CSS_COLOR_LITERAL}; +use biome_css_syntax::{TextRange, T}; +use biome_parser::diagnostic::{expected_node, ParseDiagnostic}; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::{Absent, Present}; +use biome_parser::Parser; + +#[inline] +pub(crate) fn is_at_color(p: &mut CssParser) -> bool { + p.at(T![#]) +} +#[inline] +pub(crate) fn parse_color(p: &mut CssParser) -> ParsedSyntax { + if !is_at_color(p) { + return Absent; + } + + let m = p.start(); + let hash_range = p.cur_range(); + p.bump_with_context(T![#], CssLexContext::Color); + + if !p.eat(CSS_COLOR_LITERAL) { + p.error(expected_color(p, hash_range)); + } + + Present(m.complete(p, CSS_COLOR)) +} + +/// Generates a parse diagnostic for an expected "color" error message at the given range. +pub(crate) fn expected_color(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expected_node("color", range, p) + .with_hint("Ensure the color is specified in a valid hexadecimal format. Examples: #000, #000f, #ffffff, #ffffffff") +} diff --git a/crates/biome_css_parser/src/syntax/property/mod.rs b/crates/biome_css_parser/src/syntax/property/mod.rs index b94377bd4399..36e1fe69404b 100644 --- a/crates/biome_css_parser/src/syntax/property/mod.rs +++ b/crates/biome_css_parser/src/syntax/property/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod color; pub(crate) mod unicode_range; use crate::lexer::CssLexContext; diff --git a/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css new file mode 100644 index 000000000000..17a1975f8ead --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css @@ -0,0 +1,3 @@ +.formTable tbody td { + border-left: 1px # solid; +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css.snap new file mode 100644 index 000000000000..08dcb4ecf751 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css.snap @@ -0,0 +1,181 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +.formTable tbody td { + border-left: 1px # solid; +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssComplexSelector { + left: CssComplexSelector { + left: CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssClassSelector { + dot_token: DOT@0..1 "." [] [], + name: CssCustomIdentifier { + value_token: IDENT@1..10 "formTable" [] [], + }, + }, + ], + }, + combinator: CSS_SPACE_LITERAL@10..11 " " [] [], + right: CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: CssTypeSelector { + namespace: missing (optional), + ident: CssIdentifier { + value_token: IDENT@11..16 "tbody" [] [], + }, + }, + sub_selectors: CssSubSelectorList [], + }, + }, + combinator: CSS_SPACE_LITERAL@16..17 " " [] [], + right: CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: CssTypeSelector { + namespace: missing (optional), + ident: CssIdentifier { + value_token: IDENT@17..20 "td" [] [Whitespace(" ")], + }, + }, + sub_selectors: CssSubSelectorList [], + }, + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@20..21 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@21..34 "border-left" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@34..36 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@36..37 "1" [] [], + unit_token: IDENT@37..40 "px" [] [Whitespace(" ")], + }, + CssColor { + hash_token: HASH@40..42 "#" [] [Whitespace(" ")], + value_token: missing (required), + }, + CssIdentifier { + value_token: IDENT@42..47 "solid" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@47..48 ";" [] [], + }, + ], + r_curly_token: R_CURLY@48..50 "}" [Newline("\n")] [], + }, + }, + ], + eof_token: EOF@50..51 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..51 + 0: (empty) + 1: CSS_RULE_LIST@0..50 + 0: CSS_QUALIFIED_RULE@0..50 + 0: CSS_SELECTOR_LIST@0..20 + 0: CSS_COMPLEX_SELECTOR@0..20 + 0: CSS_COMPLEX_SELECTOR@0..16 + 0: CSS_COMPOUND_SELECTOR@0..10 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@0..10 + 0: CSS_CLASS_SELECTOR@0..10 + 0: DOT@0..1 "." [] [] + 1: CSS_CUSTOM_IDENTIFIER@1..10 + 0: IDENT@1..10 "formTable" [] [] + 1: CSS_SPACE_LITERAL@10..11 " " [] [] + 2: CSS_COMPOUND_SELECTOR@11..16 + 0: (empty) + 1: CSS_TYPE_SELECTOR@11..16 + 0: (empty) + 1: CSS_IDENTIFIER@11..16 + 0: IDENT@11..16 "tbody" [] [] + 2: CSS_SUB_SELECTOR_LIST@16..16 + 1: CSS_SPACE_LITERAL@16..17 " " [] [] + 2: CSS_COMPOUND_SELECTOR@17..20 + 0: (empty) + 1: CSS_TYPE_SELECTOR@17..20 + 0: (empty) + 1: CSS_IDENTIFIER@17..20 + 0: IDENT@17..20 "td" [] [Whitespace(" ")] + 2: CSS_SUB_SELECTOR_LIST@20..20 + 1: CSS_DECLARATION_OR_RULE_BLOCK@20..50 + 0: L_CURLY@20..21 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@21..48 + 0: CSS_DECLARATION_WITH_SEMICOLON@21..48 + 0: CSS_DECLARATION@21..47 + 0: CSS_GENERIC_PROPERTY@21..47 + 0: CSS_IDENTIFIER@21..34 + 0: IDENT@21..34 "border-left" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@34..36 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@36..47 + 0: CSS_REGULAR_DIMENSION@36..40 + 0: CSS_NUMBER_LITERAL@36..37 "1" [] [] + 1: IDENT@37..40 "px" [] [Whitespace(" ")] + 1: CSS_COLOR@40..42 + 0: HASH@40..42 "#" [] [Whitespace(" ")] + 1: (empty) + 2: CSS_IDENTIFIER@42..47 + 0: IDENT@42..47 "solid" [] [] + 1: (empty) + 1: SEMICOLON@47..48 ";" [] [] + 2: R_CURLY@48..50 "}" [Newline("\n")] [] + 2: EOF@50..51 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +color_error.css:2:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a color but instead found '#'. + + 1 │ .formTable tbody td { + > 2 │ border-left: 1px # solid; + │ ^ + 3 │ } + 4 │ + + i Expected a color here. + + 1 │ .formTable tbody td { + > 2 │ border-left: 1px # solid; + │ ^ + 3 │ } + 4 │ + + i Ensure the color is specified in a valid hexadecimal format. Examples: #000, #000f, #ffffff, #ffffffff + +``` diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index 781507ab175c..65c60978beeb 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -174,8 +174,9 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" -@font-face { color: U+0100-024F; } - +.formTable tbody td { + border-left: 1px # solid; +} "#; let root = parse_css(