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