diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 31950ed63b53..5eb597db92ba 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -24,8 +24,10 @@ use biome_parser::parse_recovery::{ParseRecovery, ParseRecoveryTokenSet, Recover use biome_parser::prelude::ParsedSyntax; use biome_parser::prelude::ParsedSyntax::{Absent, Present}; use biome_parser::{token_set, Parser}; +use biome_parser::diagnostic::expected_token; use value::dimension::{is_at_any_dimension, parse_any_dimension}; use value::function::{is_at_any_function, parse_any_function}; +use crate::syntax::property::color::{is_at_color, parse_color}; use self::parse_error::{expected_component_value, expected_declaration_item}; @@ -287,21 +289,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..dd302eb049d9 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/property/color.rs @@ -0,0 +1,34 @@ +use biome_css_syntax::CssSyntaxKind::{CSS_COLOR, CSS_COLOR_LITERAL}; +use biome_css_syntax::{T, TextRange}; +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; +use crate::lexer::CssLexContext; +use crate::parser::CssParser; + +#[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); + + if !p.eat(CSS_COLOR_LITERAL) { + p.error(expected_color(p, p.cur_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 (#RRGGBB or #RGB). 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..c2e02e7814ea 100644 --- a/crates/biome_css_parser/src/syntax/property/mod.rs +++ b/crates/biome_css_parser/src/syntax/property/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod unicode_range; +pub(crate) mod color; use crate::lexer::CssLexContext; use crate::parser::CssParser; 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.new b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css.snap.new new file mode 100644 index 000000000000..993d7bcae29e --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/color_error.css.snap.new @@ -0,0 +1,182 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 169 +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:21 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a color but instead found 'solid'. + + 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 (#RRGGBB or #RGB). 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(