From 2a4d3720d758d71bd738765db33b8f1cb7b46740 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:43:49 +0300 Subject: [PATCH] fix(css_parser): fix CSS: Layout using named grid lines #3055 (#3131) --- CHANGELOG.md | 1 + .../src/generated/node_factory.rs | 28 +- .../src/generated/syntax_factory.rs | 38 ++- .../biome_css_formatter/src/css/any/value.rs | 1 + .../src/css/auxiliary/bracketed_value.rs | 24 ++ .../src/css/auxiliary/mod.rs | 1 + .../src/css/lists/bracketed_value_list.rs | 12 + .../biome_css_formatter/src/css/lists/mod.rs | 1 + crates/biome_css_formatter/src/generated.rs | 67 ++++ .../tests/specs/css/properties/grid.css | 11 + .../tests/specs/css/properties/grid.css.snap | 49 +++ .../character_escaping.css.snap | 35 +- .../specs/prettier/css/grid/grid.css.snap | 312 ++++-------------- .../css/variables/apply-rule.css.snap | 3 - crates/biome_css_parser/src/syntax/mod.rs | 79 ++++- .../src/syntax/property/mod.rs | 17 +- ...at_rule_font_feature_values_error.css.snap | 170 +++++----- .../error/property/grid/grid_error.css | 4 + .../error/property/grid/grid_error.css.snap | 260 +++++++++++++++ .../tests/css_test_suite/ok/property/grid.css | 6 + .../css_test_suite/ok/property/grid.css.snap | 274 ++++++++++++++- crates/biome_css_parser/tests/spec_test.rs | 7 +- crates/biome_css_syntax/src/generated/kind.rs | 3 + .../biome_css_syntax/src/generated/macros.rs | 8 + .../biome_css_syntax/src/generated/nodes.rs | 211 +++++++++++- .../src/generated/nodes_mut.rs | 20 ++ crates/biome_css_syntax/src/lib.rs | 2 + xtask/codegen/css.ungram | 23 +- xtask/codegen/src/css_kinds_src.rs | 2 + 29 files changed, 1257 insertions(+), 412 deletions(-) create mode 100644 crates/biome_css_formatter/src/css/auxiliary/bracketed_value.rs create mode 100644 crates/biome_css_formatter/src/css/lists/bracketed_value_list.rs create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/grid.css create mode 100644 crates/biome_css_formatter/tests/specs/css/properties/grid.css.snap create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css create mode 100644 crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b8d05c4c2d..87ec0cd812ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - Fix the bug where whitespace after the & character in CSS nesting was incorrectly trimmed, ensuring proper targeting of child classes [#3061](https://github.com/biomejs/biome/issues/3061). Contributed by @denbezrukov - Fix [#3091](https://github.com/biomejs/biome/issues/3091). Allows the parser to handle nested style rules and at-rules properly, enhancing the parser's compatibility with the CSS Nesting Module. Contributed by @denbezrukov - Fix [#3068](https://github.com/biomejs/biome/issues/3068) where the CSS formatter was inadvertently converting variable declarations and function calls to lowercase. Contributed by @denbezrukov +- Fix [#3055](https://github.com/biomejs/biome/issues/3055) CSS: Layout using named grid lines is now correctly parsed. Contributed by @denbezrukov ### Configuration diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index becf6150776a..c3f9a89d5226 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -129,6 +129,20 @@ pub fn css_binary_expression( ], )) } +pub fn css_bracketed_value( + l_brack_token: SyntaxToken, + items: CssBracketedValueList, + r_brack_token: SyntaxToken, +) -> CssBracketedValue { + CssBracketedValue::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_BRACKETED_VALUE, + [ + Some(SyntaxElement::Token(l_brack_token)), + Some(SyntaxElement::Node(items.into_syntax())), + Some(SyntaxElement::Token(r_brack_token)), + ], + )) +} pub fn css_charset_at_rule( charset_token: SyntaxToken, encoding: CssString, @@ -2194,6 +2208,18 @@ pub fn css_value_at_rule_named_import_specifier( ], )) } +pub fn css_bracketed_value_list(items: I) -> CssBracketedValueList +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + CssBracketedValueList::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_BRACKETED_VALUE_LIST, + items + .into_iter() + .map(|item| Some(item.into_syntax().into())), + )) +} pub fn css_component_value_list(items: I) -> CssComponentValueList where I: IntoIterator, @@ -2241,7 +2267,7 @@ where } pub fn css_custom_identifier_list(items: I) -> CssCustomIdentifierList where - I: IntoIterator, + I: IntoIterator, I::IntoIter: ExactSizeIterator, { CssCustomIdentifierList::unwrap_cast(SyntaxNode::new_detached( diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index d2252d9f803c..af49ccc23691 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -220,6 +220,39 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_BINARY_EXPRESSION, children) } + CSS_BRACKETED_VALUE => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<3usize> = 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 CssBracketedValueList::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_BRACKETED_VALUE.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(CSS_BRACKETED_VALUE, children) + } CSS_CHARSET_AT_RULE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); @@ -4450,6 +4483,9 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_VALUE_AT_RULE_NAMED_IMPORT_SPECIFIER, children) } + CSS_BRACKETED_VALUE_LIST => { + Self::make_node_list_syntax(kind, children, AnyCssCustomIdentifier::can_cast) + } CSS_COMPONENT_VALUE_LIST => { Self::make_node_list_syntax(kind, children, AnyCssValue::can_cast) } @@ -4464,7 +4500,7 @@ impl SyntaxFactory for CssSyntaxFactory { false, ), CSS_CUSTOM_IDENTIFIER_LIST => { - Self::make_node_list_syntax(kind, children, CssCustomIdentifier::can_cast) + Self::make_node_list_syntax(kind, children, AnyCssCustomIdentifier::can_cast) } CSS_DECLARATION_LIST => { Self::make_node_list_syntax(kind, children, CssDeclarationWithSemicolon::can_cast) diff --git a/crates/biome_css_formatter/src/css/any/value.rs b/crates/biome_css_formatter/src/css/any/value.rs index cb27bb21b86d..af0ad47fa6c6 100644 --- a/crates/biome_css_formatter/src/css/any/value.rs +++ b/crates/biome_css_formatter/src/css/any/value.rs @@ -10,6 +10,7 @@ impl FormatRule for FormatAnyCssValue { match node { AnyCssValue::AnyCssDimension(node) => node.format().fmt(f), AnyCssValue::AnyCssFunction(node) => node.format().fmt(f), + AnyCssValue::CssBracketedValue(node) => node.format().fmt(f), AnyCssValue::CssColor(node) => node.format().fmt(f), AnyCssValue::CssCustomIdentifier(node) => node.format().fmt(f), AnyCssValue::CssDashedIdentifier(node) => node.format().fmt(f), diff --git a/crates/biome_css_formatter/src/css/auxiliary/bracketed_value.rs b/crates/biome_css_formatter/src/css/auxiliary/bracketed_value.rs new file mode 100644 index 000000000000..88e69f739005 --- /dev/null +++ b/crates/biome_css_formatter/src/css/auxiliary/bracketed_value.rs @@ -0,0 +1,24 @@ +use crate::prelude::*; +use biome_css_syntax::{CssBracketedValue, CssBracketedValueFields}; +use biome_formatter::write; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssBracketedValue; +impl FormatNodeRule for FormatCssBracketedValue { + fn fmt_fields(&self, node: &CssBracketedValue, f: &mut CssFormatter) -> FormatResult<()> { + let CssBracketedValueFields { + l_brack_token, + items, + r_brack_token, + } = node.as_fields(); + + write!( + f, + [ + l_brack_token.format(), + soft_block_indent(&items.format()), + r_brack_token.format() + ] + ) + } +} diff --git a/crates/biome_css_formatter/src/css/auxiliary/mod.rs b/crates/biome_css_formatter/src/css/auxiliary/mod.rs index c6eb232e7051..efa42ca644f9 100644 --- a/crates/biome_css_formatter/src/css/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/css/auxiliary/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod attribute_matcher; pub(crate) mod attribute_matcher_value; pub(crate) mod attribute_name; pub(crate) mod binary_expression; +pub(crate) mod bracketed_value; pub(crate) mod composes_import_specifier; pub(crate) mod composes_property_value; pub(crate) mod container_and_query; diff --git a/crates/biome_css_formatter/src/css/lists/bracketed_value_list.rs b/crates/biome_css_formatter/src/css/lists/bracketed_value_list.rs new file mode 100644 index 000000000000..d1173e58cb23 --- /dev/null +++ b/crates/biome_css_formatter/src/css/lists/bracketed_value_list.rs @@ -0,0 +1,12 @@ +use crate::prelude::*; +use biome_css_syntax::CssBracketedValueList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatCssBracketedValueList; +impl FormatRule for FormatCssBracketedValueList { + type Context = CssFormatContext; + fn fmt(&self, node: &CssBracketedValueList, f: &mut CssFormatter) -> FormatResult<()> { + f.join_with(&space()) + .entries(node.iter().formatted()) + .finish() + } +} diff --git a/crates/biome_css_formatter/src/css/lists/mod.rs b/crates/biome_css_formatter/src/css/lists/mod.rs index 3d5910d08484..9eff4ef3725b 100644 --- a/crates/biome_css_formatter/src/css/lists/mod.rs +++ b/crates/biome_css_formatter/src/css/lists/mod.rs @@ -1,5 +1,6 @@ //! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. +pub(crate) mod bracketed_value_list; pub(crate) mod component_value_list; pub(crate) mod composes_class_list; pub(crate) mod compound_selector_list; diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index 5560e8fc0766..ca650dc578e0 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -240,6 +240,46 @@ impl IntoFormat for biome_css_syntax::CssBinaryExpression { ) } } +impl FormatRule + for crate::css::auxiliary::bracketed_value::FormatCssBracketedValue +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::CssBracketedValue, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::CssBracketedValue { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssBracketedValue, + crate::css::auxiliary::bracketed_value::FormatCssBracketedValue, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::auxiliary::bracketed_value::FormatCssBracketedValue::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssBracketedValue { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssBracketedValue, + crate::css::auxiliary::bracketed_value::FormatCssBracketedValue, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::auxiliary::bracketed_value::FormatCssBracketedValue::default(), + ) + } +} impl FormatRule for crate::css::statements::charset_at_rule::FormatCssCharsetAtRule { @@ -5270,6 +5310,33 @@ impl IntoFormat for biome_css_syntax::CssValueAtRuleNamedImpor FormatOwnedWithRule :: new (self , crate :: css :: auxiliary :: value_at_rule_named_import_specifier :: FormatCssValueAtRuleNamedImportSpecifier :: default ()) } } +impl AsFormat for biome_css_syntax::CssBracketedValueList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::CssBracketedValueList, + crate::css::lists::bracketed_value_list::FormatCssBracketedValueList, + >; + fn format(&self) -> Self::Format<'_> { + #![allow(clippy::default_constructed_unit_structs)] + FormatRefWithRule::new( + self, + crate::css::lists::bracketed_value_list::FormatCssBracketedValueList::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::CssBracketedValueList { + type Format = FormatOwnedWithRule< + biome_css_syntax::CssBracketedValueList, + crate::css::lists::bracketed_value_list::FormatCssBracketedValueList, + >; + fn into_format(self) -> Self::Format { + #![allow(clippy::default_constructed_unit_structs)] + FormatOwnedWithRule::new( + self, + crate::css::lists::bracketed_value_list::FormatCssBracketedValueList::default(), + ) + } +} impl AsFormat for biome_css_syntax::CssComponentValueList { type Format<'a> = FormatRefWithRule< 'a, diff --git a/crates/biome_css_formatter/tests/specs/css/properties/grid.css b/crates/biome_css_formatter/tests/specs/css/properties/grid.css new file mode 100644 index 000000000000..8496e5744b63 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/properties/grid.css @@ -0,0 +1,11 @@ +.chat-image { + grid-row: span 2 / span 2; + align-self: flex-end +} + + +#grid { + display: grid; + grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; + grid-template-rows: [first header-start] 50px [main-start] 1fr [footer-start] 50px [last]; +} diff --git a/crates/biome_css_formatter/tests/specs/css/properties/grid.css.snap b/crates/biome_css_formatter/tests/specs/css/properties/grid.css.snap new file mode 100644 index 000000000000..34d678b278be --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/properties/grid.css.snap @@ -0,0 +1,49 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: css/properties/grid.css +--- +# Input + +```css +.chat-image { + grid-row: span 2 / span 2; + align-self: flex-end +} + + +#grid { + display: grid; + grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; + grid-template-rows: [first header-start] 50px [main-start] 1fr [footer-start] 50px [last]; +} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +.chat-image { + grid-row: span 2 / span 2; + align-self: flex-end; +} + +#grid { + display: grid; + grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; + grid-template-rows: [first header-start] 50px [main-start] 1fr [footer-start] + 50px [last]; +} +``` 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 19353a345def..34eb64b7f75b 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 @@ -2,7 +2,6 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: css/character-escaping/character_escaping.css --- - # Input ```css @@ -107,14 +106,6 @@ info: css/character-escaping/character_escaping.css } #\[attr\=value\] { } -@@ -94,5 +87,6 @@ - } - - .grid { -- grid-template-rows: [row-1-00\:00] auto; -+ grid-template-rows: -+ [row-1-00\:00] auto; - } ``` # Output @@ -209,8 +200,7 @@ info: css/character-escaping/character_escaping.css } .grid { - grid-template-rows: - [row-1-00\:00] auto; + grid-template-rows: [row-1-00\:00] auto; } ``` @@ -389,27 +379,6 @@ character_escaping.css:7:2 parse ━━━━━━━━━━━━━━━ 8 │ #\? {} 9 │ #\@ {} -character_escaping.css:57:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 55 │ .grid { - 56 │ grid-template-rows: - > 57 │ [row-1-00\:00] auto; - │ ^^^^^^^^^^^^^^^^^^^ - 58 │ } - 59 │ - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - ``` @@ -417,5 +386,3 @@ character_escaping.css:57:5 parse ━━━━━━━━━━━━━━━ ``` 26: #\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\. { ``` - - diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/grid/grid.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/grid/grid.css.snap index cc36ca18c735..7090fadf2f56 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/grid/grid.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/grid/grid.css.snap @@ -128,7 +128,7 @@ div { ```diff --- Prettier +++ Biome -@@ -1,40 +1,33 @@ +@@ -1,40 +1,26 @@ /* quotes */ div { - grid-template-areas: @@ -140,24 +140,22 @@ div { /* numbers */ div { - grid-template-columns: +- grid-template-columns: - [full-start] minmax(1.5em, 1fr) - [main-start] minmax(0.4ch, 75ch) - [main-end] minmax(1em, 1fr) - [full-end]; -+ [full-start] minmax(1.50em, 1fr) -+ [main-start] minmax(.40ch, 75ch) -+ [main-end] minmax(1em, 1.000fr) -+ [full-end]; ++ grid-template-columns: [full-start] minmax(1.50em, 1fr) [main-start] ++ minmax(.40ch, 75ch) [main-end] minmax(1em, 1.000fr) [full-end]; } /* casing */ div { - grid: -+ GRID: - [top] 1fr - [middle] 1fr - bottom; +- [top] 1fr +- [middle] 1fr +- bottom; ++ grid: [top] 1fr [middle] 1fr bottom; - grid-template: - "a a a" 200px @@ -174,14 +172,35 @@ div { - "footer footer footer" 25px - / auto 50px auto; + grid: [wide-start] "header header header" 200px [wide-end] -+ "footer footer footer" 25px -+ / auto 50px auto; ++ "footer footer footer" 25px / auto 50px auto; } /** -@@ -65,11 +58,8 @@ - [main-end] minmax(1em, 1fr) - [full-end]; +@@ -47,42 +33,26 @@ + grid-template-columns: 1fr 100px 3em; + grid-template-rows: 1fr 100px 3em; + /* template rows/columns with named grid lines */ +- grid-template-columns: +- [wide-start] 1fr +- [main-start] 500px +- [main-end] 1fr ++ grid-template-columns: [wide-start] 1fr [main-start] 500px [main-end] 1fr + [wide-end]; +- grid-template-rows: +- [top] 1fr +- [middle] 1fr +- [bottom]; ++ grid-template-rows: [top] 1fr [middle] 1fr [bottom]; + /* template rows/columns with functions */ + grid-template-columns: minmax(1em, 1fr) minmax(1em, 80ch) minmax(1em, 1fr); + /* getting really busy with named lines + functions */ +- grid-template-columns: +- [full-start] minmax(1em, 1fr) +- [main-start] minmax(1em, 80ch) +- [main-end] minmax(1em, 1fr) +- [full-end]; ++ grid-template-columns: [full-start] minmax(1em, 1fr) [main-start] ++ minmax(1em, 80ch) [main-end] minmax(1em, 1fr) [full-end]; - grid-template-areas: - "header header header" @@ -193,6 +212,23 @@ div { /* Shorthand for grid-template-rows, grid-template-columns, and grid-template areas. In one. This can get really crazy. */ +- grid-template: +- [row1-start] "header header header" 25px [row1-end] +- [row2-start] "footer footer footer" 25px [row2-end] +- / auto 50px auto; ++ grid-template: [row1-start] "header header header" 25px [row1-end] ++ [row2-start] "footer footer footer" 25px [row2-end] / auto 50px auto; + + /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ +- grid: +- [row1-start] "header header header" 25px [row1-end] +- [row2-start] "footer footer footer" 25px [row2-end] +- / auto 50px auto; ++ grid: [row1-start] "header header header" 25px [row1-end] [row2-start] ++ "footer footer footer" 25px [row2-end] / auto 50px auto; + /* simpler use case: */ + grid: 200px auto / 1fr auto 1fr; + ``` # Output @@ -205,19 +241,13 @@ div { /* numbers */ div { - grid-template-columns: - [full-start] minmax(1.50em, 1fr) - [main-start] minmax(.40ch, 75ch) - [main-end] minmax(1em, 1.000fr) - [full-end]; + grid-template-columns: [full-start] minmax(1.50em, 1fr) [main-start] + minmax(.40ch, 75ch) [main-end] minmax(1em, 1.000fr) [full-end]; } /* casing */ div { - GRID: - [top] 1fr - [middle] 1fr - bottom; + grid: [top] 1fr [middle] 1fr bottom; grid-template: "a a a" 200px "b b b" 200px / 200px 200px auto; } @@ -226,8 +256,7 @@ div { div { grid-template-columns: 1fr 100px 3em; grid: [wide-start] "header header header" 200px [wide-end] - "footer footer footer" 25px - / auto 50px auto; + "footer footer footer" 25px / auto 50px auto; } /** @@ -240,39 +269,26 @@ div { grid-template-columns: 1fr 100px 3em; grid-template-rows: 1fr 100px 3em; /* template rows/columns with named grid lines */ - grid-template-columns: - [wide-start] 1fr - [main-start] 500px - [main-end] 1fr + grid-template-columns: [wide-start] 1fr [main-start] 500px [main-end] 1fr [wide-end]; - grid-template-rows: - [top] 1fr - [middle] 1fr - [bottom]; + grid-template-rows: [top] 1fr [middle] 1fr [bottom]; /* template rows/columns with functions */ grid-template-columns: minmax(1em, 1fr) minmax(1em, 80ch) minmax(1em, 1fr); /* getting really busy with named lines + functions */ - grid-template-columns: - [full-start] minmax(1em, 1fr) - [main-start] minmax(1em, 80ch) - [main-end] minmax(1em, 1fr) - [full-end]; + grid-template-columns: [full-start] minmax(1em, 1fr) [main-start] + minmax(1em, 80ch) [main-end] minmax(1em, 1fr) [full-end]; grid-template-areas: "header header header" "main main sidebar" "main main sidebar2" "footer footer footer"; /* Shorthand for grid-template-rows, grid-template-columns, and grid-template areas. In one. This can get really crazy. */ - grid-template: - [row1-start] "header header header" 25px [row1-end] - [row2-start] "footer footer footer" 25px [row2-end] - / auto 50px auto; + grid-template: [row1-start] "header header header" 25px [row1-end] + [row2-start] "footer footer footer" 25px [row2-end] / auto 50px auto; /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ - grid: - [row1-start] "header header header" 25px [row1-end] - [row2-start] "footer footer footer" 25px [row2-end] - / auto 50px auto; + grid: [row1-start] "header header header" 25px [row1-end] [row2-start] + "footer footer footer" 25px [row2-end] / auto 50px auto; /* simpler use case: */ grid: 200px auto / 1fr auto 1fr; @@ -304,207 +320,7 @@ div { } ``` -# Errors -``` -grid.css:12:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 10 │ div { - 11 │ grid-template-columns: - > 12 │ [full-start] minmax(1.50em, 1fr) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 13 │ [main-start] minmax(.40ch, 75ch) - > 14 │ [main-end] minmax(1em, 1.000fr) - > 15 │ [full-end]; - │ ^^^^^^^^^^ - 16 │ } - 17 │ - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:21:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 19 │ div { - 20 │ GRID: - > 21 │ [top] 1fr - │ ^^^^^^^^^ - > 22 │ [middle] 1fr - > 23 │ bottom; - │ ^^^^^^ - 24 │ - 25 │ grid-TEMPLATE: - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:35:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 33 │ grid-template-columns: - 34 │ 1fr 100px 3em; - > 35 │ grid: [wide-start] "header header header" 200px [wide-end] - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 36 │ "footer footer footer" 25px - > 37 │ / auto 50px auto; - │ ^^^^^^^^^^^^^^^^ - 38 │ } - 39 │ - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:51:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 49 │ /* template rows/columns with named grid lines */ - 50 │ grid-template-columns: - > 51 │ [wide-start] 1fr - │ ^^^^^^^^^^^^^^^^ - > 52 │ [main-start] 500px - > 53 │ [main-end] 1fr - > 54 │ [wide-end]; - │ ^^^^^^^^^^ - 55 │ grid-template-rows: - 56 │ [top] 1fr - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:56:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 54 │ [wide-end]; - 55 │ grid-template-rows: - > 56 │ [top] 1fr - │ ^^^^^^^^^ - > 57 │ [middle] 1fr - > 58 │ [bottom]; - │ ^^^^^^^^ - 59 │ /* template rows/columns with functions */ - 60 │ grid-template-columns: minmax(1em, 1fr) minmax(1em, 80ch) minmax(1em, 1fr); - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:63:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 61 │ /* getting really busy with named lines + functions */ - 62 │ grid-template-columns: - > 63 │ [full-start] minmax(1em, 1fr) - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 64 │ [main-start] minmax(1em, 80ch) - > 65 │ [main-end] minmax(1em, 1fr) - > 66 │ [full-end]; - │ ^^^^^^^^^^ - 67 │ - 68 │ grid-template-areas: - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:77:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 75 │ areas. In one. This can get really crazy. */ - 76 │ grid-template: - > 77 │ [row1-start] "header header header" 25px [row1-end] - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 78 │ [row2-start] "footer footer footer" 25px [row2-end] - > 79 │ / auto 50px auto; - │ ^^^^^^^^^^^^^^^^ - 80 │ - 81 │ /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - -grid.css:83:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Unexpected value or character. - - 81 │ /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ - 82 │ grid: - > 83 │ [row1-start] "header header header" 25px [row1-end] - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - > 84 │ [row2-start] "footer footer footer" 25px [row2-end] - > 85 │ / auto 50px auto; - │ ^^^^^^^^^^^^^^^^ - 86 │ /* simpler use case: */ - 87 │ grid: 200px auto / 1fr auto 1fr; - - i Expected one of: - - - identifier - - string - - number - - dimension - - ratio - - custom property - - function - - -``` - # Lines exceeding max width of 80 characters ``` - 71: /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ + 53: /* The. Worst. This one is shorthand for like everything here smashed into one. But rarely will you actually specify EVERYTHING. */ ``` diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/variables/apply-rule.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/variables/apply-rule.css.snap index adc2bc632705..f5ada1d549d6 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/variables/apply-rule.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/variables/apply-rule.css.snap @@ -2,7 +2,6 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: css/variables/apply-rule.css --- - # Input ```css @@ -449,5 +448,3 @@ apply-rule.css:29:4 parse ━━━━━━━━━━━━━━━━━━ ``` - - diff --git a/crates/biome_css_parser/src/syntax/mod.rs b/crates/biome_css_parser/src/syntax/mod.rs index 924b38a9d27d..ff6ae39dfe5d 100644 --- a/crates/biome_css_parser/src/syntax/mod.rs +++ b/crates/biome_css_parser/src/syntax/mod.rs @@ -10,7 +10,7 @@ use crate::lexer::CssLexContext; 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; +use crate::syntax::parse_error::{expected_any_rule, expected_non_css_wide_keyword_identifier}; use crate::syntax::property::{is_at_any_property, parse_any_property}; use crate::syntax::selector::is_nth_at_selector; use crate::syntax::selector::relative_selector::{is_at_relative_selector, RelativeSelectorList}; @@ -256,6 +256,7 @@ pub(crate) fn is_at_any_value(p: &mut CssParser) -> bool { || is_at_dashed_identifier(p) || is_at_ratio(p) || is_at_color(p) + || is_at_bracketed_value(p) } #[inline] @@ -276,6 +277,8 @@ pub(crate) fn parse_any_value(p: &mut CssParser) -> ParsedSyntax { parse_regular_number(p) } else if is_at_color(p) { parse_color(p) + } else if is_at_bracketed_value(p) { + parse_bracketed_value(p) } else { Absent } @@ -476,6 +479,80 @@ pub(crate) fn is_at_string(p: &mut CssParser) -> bool { p.at(CSS_STRING_LITERAL) } +/// Checks if the parser is currently at the start of a bracketed value. +#[inline] +pub(crate) fn is_at_bracketed_value(p: &mut CssParser) -> bool { + p.at(T!['[']) +} + +/// Parses a bracketed value from the current position in the CSS parser. +/// +/// This function parses a list of values enclosed in square brackets, commonly used in CSS properties +/// like `grid-template-areas` where the value is a list of identifiers representing grid areas. +/// For details on the syntax of bracketed values, +/// see the [CSS Syntax specification](https://drafts.csswg.org/css-grid/#named-lines) +#[inline] +pub(crate) fn parse_bracketed_value(p: &mut CssParser) -> ParsedSyntax { + if !is_at_bracketed_value(p) { + return Absent; + } + + let m = p.start(); + + p.bump(T!['[']); + BracketedValueList.parse_list(p); + p.expect(T![']']); + + Present(m.complete(p, CSS_BRACKETED_VALUE)) +} + +/// The list parser for bracketed values. +/// +/// This parser is responsible for parsing a list of identifiers inside a bracketed value. +pub(crate) struct BracketedValueList; + +impl ParseNodeList for BracketedValueList { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + const LIST_KIND: Self::Kind = CSS_BRACKETED_VALUE_LIST; + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + parse_custom_identifier(p, CssLexContext::Regular) + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + p.at(T![']']) + } + + fn recover( + &mut self, + p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + parsed_element.or_recover( + p, + &BracketedValueListRecovery, + expected_non_css_wide_keyword_identifier, + ) + } +} + +/// Recovery strategy for bracketed value lists. +/// +/// This recovery strategy handles the recovery process when parsing bracketed value lists. +struct BracketedValueListRecovery; + +impl ParseRecovery for BracketedValueListRecovery { + type Kind = CssSyntaxKind; + type Parser<'source> = CssParser<'source>; + const RECOVERED_KIND: Self::Kind = CSS_BOGUS_CUSTOM_IDENTIFIER; + + fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool { + // If the next token is the end of the list or the next element, we're at a recovery point. + p.at(T![']']) || is_at_identifier(p) + } +} + /// Attempt to parse some input with the given parsing function. If parsing /// succeeds, `Ok` is returned with the result of the parse and the state is /// preserved. If parsing fails, this function rewinds the parser back to diff --git a/crates/biome_css_parser/src/syntax/property/mod.rs b/crates/biome_css_parser/src/syntax/property/mod.rs index 467e249835fa..4999d8824393 100644 --- a/crates/biome_css_parser/src/syntax/property/mod.rs +++ b/crates/biome_css_parser/src/syntax/property/mod.rs @@ -126,7 +126,7 @@ impl ParseNodeList for ComposesClassList { } fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { - p.at_ts(CSS_END_OF_COMPOSES_CLASS_TOKEN_SET) + p.at_ts(END_OF_COMPOSES_CLASS_TOKEN_SET) } fn recover( @@ -147,12 +147,12 @@ impl ParseRecovery for ComposesClassListParseRecovery { fn is_at_recovered(&self, p: &mut Self::Parser<'_>) -> bool { // If the next token is the end of the list or the next element, we're at a recovery point. - p.at_ts(CSS_END_OF_COMPOSES_CLASS_TOKEN_SET) || is_at_identifier(p) + p.at_ts(END_OF_COMPOSES_CLASS_TOKEN_SET) || is_at_identifier(p) } } -const CSS_END_OF_COMPOSES_CLASS_TOKEN_SET: TokenSet = - CSS_END_OF_PROPERTY_VALUE_TOKEN_SET.union(token_set!(T![from])); +const END_OF_COMPOSES_CLASS_TOKEN_SET: TokenSet = + END_OF_PROPERTY_VALUE_TOKEN_SET.union(token_set!(T![from])); #[inline] fn is_at_generic_property(p: &mut CssParser) -> bool { @@ -179,7 +179,7 @@ fn parse_generic_property(p: &mut CssParser) -> ParsedSyntax { Present(m.complete(p, CSS_GENERIC_PROPERTY)) } -const CSS_END_OF_PROPERTY_VALUE_TOKEN_SET: TokenSet = token_set!(T!['}'], T![;]); +const END_OF_PROPERTY_VALUE_TOKEN_SET: TokenSet = token_set!(T!['}'], T![;]); struct GenericComponentValueList; @@ -193,7 +193,7 @@ impl ParseNodeList for GenericComponentValueList { } fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { - p.at_ts(CSS_END_OF_PROPERTY_VALUE_TOKEN_SET) || p.at(T![')']) || /* !token is !important */ p.at(T![!]) + p.at_ts(END_OF_PROPERTY_VALUE_TOKEN_SET) || p.at(T![')']) || /* !token is !important */ p.at(T![!]) } fn recover( @@ -203,10 +203,7 @@ impl ParseNodeList for GenericComponentValueList { ) -> RecoveryResult { parsed_element.or_recover_with_token_set( p, - &ParseRecoveryTokenSet::new( - CSS_BOGUS_PROPERTY_VALUE, - CSS_END_OF_PROPERTY_VALUE_TOKEN_SET, - ), + &ParseRecoveryTokenSet::new(CSS_BOGUS_PROPERTY_VALUE, END_OF_PROPERTY_VALUE_TOKEN_SET), expected_component_value, ) } diff --git a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_feature_values_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_feature_values_error.css.snap index cdfaa6e65e41..87d993e9dd5c 100644 --- a/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_feature_values_error.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/error/at_rule/at_rule_font_feature_values_error.css.snap @@ -70,56 +70,48 @@ CssRoot { }, CssAtRule { at_token: AT@58..61 "@" [Newline("\n"), Newline("\n")] [], - rule: CssBogusAtRule { - items: [ - FONT_FEATURE_VALUES_KW@61..81 "font-feature-values" [] [Whitespace(" ")], - CssBogus { - items: [ - CssBogus { + rule: CssFontFeatureValuesAtRule { + font_feature_values_token: FONT_FEATURE_VALUES_KW@61..81 "font-feature-values" [] [Whitespace(" ")], + names: CssFontFamilyNameList [ + CssFontFamilyName { + names: CssCustomIdentifierList [ + CssCustomIdentifier { + value_token: IDENT@81..86 "ident" [] [], + }, + CssBogusCustomIdentifier { items: [ - CssBogus { - items: [ - CssCustomIdentifier { - value_token: IDENT@81..86 "ident" [] [], - }, - CssBogusCustomIdentifier { - items: [ - AT@86..89 "@" [Newline("\n"), Newline("\n")] [], - ], - }, - CssCustomIdentifier { - value_token: IDENT@89..109 "font-feature-values" [] [Whitespace(" ")], - }, - CssBogusCustomIdentifier { - items: [ - SEMICOLON@109..110 ";" [] [], - AT@110..113 "@" [Newline("\n"), Newline("\n")] [], - ], - }, - CssCustomIdentifier { - value_token: IDENT@113..133 "font-feature-values" [] [Whitespace(" ")], - }, - CssCustomIdentifier { - value_token: IDENT@133..139 "ident" [] [Whitespace(" ")], - }, - ], - }, + AT@86..89 "@" [Newline("\n"), Newline("\n")] [], ], }, - ], - }, - CssFontFeatureValuesBlock { - l_curly_token: L_CURLY@139..140 "{" [] [], - items: CssFontFeatureValuesItemList [ - CssBogusFontFeatureValuesItem { + CssCustomIdentifier { + value_token: IDENT@89..109 "font-feature-values" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { items: [ - L_CURLY@140..144 "{" [Newline("\n"), Whitespace("\t")] [Whitespace(" ")], + SEMICOLON@109..110 ";" [] [], + AT@110..113 "@" [Newline("\n"), Newline("\n")] [], ], }, + CssCustomIdentifier { + value_token: IDENT@113..133 "font-feature-values" [] [Whitespace(" ")], + }, + CssCustomIdentifier { + value_token: IDENT@133..139 "ident" [] [Whitespace(" ")], + }, ], - r_curly_token: R_CURLY@144..145 "}" [] [], }, ], + block: CssFontFeatureValuesBlock { + l_curly_token: L_CURLY@139..140 "{" [] [], + items: CssFontFeatureValuesItemList [ + CssBogusFontFeatureValuesItem { + items: [ + L_CURLY@140..144 "{" [Newline("\n"), Whitespace("\t")] [Whitespace(" ")], + ], + }, + ], + r_curly_token: R_CURLY@144..145 "}" [] [], + }, }, }, CssBogusRule { @@ -194,60 +186,48 @@ CssRoot { }, CssAtRule { at_token: AT@239..242 "@" [Newline("\n"), Newline("\n")] [], - rule: CssBogusAtRule { - items: [ - FONT_FEATURE_VALUES_KW@242..262 "font-feature-values" [] [Whitespace(" ")], - CssBogus { - items: [ - CssBogus { + rule: CssFontFeatureValuesAtRule { + font_feature_values_token: FONT_FEATURE_VALUES_KW@242..262 "font-feature-values" [] [Whitespace(" ")], + names: CssFontFamilyNameList [ + CssFontFamilyName { + names: CssCustomIdentifierList [ + CssCustomIdentifier { + value_token: IDENT@262..268 "first" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { items: [ - CssBogus { - items: [ - CssCustomIdentifier { - value_token: IDENT@262..268 "first" [] [Whitespace(" ")], - }, - CssBogusCustomIdentifier { - items: [ - CSS_NUMBER_LITERAL@268..272 "123" [] [Whitespace(" ")], - ], - }, - CssCustomIdentifier { - value_token: IDENT@272..279 "second" [] [Whitespace(" ")], - }, - CssBogusCustomIdentifier { - items: [ - CSS_NUMBER_LITERAL@279..282 "123" [] [], - ], - }, - ], - }, + CSS_NUMBER_LITERAL@268..272 "123" [] [Whitespace(" ")], ], }, - COMMA@282..284 "," [] [Whitespace(" ")], - CssBogus { + CssCustomIdentifier { + value_token: IDENT@272..279 "second" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { items: [ - CssBogus { - items: [ - CssCustomIdentifier { - value_token: IDENT@284..290 "third" [] [Whitespace(" ")], - }, - CssBogusCustomIdentifier { - items: [ - CSS_STRING_LITERAL@290..299 "\"second\"" [] [Whitespace(" ")], - ], - }, - ], - }, + CSS_NUMBER_LITERAL@279..282 "123" [] [], ], }, ], }, - CssFontFeatureValuesBlock { - l_curly_token: L_CURLY@299..300 "{" [] [], - items: CssFontFeatureValuesItemList [], - r_curly_token: R_CURLY@300..303 "}" [Newline("\n"), Newline("\n")] [], + COMMA@282..284 "," [] [Whitespace(" ")], + CssFontFamilyName { + names: CssCustomIdentifierList [ + CssCustomIdentifier { + value_token: IDENT@284..290 "third" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { + items: [ + CSS_STRING_LITERAL@290..299 "\"second\"" [] [Whitespace(" ")], + ], + }, + ], }, ], + block: CssFontFeatureValuesBlock { + l_curly_token: L_CURLY@299..300 "{" [] [], + items: CssFontFeatureValuesItemList [], + r_curly_token: R_CURLY@300..303 "}" [Newline("\n"), Newline("\n")] [], + }, }, }, ], @@ -283,11 +263,11 @@ CssRoot { 2: R_CURLY@57..58 "}" [] [] 2: CSS_AT_RULE@58..145 0: AT@58..61 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_BOGUS_AT_RULE@61..145 + 1: CSS_FONT_FEATURE_VALUES_AT_RULE@61..145 0: FONT_FEATURE_VALUES_KW@61..81 "font-feature-values" [] [Whitespace(" ")] - 1: CSS_BOGUS@81..139 - 0: CSS_BOGUS@81..139 - 0: CSS_BOGUS@81..139 + 1: CSS_FONT_FAMILY_NAME_LIST@81..139 + 0: CSS_FONT_FAMILY_NAME@81..139 + 0: CSS_CUSTOM_IDENTIFIER_LIST@81..139 0: CSS_CUSTOM_IDENTIFIER@81..86 0: IDENT@81..86 "ident" [] [] 1: CSS_BOGUS_CUSTOM_IDENTIFIER@86..89 @@ -350,11 +330,11 @@ CssRoot { 2: R_CURLY@236..239 "}" [Newline("\n"), Newline("\n")] [] 7: CSS_AT_RULE@239..303 0: AT@239..242 "@" [Newline("\n"), Newline("\n")] [] - 1: CSS_BOGUS_AT_RULE@242..303 + 1: CSS_FONT_FEATURE_VALUES_AT_RULE@242..303 0: FONT_FEATURE_VALUES_KW@242..262 "font-feature-values" [] [Whitespace(" ")] - 1: CSS_BOGUS@262..299 - 0: CSS_BOGUS@262..282 - 0: CSS_BOGUS@262..282 + 1: CSS_FONT_FAMILY_NAME_LIST@262..299 + 0: CSS_FONT_FAMILY_NAME@262..282 + 0: CSS_CUSTOM_IDENTIFIER_LIST@262..282 0: CSS_CUSTOM_IDENTIFIER@262..268 0: IDENT@262..268 "first" [] [Whitespace(" ")] 1: CSS_BOGUS_CUSTOM_IDENTIFIER@268..272 @@ -364,8 +344,8 @@ CssRoot { 3: CSS_BOGUS_CUSTOM_IDENTIFIER@279..282 0: CSS_NUMBER_LITERAL@279..282 "123" [] [] 1: COMMA@282..284 "," [] [Whitespace(" ")] - 2: CSS_BOGUS@284..299 - 0: CSS_BOGUS@284..299 + 2: CSS_FONT_FAMILY_NAME@284..299 + 0: CSS_CUSTOM_IDENTIFIER_LIST@284..299 0: CSS_CUSTOM_IDENTIFIER@284..290 0: IDENT@284..290 "third" [] [Whitespace(" ")] 1: CSS_BOGUS_CUSTOM_IDENTIFIER@290..299 diff --git a/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css b/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css new file mode 100644 index 000000000000..c6188e1f029b --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css @@ -0,0 +1,4 @@ +#grid { + display: grid; + grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; +} diff --git a/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css.snap b/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css.snap new file mode 100644 index 000000000000..61d3e84cbb10 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/property/grid/grid_error.css.snap @@ -0,0 +1,260 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```css +#grid { + display: grid; + grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; +} + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + rules: CssRuleList [ + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssIdSelector { + hash_token: HASH@0..1 "#" [] [], + name: CssCustomIdentifier { + value_token: IDENT@1..6 "grid" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@6..7 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@7..16 "display" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@16..18 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@18..22 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@22..23 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@23..46 "grid-template-columns" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@46..48 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssBracketedValue { + l_brack_token: L_BRACK@48..49 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@49..55 "first" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { + items: [ + CSS_NUMBER_LITERAL@55..59 "123" [] [Whitespace(" ")], + ], + }, + CssCustomIdentifier { + value_token: IDENT@59..68 "nav-start" [] [], + }, + ], + r_brack_token: R_BRACK@68..70 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@70..73 "150" [] [], + unit_token: IDENT@73..76 "px" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@76..77 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@77..88 "main-start" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { + items: [ + CSS_DIMENSION_VALUE@88..89 "1" [] [], + ], + }, + CssCustomIdentifier { + value_token: IDENT@89..92 "fr" [] [Whitespace(" ")], + }, + CssBogusCustomIdentifier { + items: [ + L_BRACK@92..93 "[" [] [], + ], + }, + CssCustomIdentifier { + value_token: IDENT@93..97 "last" [] [], + }, + ], + r_brack_token: R_BRACK@97..98 "]" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@98..99 ";" [] [], + }, + ], + r_curly_token: R_CURLY@99..101 "}" [Newline("\n")] [], + }, + }, + ], + eof_token: EOF@101..102 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..102 + 0: (empty) + 1: CSS_RULE_LIST@0..101 + 0: CSS_QUALIFIED_RULE@0..101 + 0: CSS_SELECTOR_LIST@0..6 + 0: CSS_COMPOUND_SELECTOR@0..6 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@0..6 + 0: CSS_ID_SELECTOR@0..6 + 0: HASH@0..1 "#" [] [] + 1: CSS_CUSTOM_IDENTIFIER@1..6 + 0: IDENT@1..6 "grid" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@6..101 + 0: L_CURLY@6..7 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@7..99 + 0: CSS_DECLARATION_WITH_SEMICOLON@7..23 + 0: CSS_DECLARATION@7..22 + 0: CSS_GENERIC_PROPERTY@7..22 + 0: CSS_IDENTIFIER@7..16 + 0: IDENT@7..16 "display" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@16..18 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@18..22 + 0: CSS_IDENTIFIER@18..22 + 0: IDENT@18..22 "grid" [] [] + 1: (empty) + 1: SEMICOLON@22..23 ";" [] [] + 1: CSS_DECLARATION_WITH_SEMICOLON@23..99 + 0: CSS_DECLARATION@23..98 + 0: CSS_GENERIC_PROPERTY@23..98 + 0: CSS_IDENTIFIER@23..46 + 0: IDENT@23..46 "grid-template-columns" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@46..48 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@48..98 + 0: CSS_BRACKETED_VALUE@48..70 + 0: L_BRACK@48..49 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@49..68 + 0: CSS_CUSTOM_IDENTIFIER@49..55 + 0: IDENT@49..55 "first" [] [Whitespace(" ")] + 1: CSS_BOGUS_CUSTOM_IDENTIFIER@55..59 + 0: CSS_NUMBER_LITERAL@55..59 "123" [] [Whitespace(" ")] + 2: CSS_CUSTOM_IDENTIFIER@59..68 + 0: IDENT@59..68 "nav-start" [] [] + 2: R_BRACK@68..70 "]" [] [Whitespace(" ")] + 1: CSS_REGULAR_DIMENSION@70..76 + 0: CSS_NUMBER_LITERAL@70..73 "150" [] [] + 1: IDENT@73..76 "px" [] [Whitespace(" ")] + 2: CSS_BRACKETED_VALUE@76..98 + 0: L_BRACK@76..77 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@77..97 + 0: CSS_CUSTOM_IDENTIFIER@77..88 + 0: IDENT@77..88 "main-start" [] [Whitespace(" ")] + 1: CSS_BOGUS_CUSTOM_IDENTIFIER@88..89 + 0: CSS_DIMENSION_VALUE@88..89 "1" [] [] + 2: CSS_CUSTOM_IDENTIFIER@89..92 + 0: IDENT@89..92 "fr" [] [Whitespace(" ")] + 3: CSS_BOGUS_CUSTOM_IDENTIFIER@92..93 + 0: L_BRACK@92..93 "[" [] [] + 4: CSS_CUSTOM_IDENTIFIER@93..97 + 0: IDENT@93..97 "last" [] [] + 2: R_BRACK@97..98 "]" [] [] + 1: (empty) + 1: SEMICOLON@98..99 ";" [] [] + 2: R_CURLY@99..101 "}" [Newline("\n")] [] + 2: EOF@101..102 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +grid_error.css:3:32 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier but instead found '123'. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^^^ + 4 │ } + 5 │ + + i Expected an identifier here. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^^^ + 4 │ } + 5 │ + +grid_error.css:3:65 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier but instead found '1'. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^ + 4 │ } + 5 │ + + i Expected an identifier here. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^ + 4 │ } + 5 │ + +grid_error.css:3:69 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier but instead found '['. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^ + 4 │ } + 5 │ + + i Expected an identifier here. + + 1 │ #grid { + 2 │ display: grid; + > 3 │ grid-template-columns: [first 123 nav-start] 150px [main-start 1fr [last]; + │ ^ + 4 │ } + 5 │ + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css b/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css index 0acbfb12eedb..c2f4209a78de 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css @@ -2,3 +2,9 @@ grid-row: span 2 / span 2; align-self: flex-end } + +#grid { + display: grid; + grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; + grid-template-rows: [first header-start] 50px [main-start] 1fr [footer-start] 50px [last]; +} diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css.snap index aad11e38bcac..06fbd381f3c5 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/property/grid.css.snap @@ -10,6 +10,12 @@ expression: snapshot align-self: flex-end } +#grid { + display: grid; + grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; + grid-template-rows: [first header-start] 50px [main-start] 1fr [footer-start] 50px [last]; +} + ``` @@ -87,17 +93,173 @@ CssRoot { r_curly_token: R_CURLY@63..65 "}" [Newline("\n")] [], }, }, + CssQualifiedRule { + prelude: CssSelectorList [ + CssCompoundSelector { + nesting_selector_token: missing (optional), + simple_selector: missing (optional), + sub_selectors: CssSubSelectorList [ + CssIdSelector { + hash_token: HASH@65..68 "#" [Newline("\n"), Newline("\n")] [], + name: CssCustomIdentifier { + value_token: IDENT@68..73 "grid" [] [Whitespace(" ")], + }, + }, + ], + }, + ], + block: CssDeclarationOrRuleBlock { + l_curly_token: L_CURLY@73..74 "{" [] [], + items: CssDeclarationOrRuleList [ + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@74..83 "display" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@83..85 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssIdentifier { + value_token: IDENT@85..89 "grid" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@89..90 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@90..113 "grid-template-columns" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@113..115 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssBracketedValue { + l_brack_token: L_BRACK@115..116 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@116..122 "first" [] [Whitespace(" ")], + }, + CssCustomIdentifier { + value_token: IDENT@122..131 "nav-start" [] [], + }, + ], + r_brack_token: R_BRACK@131..133 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@133..136 "150" [] [], + unit_token: IDENT@136..139 "px" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@139..140 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@140..150 "main-start" [] [], + }, + ], + r_brack_token: R_BRACK@150..152 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@152..153 "1" [] [], + unit_token: IDENT@153..156 "fr" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@156..157 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@157..161 "last" [] [], + }, + ], + r_brack_token: R_BRACK@161..162 "]" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@162..163 ";" [] [], + }, + CssDeclarationWithSemicolon { + declaration: CssDeclaration { + property: CssGenericProperty { + name: CssIdentifier { + value_token: IDENT@163..183 "grid-template-rows" [Newline("\n"), Whitespace("\t")] [], + }, + colon_token: COLON@183..185 ":" [] [Whitespace(" ")], + value: CssGenericComponentValueList [ + CssBracketedValue { + l_brack_token: L_BRACK@185..186 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@186..192 "first" [] [Whitespace(" ")], + }, + CssCustomIdentifier { + value_token: IDENT@192..204 "header-start" [] [], + }, + ], + r_brack_token: R_BRACK@204..206 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@206..208 "50" [] [], + unit_token: IDENT@208..211 "px" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@211..212 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@212..222 "main-start" [] [], + }, + ], + r_brack_token: R_BRACK@222..224 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@224..225 "1" [] [], + unit_token: IDENT@225..228 "fr" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@228..229 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@229..241 "footer-start" [] [], + }, + ], + r_brack_token: R_BRACK@241..243 "]" [] [Whitespace(" ")], + }, + CssRegularDimension { + value_token: CSS_NUMBER_LITERAL@243..245 "50" [] [], + unit_token: IDENT@245..248 "px" [] [Whitespace(" ")], + }, + CssBracketedValue { + l_brack_token: L_BRACK@248..249 "[" [] [], + items: CssBracketedValueList [ + CssCustomIdentifier { + value_token: IDENT@249..253 "last" [] [], + }, + ], + r_brack_token: R_BRACK@253..254 "]" [] [], + }, + ], + }, + important: missing (optional), + }, + semicolon_token: SEMICOLON@254..255 ";" [] [], + }, + ], + r_curly_token: R_CURLY@255..257 "}" [Newline("\n")] [], + }, + }, ], - eof_token: EOF@65..66 "" [Newline("\n")] [], + eof_token: EOF@257..258 "" [Newline("\n")] [], } ``` ## CST ``` -0: CSS_ROOT@0..66 +0: CSS_ROOT@0..258 0: (empty) - 1: CSS_RULE_LIST@0..65 + 1: CSS_RULE_LIST@0..257 0: CSS_QUALIFIED_RULE@0..65 0: CSS_SELECTOR_LIST@0..12 0: CSS_COMPOUND_SELECTOR@0..12 @@ -142,6 +304,110 @@ CssRoot { 1: (empty) 1: (empty) 2: R_CURLY@63..65 "}" [Newline("\n")] [] - 2: EOF@65..66 "" [Newline("\n")] [] + 1: CSS_QUALIFIED_RULE@65..257 + 0: CSS_SELECTOR_LIST@65..73 + 0: CSS_COMPOUND_SELECTOR@65..73 + 0: (empty) + 1: (empty) + 2: CSS_SUB_SELECTOR_LIST@65..73 + 0: CSS_ID_SELECTOR@65..73 + 0: HASH@65..68 "#" [Newline("\n"), Newline("\n")] [] + 1: CSS_CUSTOM_IDENTIFIER@68..73 + 0: IDENT@68..73 "grid" [] [Whitespace(" ")] + 1: CSS_DECLARATION_OR_RULE_BLOCK@73..257 + 0: L_CURLY@73..74 "{" [] [] + 1: CSS_DECLARATION_OR_RULE_LIST@74..255 + 0: CSS_DECLARATION_WITH_SEMICOLON@74..90 + 0: CSS_DECLARATION@74..89 + 0: CSS_GENERIC_PROPERTY@74..89 + 0: CSS_IDENTIFIER@74..83 + 0: IDENT@74..83 "display" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@83..85 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@85..89 + 0: CSS_IDENTIFIER@85..89 + 0: IDENT@85..89 "grid" [] [] + 1: (empty) + 1: SEMICOLON@89..90 ";" [] [] + 1: CSS_DECLARATION_WITH_SEMICOLON@90..163 + 0: CSS_DECLARATION@90..162 + 0: CSS_GENERIC_PROPERTY@90..162 + 0: CSS_IDENTIFIER@90..113 + 0: IDENT@90..113 "grid-template-columns" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@113..115 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@115..162 + 0: CSS_BRACKETED_VALUE@115..133 + 0: L_BRACK@115..116 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@116..131 + 0: CSS_CUSTOM_IDENTIFIER@116..122 + 0: IDENT@116..122 "first" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@122..131 + 0: IDENT@122..131 "nav-start" [] [] + 2: R_BRACK@131..133 "]" [] [Whitespace(" ")] + 1: CSS_REGULAR_DIMENSION@133..139 + 0: CSS_NUMBER_LITERAL@133..136 "150" [] [] + 1: IDENT@136..139 "px" [] [Whitespace(" ")] + 2: CSS_BRACKETED_VALUE@139..152 + 0: L_BRACK@139..140 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@140..150 + 0: CSS_CUSTOM_IDENTIFIER@140..150 + 0: IDENT@140..150 "main-start" [] [] + 2: R_BRACK@150..152 "]" [] [Whitespace(" ")] + 3: CSS_REGULAR_DIMENSION@152..156 + 0: CSS_NUMBER_LITERAL@152..153 "1" [] [] + 1: IDENT@153..156 "fr" [] [Whitespace(" ")] + 4: CSS_BRACKETED_VALUE@156..162 + 0: L_BRACK@156..157 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@157..161 + 0: CSS_CUSTOM_IDENTIFIER@157..161 + 0: IDENT@157..161 "last" [] [] + 2: R_BRACK@161..162 "]" [] [] + 1: (empty) + 1: SEMICOLON@162..163 ";" [] [] + 2: CSS_DECLARATION_WITH_SEMICOLON@163..255 + 0: CSS_DECLARATION@163..254 + 0: CSS_GENERIC_PROPERTY@163..254 + 0: CSS_IDENTIFIER@163..183 + 0: IDENT@163..183 "grid-template-rows" [Newline("\n"), Whitespace("\t")] [] + 1: COLON@183..185 ":" [] [Whitespace(" ")] + 2: CSS_GENERIC_COMPONENT_VALUE_LIST@185..254 + 0: CSS_BRACKETED_VALUE@185..206 + 0: L_BRACK@185..186 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@186..204 + 0: CSS_CUSTOM_IDENTIFIER@186..192 + 0: IDENT@186..192 "first" [] [Whitespace(" ")] + 1: CSS_CUSTOM_IDENTIFIER@192..204 + 0: IDENT@192..204 "header-start" [] [] + 2: R_BRACK@204..206 "]" [] [Whitespace(" ")] + 1: CSS_REGULAR_DIMENSION@206..211 + 0: CSS_NUMBER_LITERAL@206..208 "50" [] [] + 1: IDENT@208..211 "px" [] [Whitespace(" ")] + 2: CSS_BRACKETED_VALUE@211..224 + 0: L_BRACK@211..212 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@212..222 + 0: CSS_CUSTOM_IDENTIFIER@212..222 + 0: IDENT@212..222 "main-start" [] [] + 2: R_BRACK@222..224 "]" [] [Whitespace(" ")] + 3: CSS_REGULAR_DIMENSION@224..228 + 0: CSS_NUMBER_LITERAL@224..225 "1" [] [] + 1: IDENT@225..228 "fr" [] [Whitespace(" ")] + 4: CSS_BRACKETED_VALUE@228..243 + 0: L_BRACK@228..229 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@229..241 + 0: CSS_CUSTOM_IDENTIFIER@229..241 + 0: IDENT@229..241 "footer-start" [] [] + 2: R_BRACK@241..243 "]" [] [Whitespace(" ")] + 5: CSS_REGULAR_DIMENSION@243..248 + 0: CSS_NUMBER_LITERAL@243..245 "50" [] [] + 1: IDENT@245..248 "px" [] [Whitespace(" ")] + 6: CSS_BRACKETED_VALUE@248..254 + 0: L_BRACK@248..249 "[" [] [] + 1: CSS_BRACKETED_VALUE_LIST@249..253 + 0: CSS_CUSTOM_IDENTIFIER@249..253 + 0: IDENT@249..253 "last" [] [] + 2: R_BRACK@253..254 "]" [] [] + 1: (empty) + 1: SEMICOLON@254..255 ";" [] [] + 2: R_CURLY@255..257 "}" [Newline("\n")] [] + 2: EOF@257..258 "" [Newline("\n")] [] ``` diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index bd9be212e6eb..3a801db17809 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -174,12 +174,11 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" -.div { - & .class { - color: red - } +div { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); } + "#; let root = parse_css( diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index a778634a4ac0..e320fc0767a8 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -333,6 +333,8 @@ pub enum CssSyntaxKind { CSS_URL_MODIFIER_LIST, CSS_COLOR, CSS_BORDER, + CSS_BRACKETED_VALUE, + CSS_BRACKETED_VALUE_LIST, CSS_AT_RULE, CSS_CHARSET_AT_RULE, CSS_COLOR_PROFILE_AT_RULE, @@ -512,6 +514,7 @@ impl CssSyntaxKind { | CSS_PSEUDO_CLASS_FUNCTION_VALUE_LIST | CSS_PSEUDO_VALUE_LIST | CSS_URL_MODIFIER_LIST + | CSS_BRACKETED_VALUE_LIST | CSS_FONT_FAMILY_NAME_LIST | CSS_CUSTOM_IDENTIFIER_LIST | CSS_FONT_FEATURE_VALUES_ITEM_LIST diff --git a/crates/biome_css_syntax/src/generated/macros.rs b/crates/biome_css_syntax/src/generated/macros.rs index 4d1a0206da6a..2fa3d115592a 100644 --- a/crates/biome_css_syntax/src/generated/macros.rs +++ b/crates/biome_css_syntax/src/generated/macros.rs @@ -40,6 +40,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssBinaryExpression::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_BRACKETED_VALUE => { + let $pattern = unsafe { $crate::CssBracketedValue::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_CHARSET_AT_RULE => { let $pattern = unsafe { $crate::CssCharsetAtRule::new_unchecked(node) }; $body @@ -747,6 +751,10 @@ macro_rules! map_syntax_node { unsafe { $crate::CssValueAtRuleGenericValue::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_BRACKETED_VALUE_LIST => { + let $pattern = unsafe { $crate::CssBracketedValueList::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_COMPONENT_VALUE_LIST => { let $pattern = unsafe { $crate::CssComponentValueList::new_unchecked(node) }; $body diff --git a/crates/biome_css_syntax/src/generated/nodes.rs b/crates/biome_css_syntax/src/generated/nodes.rs index 09954f29908f..040d8a049268 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -284,6 +284,52 @@ pub struct CssBinaryExpressionFields { pub right: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssBracketedValue { + pub(crate) syntax: SyntaxNode, +} +impl CssBracketedValue { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> CssBracketedValueFields { + CssBracketedValueFields { + l_brack_token: self.l_brack_token(), + items: self.items(), + r_brack_token: self.r_brack_token(), + } + } + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn items(&self) -> CssBracketedValueList { + support::list(&self.syntax, 1usize) + } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssBracketedValue { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssBracketedValueFields { + pub l_brack_token: SyntaxResult, + pub items: CssBracketedValueList, + pub r_brack_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssCharsetAtRule { pub(crate) syntax: SyntaxNode, } @@ -8387,6 +8433,7 @@ impl AnyCssUrlValue { pub enum AnyCssValue { AnyCssDimension(AnyCssDimension), AnyCssFunction(AnyCssFunction), + CssBracketedValue(CssBracketedValue), CssColor(CssColor), CssCustomIdentifier(CssCustomIdentifier), CssDashedIdentifier(CssDashedIdentifier), @@ -8408,6 +8455,12 @@ impl AnyCssValue { _ => None, } } + pub fn as_css_bracketed_value(&self) -> Option<&CssBracketedValue> { + match &self { + AnyCssValue::CssBracketedValue(item) => Some(item), + _ => None, + } + } pub fn as_css_color(&self) -> Option<&CssColor> { match &self { AnyCssValue::CssColor(item) => Some(item), @@ -8786,6 +8839,52 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for CssBracketedValue { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_BRACKETED_VALUE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_BRACKETED_VALUE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for CssBracketedValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssBracketedValue") + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("items", &self.items()) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssBracketedValue) -> SyntaxNode { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: CssBracketedValue) -> SyntaxElement { + n.syntax.into() + } +} impl AstNode for CssCharsetAtRule { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -20559,6 +20658,11 @@ impl From for SyntaxElement { node.into() } } +impl From for AnyCssValue { + fn from(node: CssBracketedValue) -> AnyCssValue { + AnyCssValue::CssBracketedValue(node) + } +} impl From for AnyCssValue { fn from(node: CssColor) -> AnyCssValue { AnyCssValue::CssColor(node) @@ -20598,6 +20702,7 @@ impl AstNode for AnyCssValue { type Language = Language; const KIND_SET: SyntaxKindSet = AnyCssDimension::KIND_SET .union(AnyCssFunction::KIND_SET) + .union(CssBracketedValue::KIND_SET) .union(CssColor::KIND_SET) .union(CssCustomIdentifier::KIND_SET) .union(CssDashedIdentifier::KIND_SET) @@ -20607,7 +20712,8 @@ impl AstNode for AnyCssValue { .union(CssString::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { match kind { - CSS_COLOR + CSS_BRACKETED_VALUE + | CSS_COLOR | CSS_CUSTOM_IDENTIFIER | CSS_DASHED_IDENTIFIER | CSS_IDENTIFIER @@ -20621,6 +20727,7 @@ impl AstNode for AnyCssValue { } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { + CSS_BRACKETED_VALUE => AnyCssValue::CssBracketedValue(CssBracketedValue { syntax }), CSS_COLOR => AnyCssValue::CssColor(CssColor { syntax }), CSS_CUSTOM_IDENTIFIER => { AnyCssValue::CssCustomIdentifier(CssCustomIdentifier { syntax }) @@ -20646,6 +20753,7 @@ impl AstNode for AnyCssValue { } fn syntax(&self) -> &SyntaxNode { match self { + AnyCssValue::CssBracketedValue(it) => &it.syntax, AnyCssValue::CssColor(it) => &it.syntax, AnyCssValue::CssCustomIdentifier(it) => &it.syntax, AnyCssValue::CssDashedIdentifier(it) => &it.syntax, @@ -20659,6 +20767,7 @@ impl AstNode for AnyCssValue { } fn into_syntax(self) -> SyntaxNode { match self { + AnyCssValue::CssBracketedValue(it) => it.syntax, AnyCssValue::CssColor(it) => it.syntax, AnyCssValue::CssCustomIdentifier(it) => it.syntax, AnyCssValue::CssDashedIdentifier(it) => it.syntax, @@ -20676,6 +20785,7 @@ impl std::fmt::Debug for AnyCssValue { match self { AnyCssValue::AnyCssDimension(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::AnyCssFunction(it) => std::fmt::Debug::fmt(it, f), + AnyCssValue::CssBracketedValue(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssColor(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssCustomIdentifier(it) => std::fmt::Debug::fmt(it, f), AnyCssValue::CssDashedIdentifier(it) => std::fmt::Debug::fmt(it, f), @@ -20691,6 +20801,7 @@ impl From for SyntaxNode { match n { AnyCssValue::AnyCssDimension(it) => it.into(), AnyCssValue::AnyCssFunction(it) => it.into(), + AnyCssValue::CssBracketedValue(it) => it.into(), AnyCssValue::CssColor(it) => it.into(), AnyCssValue::CssCustomIdentifier(it) => it.into(), AnyCssValue::CssDashedIdentifier(it) => it.into(), @@ -21407,6 +21518,11 @@ impl std::fmt::Display for CssBinaryExpression { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CssBracketedValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for CssCharsetAtRule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -23476,6 +23592,89 @@ impl From for SyntaxElement { } } #[derive(Clone, Eq, PartialEq, Hash)] +pub struct CssBracketedValueList { + syntax_list: SyntaxList, +} +impl CssBracketedValueList { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { + syntax_list: syntax.into_list(), + } + } +} +impl AstNode for CssBracketedValueList { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_BRACKETED_VALUE_LIST as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == CSS_BRACKETED_VALUE_LIST + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(CssBracketedValueList { + syntax_list: syntax.into_list(), + }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + self.syntax_list.node() + } + fn into_syntax(self) -> SyntaxNode { + self.syntax_list.into_node() + } +} +#[cfg(feature = "serde")] +impl Serialize for CssBracketedValueList { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for e in self.iter() { + seq.serialize_element(&e)?; + } + seq.end() + } +} +impl AstNodeList for CssBracketedValueList { + type Language = Language; + type Node = AnyCssCustomIdentifier; + fn syntax_list(&self) -> &SyntaxList { + &self.syntax_list + } + fn into_syntax_list(self) -> SyntaxList { + self.syntax_list + } +} +impl Debug for CssBracketedValueList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str("CssBracketedValueList ")?; + f.debug_list().entries(self.iter()).finish() + } +} +impl IntoIterator for &CssBracketedValueList { + type Item = AnyCssCustomIdentifier; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +impl IntoIterator for CssBracketedValueList { + type Item = AnyCssCustomIdentifier; + type IntoIter = AstNodeListIterator; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} +#[derive(Clone, Eq, PartialEq, Hash)] pub struct CssComponentValueList { syntax_list: SyntaxList, } @@ -23779,7 +23978,7 @@ impl Serialize for CssCustomIdentifierList { } impl AstNodeList for CssCustomIdentifierList { type Language = Language; - type Node = CssCustomIdentifier; + type Node = AnyCssCustomIdentifier; fn syntax_list(&self) -> &SyntaxList { &self.syntax_list } @@ -23794,15 +23993,15 @@ impl Debug for CssCustomIdentifierList { } } impl IntoIterator for &CssCustomIdentifierList { - type Item = CssCustomIdentifier; - type IntoIter = AstNodeListIterator; + type Item = AnyCssCustomIdentifier; + type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl IntoIterator for CssCustomIdentifierList { - type Item = CssCustomIdentifier; - type IntoIter = AstNodeListIterator; + type Item = AnyCssCustomIdentifier; + type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } diff --git a/crates/biome_css_syntax/src/generated/nodes_mut.rs b/crates/biome_css_syntax/src/generated/nodes_mut.rs index 2f8ac2917ca6..1e5397ea04c0 100644 --- a/crates/biome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_css_syntax/src/generated/nodes_mut.rs @@ -105,6 +105,26 @@ impl CssBinaryExpression { ) } } +impl CssBracketedValue { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_items(self, element: CssBracketedValueList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } +} impl CssCharsetAtRule { pub fn with_charset_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( diff --git a/crates/biome_css_syntax/src/lib.rs b/crates/biome_css_syntax/src/lib.rs index 085dc56c94eb..86a5b3c1f069 100644 --- a/crates/biome_css_syntax/src/lib.rs +++ b/crates/biome_css_syntax/src/lib.rs @@ -94,6 +94,7 @@ impl biome_rowan::SyntaxKind for CssSyntaxKind { | CSS_BOGUS_PROPERTY_VALUE | CSS_BOGUS_DOCUMENT_MATCHER | CSS_BOGUS_KEYFRAMES_NAME + | CSS_BOGUS_CUSTOM_IDENTIFIER ) } @@ -116,6 +117,7 @@ impl biome_rowan::SyntaxKind for CssSyntaxKind { kind if AnyCssProperty::can_cast(*kind) => CSS_BOGUS_PROPERTY, kind if AnyCssDocumentMatcher::can_cast(*kind) => CSS_BOGUS_DOCUMENT_MATCHER, kind if AnyCssKeyframesName::can_cast(*kind) => CSS_BOGUS_KEYFRAMES_NAME, + kind if AnyCssCustomIdentifier::can_cast(*kind) => CSS_BOGUS_CUSTOM_IDENTIFIER, _ => CSS_BOGUS, } diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index b5da528d00f4..cb7328a38e43 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -668,11 +668,7 @@ CssFontFamilyName = // @font-feature-values Font Second Gothic { } // ^^^^^^^^^^^^^^^^^^ -CssCustomIdentifierList = CssCustomIdentifier* - -AnyCssCustomIdentifier = - CssCustomIdentifier - | CssBogusCustomIdentifier +CssCustomIdentifierList = AnyCssCustomIdentifier* AnyCssFontFeatureValuesBlock = CssFontFeatureValuesBlock @@ -1596,6 +1592,7 @@ AnyCssValue = | CssRatio | AnyCssFunction | CssColor + | CssBracketedValue // https://drafts.csswg.org/css-syntax/#typedef-dimension-token @@ -1711,6 +1708,22 @@ CssColor = '#' value:'css_color_literal' +// The CssBracketedValue is a special construct used to represent CSS values that are enclosed in brackets. +// This is commonly seen in properties like grid-template-areas where the value is a list of identifiers representing grid areas. +// [full-start] 1fr [main-start end] 480px [main-end] 1fr [full-end] +// ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ +// https://drafts.csswg.org/css-grid/#named-lines +CssBracketedValue = + '[' + items: CssBracketedValueList + ']' + +CssBracketedValueList = AnyCssCustomIdentifier* + +AnyCssCustomIdentifier = + CssCustomIdentifier + | CssBogusCustomIdentifier + // Any identifier. Case insensitve, used for standard property names, values, type selectors, etc. CssIdentifier = value: 'ident' // Any non-standard identifier. Case sensitive, used for class names, ids, etc. Custom identifiers diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 9bce982dbc8d..fd7167557d52 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -360,6 +360,8 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_URL_MODIFIER_LIST", "CSS_COLOR", "CSS_BORDER", + "CSS_BRACKETED_VALUE", + "CSS_BRACKETED_VALUE_LIST", // At rule nodes "CSS_AT_RULE", "CSS_CHARSET_AT_RULE",