1use super::{
2 Parser,
3 state::{ParserState, SASS_CTX_ALLOW_DIV, SASS_CTX_IN_PARENS},
4};
5use crate::{
6 Parse,
7 ast::*,
8 config::Syntax,
9 error::{Error, ErrorKind, PResult},
10 pos::Span,
11 tokenizer::{Token, TokenWithSpan},
12 util,
13};
14
15const PRECEDENCE_MULTIPLY: u8 = 6;
16const PRECEDENCE_PLUS: u8 = 5;
17const PRECEDENCE_RELATIONAL: u8 = 4;
18const PRECEDENCE_EQUALITY: u8 = 3;
19const PRECEDENCE_AND: u8 = 2;
20const PRECEDENCE_OR: u8 = 1;
21
22type SassParams<'a> = (
23 oxc_allocator::Vec<'a, SassParameter<'a>>,
24 Option<SassArbitraryParameter<'a>>,
25 oxc_allocator::Vec<'a, Span>, usize, );
28
29impl<'a> Parser<'a> {
30 pub(super) fn eat_sass_line_continuation(&mut self) -> PResult<()> {
38 if self.syntax != Syntax::Sass {
39 return Ok(());
40 }
41 loop {
42 match &self.cursor.peek()?.token {
43 Token::Indent(..) => {
44 self.cursor.bump()?;
45 self.sass_pending_indents += 1;
46 }
47 Token::Linebreak(..) => {
48 self.cursor.bump()?;
49 }
50 _ => break,
51 }
52 }
53 Ok(())
54 }
55
56 pub(super) fn drain_sass_pending_dedents(&mut self) -> PResult<bool> {
59 let mut drained = false;
60 while self.sass_pending_indents > 0
61 && matches!(self.cursor.peek()?.token, Token::Dedent(..))
62 {
63 self.cursor.bump()?;
64 self.sass_pending_indents -= 1;
65 drained = true;
66 }
67 Ok(drained)
68 }
69
70 pub(super) fn parse_maybe_sass_list(
71 &mut self,
72 allow_comma: bool,
73 ) -> PResult<ComponentValue<'a>> {
74 use util::ListSeparatorKind;
75
76 let single_value = if allow_comma {
77 self.parse_maybe_sass_list(false)?
78 } else if let Token::Exclamation(..) = self.cursor.peek()?.token {
79 self.parse().map(ComponentValue::ImportantAnnotation)?
80 } else {
81 self.parse_sass_bin_expr(true)?
82 };
83
84 let mut elements = self.vec();
85 let mut comma_spans: Option<oxc_allocator::Vec<'a, Span>> = None;
86 let mut separator = ListSeparatorKind::Unknown;
87 let mut end = single_value.span().end;
88 loop {
89 match self.cursor.peek()?.token {
90 Token::LBrace(..)
91 | Token::RBrace(..)
92 | Token::RParen(..)
93 | Token::Semicolon(..)
94 | Token::Colon(..)
95 | Token::DotDotDot(..)
96 | Token::Eof(..) => break,
97 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
98 if comma_spans.as_ref().is_none_or(|spans| spans.is_empty())
99 || self.state.sass_ctx & SASS_CTX_IN_PARENS == 0
100 {
101 break;
102 } else {
103 self.cursor.bump()?;
104 }
105 }
106 Token::Comma(..) => {
107 if !allow_comma {
108 break;
109 }
110 if separator == ListSeparatorKind::Space {
111 break;
112 } else {
113 if separator == ListSeparatorKind::Unknown {
114 separator = ListSeparatorKind::Comma;
115 }
116 let TokenWithSpan { span, .. } = self.cursor.bump()?;
117 end = span.end;
118 if let Some(spans) = &mut comma_spans {
119 spans.push(span);
120 } else {
121 comma_spans = Some(self.vec1(span));
122 }
123 }
124 }
125 Token::Exclamation(..) => {
126 if let Ok(important_annotation) = self.try_parse(ImportantAnnotation::parse) {
127 if end < important_annotation.span.start
128 && matches!(separator, ListSeparatorKind::Unknown)
129 {
130 separator = ListSeparatorKind::Space;
131 }
132 end = important_annotation.span.end;
133 elements.push(ComponentValue::ImportantAnnotation(important_annotation));
134 } else {
135 break;
136 }
137 }
138 _ => {
139 if separator == ListSeparatorKind::Unknown {
140 separator = ListSeparatorKind::Space;
141 }
142 let item = if separator == ListSeparatorKind::Comma {
143 self.parse_maybe_sass_list(false)?
144 } else {
145 self.parse_sass_bin_expr(true)?
146 };
147 end = item.span().end;
148 elements.push(item);
149 }
150 }
151 }
152
153 if elements.is_empty() && separator != ListSeparatorKind::Comma {
154 Ok(single_value)
157 } else {
158 debug_assert_ne!(separator, ListSeparatorKind::Unknown);
159
160 let span = Span { start: single_value.span().start, end };
161 elements.insert(0, single_value);
162 Ok(ComponentValue::SassList(SassList { elements, comma_spans, span }))
163 }
164 }
165
166 pub(super) fn parse_sass_bin_expr(
167 &mut self,
168 allow_comparison: bool,
169 ) -> PResult<ComponentValue<'a>> {
170 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
171 self.parse_sass_bin_expr_with_min_precedence(PRECEDENCE_OR, allow_comparison)
172 }
173
174 fn parse_sass_bin_expr_with_min_precedence(
175 &mut self,
176 min_precedence: u8,
177 allow_comparison: bool,
178 ) -> PResult<ComponentValue<'a>> {
179 let mut left = self.parse_sass_unary_expression()?;
180
181 if matches!(left, ComponentValue::Delimiter(..)) {
183 return Ok(left);
184 }
185
186 loop {
187 if PRECEDENCE_PLUS >= min_precedence {
188 match self.cursor.peek()? {
189 TokenWithSpan { token: Token::Number(token), span }
190 if token.raw.starts_with('+')
191 || token.raw.starts_with('-') && span.start == left.span().end =>
192 {
193 let (number, number_span) = self.cursor.expect_number()?;
194 let op = SassBinaryOperator {
195 kind: if number.raw.starts_with('+') {
196 SassBinaryOperatorKind::Plus
197 } else {
198 SassBinaryOperatorKind::Minus
199 },
200 span: Span { start: number_span.start, end: number_span.start + 1 },
201 };
202 let span = Span { start: left.span().start, end: number_span.end };
203 let right = {
204 let span = Span { start: number_span.start + 1, end: number_span.end };
205 let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
206 raw.parse()
207 .map_err(|_| Error {
208 kind: ErrorKind::InvalidNumber,
209 span: span.clone(),
210 })
211 .map(|value| ComponentValue::Number(Number { value, raw, span }))?
212 };
213 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
214 left: self.alloc(left),
215 op,
216 right: self.alloc(right),
217 span,
218 });
219 continue;
220 }
221 TokenWithSpan { token: Token::Dimension(token), span }
222 if token.value.raw.starts_with('+')
223 || token.value.raw.starts_with('-')
224 && span.start == left.span().end =>
225 {
226 let (dimension, dimension_span) = self.cursor.expect_dimension()?;
227 let op = SassBinaryOperator {
228 kind: if dimension.value.raw.starts_with('+') {
229 SassBinaryOperatorKind::Plus
230 } else {
231 SassBinaryOperatorKind::Minus
232 },
233 span: Span {
234 start: dimension_span.start,
235 end: dimension_span.start + 1,
236 },
237 };
238 let span = Span { start: left.span().start, end: dimension_span.end };
239 let right = {
240 self.dimension(
241 crate::token::Dimension {
242 value: crate::token::Number {
243 raw: unsafe {
244 dimension
245 .value
246 .raw
247 .get_unchecked(1..dimension.value.raw.len())
248 },
249 },
250 unit: dimension.unit,
251 },
252 Span { start: dimension_span.start + 1, end: dimension_span.end },
253 )
254 .map(ComponentValue::Dimension)?
255 };
256 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
257 left: self.alloc(left),
258 op,
259 right: self.alloc(right),
260 span,
261 });
262 continue;
263 }
264 _ => {}
265 }
266 }
267
268 let (operator, precedence) = match self.cursor.peek()? {
269 TokenWithSpan { token: Token::Asterisk(..), .. }
270 if PRECEDENCE_MULTIPLY >= min_precedence =>
271 {
272 (
273 SassBinaryOperator {
274 kind: SassBinaryOperatorKind::Multiply,
275 span: self.cursor.bump()?.span,
276 },
277 PRECEDENCE_MULTIPLY,
278 )
279 }
280 TokenWithSpan { token: Token::Solidus(..), .. }
281 if PRECEDENCE_MULTIPLY >= min_precedence
282 && (self.state.sass_ctx & SASS_CTX_ALLOW_DIV != 0
283 || matches!(
284 left,
285 ComponentValue::SassParenthesizedExpression(..)
286 | ComponentValue::SassBinaryExpression(..)
287 | ComponentValue::SassUnaryExpression(..)
288 | ComponentValue::SassVariable(..)
289 | ComponentValue::Function(..)
290 | ComponentValue::SassQualifiedName(..)
291 )) =>
292 {
293 (
294 SassBinaryOperator {
295 kind: SassBinaryOperatorKind::Division,
296 span: self.cursor.bump()?.span,
297 },
298 PRECEDENCE_MULTIPLY,
299 )
300 }
301 TokenWithSpan { token: Token::Percent(..), .. }
302 if PRECEDENCE_MULTIPLY >= min_precedence =>
303 {
304 let modulo = self.try_parse(|p| {
308 let op_span = p.cursor.bump()?.span;
309 p.eat_sass_line_continuation()?;
310 let right = p.parse_sass_bin_expr_with_min_precedence(
311 PRECEDENCE_MULTIPLY + 1,
312 allow_comparison,
313 )?;
314 Ok((op_span, right))
315 });
316 match modulo {
317 Ok((op_span, right)) => {
318 let span = Span { start: left.span().start, end: right.span().end };
319 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
320 left: self.alloc(left),
321 op: SassBinaryOperator {
322 kind: SassBinaryOperatorKind::Modulo,
323 span: op_span,
324 },
325 right: self.alloc(right),
326 span,
327 });
328 continue;
329 }
330 Err(_) => break,
331 }
332 }
333 TokenWithSpan { token: Token::Plus(..), .. }
334 if PRECEDENCE_PLUS >= min_precedence =>
335 {
336 (
337 SassBinaryOperator {
338 kind: SassBinaryOperatorKind::Plus,
339 span: self.cursor.bump()?.span,
340 },
341 PRECEDENCE_PLUS,
342 )
343 }
344 TokenWithSpan { token: Token::Minus(..), .. }
345 if PRECEDENCE_PLUS >= min_precedence =>
346 {
347 (
348 SassBinaryOperator {
349 kind: SassBinaryOperatorKind::Minus,
350 span: self.cursor.bump()?.span,
351 },
352 PRECEDENCE_PLUS,
353 )
354 }
355 TokenWithSpan { token: Token::GreaterThan(..), .. }
356 if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
357 {
358 (
359 SassBinaryOperator {
360 kind: SassBinaryOperatorKind::GreaterThan,
361 span: self.cursor.bump()?.span,
362 },
363 PRECEDENCE_RELATIONAL,
364 )
365 }
366 TokenWithSpan { token: Token::GreaterThanEqual(..), .. }
367 if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
368 {
369 (
370 SassBinaryOperator {
371 kind: SassBinaryOperatorKind::GreaterThanOrEqual,
372 span: self.cursor.bump()?.span,
373 },
374 PRECEDENCE_RELATIONAL,
375 )
376 }
377 TokenWithSpan { token: Token::LessThan(..), .. }
378 if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
379 {
380 (
381 SassBinaryOperator {
382 kind: SassBinaryOperatorKind::LessThan,
383 span: self.cursor.bump()?.span,
384 },
385 PRECEDENCE_RELATIONAL,
386 )
387 }
388 TokenWithSpan { token: Token::LessThanEqual(..), .. }
389 if allow_comparison && PRECEDENCE_RELATIONAL >= min_precedence =>
390 {
391 (
392 SassBinaryOperator {
393 kind: SassBinaryOperatorKind::LessThanOrEqual,
394 span: self.cursor.bump()?.span,
395 },
396 PRECEDENCE_RELATIONAL,
397 )
398 }
399 TokenWithSpan { token: Token::EqualEqual(..), .. }
400 if PRECEDENCE_EQUALITY >= min_precedence =>
401 {
402 (
403 SassBinaryOperator {
404 kind: SassBinaryOperatorKind::EqualsEquals,
405 span: self.cursor.bump()?.span,
406 },
407 PRECEDENCE_EQUALITY,
408 )
409 }
410 TokenWithSpan { token: Token::ExclamationEqual(..), .. }
411 if PRECEDENCE_EQUALITY >= min_precedence =>
412 {
413 (
414 SassBinaryOperator {
415 kind: SassBinaryOperatorKind::ExclamationEquals,
416 span: self.cursor.bump()?.span,
417 },
418 PRECEDENCE_EQUALITY,
419 )
420 }
421 TokenWithSpan { token: Token::Ident(token), .. }
422 if token.raw == "and" && PRECEDENCE_AND >= min_precedence =>
423 {
424 (
425 SassBinaryOperator {
426 kind: SassBinaryOperatorKind::And,
427 span: self.cursor.bump()?.span,
428 },
429 PRECEDENCE_AND,
430 )
431 }
432 TokenWithSpan { token: Token::Ident(token), .. }
433 if token.raw == "or" && PRECEDENCE_OR >= min_precedence =>
434 {
435 (
436 SassBinaryOperator {
437 kind: SassBinaryOperatorKind::Or,
438 span: self.cursor.bump()?.span,
439 },
440 PRECEDENCE_OR,
441 )
442 }
443 _ => break,
444 };
445
446 self.eat_sass_line_continuation()?;
448 let right =
449 self.parse_sass_bin_expr_with_min_precedence(precedence + 1, allow_comparison)?;
450 if let ComponentValue::Delimiter(Delimiter { span, .. }) = right {
452 return Err(Error { kind: ErrorKind::ExpectSassExpression, span });
453 }
454
455 let span = Span { start: left.span().start, end: right.span().end };
456 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
457 left: self.alloc(left),
458 op: operator,
459 right: self.alloc(right),
460 span,
461 });
462 }
463
464 Ok(left)
465 }
466
467 fn parse_sass_flags(
468 &mut self,
469 ) -> PResult<(oxc_allocator::Vec<'a, SassFlag<'a>>, Option<usize>)> {
470 let mut flags: oxc_allocator::Vec<'a, SassFlag<'a>> = self.vec_with_capacity(1);
471 let mut end = None;
472 while let Some((_, exclamation_span)) = self.cursor.eat_exclamation()? {
473 let keyword = self.parse::<Ident>()?;
474 let keyword_span = keyword.span.clone();
475 util::assert_no_ws_or_comment(&exclamation_span, &keyword_span)?;
476 end = Some(keyword_span.end);
477
478 if !matches!(keyword.name, "default" | "global") {
480 self.recoverable_errors.push(Error {
481 kind: ErrorKind::InvalidSassFlagName(keyword.name.to_string()),
482 span: keyword.span.clone(),
483 });
484 }
485
486 flags.push(SassFlag {
487 keyword,
488 span: Span { start: exclamation_span.start, end: keyword_span.end },
489 });
490 }
491
492 Ok((flags, end))
493 }
494
495 pub(super) fn parse_sass_interpolated_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
496 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
497
498 let (first, Span { start, mut end }) = match self.cursor.peek()? {
499 TokenWithSpan { token: Token::Ident(..), .. } => {
500 let (ident, ident_span) = self.cursor.expect_ident()?;
501 (
502 SassInterpolatedIdentElement::Static(
503 self.interpolable_ident_static_part(ident, ident_span.clone()),
504 ),
505 ident_span,
506 )
507 }
508 TokenWithSpan { token: Token::HashLBrace(..), .. } => {
509 self.parse_sass_interpolated_ident_expr()?
510 }
511 TokenWithSpan { token, span } => {
512 return Err(Error {
513 kind: ErrorKind::ExpectOneOf(vec!["<ident>", "#{"], token.symbol()),
514 span: span.clone(),
515 });
516 }
517 };
518
519 let mut elements = self.parse_sass_interpolated_ident_rest(&mut end)?;
520 if elements.is_empty()
521 && let SassInterpolatedIdentElement::Static(ident) = first
522 {
523 return Ok(InterpolableIdent::Literal(Ident {
524 name: ident.value,
525 raw: ident.raw,
526 span: ident.span,
527 }));
528 }
529
530 elements.insert(0, first);
531 Ok(InterpolableIdent::SassInterpolated(SassInterpolatedIdent {
532 elements,
533 span: Span { start, end },
534 }))
535 }
536
537 pub(super) fn parse_sass_interpolated_ident_rest(
538 &mut self,
539 end: &mut usize,
540 ) -> PResult<oxc_allocator::Vec<'a, SassInterpolatedIdentElement<'a>>> {
541 let mut elements = self.vec();
542 loop {
543 if let Some((token, span)) = self.cursor.tokenizer.scan_ident_template()? {
544 *end = span.end;
545 elements.push(SassInterpolatedIdentElement::Static(
546 self.interpolable_ident_static_part(token, span),
547 ));
548 } else if matches!(
549 self.cursor.peek()?,
550 TokenWithSpan { token: Token::HashLBrace(..), span } if *end == span.start
551 ) {
552 let (element, span) = self.parse_sass_interpolated_ident_expr()?;
553 *end = span.end;
554 elements.push(element);
555 } else {
556 return Ok(elements);
557 }
558 }
559 }
560
561 fn parse_sass_interpolated_ident_expr(
562 &mut self,
563 ) -> PResult<(SassInterpolatedIdentElement<'a>, Span)> {
564 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
565
566 let start = self.cursor.expect_hash_l_brace()?.1.start;
567 let expr = self.parse_maybe_sass_list(true)?;
568 let end = self.cursor.expect_r_brace()?.1.end;
569 Ok((SassInterpolatedIdentElement::Expression(expr), Span { start, end }))
570 }
571
572 pub(super) fn parse_sass_invocation_args(
573 &mut self,
574 ) -> PResult<(oxc_allocator::Vec<'a, ComponentValue<'a>>, oxc_allocator::Vec<'a, Span>)> {
575 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
576
577 let mut values = self.vec_with_capacity(4);
578 let mut comma_spans = self.vec();
579 while !matches!(self.cursor.peek()?.token, Token::RParen(..) | Token::Eof(..)) {
580 match self.cursor.peek()?.token {
581 Token::Comma(..) => {
582 let TokenWithSpan { span, .. } = self.cursor.bump()?;
583 self.recoverable_errors
584 .push(Error { kind: ErrorKind::ExpectComponentValue, span });
585 continue;
586 }
587 _ => {
588 let value = self.parse_maybe_sass_list(false)?;
589 if let Some((_, span)) = self.cursor.eat_dot_dot_dot()? {
590 let span = Span { start: value.span().start, end: span.end };
591 values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
592 value: self.alloc(value),
593 span,
594 }));
595 } else if let ComponentValue::SassVariable(sass_var) = value {
596 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
597 let value = self.parse_maybe_sass_list(false)?;
598 let span = Span { start: sass_var.span.start, end: value.span().end };
599 values.push(ComponentValue::SassKeywordArgument(SassKeywordArgument {
600 name: sass_var,
601 colon_span,
602 value: self.alloc(value),
603 span,
604 }));
605 } else {
606 values.push(ComponentValue::SassVariable(sass_var));
607 }
608 } else {
609 values.push(value);
610 }
611 }
612 }
613 if !matches!(self.cursor.peek()?.token, Token::RParen(..) | Token::Eof(..)) {
614 comma_spans.push(self.cursor.expect_comma()?.1);
615 }
616 }
617 debug_assert!(values.len() - comma_spans.len() <= 1);
618 Ok((values, comma_spans))
619 }
620
621 fn parse_sass_module_config(
622 &mut self,
623 allow_overridable: bool,
624 ) -> PResult<Option<SassModuleConfig<'a>>> {
625 match &self.cursor.peek()?.token {
626 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("with") => {
627 let TokenWithSpan { span: with_span, .. } = self.cursor.bump()?;
628 let start = with_span.start;
629 let end;
630 self.eat_sass_line_continuation()?;
631 let (_, lparen_span) = self.cursor.expect_l_paren()?;
632
633 let item = self.parse_sass_module_config_item(allow_overridable)?;
634 let mut items = self.vec1(item);
635 let mut comma_spans = self.vec();
636 if let Some((_, span)) = self.cursor.eat_r_paren()? {
637 end = span.end;
638 } else {
639 comma_spans.push(self.cursor.expect_comma()?.1);
640 loop {
641 if let Some((_, span)) = self.cursor.eat_r_paren()? {
642 end = span.end;
643 break;
644 }
645
646 items.push(self.parse_sass_module_config_item(allow_overridable)?);
647 if let Some((_, span)) = self.cursor.eat_r_paren()? {
648 end = span.end;
649 break;
650 } else {
651 comma_spans.push(self.cursor.expect_comma()?.1);
652 }
653 }
654 }
655 debug_assert!(items.len() - comma_spans.len() <= 1);
656
657 Ok(Some(SassModuleConfig {
658 with_span,
659 lparen_span,
660 items,
661 comma_spans,
662 span: Span { start, end },
663 }))
664 }
665 _ => Ok(None),
666 }
667 }
668
669 fn parse_sass_module_config_item(
670 &mut self,
671 allow_overridable: bool,
672 ) -> PResult<SassModuleConfigItem<'a>> {
673 let variable = self.parse::<SassVariable>()?;
674 let (_, colon_span) = self.cursor.expect_colon()?;
675 let value = self.parse_maybe_sass_list(false)?;
676
677 let (flags, end) = if allow_overridable {
678 self.parse_sass_flags()
679 .map(|(flags, end)| (flags, end.unwrap_or_else(|| value.span().end)))?
680 } else {
681 (self.vec(), value.span().end)
682 };
683
684 let span = Span { start: variable.span.start, end };
685 Ok(SassModuleConfigItem { variable, colon_span, value, flags, span })
686 }
687
688 fn parse_sass_params(&mut self) -> PResult<SassParams<'a>> {
690 let mut parameters = self.vec();
691 let mut arbitrary_parameter = None;
692 let mut comma_spans = self.vec();
693 let end;
694 loop {
695 if let Some((_, span)) = self.cursor.eat_r_paren()? {
696 end = span.end;
697 break;
698 }
699
700 let name = self.parse::<SassVariable>()?;
701 let token_with_span = self.cursor.bump()?;
702 match token_with_span.token {
703 Token::Comma(..) => {
704 let span = name.span.clone();
705 parameters.push(SassParameter { name, default_value: None, span });
706 comma_spans.push(token_with_span.span);
707 continue;
708 }
709 Token::Colon(..) => {
710 let value = self.parse_maybe_sass_list(false)?;
711 let end = value.span().end;
712 let default_value_span = Span { start: token_with_span.span.start, end };
713 let span = Span { start: name.span.start, end };
714 parameters.push(SassParameter {
715 name,
716 default_value: Some(SassParameterDefaultValue {
717 colon_span: token_with_span.span,
718 value,
719 span: default_value_span,
720 }),
721 span,
722 });
723 }
724 Token::DotDotDot(..) => {
725 let span = Span { start: name.span().start, end: token_with_span.span.end };
726 arbitrary_parameter = Some(SassArbitraryParameter { name, span });
727 if let Some((_, comma_span)) = self.cursor.eat_comma()? {
728 comma_spans.push(comma_span);
729 }
730 end = self.cursor.expect_r_paren()?.1.end;
731 break;
732 }
733 Token::RParen(..) => {
734 let span = name.span.clone();
735 parameters.push(SassParameter { name, default_value: None, span });
736 end = token_with_span.span.end;
737 break;
738 }
739 token => {
740 return Err(Error {
741 kind: ErrorKind::Unexpected(")", token.symbol()),
742 span: token_with_span.span,
743 });
744 }
745 }
746 if let Some((_, span)) = self.cursor.eat_r_paren()? {
747 end = span.end;
748 break;
749 } else {
750 comma_spans.push(self.cursor.expect_comma()?.1);
751 }
752 }
753
754 debug_assert!(
755 parameters.len() + arbitrary_parameter.iter().count() - comma_spans.len() <= 1
756 );
757 Ok((parameters, arbitrary_parameter, comma_spans, end))
758 }
759
760 pub(super) fn parse_sass_qualified_name(
761 &mut self,
762 module: Ident<'a>,
763 ) -> PResult<SassQualifiedName<'a>> {
764 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
765
766 let (_, dot_span) = self.cursor.expect_dot()?;
767 let member = if let Token::DollarVar(..) = self.cursor.peek()?.token {
768 self.parse().map(SassModuleMemberName::Variable)?
769 } else {
770 self.parse().map(SassModuleMemberName::Ident)?
771 };
772
773 let expr_span = member.span();
774 util::assert_no_ws_or_comment(&dot_span, expr_span)?;
775
776 let span = Span { start: module.span.start, end: expr_span.end };
777 Ok(SassQualifiedName { module, member, span })
778 }
779
780 fn parse_sass_unary_expression(&mut self) -> PResult<ComponentValue<'a>> {
781 if let Token::Percent(..) = &self.cursor.peek()?.token {
785 return Ok(ComponentValue::TokenWithSpan(self.cursor.bump()?));
786 }
787 let op = match &self.cursor.peek()?.token {
788 Token::Plus(..) => SassUnaryOperator {
789 kind: SassUnaryOperatorKind::Plus,
790 span: self.cursor.bump()?.span,
791 },
792 Token::Minus(..) => SassUnaryOperator {
793 kind: SassUnaryOperatorKind::Minus,
794 span: self.cursor.bump()?.span,
795 },
796 Token::Ident(token) if token.raw == "not" => SassUnaryOperator {
797 kind: SassUnaryOperatorKind::Not,
798 span: self.cursor.bump()?.span,
799 },
800 _ => return self.parse_component_value_atom(),
801 };
802
803 self.eat_sass_line_continuation()?;
805 let expr = self.parse_sass_unary_expression()?;
806 let span = Span { start: op.span.start, end: expr.span().end };
807 Ok(ComponentValue::SassUnaryExpression(SassUnaryExpression {
808 expr: self.alloc(expr),
809 op,
810 span,
811 }))
812 }
813}
814
815impl<'a> Parse<'a> for SassAtRoot<'a> {
816 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
817 let kind = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
818 SassAtRootKind::Query(input.parse()?)
819 } else {
820 SassAtRootKind::Selector(input.parse()?)
821 };
822
823 let span = kind.span().clone();
824 Ok(SassAtRoot { kind, span })
825 }
826}
827
828impl<'a> Parse<'a> for SassAtRootQuery<'a> {
829 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
830 let start = input.cursor.expect_l_paren()?.1.start;
831
832 let modifier = {
833 let (token, span) = input.cursor.expect_ident()?;
834 let ident_name = token.name();
835 if ident_name.eq_ignore_ascii_case("with") {
836 SassAtRootQueryModifier { kind: SassAtRootQueryModifierKind::With, span }
837 } else if ident_name.eq_ignore_ascii_case("without") {
838 SassAtRootQueryModifier { kind: SassAtRootQueryModifierKind::Without, span }
839 } else {
840 return Err(Error { kind: ErrorKind::ExpectSassAtRootWithOrWithout, span });
841 }
842 };
843 let colon_span = input.cursor.expect_colon()?.1;
844
845 let mut rules = input.vec_with_capacity(1);
846 loop {
847 match &input.cursor.peek()?.token {
848 Token::Ident(..) | Token::HashLBrace(..) => {
849 rules.push(SassAtRootQueryRule::Ident(input.parse()?));
850 }
851 Token::Str(..) | Token::StrTemplate(..) => {
852 rules.push(SassAtRootQueryRule::Str(input.parse()?));
853 }
854 _ => break,
855 }
856 }
857 let end = input.cursor.expect_r_paren()?.1.end;
858
859 Ok(SassAtRootQuery { modifier, colon_span, rules, span: Span { start, end } })
860 }
861}
862
863impl<'a> Parse<'a> for SassConditionalClause<'a> {
864 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
865 input.eat_sass_line_continuation()?;
866 let condition = input.parse::<ComponentValue>()?;
867 let block = input.parse::<SimpleBlock>()?;
868 let span = Span { start: condition.span().start, end: block.span.end };
869 Ok(SassConditionalClause { condition, block, span })
870 }
871}
872
873impl<'a> Parse<'a> for SassContent<'a> {
874 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
875 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
876 let (args, comma_spans) = input.parse_sass_invocation_args()?;
877 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
878 Ok(SassContent { args, comma_spans, span: Span { start, end } })
879 }
880}
881
882impl<'a> Parse<'a> for SassEach<'a> {
883 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
884 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
885
886 input.eat_sass_line_continuation()?;
887 let first_binding = input.parse::<SassVariable>()?;
888 let start = first_binding.span().start;
889
890 let mut bindings = input.vec1(first_binding);
891 let mut comma_spans = input.vec();
892 loop {
893 input.eat_sass_line_continuation()?;
895 let Some((_, comma_span)) = input.cursor.eat_comma()? else { break };
896 comma_spans.push(comma_span);
897 input.eat_sass_line_continuation()?;
898 bindings.push(input.parse()?);
899 }
900 debug_assert_eq!(comma_spans.len() + 1, bindings.len());
901
902 input.eat_sass_line_continuation()?;
903 let (keyword_in, keyword_in_span) = input.cursor.expect_ident()?;
904 if keyword_in.name() != "in" {
905 return Err(Error { kind: ErrorKind::ExpectSassKeyword("in"), span: keyword_in_span });
906 }
907
908 input.eat_sass_line_continuation()?;
909 let expr = input.parse_maybe_sass_list(true)?;
910 let span = Span { start, end: expr.span().end };
911 Ok(SassEach { bindings, comma_spans, in_span: keyword_in_span, expr, span })
912 }
913}
914
915impl<'a> Parse<'a> for SassExtend<'a> {
916 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
917 input.eat_sass_line_continuation()?;
918 let selectors = input.parse::<CompoundSelectorList>()?;
919 let start = selectors.span.start;
920 let mut end = selectors.span.end;
921
922 let optional = if let Some((_, exclamation_span)) = input.cursor.eat_exclamation()? {
923 let (keyword, keyword_span) =
924 input.cursor.expect_ident_without_ws_or_comments(false)?;
925 if keyword.name().eq_ignore_ascii_case("optional") {
926 end = keyword_span.end;
927 let span = Span { start: exclamation_span.start, end: keyword_span.end };
928 Some(SassFlag { keyword: input.ident(keyword, keyword_span), span })
929 } else {
930 input.recoverable_errors.push(Error {
931 kind: ErrorKind::ExpectSassKeyword("optional"),
932 span: keyword_span,
933 });
934 None
935 }
936 } else {
937 None
938 };
939
940 Ok(SassExtend { selectors, optional, span: Span { start, end } })
941 }
942}
943
944impl<'a> Parse<'a> for SassFor<'a> {
945 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
946 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
947
948 input.eat_sass_line_continuation()?;
949 let binding = input.parse::<SassVariable>()?;
950
951 input.eat_sass_line_continuation()?;
952 let (keyword_from, keyword_from_span) = input.cursor.expect_ident()?;
953 if keyword_from.name() != "from" {
954 return Err(Error {
955 kind: ErrorKind::ExpectSassKeyword("from"),
956 span: keyword_from_span,
957 });
958 }
959
960 input.eat_sass_line_continuation()?;
961 let start = input.parse()?;
962 input.eat_sass_line_continuation()?;
963 let boundary = input.parse()?;
964 input.eat_sass_line_continuation()?;
965 let end = input.parse::<ComponentValue>()?;
966
967 let span = Span { start: binding.span.start, end: end.span().end };
968 Ok(SassFor { binding, from_span: keyword_from_span, start, end, boundary, span })
969 }
970}
971
972impl<'a> Parse<'a> for SassForBoundary {
973 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
974 let (keyword, span) = input.cursor.expect_ident()?;
975 match &*keyword.name() {
976 "to" => Ok(SassForBoundary { kind: SassForBoundaryKind::Exclusive, span }),
977 "through" => Ok(SassForBoundary { kind: SassForBoundaryKind::Inclusive, span }),
978 _ => Err(Error { kind: ErrorKind::ExpectSassKeyword("to"), span }),
979 }
980 }
981}
982
983impl<'a> Parse<'a> for SassForward<'a> {
984 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
985 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
986
987 input.eat_sass_line_continuation()?;
988 let path = input.parse::<InterpolableStr>()?;
989 let mut span = path.span().clone();
990
991 let prefix = match &input.cursor.peek()?.token {
992 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("as") => {
993 let TokenWithSpan { span: as_span, .. } = input.cursor.bump()?;
994 input.eat_sass_line_continuation()?;
995 let name = input.parse()?;
996 let (_, Span { end, .. }) =
997 input.cursor.expect_asterisk_without_ws_or_comments()?;
998 let span = Span { start: as_span.start, end };
999 Some(SassForwardPrefix { as_span, name, span })
1000 }
1001 _ => None,
1002 };
1003
1004 let visibility = if let TokenWithSpan { token: Token::Ident(keyword), span: keyword_span } =
1005 input.cursor.peek()?
1006 {
1007 let start = keyword_span.start;
1008 let name = keyword.name();
1009 if name.eq_ignore_ascii_case("hide") {
1010 let keyword_span = input.cursor.bump()?.span;
1011 let mut members = input.vec();
1012 let mut comma_spans = input.vec();
1013 loop {
1014 input.eat_sass_line_continuation()?;
1015 match &input.cursor.peek()?.token {
1016 Token::Ident(..) => {
1017 members.push(input.parse().map(SassForwardMember::Ident)?)
1018 }
1019 _ => members.push(input.parse().map(SassForwardMember::Variable)?),
1020 }
1021 if let Some((_, span)) = input.cursor.eat_comma()? {
1022 comma_spans.push(span);
1023 } else {
1024 break;
1025 }
1026 }
1027 Some(SassForwardVisibility {
1028 modifier: SassForwardVisibilityModifier {
1029 kind: SassForwardVisibilityModifierKind::Hide,
1030 span: keyword_span,
1031 },
1032 members,
1033 comma_spans,
1034 span: Span { start, end: input.cursor.tokenizer.current_offset() },
1035 })
1036 } else if name.eq_ignore_ascii_case("show") {
1037 let keyword_span = input.cursor.bump()?.span;
1038 let mut members = input.vec();
1039 let mut comma_spans = input.vec();
1040 loop {
1041 input.eat_sass_line_continuation()?;
1042 match &input.cursor.peek()?.token {
1043 Token::Ident(..) => {
1044 members.push(input.parse().map(SassForwardMember::Ident)?)
1045 }
1046 _ => members.push(input.parse().map(SassForwardMember::Variable)?),
1047 }
1048 if let Some((_, span)) = input.cursor.eat_comma()? {
1049 comma_spans.push(span);
1050 } else {
1051 break;
1052 }
1053 }
1054 Some(SassForwardVisibility {
1055 modifier: SassForwardVisibilityModifier {
1056 kind: SassForwardVisibilityModifierKind::Show,
1057 span: keyword_span,
1058 },
1059 members,
1060 comma_spans,
1061 span: Span { start, end: input.cursor.tokenizer.current_offset() },
1062 })
1063 } else {
1064 None
1065 }
1066 } else {
1067 None
1068 };
1069
1070 let config = input.parse_sass_module_config(true)?;
1071 if let Some(config) = &config {
1072 span.end = config.span.end;
1073 }
1074
1075 Ok(SassForward { path, prefix, visibility, config, span })
1076 }
1077}
1078
1079impl<'a> Parse<'a> for SassFunction<'a> {
1080 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1081 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1082
1083 input.eat_sass_line_continuation()?;
1084 let name = input.parse::<Ident>()?;
1085 let start = name.span.start;
1086
1087 input.eat_sass_line_continuation()?;
1088 let parameters = input.parse::<SassParameters>()?;
1089
1090 let span = Span { start, end: parameters.span.end };
1091 Ok(SassFunction { name, parameters, span })
1092 }
1093}
1094
1095impl<'a> Parse<'a> for SassIfAtRule<'a> {
1096 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1097 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1098
1099 let start = input.cursor.expect_at_keyword()?.1.start;
1100
1101 let if_clause = input.parse::<SassConditionalClause>()?;
1102 let mut else_if_clauses: oxc_allocator::Vec<'a, SassConditionalClause<'a>> = input.vec();
1103 let mut else_clause: Option<SimpleBlock> = None;
1104 let mut else_spans = input.vec();
1105
1106 loop {
1107 fn else_keyword<'a>(p: &mut Parser<'a>) -> PResult<(Span, bool)> {
1113 while p.cursor.eat_linebreak()?.is_some() {}
1114 let is_else = match &p.cursor.peek()?.token {
1115 Token::AtKeyword(at_keyword) => match &*at_keyword.ident.name() {
1116 "else" => true,
1117 "elseif" => false,
1119 _ => {
1120 let span = p.cursor.peek()?.span.clone();
1121 return Err(Error { kind: ErrorKind::TryParseError, span });
1122 }
1123 },
1124 _ => {
1125 let span = p.cursor.peek()?.span.clone();
1126 return Err(Error { kind: ErrorKind::TryParseError, span });
1127 }
1128 };
1129 Ok((p.cursor.bump()?.span, is_else))
1130 }
1131 let else_keyword = if matches!(input.cursor.peek()?.token, Token::Linebreak(..)) {
1132 input.try_parse(else_keyword)
1133 } else {
1134 else_keyword(input)
1135 };
1136 let Ok((else_span, is_else)) = else_keyword else { break };
1137 else_spans.push(else_span);
1138 if is_else {
1139 input.eat_sass_line_continuation()?;
1140 match &input.cursor.peek()?.token {
1141 Token::Ident(ident) if ident.name() == "if" => {
1142 input.cursor.bump()?;
1143 else_if_clauses.push(input.parse()?);
1144 }
1145 _ => {
1146 else_clause = Some(input.parse()?);
1147 break;
1148 }
1149 }
1150 } else {
1151 else_if_clauses.push(input.parse()?);
1152 }
1153 }
1154
1155 debug_assert_eq!(else_spans.len(), else_if_clauses.len() + else_clause.iter().count());
1156 let span = Span {
1157 start,
1158 end: else_clause
1159 .as_ref()
1160 .map(|else_clause| else_clause.span.end)
1161 .or_else(|| else_if_clauses.last().map(|else_if_clause| else_if_clause.span.end))
1162 .unwrap_or(if_clause.span.end),
1163 };
1164 Ok(SassIfAtRule { if_clause, else_if_clauses, else_clause, else_spans, span })
1165 }
1166}
1167
1168impl<'a> Parse<'a> for SassImportPrelude<'a> {
1169 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1170 let first = input.parse::<Str>()?;
1171 let mut span = first.span.clone();
1172
1173 let mut paths = input.vec1(first);
1174 let mut comma_spans = input.vec();
1175 while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1176 comma_spans.push(comma_span);
1177 paths.push(input.parse()?);
1178 }
1179 debug_assert_eq!(comma_spans.len() + 1, paths.len());
1180
1181 if let Some(Str { span: Span { end, .. }, .. }) = paths.last() {
1182 span.end = *end;
1183 }
1184 Ok(SassImportPrelude { paths, comma_spans, span })
1185 }
1186}
1187
1188impl<'a> Parse<'a> for SassInclude<'a> {
1189 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1190 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1191
1192 input.eat_sass_line_continuation()?;
1193 let name = input.parse::<FunctionName>()?;
1194 let mut span = name.span().clone();
1195
1196 let arguments = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
1197 let arguments = input.parse::<SassIncludeArgs>()?;
1198 span.end = arguments.span.end;
1199 Some(arguments)
1200 } else {
1201 None
1202 };
1203
1204 let content_block_params = match &input.cursor.peek()?.token {
1205 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("using") => {
1206 let content_block_params = input.parse::<SassIncludeContentBlockParams>()?;
1207 span.end = content_block_params.span.end;
1208 Some(content_block_params)
1209 }
1210 _ => None,
1211 };
1212
1213 Ok(SassInclude { name, arguments, content_block_params, span })
1214 }
1215}
1216
1217impl<'a> Parse<'a> for SassIncludeArgs<'a> {
1218 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1219 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
1220 let (args, comma_spans) = input.parse_sass_invocation_args()?;
1221 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1222 Ok(SassIncludeArgs { args, comma_spans, span: Span { start, end } })
1223 }
1224}
1225
1226impl<'a> Parse<'a> for SassIncludeContentBlockParams<'a> {
1227 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1228 match input.cursor.bump()? {
1229 TokenWithSpan { token: Token::Ident(ident), span: using_span }
1230 if ident.name().eq_ignore_ascii_case("using") =>
1231 {
1232 input.eat_sass_line_continuation()?;
1233 let params = input.parse::<SassParameters>()?;
1234 let span = Span { start: using_span.start, end: params.span.end };
1235 Ok(SassIncludeContentBlockParams { using_span, params, span })
1236 }
1237 TokenWithSpan { span, .. } => {
1238 Err(Error { kind: ErrorKind::ExpectSassKeyword("using"), span })
1239 }
1240 }
1241 }
1242}
1243
1244impl<'a> Parse<'a> for SassInterpolatedStr<'a> {
1245 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1246 let (first, first_span) = input.cursor.expect_str_template()?;
1247 let quote = first.raw.chars().next().unwrap();
1248 debug_assert!(quote == '\'' || quote == '"');
1249 let mut span = first_span.clone();
1250 let first = input.interpolable_str_static_part(first, first_span);
1251 let mut elements = input.vec1(SassInterpolatedStrElement::Static(first));
1252
1253 let mut is_parsing_static_part = false;
1254 loop {
1255 if is_parsing_static_part {
1256 let (token, str_tpl_span) = input.cursor.tokenizer.scan_string_template(quote)?;
1257 let tail = token.tail;
1258 let end = str_tpl_span.end;
1259 elements.push(SassInterpolatedStrElement::Static(
1260 input.interpolable_str_static_part(token, str_tpl_span),
1261 ));
1262 if tail {
1263 span.end = end;
1264 break;
1265 }
1266 } else {
1267 input.cursor.expect_l_brace()?;
1269 elements.push(SassInterpolatedStrElement::Expression(
1270 input.parse_maybe_sass_list(true)?,
1271 ));
1272 input.cursor.expect_r_brace()?;
1273 }
1274 is_parsing_static_part = !is_parsing_static_part;
1275 }
1276
1277 Ok(SassInterpolatedStr { elements, span })
1278 }
1279}
1280
1281impl<'a> Parse<'a> for SassInterpolatedUrl<'a> {
1282 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1283 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1284
1285 let (first, first_span) = match input.cursor.tokenizer.scan_url_raw_or_template()? {
1286 TokenWithSpan { token: Token::UrlTemplate(template), span } => (template, span),
1287 TokenWithSpan { token, span } => {
1288 return Err(Error {
1289 kind: ErrorKind::Unexpected("<url template>", token.symbol()),
1290 span,
1291 });
1292 }
1293 };
1294 let mut span = first_span.clone();
1295 let first = input.interpolable_url_static_part(first, first_span);
1296 let mut elements = input.vec1(SassInterpolatedUrlElement::Static(first));
1297
1298 let mut is_parsing_static_part = false;
1299 loop {
1300 if is_parsing_static_part {
1301 let (token, url_tpl_span @ Span { end, .. }) =
1302 input.cursor.tokenizer.scan_url_template()?;
1303 let tail = token.tail;
1304 elements.push(SassInterpolatedUrlElement::Static(
1305 input.interpolable_url_static_part(token, url_tpl_span),
1306 ));
1307 if tail {
1308 span.end = end;
1309 break;
1310 }
1311 } else {
1312 input.cursor.expect_l_brace()?;
1314 elements.push(SassInterpolatedUrlElement::Expression(
1315 input.parse_maybe_sass_list(true)?,
1316 ));
1317 input.cursor.expect_r_brace()?;
1318 }
1319 is_parsing_static_part = !is_parsing_static_part;
1320 }
1321
1322 Ok(SassInterpolatedUrl { elements, span })
1323 }
1324}
1325
1326impl<'a> Parse<'a> for SassList<'a> {
1327 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1328 if let ComponentValue::SassList(list) =
1329 input.parse_maybe_sass_list(true)?
1330 {
1331 Ok(list)
1332 } else {
1333 let TokenWithSpan { token, span } = input.cursor.bump()?;
1334 Err(Error { kind: ErrorKind::Unexpected(",", token.symbol()), span })
1335 }
1336 }
1337}
1338
1339impl<'a> Parse<'a> for SassMap<'a> {
1340 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1341 let start = input.cursor.expect_l_paren()?.1.start;
1342
1343 let mut items = input.vec();
1344 let mut comma_spans = input.vec();
1345 loop {
1346 match input.cursor.peek()?.token {
1347 Token::RParen(..) => break,
1348 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
1349 input.cursor.bump()?;
1350 }
1351 _ => {
1352 items.push(input.parse()?);
1353 if matches!(
1354 input.cursor.peek()?.token,
1355 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..)
1356 ) {
1357 input.cursor.bump()?;
1358 }
1359 if !matches!(&input.cursor.peek()?.token, Token::RParen(..)) {
1360 comma_spans.push(input.cursor.expect_comma()?.1);
1361 }
1362 }
1363 }
1364 }
1365 debug_assert!(items.len() - comma_spans.len() <= 1);
1366
1367 let end = input.cursor.expect_r_paren()?.1.end;
1368 Ok(SassMap { items, comma_spans, span: Span { start, end } })
1369 }
1370}
1371
1372impl<'a> Parse<'a> for SassMapItem<'a> {
1373 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1374 let key = input.parse_maybe_sass_list(false)?;
1375 let (_, colon_span) = input.cursor.expect_colon()?;
1376 let value = input.parse_maybe_sass_list(false)?;
1377 let span = Span { start: key.span().start, end: value.span().end };
1378 Ok(SassMapItem { key, colon_span, value, span })
1379 }
1380}
1381
1382impl<'a> Parse<'a> for SassMixin<'a> {
1383 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1384 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1385
1386 input.eat_sass_line_continuation()?;
1387 let name = input.parse::<Ident>()?;
1388 let start = name.span.start;
1389 let mut end = name.span.end;
1390
1391 let parameters = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
1392 let parameters = input.parse::<SassParameters>()?;
1393 end = parameters.span.end;
1394 Some(parameters)
1395 } else {
1396 None
1397 };
1398
1399 Ok(SassMixin { name, parameters, span: Span { start, end } })
1400 }
1401}
1402
1403impl<'a> Parse<'a> for SassParameters<'a> {
1404 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1405 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
1406 let (params, arbitrary_param, comma_spans, end) = input.parse_sass_params()?;
1407 Ok(SassParameters { params, arbitrary_param, comma_spans, span: Span { start, end } })
1408 }
1409}
1410
1411impl<'a> Parse<'a> for SassNestingDeclaration<'a> {
1412 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1413 let block = input.parse::<SimpleBlock>()?;
1414 let span = block.span.clone();
1415
1416 Ok(SassNestingDeclaration { block, span })
1417 }
1418}
1419
1420impl<'a> Parse<'a> for SassParenthesizedExpression<'a> {
1421 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1422 let start = input.cursor.expect_l_paren()?.1.start;
1423 input.cursor.eat_indent()?;
1424 let expr = input
1425 .with_state(ParserState {
1426 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV | SASS_CTX_IN_PARENS,
1427 ..input.state.clone()
1428 })
1429 .parse_maybe_sass_list(true)?;
1430 let expr = input.alloc(expr);
1431 let end = input.cursor.expect_r_paren()?.1.end;
1432 Ok(SassParenthesizedExpression { expr, span: Span { start, end } })
1433 }
1434}
1435
1436impl<'a> Parse<'a> for SassPlaceholderSelector<'a> {
1437 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1438 let (_, percent_span) = input.cursor.expect_percent()?;
1439 let name = input.parse::<InterpolableIdent>()?;
1440 let name_span = name.span();
1441 util::assert_no_ws_or_comment(&percent_span, name_span)?;
1442 let span = Span { start: percent_span.start, end: name_span.end };
1443 Ok(SassPlaceholderSelector { name, span })
1444 }
1445}
1446
1447impl<'a> Parse<'a> for SassUse<'a> {
1448 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1449 input.eat_sass_line_continuation()?;
1450 let path = input.parse::<InterpolableStr>()?;
1451 let mut span = path.span().clone();
1452
1453 let namespace = match &input.cursor.peek()?.token {
1454 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("as") => {
1455 let namespace = input.parse::<SassUseNamespace>()?;
1456 span.end = namespace.span.end;
1457 Some(namespace)
1458 }
1459 _ => None,
1460 };
1461
1462 let config = input.parse_sass_module_config(false)?;
1463 if let Some(config) = &config {
1464 span.end = config.span.end;
1465 }
1466
1467 Ok(SassUse { path, namespace, config, span })
1468 }
1469}
1470
1471impl<'a> Parse<'a> for SassUseNamespace<'a> {
1472 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1473 let as_span = match input.cursor.peek()? {
1474 TokenWithSpan { token: Token::Ident(ident), .. }
1475 if ident.name().eq_ignore_ascii_case("as") =>
1476 {
1477 input.cursor.bump()?.span
1478 }
1479 TokenWithSpan { span, .. } => {
1480 return Err(Error { kind: ErrorKind::ExpectSassKeyword("as"), span: span.clone() });
1481 }
1482 };
1483 input.eat_sass_line_continuation()?;
1484 match input.cursor.bump()? {
1485 TokenWithSpan { token: Token::Asterisk(..), span: asterisk_span } => {
1486 let span = Span { start: as_span.start, end: asterisk_span.end };
1487 Ok(SassUseNamespace {
1488 as_span,
1489 kind: SassUseNamespaceKind::Unnamed(SassUnnamedNamespace {
1490 span: asterisk_span,
1491 }),
1492 span,
1493 })
1494 }
1495 TokenWithSpan { token: Token::Ident(ident), span: ident_span } => {
1496 let span = Span { start: as_span.start, end: ident_span.end };
1497 Ok(SassUseNamespace {
1498 as_span,
1499 kind: SassUseNamespaceKind::Named(input.ident(ident, ident_span)),
1500 span,
1501 })
1502 }
1503 TokenWithSpan { span, .. } => {
1504 Err(Error { kind: ErrorKind::ExpectSassUseNamespace, span })
1505 }
1506 }
1507 }
1508}
1509
1510impl<'a> Parse<'a> for SassVariable<'a> {
1511 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1512 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1513
1514 let (name, span) = input.parse_dollar_var_ident()?;
1515 Ok(SassVariable { name, span })
1516 }
1517}
1518
1519impl<'a> Parse<'a> for SassVariableDeclaration<'a> {
1520 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1521 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1522
1523 let namespace = if let Some((ident_token, span)) = input.cursor.eat_ident()? {
1524 let (_, dot_span) = input.cursor.expect_dot()?;
1525 util::assert_no_ws_or_comment(&span, &dot_span)?;
1526 let TokenWithSpan { span: next_span, .. } = input.cursor.peek()?;
1527 util::assert_no_ws_or_comment(&dot_span, next_span)?;
1528 Some(input.ident(ident_token, span))
1529 } else {
1530 None
1531 };
1532
1533 let name = input.parse::<SassVariable>()?;
1534 input.eat_sass_line_continuation()?;
1535 let (_, colon_span) = input.cursor.expect_colon()?;
1536 input.eat_sass_line_continuation()?;
1537 let value = input
1538 .with_state(ParserState {
1539 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV,
1540 ..input.state.clone()
1541 })
1542 .parse_maybe_sass_list(true)?;
1543
1544 let (flags, end) = input.parse_sass_flags()?;
1545
1546 let span = Span {
1547 start: namespace
1548 .as_ref()
1549 .map(|namespace| namespace.span.start)
1550 .unwrap_or(name.span.start),
1551 end: end.unwrap_or_else(|| value.span().end),
1552 };
1553
1554 if namespace.is_some() && flags.iter().any(|flag| flag.keyword.name == "global") {
1555 input
1556 .recoverable_errors
1557 .push(Error { kind: ErrorKind::UnexpectedSassFlag("global"), span: span.clone() });
1558 }
1559
1560 Ok(SassVariableDeclaration { namespace, name, colon_span, value, flags, span })
1561 }
1562}
1563
1564impl<'a> Parse<'a> for UnknownSassAtRule<'a> {
1565 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1566 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1567
1568 let (_, at_span) = input.cursor.expect_at()?;
1569 let name = input.parse_sass_interpolated_ident()?;
1570 let name_span = name.span();
1571 util::assert_no_ws_or_comment(&at_span, name_span)?;
1572
1573 let (prelude, block, end) = input.parse_unknown_at_rule()?;
1574 let span = Span { start: at_span.start, end: end.unwrap_or(name_span.end) };
1575 Ok(UnknownSassAtRule { name, prelude, block, span })
1576 }
1577}