diff --git a/crates/biome_css_factory/src/generated/node_factory.rs b/crates/biome_css_factory/src/generated/node_factory.rs index 009cd9d4e60f..aeff206c2d03 100644 --- a/crates/biome_css_factory/src/generated/node_factory.rs +++ b/crates/biome_css_factory/src/generated/node_factory.rs @@ -3589,6 +3589,22 @@ impl ScssIncludeAtRuleBuilder { )) } } +pub fn scss_interpolation( + hash_token: SyntaxToken, + l_curly_token: SyntaxToken, + value: AnyScssExpression, + r_curly_token: SyntaxToken, +) -> ScssInterpolation { + ScssInterpolation::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::SCSS_INTERPOLATION, + [ + Some(SyntaxElement::Token(hash_token)), + Some(SyntaxElement::Token(l_curly_token)), + Some(SyntaxElement::Node(value.into_syntax())), + Some(SyntaxElement::Token(r_curly_token)), + ], + )) +} pub fn scss_keyword_argument( name: ScssIdentifier, colon_token: SyntaxToken, diff --git a/crates/biome_css_factory/src/generated/syntax_factory.rs b/crates/biome_css_factory/src/generated/syntax_factory.rs index e0017489f202..072c892853cf 100644 --- a/crates/biome_css_factory/src/generated/syntax_factory.rs +++ b/crates/biome_css_factory/src/generated/syntax_factory.rs @@ -7188,6 +7188,46 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(SCSS_INCLUDE_AT_RULE, children) } + SCSS_INTERPOLATION => { + 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 + && element.kind() == T ! [#] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T!['{'] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && AnyScssExpression::can_cast(element.kind()) + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if let Some(element) = ¤t_element + && element.kind() == T!['}'] + { + slots.mark_present(); + current_element = elements.next(); + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new( + SCSS_INTERPOLATION.to_bogus(), + children.into_iter().map(Some), + ); + } + slots.into_node(SCSS_INTERPOLATION, children) + } SCSS_KEYWORD_ARGUMENT => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default(); diff --git a/crates/biome_css_formatter/src/generated.rs b/crates/biome_css_formatter/src/generated.rs index acc4f6639f04..e4a748045f0b 100644 --- a/crates/biome_css_formatter/src/generated.rs +++ b/crates/biome_css_formatter/src/generated.rs @@ -7979,6 +7979,44 @@ impl IntoFormat for biome_css_syntax::ScssIncludeAtRule { ) } } +impl FormatRule + for crate::scss::auxiliary::interpolation::FormatScssInterpolation +{ + type Context = CssFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_css_syntax::ScssInterpolation, + f: &mut CssFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_css_syntax::ScssInterpolation { + type Format<'a> = FormatRefWithRule< + 'a, + biome_css_syntax::ScssInterpolation, + crate::scss::auxiliary::interpolation::FormatScssInterpolation, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::scss::auxiliary::interpolation::FormatScssInterpolation::default(), + ) + } +} +impl IntoFormat for biome_css_syntax::ScssInterpolation { + type Format = FormatOwnedWithRule< + biome_css_syntax::ScssInterpolation, + crate::scss::auxiliary::interpolation::FormatScssInterpolation, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::scss::auxiliary::interpolation::FormatScssInterpolation::default(), + ) + } +} impl FormatRule for crate::scss::auxiliary::keyword_argument::FormatScssKeywordArgument { diff --git a/crates/biome_css_formatter/src/scss/any/expression.rs b/crates/biome_css_formatter/src/scss/any/expression.rs index 5dcec8550e51..7e0db36e86dc 100644 --- a/crates/biome_css_formatter/src/scss/any/expression.rs +++ b/crates/biome_css_formatter/src/scss/any/expression.rs @@ -11,6 +11,7 @@ impl FormatRule for FormatAnyScssExpression { AnyScssExpression::AnyCssValue(node) => node.format().fmt(f), AnyScssExpression::ScssBinaryExpression(node) => node.format().fmt(f), AnyScssExpression::ScssExpression(node) => node.format().fmt(f), + AnyScssExpression::ScssInterpolation(node) => node.format().fmt(f), AnyScssExpression::ScssListExpression(node) => node.format().fmt(f), AnyScssExpression::ScssMapExpression(node) => node.format().fmt(f), AnyScssExpression::ScssParenthesizedExpression(node) => node.format().fmt(f), diff --git a/crates/biome_css_formatter/src/scss/any/expression_item.rs b/crates/biome_css_formatter/src/scss/any/expression_item.rs index 0dfce5eb8de6..8a58f2780b4e 100644 --- a/crates/biome_css_formatter/src/scss/any/expression_item.rs +++ b/crates/biome_css_formatter/src/scss/any/expression_item.rs @@ -13,6 +13,7 @@ impl FormatRule for FormatAnyScssExpressionItem { AnyScssExpressionItem::CssGenericDelimiter(node) => node.format().fmt(f), AnyScssExpressionItem::ScssArbitraryArgument(node) => node.format().fmt(f), AnyScssExpressionItem::ScssBinaryExpression(node) => node.format().fmt(f), + AnyScssExpressionItem::ScssInterpolation(node) => node.format().fmt(f), AnyScssExpressionItem::ScssKeywordArgument(node) => node.format().fmt(f), AnyScssExpressionItem::ScssListExpression(node) => node.format().fmt(f), AnyScssExpressionItem::ScssMapExpression(node) => node.format().fmt(f), diff --git a/crates/biome_css_formatter/src/scss/auxiliary/interpolation.rs b/crates/biome_css_formatter/src/scss/auxiliary/interpolation.rs new file mode 100644 index 000000000000..93fdf3f7a872 --- /dev/null +++ b/crates/biome_css_formatter/src/scss/auxiliary/interpolation.rs @@ -0,0 +1,26 @@ +use crate::prelude::*; +use biome_css_syntax::{ScssInterpolation, ScssInterpolationFields}; +use biome_formatter::{format_args, write}; + +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatScssInterpolation; +impl FormatNodeRule for FormatScssInterpolation { + fn fmt_fields(&self, node: &ScssInterpolation, f: &mut CssFormatter) -> FormatResult<()> { + let ScssInterpolationFields { + hash_token, + l_curly_token, + value, + r_curly_token, + } = node.as_fields(); + + write!( + f, + [group(&format_args![ + hash_token.format(), + l_curly_token.format(), + value.format(), + r_curly_token.format() + ])] + ) + } +} diff --git a/crates/biome_css_formatter/src/scss/auxiliary/mod.rs b/crates/biome_css_formatter/src/scss/auxiliary/mod.rs index e734fe41b8cb..18b3e82f478f 100644 --- a/crates/biome_css_formatter/src/scss/auxiliary/mod.rs +++ b/crates/biome_css_formatter/src/scss/auxiliary/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod extend_optional_modifier; pub(crate) mod forward_as_clause; pub(crate) mod hide_clause; pub(crate) mod include_argument_list; +pub(crate) mod interpolation; pub(crate) mod keyword_argument; pub(crate) mod list_expression; pub(crate) mod list_expression_element; diff --git a/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss b/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss new file mode 100644 index 000000000000..c99924dd836b --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss @@ -0,0 +1,6 @@ +$direct:#{$name}; +$binary:#{1+2}; +$nested:#{($a+1)*2}; +$call:#{map-get($map, +key)}; +$list:#{1,2,3}; diff --git a/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss.snap b/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss.snap new file mode 100644 index 000000000000..ab1b22fb6385 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/scss/expression/interpolation.scss.snap @@ -0,0 +1,42 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +assertion_line: 212 +info: css/scss/expression/interpolation.scss +--- + +# Input + +```scss +$direct:#{$name}; +$binary:#{1+2}; +$nested:#{($a+1)*2}; +$call:#{map-get($map, +key)}; +$list:#{1,2,3}; + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +Trailing newline: true +----- + +```scss +$direct: #{$name}; +$binary: #{1 + 2}; +$nested: #{($a + 1) * 2}; +$call: #{map-get($map, key)}; +$list: #{1, 2, 3}; + +``` diff --git a/crates/biome_css_parser/src/lexer/tests.rs b/crates/biome_css_parser/src/lexer/tests.rs index 5c3d428308a9..ed9972a9d72d 100644 --- a/crates/biome_css_parser/src/lexer/tests.rs +++ b/crates/biome_css_parser/src/lexer/tests.rs @@ -2,9 +2,9 @@ #![expect(unused_mut, unused_variables)] use super::{CssLexer, TextSize}; +use biome_css_syntax::CssSyntaxKind::EOF; use crate::lexer::CssLexContext; use crate::CssParserOptions; -use biome_css_syntax::CssSyntaxKind::EOF; use biome_parser::lexer::Lexer; use quickcheck_macros::quickcheck; use std::sync::mpsc::channel; diff --git a/crates/biome_css_parser/src/syntax/scss/expression/interpolation.rs b/crates/biome_css_parser/src/syntax/scss/expression/interpolation.rs new file mode 100644 index 000000000000..aacee9f5d00d --- /dev/null +++ b/crates/biome_css_parser/src/syntax/scss/expression/interpolation.rs @@ -0,0 +1,41 @@ +use crate::parser::CssParser; +use biome_css_syntax::CssSyntaxKind::SCSS_INTERPOLATION; +use biome_css_syntax::{CssSyntaxKind, T}; +use biome_parser::prelude::ParsedSyntax::{Absent, Present}; +use biome_parser::prelude::*; +use biome_parser::{TokenSet, token_set}; + +use super::list::parse_scss_inner_expression_until; +use crate::syntax::scss::expected_scss_expression; + +const SCSS_INTERPOLATION_END_TOKEN_SET: TokenSet = token_set![T!['}'], T![;]]; + +/// Parses a standalone SCSS interpolation expression such as `#{$value}`. +/// +/// Example: +/// ```scss +/// $value: #{$name}; +/// ``` +/// +/// Docs: https://sass-lang.com/documentation/interpolation/ +#[inline] +pub(super) fn parse_scss_interpolation(p: &mut CssParser) -> ParsedSyntax { + if !is_at_scss_interpolation(p) { + return Absent; + } + + let m = p.start(); + + p.bump(T![#]); + p.bump(T!['{']); + parse_scss_inner_expression_until(p, SCSS_INTERPOLATION_END_TOKEN_SET) + .or_add_diagnostic(p, expected_scss_expression); + p.expect(T!['}']); + + Present(m.complete(p, SCSS_INTERPOLATION)) +} + +#[inline] +pub(super) fn is_at_scss_interpolation(p: &mut CssParser) -> bool { + p.at(T![#]) && p.nth_at(1, T!['{']) +} diff --git a/crates/biome_css_parser/src/syntax/scss/expression/mod.rs b/crates/biome_css_parser/src/syntax/scss/expression/mod.rs index f1fce5b1ff19..ce5eb234417c 100644 --- a/crates/biome_css_parser/src/syntax/scss/expression/mod.rs +++ b/crates/biome_css_parser/src/syntax/scss/expression/mod.rs @@ -1,3 +1,4 @@ +mod interpolation; mod list; mod map; mod precedence; diff --git a/crates/biome_css_parser/src/syntax/scss/expression/primary.rs b/crates/biome_css_parser/src/syntax/scss/expression/primary.rs index a318357140bd..1cf26913e769 100644 --- a/crates/biome_css_parser/src/syntax/scss/expression/primary.rs +++ b/crates/biome_css_parser/src/syntax/scss/expression/primary.rs @@ -6,6 +6,7 @@ use biome_css_syntax::T; use biome_parser::Parser; use biome_parser::prelude::ParsedSyntax; +use super::interpolation::{is_at_scss_interpolation, parse_scss_interpolation}; use super::map::parse_scss_parenthesized_or_map_expression; /// Parses SCSS primaries such as parenthesized values, maps, `!important`, and @@ -19,7 +20,9 @@ use super::map::parse_scss_parenthesized_or_map_expression; /// Docs: https://sass-lang.com/documentation/at-rules/function #[inline] pub(super) fn parse_scss_primary_expression(p: &mut CssParser) -> ParsedSyntax { - if p.at(T!['(']) { + if is_at_scss_interpolation(p) { + parse_scss_interpolation(p) + } else if p.at(T!['(']) { parse_scss_parenthesized_or_map_expression(p) } else if is_at_declaration_important(p) { parse_declaration_important(p) diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss b/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss new file mode 100644 index 000000000000..ebfda559dc2a --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss @@ -0,0 +1,3 @@ +$unclosed: #{$name; +$empty: #{}; +$missing-rhs: #{1 + }; diff --git a/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss.snap b/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss.snap new file mode 100644 index 000000000000..2ef07f65482d --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/error/scss/expression/interpolation.scss.snap @@ -0,0 +1,228 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 208 +expression: snapshot +--- + +## Input + +```css +$unclosed: #{$name; +$empty: #{}; +$missing-rhs: #{1 + }; + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@0..1 "$" [] [], + name: CssIdentifier { + value_token: IDENT@1..9 "unclosed" [] [], + }, + }, + colon_token: COLON@9..11 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@11..12 "#" [] [], + l_curly_token: L_CURLY@12..13 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssIdentifier { + dollar_token: DOLLAR@13..14 "$" [] [], + name: CssIdentifier { + value_token: IDENT@14..18 "name" [] [], + }, + }, + ], + }, + r_curly_token: missing (required), + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@18..19 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@19..21 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@21..26 "empty" [] [], + }, + }, + colon_token: COLON@26..28 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@28..29 "#" [] [], + l_curly_token: L_CURLY@29..30 "{" [] [], + value: missing (required), + r_curly_token: R_CURLY@30..31 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@31..32 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@32..34 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@34..45 "missing-rhs" [] [], + }, + }, + colon_token: COLON@45..47 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@47..48 "#" [] [], + l_curly_token: L_CURLY@48..49 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssBinaryExpression { + left: CssNumber { + value_token: CSS_NUMBER_LITERAL@49..51 "1" [] [Whitespace(" ")], + }, + operator: PLUS@51..53 "+" [] [Whitespace(" ")], + right: missing (required), + }, + ], + }, + r_curly_token: R_CURLY@53..54 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@54..55 ";" [] [], + }, + ], + eof_token: EOF@55..56 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..56 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..55 + 0: SCSS_DECLARATION@0..19 + 0: SCSS_IDENTIFIER@0..9 + 0: DOLLAR@0..1 "$" [] [] + 1: CSS_IDENTIFIER@1..9 + 0: IDENT@1..9 "unclosed" [] [] + 1: COLON@9..11 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@11..18 + 0: SCSS_EXPRESSION_ITEM_LIST@11..18 + 0: SCSS_INTERPOLATION@11..18 + 0: HASH@11..12 "#" [] [] + 1: L_CURLY@12..13 "{" [] [] + 2: SCSS_EXPRESSION@13..18 + 0: SCSS_EXPRESSION_ITEM_LIST@13..18 + 0: SCSS_IDENTIFIER@13..18 + 0: DOLLAR@13..14 "$" [] [] + 1: CSS_IDENTIFIER@14..18 + 0: IDENT@14..18 "name" [] [] + 3: (empty) + 3: SCSS_VARIABLE_MODIFIER_LIST@18..18 + 4: SEMICOLON@18..19 ";" [] [] + 1: SCSS_DECLARATION@19..32 + 0: SCSS_IDENTIFIER@19..26 + 0: DOLLAR@19..21 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@21..26 + 0: IDENT@21..26 "empty" [] [] + 1: COLON@26..28 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@28..31 + 0: SCSS_EXPRESSION_ITEM_LIST@28..31 + 0: SCSS_INTERPOLATION@28..31 + 0: HASH@28..29 "#" [] [] + 1: L_CURLY@29..30 "{" [] [] + 2: (empty) + 3: R_CURLY@30..31 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@31..31 + 4: SEMICOLON@31..32 ";" [] [] + 2: SCSS_DECLARATION@32..55 + 0: SCSS_IDENTIFIER@32..45 + 0: DOLLAR@32..34 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@34..45 + 0: IDENT@34..45 "missing-rhs" [] [] + 1: COLON@45..47 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@47..54 + 0: SCSS_EXPRESSION_ITEM_LIST@47..54 + 0: SCSS_INTERPOLATION@47..54 + 0: HASH@47..48 "#" [] [] + 1: L_CURLY@48..49 "{" [] [] + 2: SCSS_EXPRESSION@49..53 + 0: SCSS_EXPRESSION_ITEM_LIST@49..53 + 0: SCSS_BINARY_EXPRESSION@49..53 + 0: CSS_NUMBER@49..51 + 0: CSS_NUMBER_LITERAL@49..51 "1" [] [Whitespace(" ")] + 1: PLUS@51..53 "+" [] [Whitespace(" ")] + 2: (empty) + 3: R_CURLY@53..54 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@54..54 + 4: SEMICOLON@54..55 ";" [] [] + 2: EOF@55..56 "" [Newline("\n")] [] + +``` + +## Diagnostics + +``` +interpolation.scss:1:19 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead found `;` + + > 1 │ $unclosed: #{$name; + │ ^ + 2 │ $empty: #{}; + 3 │ $missing-rhs: #{1 + }; + + i Remove ; + +interpolation.scss:2:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a SCSS expression but instead found '}'. + + 1 │ $unclosed: #{$name; + > 2 │ $empty: #{}; + │ ^ + 3 │ $missing-rhs: #{1 + }; + 4 │ + + i Expected a SCSS expression here. + + 1 │ $unclosed: #{$name; + > 2 │ $empty: #{}; + │ ^ + 3 │ $missing-rhs: #{1 + }; + 4 │ + +interpolation.scss:3:21 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected value or character. + + 1 │ $unclosed: #{$name; + 2 │ $empty: #{}; + > 3 │ $missing-rhs: #{1 + }; + │ ^ + 4 │ + + i Expected one of: + + - identifier + - string + - number + - dimension + - ratio + - custom property + - function + +``` diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss b/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss new file mode 100644 index 000000000000..cbf644e076c4 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss @@ -0,0 +1,5 @@ +$direct: #{$name}; +$binary: #{1 + 2}; +$nested: #{($a + 1) * 2}; +$call: #{map-get($map, key)}; +$list: #{1, 2, 3}; diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss.snap b/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss.snap new file mode 100644 index 000000000000..249acb312003 --- /dev/null +++ b/crates/biome_css_parser/tests/css_test_suite/ok/scss/expression/interpolation.scss.snap @@ -0,0 +1,402 @@ +--- +source: crates/biome_css_parser/tests/spec_test.rs +assertion_line: 208 +expression: snapshot +--- + +## Input + +```css +$direct: #{$name}; +$binary: #{1 + 2}; +$nested: #{($a + 1) * 2}; +$call: #{map-get($map, key)}; +$list: #{1, 2, 3}; + +``` + + +## AST + +``` +CssRoot { + bom_token: missing (optional), + items: CssRootItemList [ + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@0..1 "$" [] [], + name: CssIdentifier { + value_token: IDENT@1..7 "direct" [] [], + }, + }, + colon_token: COLON@7..9 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@9..10 "#" [] [], + l_curly_token: L_CURLY@10..11 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssIdentifier { + dollar_token: DOLLAR@11..12 "$" [] [], + name: CssIdentifier { + value_token: IDENT@12..16 "name" [] [], + }, + }, + ], + }, + r_curly_token: R_CURLY@16..17 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@17..18 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@18..20 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@20..26 "binary" [] [], + }, + }, + colon_token: COLON@26..28 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@28..29 "#" [] [], + l_curly_token: L_CURLY@29..30 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssBinaryExpression { + left: CssNumber { + value_token: CSS_NUMBER_LITERAL@30..32 "1" [] [Whitespace(" ")], + }, + operator: PLUS@32..34 "+" [] [Whitespace(" ")], + right: CssNumber { + value_token: CSS_NUMBER_LITERAL@34..35 "2" [] [], + }, + }, + ], + }, + r_curly_token: R_CURLY@35..36 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@36..37 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@37..39 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@39..45 "nested" [] [], + }, + }, + colon_token: COLON@45..47 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@47..48 "#" [] [], + l_curly_token: L_CURLY@48..49 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssBinaryExpression { + left: ScssParenthesizedExpression { + l_paren_token: L_PAREN@49..50 "(" [] [], + expression: ScssExpression { + items: ScssExpressionItemList [ + ScssBinaryExpression { + left: ScssIdentifier { + dollar_token: DOLLAR@50..51 "$" [] [], + name: CssIdentifier { + value_token: IDENT@51..53 "a" [] [Whitespace(" ")], + }, + }, + operator: PLUS@53..55 "+" [] [Whitespace(" ")], + right: CssNumber { + value_token: CSS_NUMBER_LITERAL@55..56 "1" [] [], + }, + }, + ], + }, + r_paren_token: R_PAREN@56..58 ")" [] [Whitespace(" ")], + }, + operator: STAR@58..60 "*" [] [Whitespace(" ")], + right: CssNumber { + value_token: CSS_NUMBER_LITERAL@60..61 "2" [] [], + }, + }, + ], + }, + r_curly_token: R_CURLY@61..62 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@62..63 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@63..65 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@65..69 "call" [] [], + }, + }, + colon_token: COLON@69..71 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@71..72 "#" [] [], + l_curly_token: L_CURLY@72..73 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + CssFunction { + name: CssIdentifier { + value_token: IDENT@73..80 "map-get" [] [], + }, + l_paren_token: L_PAREN@80..81 "(" [] [], + items: CssParameterList [ + ScssExpression { + items: ScssExpressionItemList [ + ScssIdentifier { + dollar_token: DOLLAR@81..82 "$" [] [], + name: CssIdentifier { + value_token: IDENT@82..85 "map" [] [], + }, + }, + ], + }, + COMMA@85..87 "," [] [Whitespace(" ")], + ScssExpression { + items: ScssExpressionItemList [ + CssIdentifier { + value_token: IDENT@87..90 "key" [] [], + }, + ], + }, + ], + r_paren_token: R_PAREN@90..91 ")" [] [], + }, + ], + }, + r_curly_token: R_CURLY@91..92 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@92..93 ";" [] [], + }, + ScssDeclaration { + name: ScssIdentifier { + dollar_token: DOLLAR@93..95 "$" [Newline("\n")] [], + name: CssIdentifier { + value_token: IDENT@95..99 "list" [] [], + }, + }, + colon_token: COLON@99..101 ":" [] [Whitespace(" ")], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssInterpolation { + hash_token: HASH@101..102 "#" [] [], + l_curly_token: L_CURLY@102..103 "{" [] [], + value: ScssExpression { + items: ScssExpressionItemList [ + ScssListExpression { + elements: ScssListExpressionElementList [ + ScssListExpressionElement { + value: ScssExpression { + items: ScssExpressionItemList [ + CssNumber { + value_token: CSS_NUMBER_LITERAL@103..104 "1" [] [], + }, + ], + }, + }, + COMMA@104..106 "," [] [Whitespace(" ")], + ScssListExpressionElement { + value: ScssExpression { + items: ScssExpressionItemList [ + CssNumber { + value_token: CSS_NUMBER_LITERAL@106..107 "2" [] [], + }, + ], + }, + }, + COMMA@107..109 "," [] [Whitespace(" ")], + ScssListExpressionElement { + value: ScssExpression { + items: ScssExpressionItemList [ + CssNumber { + value_token: CSS_NUMBER_LITERAL@109..110 "3" [] [], + }, + ], + }, + }, + ], + }, + ], + }, + r_curly_token: R_CURLY@110..111 "}" [] [], + }, + ], + }, + modifiers: ScssVariableModifierList [], + semicolon_token: SEMICOLON@111..112 ";" [] [], + }, + ], + eof_token: EOF@112..113 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..113 + 0: (empty) + 1: CSS_ROOT_ITEM_LIST@0..112 + 0: SCSS_DECLARATION@0..18 + 0: SCSS_IDENTIFIER@0..7 + 0: DOLLAR@0..1 "$" [] [] + 1: CSS_IDENTIFIER@1..7 + 0: IDENT@1..7 "direct" [] [] + 1: COLON@7..9 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@9..17 + 0: SCSS_EXPRESSION_ITEM_LIST@9..17 + 0: SCSS_INTERPOLATION@9..17 + 0: HASH@9..10 "#" [] [] + 1: L_CURLY@10..11 "{" [] [] + 2: SCSS_EXPRESSION@11..16 + 0: SCSS_EXPRESSION_ITEM_LIST@11..16 + 0: SCSS_IDENTIFIER@11..16 + 0: DOLLAR@11..12 "$" [] [] + 1: CSS_IDENTIFIER@12..16 + 0: IDENT@12..16 "name" [] [] + 3: R_CURLY@16..17 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@17..17 + 4: SEMICOLON@17..18 ";" [] [] + 1: SCSS_DECLARATION@18..37 + 0: SCSS_IDENTIFIER@18..26 + 0: DOLLAR@18..20 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@20..26 + 0: IDENT@20..26 "binary" [] [] + 1: COLON@26..28 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@28..36 + 0: SCSS_EXPRESSION_ITEM_LIST@28..36 + 0: SCSS_INTERPOLATION@28..36 + 0: HASH@28..29 "#" [] [] + 1: L_CURLY@29..30 "{" [] [] + 2: SCSS_EXPRESSION@30..35 + 0: SCSS_EXPRESSION_ITEM_LIST@30..35 + 0: SCSS_BINARY_EXPRESSION@30..35 + 0: CSS_NUMBER@30..32 + 0: CSS_NUMBER_LITERAL@30..32 "1" [] [Whitespace(" ")] + 1: PLUS@32..34 "+" [] [Whitespace(" ")] + 2: CSS_NUMBER@34..35 + 0: CSS_NUMBER_LITERAL@34..35 "2" [] [] + 3: R_CURLY@35..36 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@36..36 + 4: SEMICOLON@36..37 ";" [] [] + 2: SCSS_DECLARATION@37..63 + 0: SCSS_IDENTIFIER@37..45 + 0: DOLLAR@37..39 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@39..45 + 0: IDENT@39..45 "nested" [] [] + 1: COLON@45..47 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@47..62 + 0: SCSS_EXPRESSION_ITEM_LIST@47..62 + 0: SCSS_INTERPOLATION@47..62 + 0: HASH@47..48 "#" [] [] + 1: L_CURLY@48..49 "{" [] [] + 2: SCSS_EXPRESSION@49..61 + 0: SCSS_EXPRESSION_ITEM_LIST@49..61 + 0: SCSS_BINARY_EXPRESSION@49..61 + 0: SCSS_PARENTHESIZED_EXPRESSION@49..58 + 0: L_PAREN@49..50 "(" [] [] + 1: SCSS_EXPRESSION@50..56 + 0: SCSS_EXPRESSION_ITEM_LIST@50..56 + 0: SCSS_BINARY_EXPRESSION@50..56 + 0: SCSS_IDENTIFIER@50..53 + 0: DOLLAR@50..51 "$" [] [] + 1: CSS_IDENTIFIER@51..53 + 0: IDENT@51..53 "a" [] [Whitespace(" ")] + 1: PLUS@53..55 "+" [] [Whitespace(" ")] + 2: CSS_NUMBER@55..56 + 0: CSS_NUMBER_LITERAL@55..56 "1" [] [] + 2: R_PAREN@56..58 ")" [] [Whitespace(" ")] + 1: STAR@58..60 "*" [] [Whitespace(" ")] + 2: CSS_NUMBER@60..61 + 0: CSS_NUMBER_LITERAL@60..61 "2" [] [] + 3: R_CURLY@61..62 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@62..62 + 4: SEMICOLON@62..63 ";" [] [] + 3: SCSS_DECLARATION@63..93 + 0: SCSS_IDENTIFIER@63..69 + 0: DOLLAR@63..65 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@65..69 + 0: IDENT@65..69 "call" [] [] + 1: COLON@69..71 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@71..92 + 0: SCSS_EXPRESSION_ITEM_LIST@71..92 + 0: SCSS_INTERPOLATION@71..92 + 0: HASH@71..72 "#" [] [] + 1: L_CURLY@72..73 "{" [] [] + 2: SCSS_EXPRESSION@73..91 + 0: SCSS_EXPRESSION_ITEM_LIST@73..91 + 0: CSS_FUNCTION@73..91 + 0: CSS_IDENTIFIER@73..80 + 0: IDENT@73..80 "map-get" [] [] + 1: L_PAREN@80..81 "(" [] [] + 2: CSS_PARAMETER_LIST@81..90 + 0: SCSS_EXPRESSION@81..85 + 0: SCSS_EXPRESSION_ITEM_LIST@81..85 + 0: SCSS_IDENTIFIER@81..85 + 0: DOLLAR@81..82 "$" [] [] + 1: CSS_IDENTIFIER@82..85 + 0: IDENT@82..85 "map" [] [] + 1: COMMA@85..87 "," [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@87..90 + 0: SCSS_EXPRESSION_ITEM_LIST@87..90 + 0: CSS_IDENTIFIER@87..90 + 0: IDENT@87..90 "key" [] [] + 3: R_PAREN@90..91 ")" [] [] + 3: R_CURLY@91..92 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@92..92 + 4: SEMICOLON@92..93 ";" [] [] + 4: SCSS_DECLARATION@93..112 + 0: SCSS_IDENTIFIER@93..99 + 0: DOLLAR@93..95 "$" [Newline("\n")] [] + 1: CSS_IDENTIFIER@95..99 + 0: IDENT@95..99 "list" [] [] + 1: COLON@99..101 ":" [] [Whitespace(" ")] + 2: SCSS_EXPRESSION@101..111 + 0: SCSS_EXPRESSION_ITEM_LIST@101..111 + 0: SCSS_INTERPOLATION@101..111 + 0: HASH@101..102 "#" [] [] + 1: L_CURLY@102..103 "{" [] [] + 2: SCSS_EXPRESSION@103..110 + 0: SCSS_EXPRESSION_ITEM_LIST@103..110 + 0: SCSS_LIST_EXPRESSION@103..110 + 0: SCSS_LIST_EXPRESSION_ELEMENT_LIST@103..110 + 0: SCSS_LIST_EXPRESSION_ELEMENT@103..104 + 0: SCSS_EXPRESSION@103..104 + 0: SCSS_EXPRESSION_ITEM_LIST@103..104 + 0: CSS_NUMBER@103..104 + 0: CSS_NUMBER_LITERAL@103..104 "1" [] [] + 1: COMMA@104..106 "," [] [Whitespace(" ")] + 2: SCSS_LIST_EXPRESSION_ELEMENT@106..107 + 0: SCSS_EXPRESSION@106..107 + 0: SCSS_EXPRESSION_ITEM_LIST@106..107 + 0: CSS_NUMBER@106..107 + 0: CSS_NUMBER_LITERAL@106..107 "2" [] [] + 3: COMMA@107..109 "," [] [Whitespace(" ")] + 4: SCSS_LIST_EXPRESSION_ELEMENT@109..110 + 0: SCSS_EXPRESSION@109..110 + 0: SCSS_EXPRESSION_ITEM_LIST@109..110 + 0: CSS_NUMBER@109..110 + 0: CSS_NUMBER_LITERAL@109..110 "3" [] [] + 3: R_CURLY@110..111 "}" [] [] + 3: SCSS_VARIABLE_MODIFIER_LIST@111..111 + 4: SEMICOLON@111..112 ";" [] [] + 2: EOF@112..113 "" [Newline("\n")] [] + +``` diff --git a/crates/biome_css_syntax/src/generated/kind.rs b/crates/biome_css_syntax/src/generated/kind.rs index 8d6bd8ca60e7..32c86a2e24a4 100644 --- a/crates/biome_css_syntax/src/generated/kind.rs +++ b/crates/biome_css_syntax/src/generated/kind.rs @@ -596,7 +596,6 @@ pub enum CssSyntaxKind { SCSS_PARAMETER, SCSS_PARAMETER_DEFAULT_VALUE, SCSS_PLACEHOLDER_SELECTOR, - SCSS_INTERPOLATION, SCSS_PLAIN_IMPORT, SCSS_SHOW_CLAUSE, SCSS_USE_ALL_NAMESPACE, @@ -614,6 +613,7 @@ pub enum CssSyntaxKind { SCSS_EXPRESSION, SCSS_EXPRESSION_ITEM_LIST, SCSS_BINARY_EXPRESSION, + SCSS_INTERPOLATION, SCSS_KEYWORD_ARGUMENT, SCSS_ARBITRARY_ARGUMENT, SCSS_LIST_EXPRESSION, diff --git a/crates/biome_css_syntax/src/generated/macros.rs b/crates/biome_css_syntax/src/generated/macros.rs index e8691af2dae5..cb49dee1aeec 100644 --- a/crates/biome_css_syntax/src/generated/macros.rs +++ b/crates/biome_css_syntax/src/generated/macros.rs @@ -1036,6 +1036,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::ScssIncludeAtRule::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::SCSS_INTERPOLATION => { + let $pattern = unsafe { $crate::ScssInterpolation::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::SCSS_KEYWORD_ARGUMENT => { let $pattern = unsafe { $crate::ScssKeywordArgument::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 da9973e3c465..fc58f6a09f84 100644 --- a/crates/biome_css_syntax/src/generated/nodes.rs +++ b/crates/biome_css_syntax/src/generated/nodes.rs @@ -10128,6 +10128,56 @@ pub struct ScssIncludeAtRuleFields { pub semicolon_token: Option, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct ScssInterpolation { + pub(crate) syntax: SyntaxNode, +} +impl ScssInterpolation { + #[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) -> ScssInterpolationFields { + ScssInterpolationFields { + hash_token: self.hash_token(), + l_curly_token: self.l_curly_token(), + value: self.value(), + r_curly_token: self.r_curly_token(), + } + } + pub fn hash_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn l_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } + pub fn value(&self) -> SyntaxResult { + support::required_node(&self.syntax, 2usize) + } + pub fn r_curly_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } +} +impl Serialize for ScssInterpolation { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct ScssInterpolationFields { + pub hash_token: SyntaxResult, + pub l_curly_token: SyntaxResult, + pub value: SyntaxResult, + pub r_curly_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct ScssKeywordArgument { pub(crate) syntax: SyntaxNode, } @@ -15335,6 +15385,7 @@ pub enum AnyScssExpression { AnyCssValue(AnyCssValue), ScssBinaryExpression(ScssBinaryExpression), ScssExpression(ScssExpression), + ScssInterpolation(ScssInterpolation), ScssListExpression(ScssListExpression), ScssMapExpression(ScssMapExpression), ScssParenthesizedExpression(ScssParenthesizedExpression), @@ -15359,6 +15410,12 @@ impl AnyScssExpression { _ => None, } } + pub fn as_scss_interpolation(&self) -> Option<&ScssInterpolation> { + match &self { + Self::ScssInterpolation(item) => Some(item), + _ => None, + } + } pub fn as_scss_list_expression(&self) -> Option<&ScssListExpression> { match &self { Self::ScssListExpression(item) => Some(item), @@ -15391,6 +15448,7 @@ pub enum AnyScssExpressionItem { CssGenericDelimiter(CssGenericDelimiter), ScssArbitraryArgument(ScssArbitraryArgument), ScssBinaryExpression(ScssBinaryExpression), + ScssInterpolation(ScssInterpolation), ScssKeywordArgument(ScssKeywordArgument), ScssListExpression(ScssListExpression), ScssMapExpression(ScssMapExpression), @@ -15428,6 +15486,12 @@ impl AnyScssExpressionItem { _ => None, } } + pub fn as_scss_interpolation(&self) -> Option<&ScssInterpolation> { + match &self { + Self::ScssInterpolation(item) => Some(item), + _ => None, + } + } pub fn as_scss_keyword_argument(&self) -> Option<&ScssKeywordArgument> { match &self { Self::ScssKeywordArgument(item) => Some(item), @@ -27858,6 +27922,62 @@ impl From for SyntaxElement { n.syntax.into() } } +impl AstNode for ScssInterpolation { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(SCSS_INTERPOLATION as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == SCSS_INTERPOLATION + } + 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 ScssInterpolation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("ScssInterpolation") + .field("hash_token", &support::DebugSyntaxResult(self.hash_token())) + .field( + "l_curly_token", + &support::DebugSyntaxResult(self.l_curly_token()), + ) + .field("value", &support::DebugSyntaxResult(self.value())) + .field( + "r_curly_token", + &support::DebugSyntaxResult(self.r_curly_token()), + ) + .finish() + } else { + f.debug_struct("ScssInterpolation").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: ScssInterpolation) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: ScssInterpolation) -> Self { + n.syntax.into() + } +} impl AstNode for ScssKeywordArgument { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -39242,6 +39362,11 @@ impl From for AnyScssExpression { Self::ScssExpression(node) } } +impl From for AnyScssExpression { + fn from(node: ScssInterpolation) -> Self { + Self::ScssInterpolation(node) + } +} impl From for AnyScssExpression { fn from(node: ScssListExpression) -> Self { Self::ScssListExpression(node) @@ -39267,6 +39392,7 @@ impl AstNode for AnyScssExpression { const KIND_SET: SyntaxKindSet = AnyCssValue::KIND_SET .union(ScssBinaryExpression::KIND_SET) .union(ScssExpression::KIND_SET) + .union(ScssInterpolation::KIND_SET) .union(ScssListExpression::KIND_SET) .union(ScssMapExpression::KIND_SET) .union(ScssParenthesizedExpression::KIND_SET) @@ -39275,6 +39401,7 @@ impl AstNode for AnyScssExpression { match kind { SCSS_BINARY_EXPRESSION | SCSS_EXPRESSION + | SCSS_INTERPOLATION | SCSS_LIST_EXPRESSION | SCSS_MAP_EXPRESSION | SCSS_PARENTHESIZED_EXPRESSION @@ -39287,6 +39414,7 @@ impl AstNode for AnyScssExpression { let res = match syntax.kind() { SCSS_BINARY_EXPRESSION => Self::ScssBinaryExpression(ScssBinaryExpression { syntax }), SCSS_EXPRESSION => Self::ScssExpression(ScssExpression { syntax }), + SCSS_INTERPOLATION => Self::ScssInterpolation(ScssInterpolation { syntax }), SCSS_LIST_EXPRESSION => Self::ScssListExpression(ScssListExpression { syntax }), SCSS_MAP_EXPRESSION => Self::ScssMapExpression(ScssMapExpression { syntax }), SCSS_PARENTHESIZED_EXPRESSION => { @@ -39306,6 +39434,7 @@ impl AstNode for AnyScssExpression { match self { Self::ScssBinaryExpression(it) => it.syntax(), Self::ScssExpression(it) => it.syntax(), + Self::ScssInterpolation(it) => it.syntax(), Self::ScssListExpression(it) => it.syntax(), Self::ScssMapExpression(it) => it.syntax(), Self::ScssParenthesizedExpression(it) => it.syntax(), @@ -39317,6 +39446,7 @@ impl AstNode for AnyScssExpression { match self { Self::ScssBinaryExpression(it) => it.into_syntax(), Self::ScssExpression(it) => it.into_syntax(), + Self::ScssInterpolation(it) => it.into_syntax(), Self::ScssListExpression(it) => it.into_syntax(), Self::ScssMapExpression(it) => it.into_syntax(), Self::ScssParenthesizedExpression(it) => it.into_syntax(), @@ -39331,6 +39461,7 @@ impl std::fmt::Debug for AnyScssExpression { Self::AnyCssValue(it) => std::fmt::Debug::fmt(it, f), Self::ScssBinaryExpression(it) => std::fmt::Debug::fmt(it, f), Self::ScssExpression(it) => std::fmt::Debug::fmt(it, f), + Self::ScssInterpolation(it) => std::fmt::Debug::fmt(it, f), Self::ScssListExpression(it) => std::fmt::Debug::fmt(it, f), Self::ScssMapExpression(it) => std::fmt::Debug::fmt(it, f), Self::ScssParenthesizedExpression(it) => std::fmt::Debug::fmt(it, f), @@ -39344,6 +39475,7 @@ impl From for SyntaxNode { AnyScssExpression::AnyCssValue(it) => it.into_syntax(), AnyScssExpression::ScssBinaryExpression(it) => it.into_syntax(), AnyScssExpression::ScssExpression(it) => it.into_syntax(), + AnyScssExpression::ScssInterpolation(it) => it.into_syntax(), AnyScssExpression::ScssListExpression(it) => it.into_syntax(), AnyScssExpression::ScssMapExpression(it) => it.into_syntax(), AnyScssExpression::ScssParenthesizedExpression(it) => it.into_syntax(), @@ -39377,6 +39509,11 @@ impl From for AnyScssExpressionItem { Self::ScssBinaryExpression(node) } } +impl From for AnyScssExpressionItem { + fn from(node: ScssInterpolation) -> Self { + Self::ScssInterpolation(node) + } +} impl From for AnyScssExpressionItem { fn from(node: ScssKeywordArgument) -> Self { Self::ScssKeywordArgument(node) @@ -39409,6 +39546,7 @@ impl AstNode for AnyScssExpressionItem { .union(CssGenericDelimiter::KIND_SET) .union(ScssArbitraryArgument::KIND_SET) .union(ScssBinaryExpression::KIND_SET) + .union(ScssInterpolation::KIND_SET) .union(ScssKeywordArgument::KIND_SET) .union(ScssListExpression::KIND_SET) .union(ScssMapExpression::KIND_SET) @@ -39420,6 +39558,7 @@ impl AstNode for AnyScssExpressionItem { | CSS_GENERIC_DELIMITER | SCSS_ARBITRARY_ARGUMENT | SCSS_BINARY_EXPRESSION + | SCSS_INTERPOLATION | SCSS_KEYWORD_ARGUMENT | SCSS_LIST_EXPRESSION | SCSS_MAP_EXPRESSION @@ -39439,6 +39578,7 @@ impl AstNode for AnyScssExpressionItem { Self::ScssArbitraryArgument(ScssArbitraryArgument { syntax }) } SCSS_BINARY_EXPRESSION => Self::ScssBinaryExpression(ScssBinaryExpression { syntax }), + SCSS_INTERPOLATION => Self::ScssInterpolation(ScssInterpolation { syntax }), SCSS_KEYWORD_ARGUMENT => Self::ScssKeywordArgument(ScssKeywordArgument { syntax }), SCSS_LIST_EXPRESSION => Self::ScssListExpression(ScssListExpression { syntax }), SCSS_MAP_EXPRESSION => Self::ScssMapExpression(ScssMapExpression { syntax }), @@ -39461,6 +39601,7 @@ impl AstNode for AnyScssExpressionItem { Self::CssGenericDelimiter(it) => it.syntax(), Self::ScssArbitraryArgument(it) => it.syntax(), Self::ScssBinaryExpression(it) => it.syntax(), + Self::ScssInterpolation(it) => it.syntax(), Self::ScssKeywordArgument(it) => it.syntax(), Self::ScssListExpression(it) => it.syntax(), Self::ScssMapExpression(it) => it.syntax(), @@ -39475,6 +39616,7 @@ impl AstNode for AnyScssExpressionItem { Self::CssGenericDelimiter(it) => it.into_syntax(), Self::ScssArbitraryArgument(it) => it.into_syntax(), Self::ScssBinaryExpression(it) => it.into_syntax(), + Self::ScssInterpolation(it) => it.into_syntax(), Self::ScssKeywordArgument(it) => it.into_syntax(), Self::ScssListExpression(it) => it.into_syntax(), Self::ScssMapExpression(it) => it.into_syntax(), @@ -39492,6 +39634,7 @@ impl std::fmt::Debug for AnyScssExpressionItem { Self::CssGenericDelimiter(it) => std::fmt::Debug::fmt(it, f), Self::ScssArbitraryArgument(it) => std::fmt::Debug::fmt(it, f), Self::ScssBinaryExpression(it) => std::fmt::Debug::fmt(it, f), + Self::ScssInterpolation(it) => std::fmt::Debug::fmt(it, f), Self::ScssKeywordArgument(it) => std::fmt::Debug::fmt(it, f), Self::ScssListExpression(it) => std::fmt::Debug::fmt(it, f), Self::ScssMapExpression(it) => std::fmt::Debug::fmt(it, f), @@ -39508,6 +39651,7 @@ impl From for SyntaxNode { AnyScssExpressionItem::CssGenericDelimiter(it) => it.into_syntax(), AnyScssExpressionItem::ScssArbitraryArgument(it) => it.into_syntax(), AnyScssExpressionItem::ScssBinaryExpression(it) => it.into_syntax(), + AnyScssExpressionItem::ScssInterpolation(it) => it.into_syntax(), AnyScssExpressionItem::ScssKeywordArgument(it) => it.into_syntax(), AnyScssExpressionItem::ScssListExpression(it) => it.into_syntax(), AnyScssExpressionItem::ScssMapExpression(it) => it.into_syntax(), @@ -41979,6 +42123,11 @@ impl std::fmt::Display for ScssIncludeAtRule { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for ScssInterpolation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for ScssKeywordArgument { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/crates/biome_css_syntax/src/generated/nodes_mut.rs b/crates/biome_css_syntax/src/generated/nodes_mut.rs index 7b9414ec60aa..38a3538c5d4c 100644 --- a/crates/biome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_css_syntax/src/generated/nodes_mut.rs @@ -4123,6 +4123,32 @@ impl ScssIncludeAtRule { ) } } +impl ScssInterpolation { + pub fn with_hash_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_l_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_value(self, element: AnyScssExpression) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_curly_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } +} impl ScssKeywordArgument { pub fn with_name(self, element: ScssIdentifier) -> Self { Self::unwrap_cast( diff --git a/crates/biome_grit_patterns/src/grit_target_language/css_target_language/generated_mappings.rs b/crates/biome_grit_patterns/src/grit_target_language/css_target_language/generated_mappings.rs index 69e771ea86f2..e1753e29adc4 100644 --- a/crates/biome_grit_patterns/src/grit_target_language/css_target_language/generated_mappings.rs +++ b/crates/biome_grit_patterns/src/grit_target_language/css_target_language/generated_mappings.rs @@ -314,6 +314,7 @@ pub fn kind_by_name(node_name: &str) -> Option { "ScssIfAtRule" => lang::ScssIfAtRule::KIND_SET.iter().next(), "ScssImportAtRule" => lang::ScssImportAtRule::KIND_SET.iter().next(), "ScssIncludeAtRule" => lang::ScssIncludeAtRule::KIND_SET.iter().next(), + "ScssInterpolation" => lang::ScssInterpolation::KIND_SET.iter().next(), "ScssKeywordArgument" => lang::ScssKeywordArgument::KIND_SET.iter().next(), "ScssMapExpression" => lang::ScssMapExpression::KIND_SET.iter().next(), "ScssMapExpressionPair" => lang::ScssMapExpressionPair::KIND_SET.iter().next(), diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index 9802e7b2626b..6576fbf2bf3f 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -2936,6 +2936,7 @@ AnyScssExpressionItem = | ScssKeywordArgument | ScssArbitraryArgument | ScssBinaryExpression + | ScssInterpolation | ScssUnaryExpression | ScssParenthesizedExpression | ScssListExpression @@ -2982,6 +2983,14 @@ ScssParenthesizedExpression = expression: AnyScssExpression ')' +// width: #{$a + 1}; +// ^^^^^^^^^ +ScssInterpolation = + '#' + '{' + value: AnyScssExpression + '}' + // box-shadow: 1px, 2px, 3px; // ^^^^^^^^^^^^^ ScssListExpression = @@ -3019,6 +3028,7 @@ ScssMapExpressionPair = AnyScssExpression = ScssExpression | ScssBinaryExpression + | ScssInterpolation | ScssUnaryExpression | ScssParenthesizedExpression | ScssListExpression diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 57ab3a58feb7..3b3e20eae415 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -627,7 +627,6 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "SCSS_PARAMETER", "SCSS_PARAMETER_DEFAULT_VALUE", "SCSS_PLACEHOLDER_SELECTOR", - "SCSS_INTERPOLATION", "SCSS_PLAIN_IMPORT", "SCSS_SHOW_CLAUSE", "SCSS_USE_ALL_NAMESPACE", @@ -645,6 +644,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "SCSS_EXPRESSION", "SCSS_EXPRESSION_ITEM_LIST", "SCSS_BINARY_EXPRESSION", + "SCSS_INTERPOLATION", "SCSS_KEYWORD_ARGUMENT", "SCSS_ARBITRARY_ARGUMENT", "SCSS_LIST_EXPRESSION",