From aed440aa065b6db94ea39812b93bcc0aa4659cf3 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Tue, 28 Nov 2023 23:18:53 +0200 Subject: [PATCH 1/2] feat(css_parser): CSS Parser charset #268 --- .../src/generated/node_factory.rs | 26 ++ .../src/generated/syntax_factory.rs | 41 +++ crates/biome_css_parser/src/lexer/mod.rs | 2 + .../src/syntax/at_rule/charset.rs | 71 +++++ .../src/syntax/at_rule/mod.rs | 25 ++ crates/biome_css_parser/src/syntax/mod.rs | 12 +- .../src/syntax/parse_error.rs | 31 +++ .../src/syntax/selector/attribute.rs | 4 +- .../src/syntax/selector/mod.rs | 6 +- .../pseudo_class/function_value_list.rs | 4 +- .../src/syntax/selector/pseudo_class/mod.rs | 10 +- .../error/at_rule/at_rule_charset_error.css | 7 + .../at_rule/at_rule_charset_error.css.snap | 257 ++++++++++++++++++ .../ok/at_rule/at_rule_charset.css | 2 + .../ok/at_rule/at_rule_charset.css.snap | 64 +++++ crates/biome_css_parser/tests/spec_test.rs | 3 +- crates/biome_css_syntax/src/generated/kind.rs | 7 +- .../biome_css_syntax/src/generated/macros.rs | 8 + .../biome_css_syntax/src/generated/nodes.rs | 204 +++++++++++++- .../src/generated/nodes_mut.rs | 26 ++ crates/biome_css_syntax/src/lib.rs | 2 + xtask/codegen/css.ungram | 8 + xtask/codegen/src/css_kinds_src.rs | 4 + 23 files changed, 809 insertions(+), 15 deletions(-) create mode 100644 crates/biome_css_parser/src/syntax/at_rule/charset.rs create mode 100644 crates/biome_css_parser/src/syntax/at_rule/mod.rs create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css.snap diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index c391ea2fbc4d..810461861f32 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -13,6 +13,22 @@ pub fn css_any_function(css_simple_function: CssSimpleFunction) -> CssAnyFunctio [Some(SyntaxElement::Node(css_simple_function.into_syntax()))], )) } +pub fn css_at_charset_rule( + at_token: SyntaxToken, + charset_token: SyntaxToken, + encoding: CssString, + semicolon_token: SyntaxToken, +) -> CssAtCharsetRule { + CssAtCharsetRule::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_AT_CHARSET_RULE, + [ + Some(SyntaxElement::Token(at_token)), + Some(SyntaxElement::Token(charset_token)), + Some(SyntaxElement::Node(encoding.into_syntax())), + Some(SyntaxElement::Token(semicolon_token)), + ], + )) +} pub fn css_at_keyframes( at_token: SyntaxToken, keyframes_token: SyntaxToken, @@ -1273,6 +1289,16 @@ where { CssBogus::unwrap_cast(SyntaxNode::new_detached(CssSyntaxKind::CSS_BOGUS, slots)) } +pub fn css_bogus_at_rule(slots: I) -> CssBogusAtRule +where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, +{ + CssBogusAtRule::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_BOGUS_AT_RULE, + slots, + )) +} pub fn css_bogus_body(slots: I) -> CssBogusBody where I: IntoIterator>, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index 3b9897579495..f658c3e10e45 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -15,6 +15,7 @@ impl SyntaxFactory for CssSyntaxFactory { ) -> RawSyntaxNode { match kind { CSS_BOGUS + | CSS_BOGUS_AT_RULE | CSS_BOGUS_BODY | CSS_BOGUS_PSEUDO_CLASS | CSS_BOGUS_PSEUDO_ELEMENT @@ -40,6 +41,46 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_ANY_FUNCTION, children) } + CSS_AT_CHARSET_RULE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<4usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [@] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T![charset] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if CssString::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T ! [;] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + CSS_AT_CHARSET_RULE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_AT_CHARSET_RULE, children) + } CSS_AT_KEYFRAMES => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<5usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_parser/src/lexer/mod.rs b/crates/biome_css_parser/src/lexer/mod.rs index 2bbd979e1193..fb5fdd9de3bf 100644 --- a/crates/biome_css_parser/src/lexer/mod.rs +++ b/crates/biome_css_parser/src/lexer/mod.rs @@ -483,6 +483,7 @@ impl<'src> CssLexer<'src> { CRT => self.consume_ctr(), COL => self.consume_col(), AT_ => self.consume_byte(T![@]), + SEM => self.consume_byte(T![;]), HAS => self.consume_byte(T![#]), PNO => self.consume_byte(T!['(']), PNC => self.consume_byte(T![')']), @@ -764,6 +765,7 @@ impl<'src> CssLexer<'src> { b"nth-last-col" => NTHLASTCOL_KW, b"ltr" => LTR_KW, b"rtl" => RTL_KW, + b"charset" => CHARSET_KW, _ => IDENT, } } diff --git a/crates/biome_css_parser/src/syntax/at_rule/charset.rs b/crates/biome_css_parser/src/syntax/at_rule/charset.rs new file mode 100644 index 000000000000..aed2e64692ab --- /dev/null +++ b/crates/biome_css_parser/src/syntax/at_rule/charset.rs @@ -0,0 +1,71 @@ +use crate::parser::CssParser; +use crate::syntax::parse_error::expected_string; +use crate::syntax::parse_string; +use biome_css_syntax::CssSyntaxKind::*; +use biome_css_syntax::{CssSyntaxKind, TextRange, T}; +use biome_parser::parse_recovery::ParseRecovery; +use biome_parser::parsed_syntax::ParsedSyntax::Present; +use biome_parser::prelude::ParsedSyntax::Absent; +use biome_parser::prelude::*; +use biome_rowan::SyntaxKind; + +#[inline] +pub(crate) fn is_at_charset_rule(p: &mut CssParser) -> bool { + p.at(T![@]) && p.nth_at(1, T![charset]) +} + +#[inline] +pub(crate) fn parse_at_charset_rule(p: &mut CssParser) -> ParsedSyntax { + if !is_at_charset_rule(p) { + return Absent; + } + + let m = p.start(); + + p.bump(T![@]); + p.bump(T![charset]); + + let kind = match parse_string(p).or_recover( + p, + &ParseRecovery::new(CSS_BOGUS, CHARTSET_RECOVERY_SET).enable_recovery_on_line_break(), + expected_string, + ) { + Ok(encoding) if !encoding.kind(p).is_bogus() => { + if eat_or_recover_close_token(p, encoding) { + CSS_AT_CHARSET_RULE + } else { + CSS_BOGUS_AT_RULE + } + } + _ => { + p.expect(T![;]); + CSS_BOGUS_AT_RULE + } + }; + + Present(m.complete(p, kind)) +} + +const CHARTSET_RECOVERY_SET: TokenSet = token_set![T![;]]; + +#[inline] +fn eat_or_recover_close_token(p: &mut CssParser, encoding: CompletedMarker) -> bool { + if p.eat(T![;]) { + true + } else { + if let Ok(m) = ParseRecovery::new(CSS_BOGUS, CHARTSET_RECOVERY_SET) + .enable_recovery_on_line_break() + .recover(p) + { + let diagnostic = expected_string( + p, + TextRange::new(encoding.range(p).start(), m.range(p).end()), + ); + p.error(diagnostic); + } + + p.expect(T![;]); + + false + } +} diff --git a/crates/biome_css_parser/src/syntax/at_rule/mod.rs b/crates/biome_css_parser/src/syntax/at_rule/mod.rs new file mode 100644 index 000000000000..14cdff7724be --- /dev/null +++ b/crates/biome_css_parser/src/syntax/at_rule/mod.rs @@ -0,0 +1,25 @@ +mod charset; + +use crate::parser::CssParser; +use crate::syntax::at_rule::charset::{is_at_charset_rule, parse_at_charset_rule}; +use biome_css_syntax::T; +use biome_parser::prelude::ParsedSyntax::Absent; +use biome_parser::prelude::*; + +#[inline] +pub(crate) fn at_at_rule(p: &mut CssParser) -> bool { + p.at(T![@]) +} + +#[inline] +pub(crate) fn parse_at_rule(p: &mut CssParser) -> ParsedSyntax { + if !at_at_rule(p) { + return Absent; + } + + if is_at_charset_rule(p) { + parse_at_charset_rule(p) + } else { + Absent + } +} diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 50d89db33b09..804d1db211cb 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -1,9 +1,11 @@ +mod at_rule; mod parse_error; mod selector; use crate::lexer::CssLexContext; use crate::parser::CssParser; -use crate::syntax::parse_error::expected_block; +use crate::syntax::at_rule::{at_at_rule, parse_at_rule}; +use crate::syntax::parse_error::{expected_any_at_rule, expected_block}; use crate::syntax::selector::CssSelectorList; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; @@ -36,7 +38,11 @@ pub(crate) fn parse_rule_list(p: &mut CssParser) { while !p.at(EOF) { progress.assert_progressing(p); - parse_rule(p); + if at_at_rule(p) { + parse_at_rule(p).or_add_diagnostic(p, expected_any_at_rule); + } else { + parse_rule(p); + } } rules.complete(p, CSS_RULE_LIST); @@ -120,7 +126,7 @@ pub(crate) fn parse_number(p: &mut CssParser, context: CssLexContext) -> ParsedS } #[inline] -pub(crate) fn parse_css_string(p: &mut CssParser) -> ParsedSyntax { +pub(crate) fn parse_string(p: &mut CssParser) -> ParsedSyntax { if !p.at(CSS_STRING_LITERAL) { return Absent; } diff --git a/crates/biome_css_parser/src/syntax/parse_error.rs b/crates/biome_css_parser/src/syntax/parse_error.rs index 750cb2dc144e..2aab631f08d2 100644 --- a/crates/biome_css_parser/src/syntax/parse_error.rs +++ b/crates/biome_css_parser/src/syntax/parse_error.rs @@ -11,6 +11,10 @@ pub(crate) fn expected_number(p: &CssParser, range: TextRange) -> ParseDiagnosti expected_node("number", range, p) } +pub(crate) fn expected_string(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expected_node("string", range, p) +} + pub(crate) fn expected_any_pseudo_class_nth(p: &CssParser, range: TextRange) -> ParseDiagnostic { expected_any(&["even", "odd", "n", "", "number"], range, p) } @@ -130,6 +134,33 @@ pub(crate) fn expected_any_pseudo_class(p: &CssParser, range: TextRange) -> Pars .into_diagnostic(p) } +pub(crate) fn expected_any_at_rule(p: &CssParser, range: TextRange) -> ParseDiagnostic { + expect_one_of( + &[ + "charset", + "color-profile", + "container", + "counter-style", + "document", + "font-face", + "font-feature-values", + "font-palette-values", + "import", + "keyframes", + "layer", + "media", + "namespace", + "page", + "property", + "supports", + "viewport", + "scope", + ], + range, + ) + .into_diagnostic(p) +} + pub(crate) fn expected_block(p: &CssParser, range: TextRange) -> ParseDiagnostic { expected_node("body", range, p) } diff --git a/crates/biome_css_parser/src/syntax/selector/attribute.rs b/crates/biome_css_parser/src/syntax/selector/attribute.rs index e0a32b620fa3..a98905ffd35b 100644 --- a/crates/biome_css_parser/src/syntax/selector/attribute.rs +++ b/crates/biome_css_parser/src/syntax/selector/attribute.rs @@ -3,7 +3,7 @@ use crate::syntax::parse_error::{ expected_any_attribute_matcher_name, expected_any_attribute_modifier, expected_identifier, }; use crate::syntax::selector::{is_at_namespace, parse_namespace, selector_lex_context}; -use crate::syntax::{is_at_identifier, parse_css_string, parse_regular_identifier}; +use crate::syntax::{is_at_identifier, parse_regular_identifier, parse_string}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::diagnostic::expected_token; @@ -103,7 +103,7 @@ fn parse_attribute_matcher_value(p: &mut CssParser) -> ParsedSyntax { let m = p.start(); if p.at(CSS_STRING_LITERAL) { - parse_css_string(p).ok(); + parse_string(p).ok(); } else { parse_regular_identifier(p).ok(); } diff --git a/crates/biome_css_parser/src/syntax/selector/mod.rs b/crates/biome_css_parser/src/syntax/selector/mod.rs index 4199f82e5e70..fa566fe9303f 100644 --- a/crates/biome_css_parser/src/syntax/selector/mod.rs +++ b/crates/biome_css_parser/src/syntax/selector/mod.rs @@ -338,7 +338,10 @@ where if p.eat_with_context(T![')'], context) { true } else { - if let Ok(m) = ParseRecovery::new(CSS_BOGUS, SELECTOR_FUNCTION_RECOVERY_SET).recover(p) { + if let Ok(m) = ParseRecovery::new(CSS_BOGUS, SELECTOR_FUNCTION_RECOVERY_SET) + .enable_recovery_on_line_break() + .recover(p) + { let diagnostic = error_builder( p, TextRange::new(parameter.range(p).start(), m.range(p).end()), @@ -362,6 +365,7 @@ where let start = p.cur_range().start(); let range = ParseRecovery::new(CSS_BOGUS, SELECTOR_FUNCTION_RECOVERY_SET) + .enable_recovery_on_line_break() .recover(p) .map(|m| m.range(p)) .unwrap_or_else(|_| p.cur_range()); diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs index 3f89bb38b5ff..06e753d4b149 100644 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/function_value_list.rs @@ -1,7 +1,7 @@ use crate::parser::CssParser; use crate::syntax::parse_error::expected_identifier; use crate::syntax::selector::eat_or_recover_selector_function_close_token; -use crate::syntax::{is_at_identifier, parse_css_string, parse_regular_identifier}; +use crate::syntax::{is_at_identifier, parse_regular_identifier, parse_string}; use biome_css_syntax::CssSyntaxKind::*; use biome_css_syntax::{CssSyntaxKind, T}; use biome_parser::parse_lists::ParseSeparatedList; @@ -89,7 +89,7 @@ fn parse_pseudo_value(p: &mut CssParser) -> ParsedSyntax { } if p.at(CSS_STRING_LITERAL) { - parse_css_string(p) + parse_string(p) } else { parse_regular_identifier(p) } diff --git a/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs index 2f833277af43..48d9adaa15c8 100644 --- a/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs +++ b/crates/biome_css_parser/src/syntax/selector/pseudo_class/mod.rs @@ -52,9 +52,13 @@ pub(crate) fn parse_pseudo_class_selector(p: &mut CssParser) -> ParsedSyntax { p.bump(T![:]); - let kind = match parse_pseudo_class(p).or_add_diagnostic(p, expected_any_pseudo_class) { - Some(_) => CSS_PSEUDO_CLASS_SELECTOR, - None => CSS_BOGUS_SUB_SELECTOR, + let kind = if parse_pseudo_class(p) + .or_add_diagnostic(p, expected_any_pseudo_class) + .is_some() + { + CSS_PSEUDO_CLASS_SELECTOR + } else { + CSS_BOGUS_SUB_SELECTOR }; Present(m.complete(p, kind)) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css new file mode 100644 index 000000000000..d3690760c9a4 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css @@ -0,0 +1,7 @@ +@charset dsadsa; +@charset ; +@charset "iso-8859-15" +@charset "UTF-8" 12321321; +@charset "UTF-8" 12321321 +@charset + diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css.snap new file mode 100644 index 000000000000..2f2c0ec722ba --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_charset_error.css.snap @@ -0,0 +1,257 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@charset dsadsa; +@charset ; +@charset "iso-8859-15" +@charset "UTF-8" 12321321; +@charset "UTF-8" 12321321 +@charset + + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssBogusAtRule { + items: [ + AT@0..1 "@" [] [], + CHARSET_KW@1..9 "charset" [] [Whitespace(" ")], + CssBogus { + items: [ + IDENT@9..15 "dsadsa" [] [], + ], + }, + SEMICOLON@15..16 ";" [] [], + ], + }, + CssBogusAtRule { + items: [ + AT@16..18 "@" [Newline("\n")] [], + CHARSET_KW@18..26 "charset" [] [Whitespace(" ")], + SEMICOLON@26..27 ";" [] [], + ], + }, + CssBogusAtRule { + items: [ + AT@27..29 "@" [Newline("\n")] [], + CHARSET_KW@29..37 "charset" [] [Whitespace(" ")], + CssString { + value_token: CSS_STRING_LITERAL@37..50 "\"iso-8859-15\"" [] [], + }, + ], + }, + CssBogusAtRule { + items: [ + AT@50..52 "@" [Newline("\n")] [], + CHARSET_KW@52..60 "charset" [] [Whitespace(" ")], + CssString { + value_token: CSS_STRING_LITERAL@60..68 "\"UTF-8\"" [] [Whitespace(" ")], + }, + CssBogus { + items: [ + CSS_NUMBER_LITERAL@68..76 "12321321" [] [], + ], + }, + SEMICOLON@76..77 ";" [] [], + ], + }, + CssBogusAtRule { + items: [ + AT@77..79 "@" [Newline("\n")] [], + CHARSET_KW@79..87 "charset" [] [Whitespace(" ")], + CssString { + value_token: CSS_STRING_LITERAL@87..95 "\"UTF-8\"" [] [Whitespace(" ")], + }, + CssBogus { + items: [ + CSS_NUMBER_LITERAL@95..103 "12321321" [] [], + ], + }, + ], + }, + CssBogusAtRule { + items: [ + AT@103..105 "@" [Newline("\n")] [], + CHARSET_KW@105..112 "charset" [] [], + ], + }, + ], + eof_token: EOF@112..114 "" [Newline("\n"), Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..114 + 0: (empty) + 1: CSS_RULE_LIST@0..112 + 0: CSS_BOGUS_AT_RULE@0..16 + 0: AT@0..1 "@" [] [] + 1: CHARSET_KW@1..9 "charset" [] [Whitespace(" ")] + 2: CSS_BOGUS@9..15 + 0: IDENT@9..15 "dsadsa" [] [] + 3: SEMICOLON@15..16 ";" [] [] + 1: CSS_BOGUS_AT_RULE@16..27 + 0: AT@16..18 "@" [Newline("\n")] [] + 1: CHARSET_KW@18..26 "charset" [] [Whitespace(" ")] + 2: SEMICOLON@26..27 ";" [] [] + 2: CSS_BOGUS_AT_RULE@27..50 + 0: AT@27..29 "@" [Newline("\n")] [] + 1: CHARSET_KW@29..37 "charset" [] [Whitespace(" ")] + 2: CSS_STRING@37..50 + 0: CSS_STRING_LITERAL@37..50 "\"iso-8859-15\"" [] [] + 3: CSS_BOGUS_AT_RULE@50..77 + 0: AT@50..52 "@" [Newline("\n")] [] + 1: CHARSET_KW@52..60 "charset" [] [Whitespace(" ")] + 2: CSS_STRING@60..68 + 0: CSS_STRING_LITERAL@60..68 "\"UTF-8\"" [] [Whitespace(" ")] + 3: CSS_BOGUS@68..76 + 0: CSS_NUMBER_LITERAL@68..76 "12321321" [] [] + 4: SEMICOLON@76..77 ";" [] [] + 4: CSS_BOGUS_AT_RULE@77..103 + 0: AT@77..79 "@" [Newline("\n")] [] + 1: CHARSET_KW@79..87 "charset" [] [Whitespace(" ")] + 2: CSS_STRING@87..95 + 0: CSS_STRING_LITERAL@87..95 "\"UTF-8\"" [] [Whitespace(" ")] + 3: CSS_BOGUS@95..103 + 0: CSS_NUMBER_LITERAL@95..103 "12321321" [] [] + 5: CSS_BOGUS_AT_RULE@103..112 + 0: AT@103..105 "@" [Newline("\n")] [] + 1: CHARSET_KW@105..112 "charset" [] [] + 2: EOF@112..114 "" [Newline("\n"), Newline("\n")] [] + +``` + +## Diagnostics + +``` +at_rule_charset_error.css:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string but instead found 'dsadsa'. + + > 1 │ @charset dsadsa; + │ ^^^^^^ + 2 │ @charset ; + 3 │ @charset "iso-8859-15" + + i Expected a string here. + + > 1 │ @charset dsadsa; + │ ^^^^^^ + 2 │ @charset ; + 3 │ @charset "iso-8859-15" + +at_rule_charset_error.css:2:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string but instead found ';'. + + 1 │ @charset dsadsa; + > 2 │ @charset ; + │ ^ + 3 │ @charset "iso-8859-15" + 4 │ @charset "UTF-8" 12321321; + + i Expected a string here. + + 1 │ @charset dsadsa; + > 2 │ @charset ; + │ ^ + 3 │ @charset "iso-8859-15" + 4 │ @charset "UTF-8" 12321321; + +at_rule_charset_error.css:4:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `;` but instead found `@` + + 2 │ @charset ; + 3 │ @charset "iso-8859-15" + > 4 │ @charset "UTF-8" 12321321; + │ ^ + 5 │ @charset "UTF-8" 12321321 + 6 │ @charset + + i Remove @ + +at_rule_charset_error.css:4:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string but instead found '"UTF-8" 12321321'. + + 2 │ @charset ; + 3 │ @charset "iso-8859-15" + > 4 │ @charset "UTF-8" 12321321; + │ ^^^^^^^^^^^^^^^^ + 5 │ @charset "UTF-8" 12321321 + 6 │ @charset + + i Expected a string here. + + 2 │ @charset ; + 3 │ @charset "iso-8859-15" + > 4 │ @charset "UTF-8" 12321321; + │ ^^^^^^^^^^^^^^^^ + 5 │ @charset "UTF-8" 12321321 + 6 │ @charset + +at_rule_charset_error.css:5:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string but instead found '"UTF-8" 12321321'. + + 3 │ @charset "iso-8859-15" + 4 │ @charset "UTF-8" 12321321; + > 5 │ @charset "UTF-8" 12321321 + │ ^^^^^^^^^^^^^^^^ + 6 │ @charset + 7 │ + + i Expected a string here. + + 3 │ @charset "iso-8859-15" + 4 │ @charset "UTF-8" 12321321; + > 5 │ @charset "UTF-8" 12321321 + │ ^^^^^^^^^^^^^^^^ + 6 │ @charset + 7 │ + +at_rule_charset_error.css:6:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `;` but instead found `@` + + 4 │ @charset "UTF-8" 12321321; + 5 │ @charset "UTF-8" 12321321 + > 6 │ @charset + │ ^ + 7 │ + + i Remove @ + +at_rule_charset_error.css:8:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a string but instead found the end of the file. + + 6 │ @charset + 7 │ + > 8 │ + │ + + i Expected a string here. + + 6 │ @charset + 7 │ + > 8 │ + │ + +``` + + diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css new file mode 100644 index 000000000000..8dfd39c45a8f --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css @@ -0,0 +1,2 @@ +@charset "UTF-8"; +@charset "iso-8859-15"; diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css.snap new file mode 100644 index 000000000000..09cc62477cd8 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/at_rule/at_rule_charset.css.snap @@ -0,0 +1,64 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css +@charset "UTF-8"; +@charset "iso-8859-15"; + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssAtCharsetRule { + at_token: AT@0..1 "@" [] [], + charset_token: CHARSET_KW@1..9 "charset" [] [Whitespace(" ")], + encoding: CssString { + value_token: CSS_STRING_LITERAL@9..16 "\"UTF-8\"" [] [], + }, + semicolon_token: SEMICOLON@16..17 ";" [] [], + }, + CssAtCharsetRule { + at_token: AT@17..19 "@" [Newline("\n")] [], + charset_token: CHARSET_KW@19..27 "charset" [] [Whitespace(" ")], + encoding: CssString { + value_token: CSS_STRING_LITERAL@27..40 "\"iso-8859-15\"" [] [], + }, + semicolon_token: SEMICOLON@40..41 ";" [] [], + }, + ], + eof_token: EOF@41..42 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..42 + 0: (empty) + 1: CSS_RULE_LIST@0..41 + 0: CSS_AT_CHARSET_RULE@0..17 + 0: AT@0..1 "@" [] [] + 1: CHARSET_KW@1..9 "charset" [] [Whitespace(" ")] + 2: CSS_STRING@9..16 + 0: CSS_STRING_LITERAL@9..16 "\"UTF-8\"" [] [] + 3: SEMICOLON@16..17 ";" [] [] + 1: CSS_AT_CHARSET_RULE@17..41 + 0: AT@17..19 "@" [Newline("\n")] [] + 1: CHARSET_KW@19..27 "charset" [] [Whitespace(" ")] + 2: CSS_STRING@27..40 + 0: CSS_STRING_LITERAL@27..40 "\"iso-8859-15\"" [] [] + 3: SEMICOLON@40..41 ";" [] [] + 2: EOF@41..42 "" [Newline("\n")] [] + +``` + + diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index 858999be5bfe..707f865d4392 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -134,7 +134,8 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" - |h1 {} + @charset dsadsa; + "#; let root = parse_css( code, diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 318aa144b6cf..d0869a80037d 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -227,6 +227,7 @@ pub enum CssSyntaxKind { NTHLASTOFTYPE_KW, NTHCOL_KW, NTHLASTCOL_KW, + CHARSET_KW, LTR_KW, RTL_KW, N_KW, @@ -324,6 +325,7 @@ pub enum CssSyntaxKind { CSS_ATTRIBUTE_NAME, CSS_ATTRIBUTE_MATCHER, CSS_ATTRIBUTE_MATCHER_VALUE, + CSS_AT_CHARSET_RULE, CSS_BOGUS, CSS_BOGUS_BODY, CSS_BOGUS_RULE, @@ -331,6 +333,7 @@ pub enum CssSyntaxKind { CSS_BOGUS_SUB_SELECTOR, CSS_BOGUS_PSEUDO_CLASS, CSS_BOGUS_PSEUDO_ELEMENT, + CSS_BOGUS_AT_RULE, #[doc(hidden)] __LAST, } @@ -551,6 +554,7 @@ impl CssSyntaxKind { "nthlastoftype" => NTHLASTOFTYPE_KW, "nthcol" => NTHCOL_KW, "nthlastcol" => NTHLASTCOL_KW, + "charset" => CHARSET_KW, "ltr" => LTR_KW, "rtl" => RTL_KW, "n" => N_KW, @@ -780,6 +784,7 @@ impl CssSyntaxKind { NTHLASTOFTYPE_KW => "nthlastoftype", NTHCOL_KW => "nthcol", NTHLASTCOL_KW => "nthlastcol", + CHARSET_KW => "charset", LTR_KW => "ltr", RTL_KW => "rtl", N_KW => "n", @@ -797,4 +802,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 } ; [