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 token @ TokenWithSpan { token: Token::Number(..), span }
196 if token.number_raw(self.source).is_some_and(|raw| {
197 raw.starts_with('+')
198 || raw.starts_with('-') && span.start == left.span().end
199 }) =>
200 {
201 let (number, number_span) = self.cursor.expect_number()?;
202 let op = SassBinaryOperator {
203 kind: if number.raw.starts_with('+') {
204 SassBinaryOperatorKind::Plus
205 } else {
206 SassBinaryOperatorKind::Minus
207 },
208 span: Span { start: number_span.start, end: number_span.start + 1 },
209 };
210 let span = Span { start: left.span().start, end: number_span.end };
211 let right = {
212 let span = Span { start: number_span.start + 1, end: number_span.end };
213 let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
214 raw.parse()
215 .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span })
216 .map(|value| ComponentValue::Number(Number { value, raw, span }))?
217 };
218 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
219 left: self.alloc(left),
220 op,
221 right: self.alloc(right),
222 span,
223 });
224 continue;
225 }
226 token @ TokenWithSpan { token: Token::Dimension(..), span }
227 if token.dimension_value_raw(self.source).is_some_and(|raw| {
228 raw.starts_with('+')
229 || raw.starts_with('-') && span.start == left.span().end
230 }) =>
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 token @ TokenWithSpan { token: Token::Ident(..), .. }
428 if token.is_ident_raw(self.source, "and")
429 && PRECEDENCE_AND >= min_precedence =>
430 {
431 (
432 SassBinaryOperator {
433 kind: SassBinaryOperatorKind::And,
434 span: self.cursor.bump()?.span,
435 },
436 PRECEDENCE_AND,
437 )
438 }
439 token @ TokenWithSpan { token: Token::Ident(..), .. }
440 if token.is_ident_raw(self.source, "or") && PRECEDENCE_OR >= min_precedence =>
441 {
442 (
443 SassBinaryOperator {
444 kind: SassBinaryOperatorKind::Or,
445 span: self.cursor.bump()?.span,
446 },
447 PRECEDENCE_OR,
448 )
449 }
450 _ => break,
451 };
452
453 self.eat_sass_line_continuation()?;
455 let right =
456 self.parse_sass_bin_expr_with_min_precedence(precedence + 1, allow_comparison)?;
457 if let ComponentValue::Delimiter(Delimiter { span, .. }) = right {
459 return Err(Error { kind: ErrorKind::ExpectSassExpression, span });
460 }
461
462 let span = Span { start: left.span().start, end: right.span().end };
463 left = ComponentValue::SassBinaryExpression(SassBinaryExpression {
464 left: self.alloc(left),
465 op: operator,
466 right: self.alloc(right),
467 span,
468 });
469 }
470
471 Ok(left)
472 }
473
474 fn parse_sass_flags(
476 &mut self,
477 ) -> PResult<(oxc_allocator::Vec<'a, SassFlag<'a>>, Option<usize>)> {
478 let mut flags: oxc_allocator::Vec<'a, SassFlag<'a>> = self.vec_with_capacity(1);
479 let mut end = None;
480 while let Some((_, exclamation_span)) = self.cursor.eat_exclamation()? {
481 let keyword = self.parse::<Ident>()?;
482 let keyword_span = keyword.span;
483 util::assert_no_ws_or_comment(&exclamation_span, &keyword_span)?;
484 end = Some(keyword_span.end);
485
486 if !matches!(keyword.name, "default" | "global") {
488 self.recoverable_errors.push(Error {
489 kind: ErrorKind::InvalidSassFlagName(keyword.name.to_string()),
490 span: keyword.span,
491 });
492 }
493
494 flags.push(SassFlag {
495 keyword,
496 span: Span { start: exclamation_span.start, end: keyword_span.end },
497 });
498 }
499
500 Ok((flags, end))
501 }
502
503 pub(super) fn parse_sass_interpolated_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
506 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
507
508 let (first, Span { start, mut end }) = match self.cursor.peek()? {
509 TokenWithSpan { token: Token::Ident(..), .. } => {
510 let (ident, ident_span) = self.cursor.expect_ident()?;
511 (
512 SassInterpolatedIdentElement::Static(
513 self.interpolable_ident_static_part(ident, ident_span),
514 ),
515 ident_span,
516 )
517 }
518 TokenWithSpan { token: Token::HashLBrace(..), .. } => {
519 self.parse_sass_interpolated_ident_expr()?
520 }
521 TokenWithSpan { token, span } => {
522 return Err(Error {
523 kind: ErrorKind::ExpectOneOf(vec!["<ident>", "#{"], token.symbol()),
524 span: *span,
525 });
526 }
527 };
528
529 let mut elements = self.parse_sass_interpolated_ident_rest(&mut end)?;
530 if elements.is_empty()
531 && let SassInterpolatedIdentElement::Static(ident) = first
532 {
533 return Ok(InterpolableIdent::Literal(Ident {
534 name: ident.value,
535 raw: ident.raw,
536 span: ident.span,
537 }));
538 }
539
540 elements.insert(0, first);
541 Ok(InterpolableIdent::SassInterpolated(SassInterpolatedIdent {
542 elements,
543 span: Span { start, end },
544 }))
545 }
546
547 pub(super) fn parse_sass_interpolated_ident_rest(
549 &mut self,
550 end: &mut usize,
551 ) -> PResult<oxc_allocator::Vec<'a, SassInterpolatedIdentElement<'a>>> {
552 let mut elements = self.vec();
553 loop {
554 if let Some((token, span)) = self.cursor.tokenizer.scan_ident_template()? {
555 *end = span.end;
556 elements.push(SassInterpolatedIdentElement::Static(
557 self.interpolable_ident_static_part(token, span),
558 ));
559 } else if matches!(
560 self.cursor.peek()?,
561 TokenWithSpan { token: Token::HashLBrace(..), span } if *end == span.start
562 ) {
563 let (element, span) = self.parse_sass_interpolated_ident_expr()?;
564 *end = span.end;
565 elements.push(element);
566 } else {
567 return Ok(elements);
568 }
569 }
570 }
571
572 fn parse_sass_interpolated_ident_expr(
574 &mut self,
575 ) -> PResult<(SassInterpolatedIdentElement<'a>, Span)> {
576 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
577
578 let start = self.cursor.expect_hash_l_brace()?.1.start;
579 let expr = self.parse_maybe_sass_list(true)?;
580 let end = self.cursor.expect_r_brace()?.1.end;
581 Ok((SassInterpolatedIdentElement::Expression(expr), Span { start, end }))
582 }
583
584 pub(super) fn parse_sass_invocation_args(
588 &mut self,
589 ) -> PResult<(oxc_allocator::Vec<'a, ComponentValue<'a>>, oxc_allocator::Vec<'a, Span>)> {
590 debug_assert!(matches!(self.syntax, Syntax::Scss | Syntax::Sass));
591
592 let mut values = self.vec_with_capacity(4);
593 let mut comma_spans = self.vec();
594 while !matches!(self.cursor.peek()?.token, Token::RParen(..) | Token::Eof(..)) {
595 match self.cursor.peek()?.token {
596 Token::Comma(..) => {
597 let TokenWithSpan { span, .. } = self.cursor.bump()?;
598 self.recoverable_errors
599 .push(Error { kind: ErrorKind::ExpectComponentValue, span });
600 continue;
601 }
602 _ => {
603 let value = self.parse_maybe_sass_list(false)?;
604 if let Some((_, span)) = self.cursor.eat_dot_dot_dot()? {
605 let span = Span { start: value.span().start, end: span.end };
606 values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
607 value: self.alloc(value),
608 span,
609 }));
610 } else if let ComponentValue::SassVariable(sass_var) = value {
611 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
612 let value = self.parse_maybe_sass_list(false)?;
613 let span = Span { start: sass_var.span.start, end: value.span().end };
614 values.push(ComponentValue::SassKeywordArgument(SassKeywordArgument {
615 name: sass_var,
616 colon_span,
617 value: self.alloc(value),
618 span,
619 }));
620 } else {
621 values.push(ComponentValue::SassVariable(sass_var));
622 }
623 } else {
624 values.push(value);
625 }
626 }
627 }
628 if !matches!(self.cursor.peek()?.token, Token::RParen(..) | Token::Eof(..)) {
629 comma_spans.push(self.cursor.expect_comma()?.1);
630 }
631 }
632 debug_assert!(values.len() - comma_spans.len() <= 1);
633 Ok((values, comma_spans))
634 }
635
636 fn parse_sass_module_config(
640 &mut self,
641 allow_overridable: bool,
642 ) -> PResult<Option<SassModuleConfig<'a>>> {
643 if self.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(self.source, "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 } else {
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;
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;
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()? {
811 TokenWithSpan { token: Token::Plus(..), .. } => SassUnaryOperator {
812 kind: SassUnaryOperatorKind::Plus,
813 span: self.cursor.bump()?.span,
814 },
815 TokenWithSpan { token: Token::Minus(..), .. } => SassUnaryOperator {
816 kind: SassUnaryOperatorKind::Minus,
817 span: self.cursor.bump()?.span,
818 },
819 token if token.is_ident_raw(self.source, "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();
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();
1034
1035 let prefix = if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "as")
1036 {
1037 let TokenWithSpan { span: as_span, .. } = input.cursor.bump()?;
1038 input.eat_sass_line_continuation()?;
1039 let name = input.parse()?;
1040 let (_, Span { end, .. }) = input.cursor.expect_asterisk_without_ws_or_comments()?;
1041 let span = Span { start: as_span.start, end };
1042 Some(SassForwardPrefix { as_span, name, span })
1043 } else {
1044 None
1045 };
1046
1047 let visibility = if let Some(keyword) = input.cursor.peek()?.ident(input.source) {
1048 let keyword_span = input.cursor.peek()?.span;
1049 let start = keyword_span.start;
1050 let name = keyword.name();
1051 if name.eq_ignore_ascii_case("hide") {
1052 let keyword_span = input.cursor.bump()?.span;
1053 let mut members = input.vec();
1054 let mut comma_spans = input.vec();
1055 loop {
1056 input.eat_sass_line_continuation()?;
1057 match &input.cursor.peek()?.token {
1058 Token::Ident(..) => {
1059 members.push(input.parse().map(SassForwardMember::Ident)?)
1060 }
1061 _ => members.push(input.parse().map(SassForwardMember::Variable)?),
1062 }
1063 if let Some((_, span)) = input.cursor.eat_comma()? {
1064 comma_spans.push(span);
1065 } else {
1066 break;
1067 }
1068 }
1069 Some(SassForwardVisibility {
1070 modifier: SassForwardVisibilityModifier {
1071 kind: SassForwardVisibilityModifierKind::Hide,
1072 span: keyword_span,
1073 },
1074 members,
1075 comma_spans,
1076 span: Span { start, end: input.cursor.tokenizer.current_offset() },
1077 })
1078 } else if name.eq_ignore_ascii_case("show") {
1079 let keyword_span = input.cursor.bump()?.span;
1080 let mut members = input.vec();
1081 let mut comma_spans = input.vec();
1082 loop {
1083 input.eat_sass_line_continuation()?;
1084 match &input.cursor.peek()?.token {
1085 Token::Ident(..) => {
1086 members.push(input.parse().map(SassForwardMember::Ident)?)
1087 }
1088 _ => members.push(input.parse().map(SassForwardMember::Variable)?),
1089 }
1090 if let Some((_, span)) = input.cursor.eat_comma()? {
1091 comma_spans.push(span);
1092 } else {
1093 break;
1094 }
1095 }
1096 Some(SassForwardVisibility {
1097 modifier: SassForwardVisibilityModifier {
1098 kind: SassForwardVisibilityModifierKind::Show,
1099 span: keyword_span,
1100 },
1101 members,
1102 comma_spans,
1103 span: Span { start, end: input.cursor.tokenizer.current_offset() },
1104 })
1105 } else {
1106 None
1107 }
1108 } else {
1109 None
1110 };
1111
1112 let config = input.parse_sass_module_config(true)?;
1113 if let Some(config) = &config {
1114 span.end = config.span.end;
1115 }
1116
1117 Ok(SassForward { path, prefix, visibility, config, span })
1118 }
1119}
1120
1121impl<'a> Parse<'a> for SassFunction<'a> {
1125 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1126 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1127
1128 input.eat_sass_line_continuation()?;
1129 let name = input.parse::<Ident>()?;
1130 let start = name.span.start;
1131
1132 input.eat_sass_line_continuation()?;
1133 let parameters = input.parse::<SassParameters>()?;
1134
1135 let span = Span { start, end: parameters.span.end };
1136 Ok(SassFunction { name, parameters, span })
1137 }
1138}
1139
1140impl<'a> Parse<'a> for SassIfAtRule<'a> {
1144 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1145 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1146
1147 let start = input.cursor.expect_at_keyword()?.1.start;
1148
1149 let if_clause = input.parse::<SassConditionalClause>()?;
1150 let mut else_if_clauses: oxc_allocator::Vec<'a, SassConditionalClause<'a>> = input.vec();
1151 let mut else_clause: Option<SimpleBlock> = None;
1152 let mut else_spans = input.vec();
1153
1154 loop {
1155 fn else_keyword<'a>(p: &mut Parser<'a>) -> PResult<(Span, bool)> {
1161 while p.cursor.eat_linebreak()?.is_some() {}
1162 let is_else = match &p.cursor.peek()?.token {
1163 Token::AtKeyword(..) => {
1164 let at_keyword = p.cursor.peek()?.at_keyword(p.source).unwrap();
1165 match &*at_keyword.ident.name() {
1166 "else" => true,
1167 "elseif" => false,
1169 _ => {
1170 let span = p.cursor.peek()?.span;
1171 return Err(Error { kind: ErrorKind::TryParseError, span });
1172 }
1173 }
1174 }
1175 _ => {
1176 let span = p.cursor.peek()?.span;
1177 return Err(Error { kind: ErrorKind::TryParseError, span });
1178 }
1179 };
1180 Ok((p.cursor.bump()?.span, is_else))
1181 }
1182 let else_keyword = if matches!(input.cursor.peek()?.token, Token::Linebreak(..)) {
1183 input.try_parse(else_keyword)
1184 } else {
1185 else_keyword(input)
1186 };
1187 let Ok((else_span, is_else)) = else_keyword else { break };
1188 else_spans.push(else_span);
1189 if is_else {
1190 input.eat_sass_line_continuation()?;
1191 if input.cursor.peek()?.is_ident_raw(input.source, "if") {
1192 input.cursor.bump()?;
1193 else_if_clauses.push(input.parse()?);
1194 } else {
1195 else_clause = Some(input.parse()?);
1196 break;
1197 }
1198 } else {
1199 else_if_clauses.push(input.parse()?);
1200 }
1201 }
1202
1203 debug_assert_eq!(else_spans.len(), else_if_clauses.len() + else_clause.iter().count());
1204 let span = Span {
1205 start,
1206 end: else_clause
1207 .as_ref()
1208 .map(|else_clause| else_clause.span.end)
1209 .or_else(|| else_if_clauses.last().map(|else_if_clause| else_if_clause.span.end))
1210 .unwrap_or(if_clause.span.end),
1211 };
1212 Ok(SassIfAtRule { if_clause, else_if_clauses, else_clause, else_spans, span })
1213 }
1214}
1215
1216impl<'a> Parse<'a> for SassImportPrelude<'a> {
1220 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1221 let first = input.parse::<Str>()?;
1222 let mut span = first.span;
1223
1224 let mut paths = input.vec1(first);
1225 let mut comma_spans = input.vec();
1226 while let Some((_, comma_span)) = input.cursor.eat_comma()? {
1227 comma_spans.push(comma_span);
1228 paths.push(input.parse()?);
1229 }
1230 debug_assert_eq!(comma_spans.len() + 1, paths.len());
1231
1232 if let Some(Str { span: Span { end, .. }, .. }) = paths.last() {
1233 span.end = *end;
1234 }
1235 Ok(SassImportPrelude { paths, comma_spans, span })
1236 }
1237}
1238
1239impl<'a> Parse<'a> for SassInclude<'a> {
1243 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1244 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1245
1246 input.eat_sass_line_continuation()?;
1247 let name = input.parse::<FunctionName>()?;
1248 let mut span = *name.span();
1249
1250 let arguments = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
1251 let arguments = input.parse::<SassIncludeArgs>()?;
1252 span.end = arguments.span.end;
1253 Some(arguments)
1254 } else {
1255 None
1256 };
1257
1258 let content_block_params =
1259 if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "using") {
1260 let content_block_params = input.parse::<SassIncludeContentBlockParams>()?;
1261 span.end = content_block_params.span.end;
1262 Some(content_block_params)
1263 } else {
1264 None
1265 };
1266
1267 Ok(SassInclude { name, arguments, content_block_params, span })
1268 }
1269}
1270
1271impl<'a> Parse<'a> for SassIncludeArgs<'a> {
1273 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1274 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
1275 let (args, comma_spans) = input.parse_sass_invocation_args()?;
1276 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1277 Ok(SassIncludeArgs { args, comma_spans, span: Span { start, end } })
1278 }
1279}
1280
1281impl<'a> Parse<'a> for SassIncludeContentBlockParams<'a> {
1283 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1284 let token = input.cursor.bump()?;
1285 if token.is_ident_name_eq_ignore_ascii_case(input.source, "using") {
1286 let using_span = token.span;
1287 input.eat_sass_line_continuation()?;
1288 let params = input.parse::<SassParameters>()?;
1289 let span = Span { start: using_span.start, end: params.span.end };
1290 Ok(SassIncludeContentBlockParams { using_span, params, span })
1291 } else {
1292 Err(Error { kind: ErrorKind::ExpectSassKeyword("using"), span: token.span })
1293 }
1294 }
1295}
1296
1297impl<'a> Parse<'a> for SassInterpolatedStr<'a> {
1300 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1301 let (first, first_span) = input.cursor.expect_str_template()?;
1302 let quote = first.raw.bytes().next().unwrap();
1303 debug_assert!(quote == b'\'' || quote == b'"');
1304 let mut span = first_span;
1305 let first = input.interpolable_str_static_part(first, first_span);
1306 let mut elements = input.vec1(SassInterpolatedStrElement::Static(first));
1307
1308 let mut is_parsing_static_part = false;
1309 loop {
1310 if is_parsing_static_part {
1311 let (token, str_tpl_span) = input.cursor.tokenizer.scan_string_template(quote)?;
1312 let tail = token.tail;
1313 let end = str_tpl_span.end;
1314 elements.push(SassInterpolatedStrElement::Static(
1315 input.interpolable_str_static_part(token, str_tpl_span),
1316 ));
1317 if tail {
1318 span.end = end;
1319 break;
1320 }
1321 } else {
1322 input.cursor.expect_l_brace()?;
1324 elements.push(SassInterpolatedStrElement::Expression(
1325 input.parse_maybe_sass_list(true)?,
1326 ));
1327 input.cursor.expect_r_brace()?;
1328 }
1329 is_parsing_static_part = !is_parsing_static_part;
1330 }
1331
1332 Ok(SassInterpolatedStr { elements, span })
1333 }
1334}
1335
1336impl<'a> Parse<'a> for SassInterpolatedUrl<'a> {
1338 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1339 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1340
1341 let token_with_span = input.cursor.tokenizer.scan_url_raw_or_template()?;
1342 let (first, first_span) = match token_with_span.url_template(input.source) {
1343 Some(template) => (template, token_with_span.span),
1344 None => {
1345 return Err(Error {
1346 kind: ErrorKind::Unexpected("<url template>", token_with_span.token.symbol()),
1347 span: token_with_span.span,
1348 });
1349 }
1350 };
1351 let mut span = first_span;
1352 let first = input.interpolable_url_static_part(first, first_span);
1353 let mut elements = input.vec1(SassInterpolatedUrlElement::Static(first));
1354
1355 let mut is_parsing_static_part = false;
1356 loop {
1357 if is_parsing_static_part {
1358 let (token, url_tpl_span @ Span { end, .. }) =
1359 input.cursor.tokenizer.scan_url_template()?;
1360 let tail = token.tail;
1361 elements.push(SassInterpolatedUrlElement::Static(
1362 input.interpolable_url_static_part(token, url_tpl_span),
1363 ));
1364 if tail {
1365 span.end = end;
1366 break;
1367 }
1368 } else {
1369 input.cursor.expect_l_brace()?;
1371 elements.push(SassInterpolatedUrlElement::Expression(
1372 input.parse_maybe_sass_list(true)?,
1373 ));
1374 input.cursor.expect_r_brace()?;
1375 }
1376 is_parsing_static_part = !is_parsing_static_part;
1377 }
1378
1379 Ok(SassInterpolatedUrl { elements, span })
1380 }
1381}
1382
1383impl<'a> Parse<'a> for SassList<'a> {
1387 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1388 if let ComponentValue::SassList(list) =
1389 input.parse_maybe_sass_list(true)?
1390 {
1391 Ok(list)
1392 } else {
1393 let TokenWithSpan { token, span } = input.cursor.bump()?;
1394 Err(Error { kind: ErrorKind::Unexpected(",", token.symbol()), span })
1395 }
1396 }
1397}
1398
1399impl<'a> Parse<'a> for SassMap<'a> {
1403 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1404 let start = input.cursor.expect_l_paren()?.1.start;
1405
1406 let mut items = input.vec();
1407 let mut comma_spans = input.vec();
1408 loop {
1409 match input.cursor.peek()?.token {
1410 Token::RParen(..) => break,
1411 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
1412 input.cursor.bump()?;
1413 }
1414 _ => {
1415 items.push(input.parse()?);
1416 if matches!(
1417 input.cursor.peek()?.token,
1418 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..)
1419 ) {
1420 input.cursor.bump()?;
1421 }
1422 if !matches!(&input.cursor.peek()?.token, Token::RParen(..)) {
1423 comma_spans.push(input.cursor.expect_comma()?.1);
1424 }
1425 }
1426 }
1427 }
1428 debug_assert!(items.len() - comma_spans.len() <= 1);
1429
1430 let end = input.cursor.expect_r_paren()?.1.end;
1431 Ok(SassMap { items, comma_spans, span: Span { start, end } })
1432 }
1433}
1434
1435impl<'a> Parse<'a> for SassMapItem<'a> {
1437 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1438 let key = input.parse_maybe_sass_list(false)?;
1439 let (_, colon_span) = input.cursor.expect_colon()?;
1440 let value = input.parse_maybe_sass_list(false)?;
1441 let span = Span { start: key.span().start, end: value.span().end };
1442 Ok(SassMapItem { key, colon_span, value, span })
1443 }
1444}
1445
1446impl<'a> Parse<'a> for SassMixin<'a> {
1450 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1451 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1452
1453 input.eat_sass_line_continuation()?;
1454 let name = input.parse::<Ident>()?;
1455 let start = name.span.start;
1456 let mut end = name.span.end;
1457
1458 let parameters = if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
1459 let parameters = input.parse::<SassParameters>()?;
1460 end = parameters.span.end;
1461 Some(parameters)
1462 } else {
1463 None
1464 };
1465
1466 Ok(SassMixin { name, parameters, span: Span { start, end } })
1467 }
1468}
1469
1470impl<'a> Parse<'a> for SassParameters<'a> {
1472 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1473 let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
1474 let (params, arbitrary_param, comma_spans, end) = input.parse_sass_params()?;
1475 Ok(SassParameters { params, arbitrary_param, comma_spans, span: Span { start, end } })
1476 }
1477}
1478
1479impl<'a> Parse<'a> for SassNestingDeclaration<'a> {
1482 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1483 let block = input.parse::<SimpleBlock>()?;
1484 let span = block.span;
1485
1486 Ok(SassNestingDeclaration { block, span })
1487 }
1488}
1489
1490impl<'a> Parse<'a> for SassParenthesizedExpression<'a> {
1492 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1493 let start = input.cursor.expect_l_paren()?.1.start;
1494 input.cursor.eat_indent()?;
1495 let expr = input
1496 .with_state(ParserState {
1497 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV | SASS_CTX_IN_PARENS,
1498 ..input.state.clone()
1499 })
1500 .parse_maybe_sass_list(true)?;
1501 let expr = input.alloc(expr);
1502 let end = input.cursor.expect_r_paren()?.1.end;
1503 Ok(SassParenthesizedExpression { expr, span: Span { start, end } })
1504 }
1505}
1506
1507impl<'a> Parse<'a> for SassPlaceholderSelector<'a> {
1511 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1512 let (_, percent_span) = input.cursor.expect_percent()?;
1513 let name = input.parse::<InterpolableIdent>()?;
1514 let name_span = name.span();
1515 util::assert_no_ws_or_comment(&percent_span, name_span)?;
1516 let span = Span { start: percent_span.start, end: name_span.end };
1517 Ok(SassPlaceholderSelector { name, span })
1518 }
1519}
1520
1521impl<'a> Parse<'a> for SassUse<'a> {
1525 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1526 input.eat_sass_line_continuation()?;
1527 let path = input.parse::<InterpolableStr>()?;
1528 let mut span = *path.span();
1529
1530 let namespace =
1531 if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "as") {
1532 let namespace = input.parse::<SassUseNamespace>()?;
1533 span.end = namespace.span.end;
1534 Some(namespace)
1535 } else {
1536 None
1537 };
1538
1539 let config = input.parse_sass_module_config(false)?;
1540 if let Some(config) = &config {
1541 span.end = config.span.end;
1542 }
1543
1544 Ok(SassUse { path, namespace, config, span })
1545 }
1546}
1547
1548impl<'a> Parse<'a> for SassUseNamespace<'a> {
1550 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1551 let token = input.cursor.peek()?;
1552 let as_span = if token.is_ident_name_eq_ignore_ascii_case(input.source, "as") {
1553 input.cursor.bump()?.span
1554 } else {
1555 return Err(Error { kind: ErrorKind::ExpectSassKeyword("as"), span: token.span });
1556 };
1557 input.eat_sass_line_continuation()?;
1558 match input.cursor.bump()? {
1559 TokenWithSpan { token: Token::Asterisk(..), span: asterisk_span } => {
1560 let span = Span { start: as_span.start, end: asterisk_span.end };
1561 Ok(SassUseNamespace {
1562 as_span,
1563 kind: SassUseNamespaceKind::Unnamed(SassUnnamedNamespace {
1564 span: asterisk_span,
1565 }),
1566 span,
1567 })
1568 }
1569 token @ TokenWithSpan { token: Token::Ident(..), span: ident_span } => {
1570 let ident = token.ident(input.source).unwrap();
1571 let span = Span { start: as_span.start, end: ident_span.end };
1572 Ok(SassUseNamespace {
1573 as_span,
1574 kind: SassUseNamespaceKind::Named(input.ident(ident, ident_span)),
1575 span,
1576 })
1577 }
1578 TokenWithSpan { span, .. } => {
1579 Err(Error { kind: ErrorKind::ExpectSassUseNamespace, span })
1580 }
1581 }
1582 }
1583}
1584
1585impl<'a> Parse<'a> for SassVariable<'a> {
1589 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1590 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1591
1592 let (name, span) = input.parse_dollar_var_ident()?;
1593 Ok(SassVariable { name, span })
1594 }
1595}
1596
1597impl<'a> Parse<'a> for SassVariableDeclaration<'a> {
1602 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1603 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1604
1605 let namespace = if let Some((ident_token, span)) = input.cursor.eat_ident()? {
1606 let (_, dot_span) = input.cursor.expect_dot()?;
1607 util::assert_no_ws_or_comment(&span, &dot_span)?;
1608 let TokenWithSpan { span: next_span, .. } = input.cursor.peek()?;
1609 util::assert_no_ws_or_comment(&dot_span, next_span)?;
1610 Some(input.ident(ident_token, span))
1611 } else {
1612 None
1613 };
1614
1615 let name = input.parse::<SassVariable>()?;
1616 input.eat_sass_line_continuation()?;
1617 let (_, colon_span) = input.cursor.expect_colon()?;
1618 input.eat_sass_line_continuation()?;
1619 let value = input
1620 .with_state(ParserState {
1621 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV,
1622 ..input.state.clone()
1623 })
1624 .parse_maybe_sass_list(true)?;
1625
1626 let (flags, end) = input.parse_sass_flags()?;
1627
1628 let span = Span {
1629 start: namespace
1630 .as_ref()
1631 .map(|namespace| namespace.span.start)
1632 .unwrap_or(name.span.start),
1633 end: end.unwrap_or_else(|| value.span().end),
1634 };
1635
1636 if namespace.is_some() && flags.iter().any(|flag| flag.keyword.name == "global") {
1637 input
1638 .recoverable_errors
1639 .push(Error { kind: ErrorKind::UnexpectedSassFlag("global"), span });
1640 }
1641
1642 Ok(SassVariableDeclaration { namespace, name, colon_span, value, flags, span })
1643 }
1644}
1645
1646impl<'a> Parse<'a> for UnknownSassAtRule<'a> {
1649 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1650 debug_assert!(matches!(input.syntax, Syntax::Scss | Syntax::Sass));
1651
1652 let (_, at_span) = input.cursor.expect_at()?;
1653 let name = input.parse_sass_interpolated_ident()?;
1654 let name_span = name.span();
1655 util::assert_no_ws_or_comment(&at_span, name_span)?;
1656
1657 let (prelude, block, end) = input.parse_unknown_at_rule()?;
1658 let span = Span { start: at_span.start, end: end.unwrap_or(name_span.end) };
1659 Ok(UnknownSassAtRule { name, prelude, block, span })
1660 }
1661}