diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 85e7108428d4..92a0cc76d596 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -288,6 +288,15 @@ pub fn css_declaration_important( ], )) } +pub fn css_font_face_at_rule(font_face_token: SyntaxToken, block: CssBlock) -> CssFontFaceAtRule { + CssFontFaceAtRule::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_FONT_FACE_AT_RULE, + [ + Some(SyntaxElement::Token(font_face_token)), + Some(SyntaxElement::Node(block.into_syntax())), + ], + )) +} pub fn css_id_selector(hash_token: SyntaxToken, name: CssIdentifier) -> CssIdSelector { CssIdSelector::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_ID_SELECTOR, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index eea248db2d41..28f064d94c7f 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -500,6 +500,32 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_DECLARATION_IMPORTANT, children) } + CSS_FONT_FACE_AT_RULE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T![font_face] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if CssBlock::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_FONT_FACE_AT_RULE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_FONT_FACE_AT_RULE, children) + } CSS_ID_SELECTOR => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index fedf32df8e04..c35423f4cc68 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -770,6 +770,7 @@ impl<'src> CssLexer<'src> { b"charset" => CHARSET_KW, b"color-profile" => COLOR_PROFILE_KW, b"counter-style" => COUNTER_STYLE_KW, + b"font-face" => FONT_FACE_KW, _ => IDENT, } } diff --git a/crates/biome_css_parser/src/syntax/at_rule/font_face.rs b/crates/biome_css_parser/src/syntax/at_rule/font_face.rs new file mode 100644 index 000000000000..d657968ce397 --- /dev/null +++ b/crates/biome_css_parser/src/syntax/at_rule/font_face.rs @@ -0,0 +1,29 @@ +use crate::parser::CssParser; +use crate::syntax::parse_or_recover_rule_block; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::T; +use biome_parser::parsed_syntax::ParsedSyntax::Present; +use biome_parser::prelude::ParsedSyntax::Absent; +use biome_parser::prelude::*; + +#[inline] +pub(crate) fn is_at_font_face_at_rule(p: &mut CssParser) -> bool { + p.at(T![font_face]) +} + +#[inline] +pub(crate) fn parse_font_face_at_rule(p: &mut CssParser) -> ParsedSyntax { + if !is_at_font_face_at_rule(p) { + return Absent; + } + + let m = p.start(); + + p.bump(T![font_face]); + + if parse_or_recover_rule_block(p).is_err() { + return Present(m.complete(p, CSS_BOGUS_AT_RULE)); + } + + Present(m.complete(p, CSS_FONT_FACE_AT_RULE)) +} diff --git a/crates/biome_css_parser/src/syntax/at_rule/mod.rs b/crates/biome_css_parser/src/syntax/at_rule/mod.rs index 0cbb04c5e126..58c4dc5797c2 100644 --- a/crates/biome_css_parser/src/syntax/at_rule/mod.rs +++ b/crates/biome_css_parser/src/syntax/at_rule/mod.rs @@ -1,6 +1,7 @@ mod charset; mod color_profile; mod counter_style; +mod font_face; use crate::parser::CssParser; use crate::syntax::at_rule::charset::{is_at_charset_at_rule, parse_charset_at_rule}; @@ -10,6 +11,7 @@ use crate::syntax::at_rule::color_profile::{ use crate::syntax::at_rule::counter_style::{ is_at_counter_style_at_rule, parse_counter_style_at_rule, }; +use crate::syntax::at_rule::font_face::{is_at_font_face_at_rule, parse_font_face_at_rule}; use crate::syntax::parse_error::expected_any_at_rule; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::T; @@ -51,6 +53,8 @@ pub(crate) fn parse_any_at_rule(p: &mut CssParser) -> ParsedSyntax { parse_color_profile_at_rule(p) } else if is_at_counter_style_at_rule(p) { parse_counter_style_at_rule(p) + } else if is_at_font_face_at_rule(p) { + parse_font_face_at_rule(p) } else { Absent } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css new file mode 100644 index 000000000000..e22822a84e4b --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css @@ -0,0 +1,3 @@ +@font-face foo {} +@font-face foo; +@font-face ; \ No newline at end of file diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap new file mode 100644 index 000000000000..e43e7d9f23a8 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_face_error.css.snap @@ -0,0 +1,220 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@font-face foo {} +@font-face foo; +@font-face ; +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssBogusAtRule { + items: [ + FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")], + ], + }, + }, + CssRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: CssTypeSelector { + namespace: missing (optional), + ident: CssIdentifier { + value_token: IDENT@11..15 "foo" [] [Whitespace(" ")], + }, + }, + sub_selectors: CssSubSelectorList [], + }, + ], + block: CssBlock { + l_curly_token: L_CURLY@15..16 "{" [] [], + declaration_list: CssDeclarationList [], + r_curly_token: R_CURLY@16..17 "}" [] [], + }, + }, + CssAtRule { + at_token: AT@17..19 "@" [Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + FONT_FACE_KW@19..29 "font-face" [] [Whitespace(" ")], + ], + }, + }, + CssBogusRule { + items: [ + CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: CssTypeSelector { + namespace: missing (optional), + ident: CssIdentifier { + value_token: IDENT@29..32 "foo" [] [], + }, + }, + sub_selectors: CssSubSelectorList [], + }, + missing separator, + CssBogusSelector { + items: [ + SEMICOLON@32..33 ";" [] [], + ], + }, + ], + ], + }, + CssAtRule { + at_token: AT@33..35 "@" [Newline("\n")] [], + rule: CssBogusAtRule { + items: [ + FONT_FACE_KW@35..45 "font-face" [] [Whitespace(" ")], + CssBogusBody { + items: [ + SEMICOLON@45..46 ";" [] [], + ], + }, + ], + }, + }, + ], + eof_token: EOF@46..46 "" [] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..46 + 0: (empty) + 1: CSS_RULE_LIST@0..46 + 0: CSS_AT_RULE@0..11 + 0: AT@0..1 "@" [] [] + 1: CSS_BOGUS_AT_RULE@1..11 + 0: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")] + 1: CSS_RULE@11..17 + 0: CSS_SELECTOR_LIST@11..15 + 0: CSS_COMPOUND_SELECTOR@11..15 + 0: (empty) + 1: CSS_TYPE_SELECTOR@11..15 + 0: (empty) + 1: CSS_IDENTIFIER@11..15 + 0: IDENT@11..15 "foo" [] [Whitespace(" ")] + 2: CSS_SUB_SELECTOR_LIST@15..15 + 1: CSS_BLOCK@15..17 + 0: L_CURLY@15..16 "{" [] [] + 1: CSS_DECLARATION_LIST@16..16 + 2: R_CURLY@16..17 "}" [] [] + 2: CSS_AT_RULE@17..29 + 0: AT@17..19 "@" [Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@19..29 + 0: FONT_FACE_KW@19..29 "font-face" [] [Whitespace(" ")] + 3: CSS_BOGUS_RULE@29..33 + 0: CSS_SELECTOR_LIST@29..33 + 0: CSS_COMPOUND_SELECTOR@29..32 + 0: (empty) + 1: CSS_TYPE_SELECTOR@29..32 + 0: (empty) + 1: CSS_IDENTIFIER@29..32 + 0: IDENT@29..32 "foo" [] [] + 2: CSS_SUB_SELECTOR_LIST@32..32 + 1: (empty) + 2: CSS_BOGUS_SELECTOR@32..33 + 0: SEMICOLON@32..33 ";" [] [] + 4: CSS_AT_RULE@33..46 + 0: AT@33..35 "@" [Newline("\n")] [] + 1: CSS_BOGUS_AT_RULE@35..46 + 0: FONT_FACE_KW@35..45 "font-face" [] [Whitespace(" ")] + 1: CSS_BOGUS_BODY@45..46 + 0: SEMICOLON@45..46 ";" [] [] + 2: EOF@46..46 "" [] [] + +``` + +## Diagnostics + +``` +at_rule_font_face_error.css:1:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a body but instead found 'foo'. + + > 1 │ @font-face foo {} + │ ^^^ + 2 │ @font-face foo; + 3 │ @font-face ; + + i Expected a body here. + + > 1 │ @font-face foo {} + │ ^^^ + 2 │ @font-face foo; + 3 │ @font-face ; + +at_rule_font_face_error.css:2:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a body but instead found 'foo'. + + 1 │ @font-face foo {} + > 2 │ @font-face foo; + │ ^^^ + 3 │ @font-face ; + + i Expected a body here. + + 1 │ @font-face foo {} + > 2 │ @font-face foo; + │ ^^^ + 3 │ @font-face ; + +at_rule_font_face_error.css:2:15 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `;` + + 1 │ @font-face foo {} + > 2 │ @font-face foo; + │ ^ + 3 │ @font-face ; + + i Remove ; + +at_rule_font_face_error.css:3:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `@` + + 1 │ @font-face foo {} + 2 │ @font-face foo; + > 3 │ @font-face ; + │ ^ + + i Remove @ + +at_rule_font_face_error.css:3:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a body but instead found ';'. + + 1 │ @font-face foo {} + 2 │ @font-face foo; + > 3 │ @font-face ; + │ ^ + + i Expected a body here. + + 1 │ @font-face foo {} + 2 │ @font-face foo; + > 3 │ @font-face ; + │ ^ + +``` + + diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css new file mode 100644 index 000000000000..80d4821ead4e --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css @@ -0,0 +1 @@ +@font-face {} \ No newline at end of file diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css.snap new file mode 100644 index 000000000000..5bfbae040464 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_font_face.css.snap @@ -0,0 +1,53 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@font-face {} +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtRule { + at_token: AT@0..1 "@" [] [], + rule: CssFontFaceAtRule { + font_face_token: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")], + block: CssBlock { + l_curly_token: L_CURLY@11..12 "{" [] [], + declaration_list: CssDeclarationList [], + r_curly_token: R_CURLY@12..13 "}" [] [], + }, + }, + }, + ], + eof_token: EOF@13..13 "" [] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..13 + 0: (empty) + 1: CSS_RULE_LIST@0..13 + 0: CSS_AT_RULE@0..13 + 0: AT@0..1 "@" [] [] + 1: CSS_FONT_FACE_AT_RULE@1..13 + 0: FONT_FACE_KW@1..11 "font-face" [] [Whitespace(" ")] + 1: CSS_BLOCK@11..13 + 0: L_CURLY@11..12 "{" [] [] + 1: CSS_DECLARATION_LIST@12..12 + 2: R_CURLY@12..13 "}" [] [] + 2: EOF@13..13 "" [] [] + +``` + + diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 7bb13732819b..e3d8f0bb961a 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -239,6 +239,7 @@ pub enum CssSyntaxKind { FROM_KW, TO_KW, VAR_KW, + FONT_FACE_KW, CSS_STRING_LITERAL, CSS_NUMBER_LITERAL, CSS_CUSTOM_PROPERTY, @@ -321,6 +322,7 @@ pub enum CssSyntaxKind { CSS_CHARSET_AT_RULE, CSS_COLOR_PROFILE_AT_RULE, CSS_COUNTER_STYLE_AT_RULE, + CSS_FONT_FACE_AT_RULE, CSS_KEYFRAMES_AT_RULE, CSS_KEYFRAMES_BODY, CSS_MEDIA_AT_RULE, @@ -577,6 +579,7 @@ impl CssSyntaxKind { "from" => FROM_KW, "to" => TO_KW, "var" => VAR_KW, + "font_face" => FONT_FACE_KW, _ => return None, }; Some(kw) @@ -809,6 +812,7 @@ impl CssSyntaxKind { FROM_KW => "from", TO_KW => "to", VAR_KW => "var", + FONT_FACE_KW => "font_face", CSS_STRING_LITERAL => "string literal", _ => return None, }; @@ -817,4 +821,4 @@ impl CssSyntaxKind { } #[doc = r" Utility macro for creating a SyntaxKind through simple macro syntax"] #[macro_export] -macro_rules ! T { [;] => { $ crate :: CssSyntaxKind :: SEMICOLON } ; [,] => { $ crate :: CssSyntaxKind :: COMMA } ; ['('] => { $ crate :: CssSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: CssSyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: CssSyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: CssSyntaxKind :: R_CURLY } ; ['['] => { $ crate :: CssSyntaxKind :: L_BRACK } ; [']'] => { $ crate :: CssSyntaxKind :: R_BRACK } ; [<] => { $ crate :: CssSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: CssSyntaxKind :: R_ANGLE } ; [~] => { $ crate :: CssSyntaxKind :: TILDE } ; [#] => { $ crate :: CssSyntaxKind :: HASH } ; [&] => { $ crate :: CssSyntaxKind :: AMP } ; [|] => { $ crate :: CssSyntaxKind :: PIPE } ; [||] => { $ crate :: CssSyntaxKind :: PIPE2 } ; [+] => { $ crate :: CssSyntaxKind :: PLUS } ; [*] => { $ crate :: CssSyntaxKind :: STAR } ; [/] => { $ crate :: CssSyntaxKind :: SLASH } ; [^] => { $ crate :: CssSyntaxKind :: CARET } ; [%] => { $ crate :: CssSyntaxKind :: PERCENT } ; [.] => { $ crate :: CssSyntaxKind :: DOT } ; [:] => { $ crate :: CssSyntaxKind :: COLON } ; [::] => { $ crate :: CssSyntaxKind :: COLON2 } ; [=] => { $ crate :: CssSyntaxKind :: EQ } ; [!] => { $ crate :: CssSyntaxKind :: BANG } ; [!=] => { $ crate :: CssSyntaxKind :: NEQ } ; [-] => { $ crate :: CssSyntaxKind :: MINUS } ; [<=] => { $ crate :: CssSyntaxKind :: LTEQ } ; [>=] => { $ crate :: CssSyntaxKind :: GTEQ } ; [+=] => { $ crate :: CssSyntaxKind :: PLUSEQ } ; [|=] => { $ crate :: CssSyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: CssSyntaxKind :: AMPEQ } ; [^=] => { $ crate :: CssSyntaxKind :: CARETEQ } ; [/=] => { $ crate :: CssSyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: CssSyntaxKind :: STAREQ } ; [%=] => { $ crate :: CssSyntaxKind :: PERCENTEQ } ; [@] => { $ crate :: CssSyntaxKind :: AT } ; ["$="] => { $ crate :: CssSyntaxKind :: DOLLAR_EQ } ; [~=] => { $ crate :: CssSyntaxKind :: TILDE_EQ } ; [-->] => { $ crate :: CssSyntaxKind :: CDC } ; [] => { $ crate :: CssSyntaxKind :: CDC } ; [