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