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