1+ use crate :: lexer:: CssReLexContext ;
12use crate :: parser:: CssParser ;
23use crate :: syntax:: parse_error:: {
34 expected_component_value, expected_scss_expression, scss_ellipsis_not_allowed,
45} ;
5- use crate :: syntax:: property:: parse_generic_component_value;
6+ use crate :: syntax:: property:: { is_at_generic_delimiter , parse_generic_component_value} ;
67use crate :: syntax:: scss:: { is_at_scss_identifier, parse_scss_identifier} ;
8+ use crate :: syntax:: value:: dimension:: is_at_any_dimension;
79use biome_css_syntax:: CssSyntaxKind :: {
8- CSS_BOGUS_PROPERTY_VALUE , EOF , SCSS_ARBITRARY_ARGUMENT , SCSS_BINARY_EXPRESSION ,
9- SCSS_EXPRESSION , SCSS_EXPRESSION_ITEM_LIST , SCSS_KEYWORD_ARGUMENT , SCSS_LIST_EXPRESSION ,
10- SCSS_LIST_EXPRESSION_ELEMENT , SCSS_LIST_EXPRESSION_ELEMENT_LIST , SCSS_MAP_EXPRESSION ,
11- SCSS_MAP_EXPRESSION_PAIR , SCSS_MAP_EXPRESSION_PAIR_LIST , SCSS_PARENTHESIZED_EXPRESSION ,
12- SCSS_UNARY_EXPRESSION ,
10+ CSS_BOGUS_PROPERTY_VALUE , CSS_NUMBER_LITERAL , EOF , SCSS_ARBITRARY_ARGUMENT ,
11+ SCSS_BINARY_EXPRESSION , SCSS_EXPRESSION , SCSS_EXPRESSION_ITEM_LIST , SCSS_KEYWORD_ARGUMENT ,
12+ SCSS_LIST_EXPRESSION , SCSS_LIST_EXPRESSION_ELEMENT , SCSS_LIST_EXPRESSION_ELEMENT_LIST ,
13+ SCSS_MAP_EXPRESSION , SCSS_MAP_EXPRESSION_PAIR , SCSS_MAP_EXPRESSION_PAIR_LIST ,
14+ SCSS_PARENTHESIZED_EXPRESSION , SCSS_UNARY_EXPRESSION ,
1315} ;
1416use biome_css_syntax:: { CssSyntaxKind , T } ;
1517use biome_parser:: parse_recovery:: ParseRecoveryTokenSet ;
@@ -40,7 +42,6 @@ const SCSS_MAP_EXPRESSION_KEY_END_TOKEN_SET: TokenSet<CssSyntaxKind> =
4042const SCSS_MAP_EXPRESSION_VALUE_END_TOKEN_SET : TokenSet < CssSyntaxKind > = token_set ! [ T ![ , ] , T ![ ')' ] ] ;
4143const SCSS_LIST_EXPRESSION_ELEMENT_END_TOKEN_SET : TokenSet < CssSyntaxKind > =
4244 token_set ! [ T ![ , ] , T ![ ')' ] ] ;
43-
4445pub ( crate ) const END_OF_SCSS_EXPRESSION_TOKEN_SET : TokenSet < CssSyntaxKind > =
4546 token_set ! [ T ![ , ] , T ![ ')' ] , T ![ ; ] , T ![ '}' ] ] ;
4647
@@ -158,27 +159,28 @@ fn parse_scss_expression_item(p: &mut CssParser, options: ScssExpressionOptions)
158159 return parse_scss_keyword_argument ( p, options) ;
159160 }
160161
161- let expression = parse_scss_binary_expression ( p, 0 ) ;
162+ if is_at_generic_delimiter ( p) {
163+ return parse_generic_component_value ( p) ;
164+ }
165+
166+ let expression = parse_scss_binary_expression ( p, 0 ) . or_else ( || {
167+ if p. at ( T ! [ ...] ) {
168+ report_and_bump_scss_ellipsis ( p) ;
169+ }
170+
171+ Absent
172+ } ) ;
162173 let expression = match expression {
163174 Present ( expression) => expression,
164- Absent => {
165- if p. at ( T ! [ ...] ) {
166- let range = p. cur_range ( ) ;
167- p. error ( scss_ellipsis_not_allowed ( p, range) ) ;
168- p. bump ( T ! [ ...] ) ;
169- }
170- return Absent ;
171- }
175+ Absent => return Absent ,
172176 } ;
173177
174178 if !p. at ( T ! [ ...] ) {
175179 return Present ( expression) ;
176180 }
177181
178182 if !options. allows_ellipsis {
179- let range = p. cur_range ( ) ;
180- p. error ( scss_ellipsis_not_allowed ( p, range) ) ;
181- p. bump ( T ! [ ...] ) ;
183+ report_and_bump_scss_ellipsis ( p) ;
182184 return Present ( expression) ;
183185 }
184186
@@ -187,6 +189,13 @@ fn parse_scss_expression_item(p: &mut CssParser, options: ScssExpressionOptions)
187189 Present ( m. complete ( p, SCSS_ARBITRARY_ARGUMENT ) )
188190}
189191
192+ #[ inline]
193+ fn report_and_bump_scss_ellipsis ( p : & mut CssParser ) {
194+ let range = p. cur_range ( ) ;
195+ p. error ( scss_ellipsis_not_allowed ( p, range) ) ;
196+ p. bump ( T ! [ ...] ) ;
197+ }
198+
190199#[ inline]
191200fn is_at_scss_keyword_argument ( p : & mut CssParser , options : ScssExpressionOptions ) -> bool {
192201 options. allows_keyword_arguments
@@ -266,11 +275,29 @@ fn parse_scss_primary_expression(p: &mut CssParser) -> ParsedSyntax {
266275 }
267276}
268277
278+ /// Re-lexes signed numeric tokens in SCSS expression context.
279+ ///
280+ /// If the current token is `CSS_NUMBER_LITERAL` or any dimension starting with `+` or `-`,
281+ /// this mutates parser state via `CssParser::re_lex(CssReLexContext::ScssExpression)`.
282+ #[ inline]
283+ fn re_lex_signed_numeric_as_scss_operator ( p : & mut CssParser ) {
284+ if !( p. at ( CSS_NUMBER_LITERAL ) || is_at_any_dimension ( p) ) {
285+ return ;
286+ }
287+
288+ let text = p. cur_text ( ) ;
289+ if matches ! ( text. as_bytes( ) . first( ) , Some ( b'+' | b'-' ) ) {
290+ p. re_lex ( CssReLexContext :: ScssExpression ) ;
291+ }
292+ }
293+
269294/// Returns the precedence level for the current SCSS binary operator token.
270295///
271296/// Docs: https://sass-lang.com/documentation/operators/#order-of-operations
272297#[ inline]
273298fn scss_binary_precedence ( p : & mut CssParser ) -> Option < u8 > {
299+ re_lex_signed_numeric_as_scss_operator ( p) ;
300+
274301 if !p. at_ts ( SCSS_BINARY_OPERATOR_TOKEN_SET ) {
275302 return None ;
276303 }
0 commit comments