1use super::{Parser, state::QualifiedRuleContext};
2use crate::{
3 Parse, Syntax,
4 ast::*,
5 error::{Error, ErrorKind, PResult},
6 pos::Span,
7 tokenizer::{Token, TokenWithSpan},
8 util,
9};
10
11const PRECEDENCE_MULTIPLY: u8 = 2;
12const PRECEDENCE_PLUS: u8 = 1;
13
14fn unvendored(name: &str) -> &str {
17 name.strip_prefix('-').and_then(|rest| rest.split_once('-')).map_or(name, |(_, base)| base)
18}
19
20fn is_special_typed_or_raw_function(name: &str) -> bool {
27 let base = unvendored(name);
28 let vendored = base.len() != name.len();
29 base.eq_ignore_ascii_case("element")
30 || (!vendored && (base.eq_ignore_ascii_case("type") || base.eq_ignore_ascii_case("if")))
31 || (vendored && (base.eq_ignore_ascii_case("calc") || base.eq_ignore_ascii_case("url")))
32}
33
34impl<'a> Parser<'a> {
35 pub(in crate::parser) fn parse_calc_expr(
36 &mut self,
37 allow_modulo: bool,
38 ) -> PResult<ComponentValue<'a>> {
39 self.parse_calc_expr_recursively(0, allow_modulo)
40 }
41
42 fn parse_calc_expr_recursively(
50 &mut self,
51 precedence: u8,
52 allow_modulo: bool,
53 ) -> PResult<ComponentValue<'a>> {
54 let mut left = if precedence >= PRECEDENCE_MULTIPLY {
55 if self.cursor.eat_l_paren()?.is_some() {
56 let expr = self.parse_calc_expr(allow_modulo)?;
57 self.cursor.expect_r_paren()?;
58 expr
59 } else if matches!(self.syntax, Syntax::Scss | Syntax::Sass)
60 && matches!(&self.cursor.peek()?.token, Token::Minus(..) | Token::Plus(..))
61 && {
62 let span = &self.cursor.peek()?.span;
63 self.source.as_bytes().get(span.end) == Some(&b'(')
64 }
65 {
66 let op = match &self.cursor.peek()?.token {
70 Token::Minus(..) => SassUnaryOperator {
71 kind: SassUnaryOperatorKind::Minus,
72 span: self.cursor.bump()?.span,
73 },
74 _ => SassUnaryOperator {
75 kind: SassUnaryOperatorKind::Plus,
76 span: self.cursor.bump()?.span,
77 },
78 };
79 let expr = self.parse_calc_expr_recursively(PRECEDENCE_MULTIPLY, allow_modulo)?;
80 let span = Span { start: op.span.start, end: expr.span().end };
81 ComponentValue::SassUnaryExpression(SassUnaryExpression {
82 expr: self.alloc(expr),
83 op,
84 span,
85 })
86 } else if self.syntax == Syntax::Less {
87 if matches!(self.cursor.peek()?.token, Token::Minus(..)) {
88 ComponentValue::LessNegativeValue(self.parse()?)
89 } else {
90 self.parse_component_value_atom()?
91 }
92 } else {
93 self.parse_component_value_atom()?
94 }
95 } else {
96 self.parse_calc_expr_recursively(precedence + 1, allow_modulo)?
97 };
98
99 loop {
100 let operator = match &self.cursor.peek()?.token {
101 Token::Asterisk(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
102 kind: CalcOperatorKind::Multiply,
103 span: self.cursor.bump()?.span,
104 },
105 Token::Solidus(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
106 kind: CalcOperatorKind::Division,
107 span: self.cursor.bump()?.span,
108 },
109 Token::Percent(..) if precedence == PRECEDENCE_MULTIPLY && allow_modulo => {
113 CalcOperator { kind: CalcOperatorKind::Modulo, span: self.cursor.bump()?.span }
114 }
115 Token::Plus(..) if precedence == PRECEDENCE_PLUS => {
116 CalcOperator { kind: CalcOperatorKind::Plus, span: self.cursor.bump()?.span }
117 }
118 Token::Minus(..) if precedence == PRECEDENCE_PLUS => {
119 CalcOperator { kind: CalcOperatorKind::Minus, span: self.cursor.bump()?.span }
120 }
121 _ => break,
122 };
123
124 let right = self.parse_calc_expr_recursively(precedence + 1, allow_modulo)?;
125 let span = Span { start: left.span().start, end: right.span().end };
126 left = ComponentValue::Calc(Calc {
127 left: self.alloc(left),
128 op: operator,
129 right: self.alloc(right),
130 span,
131 });
132 }
133
134 Ok(left)
135 }
136
137 pub(super) fn parse_component_value_atom(&mut self) -> PResult<ComponentValue<'a>> {
141 let token_with_span = self.cursor.peek()?;
142 match &token_with_span.token {
143 Token::Ident(..) => {
144 let ident = token_with_span.ident(self.source).unwrap();
145 if unvendored(&ident.name()).eq_ignore_ascii_case("url") {
146 match self.try_parse(Url::parse) {
147 Ok(url) => return Ok(ComponentValue::Url(self.alloc(url))),
148 Err(Error { kind: ErrorKind::TryParseError, .. }) => {}
149 Err(error) => {
150 let (function_name, function_name_span) = self.cursor.expect_ident()?;
157 let function_name = self.ident(function_name, function_name_span);
158 return self
159 .parse_function_typed_or_raw(function_name)
160 .map(ComponentValue::Function)
161 .map_err(|_| error);
162 }
163 }
164 }
165 let ident = self.parse::<InterpolableIdent>()?;
166 let ident_end = ident.span().end;
167 match self.cursor.peek()? {
168 TokenWithSpan { token: Token::LParen(..), span } if span.start == ident_end => {
169 return match ident {
170 InterpolableIdent::Literal(ident)
171 if ident.name.eq_ignore_ascii_case("src") =>
172 {
173 self.parse_src_url(ident)
174 .map(|url| ComponentValue::Url(self.alloc(url)))
175 }
176 InterpolableIdent::Literal(ident)
177 if unvendored(ident.name).eq_ignore_ascii_case("expression") =>
178 {
179 self.parse_raw_function(InterpolableIdent::Literal(ident))
182 .map(ComponentValue::Function)
183 }
184 InterpolableIdent::Literal(ident)
185 if is_special_typed_or_raw_function(ident.name) =>
186 {
187 self.parse_function_typed_or_raw(ident)
188 .map(ComponentValue::Function)
189 }
190 ident => self.parse_function(ident).map(ComponentValue::Function),
191 };
192 }
193 TokenWithSpan { token: Token::Colon(..), span }
198 if span.start == ident_end
199 && matches!(
200 &ident,
201 InterpolableIdent::Literal(id)
202 if unvendored(id.name).eq_ignore_ascii_case("progid")
203 ) =>
204 {
205 if let InterpolableIdent::Literal(ident) = ident {
206 return self.parse_progid_function(ident).map(ComponentValue::Function);
207 }
208 unreachable!("guard matched a literal ident");
209 }
210 TokenWithSpan { token: Token::Dot(..), span }
211 if matches!(self.syntax, Syntax::Scss | Syntax::Sass)
212 && span.start == ident_end =>
213 {
214 if let InterpolableIdent::Literal(module) = &ident {
215 let module =
216 Ident { name: module.name, raw: module.raw, span: module.span };
217 let qualified = self.try_parse(|parser| {
220 let name = parser.parse_sass_qualified_name(module)?;
221 if let SassQualifiedName {
222 member: SassModuleMemberName::Ident(..),
223 ..
224 } = name
225 {
226 let (_, lparen_span) = parser.cursor.expect_l_paren()?;
227 util::assert_no_ws_or_comment(&name.span, &lparen_span)?;
228 let args = parser.parse_function_args()?;
229 let (_, Span { end, .. }) = parser.cursor.expect_r_paren()?;
230 let span = Span { start: name.span.start, end };
231 Ok(ComponentValue::Function(Function {
232 name: FunctionName::SassQualifiedName(parser.alloc(name)),
233 args,
234 span,
235 }))
236 } else {
237 Ok(ComponentValue::SassQualifiedName(parser.alloc(name)))
238 }
239 });
240 return match qualified {
241 Ok(value) => Ok(value),
242 Err(_) => Ok(ComponentValue::InterpolableIdent(ident)),
250 };
251 }
252 }
253 _ => {}
254 }
255 match ident {
256 InterpolableIdent::Literal(ident) if ident.raw.eq_ignore_ascii_case("u") => {
257 match self.cursor.peek()? {
258 TokenWithSpan { token: Token::Plus(..), span }
259 if span.start == ident_end =>
260 {
261 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
262 }
263 token @ TokenWithSpan { token: Token::Number(..), span }
264 if token
265 .number_raw(self.source)
266 .is_some_and(|raw| raw.starts_with('+'))
267 && span.start == ident_end =>
268 {
269 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
270 }
271 token @ TokenWithSpan { token: Token::Dimension(..), span }
272 if token
273 .dimension_value_raw(self.source)
274 .is_some_and(|raw| raw.starts_with('+'))
275 && span.start == ident_end =>
276 {
277 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
278 }
279 _ => Ok(ComponentValue::InterpolableIdent(InterpolableIdent::Literal(
280 ident,
281 ))),
282 }
283 }
284 _ => Ok(ComponentValue::InterpolableIdent(ident)),
285 }
286 }
287 Token::Solidus(..) | Token::Comma(..) => self.parse().map(ComponentValue::Delimiter),
288 Token::Number(..) => self.parse().map(ComponentValue::Number),
289 Token::Dimension(..) => self.parse().map(ComponentValue::Dimension),
290 Token::Percentage(..) => self.parse().map(ComponentValue::Percentage),
291 Token::Hash(..) => {
292 if self.syntax == Syntax::Less {
293 self.parse_maybe_hex_color_or_less_mixin_call()
294 } else {
295 self.parse().map(ComponentValue::HexColor)
296 }
297 }
298 Token::Str(..) => {
299 self.parse().map(InterpolableStr::Literal).map(ComponentValue::InterpolableStr)
300 }
301 Token::LBracket(..) => self.parse().map(ComponentValue::BracketBlock),
302 Token::DollarVar(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
303 self.parse().map(ComponentValue::SassVariable)
304 }
305 Token::DollarVar(..)
306 if self.syntax == Syntax::Css && self.options.allow_postcss_simple_vars =>
307 {
308 self.parse().map(ComponentValue::PostcssSimpleVar)
309 }
310 Token::LParen(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
311 match self.try_parse(SassParenthesizedExpression::parse) {
312 Ok(expr) => Ok(ComponentValue::SassParenthesizedExpression(expr)),
313 Err(err) => self.parse().map(ComponentValue::SassMap).map_err(|_| err),
314 }
315 }
316 Token::HashLBrace(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
317 let ident = self.parse_sass_interpolated_ident()?;
318 match self.cursor.peek()? {
319 TokenWithSpan { token: Token::LParen(..), span }
320 if span.start == ident.span().end =>
321 {
322 self.parse_function(ident).map(ComponentValue::Function)
323 }
324 _ => Ok(ComponentValue::InterpolableIdent(ident)),
325 }
326 }
327 Token::StrTemplate(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => self
328 .parse()
329 .map(InterpolableStr::SassInterpolated)
330 .map(ComponentValue::InterpolableStr),
331 Token::Ampersand(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
332 self.parse().map(ComponentValue::SassParentSelector)
333 }
334 Token::LBrace(..)
335 if self.syntax == Syntax::Scss
336 && matches!(
337 self.state.qualified_rule_ctx,
338 Some(QualifiedRuleContext::DeclarationValue)
339 ) =>
340 {
341 self.parse().map(ComponentValue::SassNestingDeclaration)
342 }
343 Token::Indent(..)
344 if self.syntax == Syntax::Sass
345 && matches!(
346 self.state.qualified_rule_ctx,
347 Some(QualifiedRuleContext::DeclarationValue)
348 ) =>
349 {
350 self.parse().map(ComponentValue::SassNestingDeclaration)
351 }
352 Token::AtKeyword(..) if self.syntax == Syntax::Less => {
353 self.parse_less_maybe_variable_or_with_lookups()
354 }
355 Token::Dot(..) if self.syntax == Syntax::Less => {
356 self.parse_less_maybe_mixin_call_or_with_lookups()
357 }
358 Token::Dot(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
364 let dot = self.cursor.bump()?;
365 match self.cursor.peek()? {
366 TokenWithSpan { token: Token::Ident(..), span }
367 if span.start == dot.span.end =>
368 {
369 Ok(ComponentValue::TokenWithSpan(dot))
370 }
371 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: dot.span }),
372 }
373 }
374 Token::StrTemplate(..) if self.syntax == Syntax::Less => self
375 .parse()
376 .map(InterpolableStr::LessInterpolated)
377 .map(ComponentValue::InterpolableStr),
378 Token::At(..) if self.syntax == Syntax::Less => {
379 self.parse().map(ComponentValue::LessVariableVariable)
380 }
381 Token::DollarVar(..) if self.syntax == Syntax::Less => {
382 self.parse().map(ComponentValue::LessPropertyVariable)
383 }
384 Token::Tilde(..) if self.syntax == Syntax::Less => {
385 if let Ok(list_function_call) = self.try_parse(Function::parse) {
386 Ok(ComponentValue::Function(list_function_call))
387 } else if let Ok(less_escaped_str) = self.try_parse(LessEscapedStr::parse) {
388 Ok(ComponentValue::LessEscapedStr(less_escaped_str))
389 } else {
390 self.parse().map(ComponentValue::LessJavaScriptSnippet)
391 }
392 }
393 Token::Percent(..) if self.syntax == Syntax::Less => self
394 .try_parse(Function::parse)
395 .map(ComponentValue::Function)
396 .or_else(|_| self.parse().map(ComponentValue::LessPercentKeyword)),
397 Token::BacktickCode(..) if self.syntax == Syntax::Less => {
398 self.parse().map(ComponentValue::LessJavaScriptSnippet)
399 }
400 Token::Placeholder(..) => {
401 let (placeholder, span) = self.cursor.expect_placeholder()?;
402 Ok(ComponentValue::Placeholder((placeholder, span).into()))
403 }
404 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: token_with_span.span }),
405 }
406 }
407
408 pub(super) fn parse_dashed_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
410 let ident = self.parse()?;
411 if let InterpolableIdent::Literal(ident) = &ident
412 && !ident.name.starts_with("--")
413 {
414 self.recoverable_errors
415 .push(Error { kind: ErrorKind::ExpectDashedIdent, span: ident.span });
416 }
417 Ok(ident)
418 }
419
420 pub(super) fn parse_function(&mut self, name: InterpolableIdent<'a>) -> PResult<Function<'a>> {
422 self.cursor.expect_l_paren()?;
423 let args = if let Token::RParen(..) = &self.cursor.peek()?.token {
424 self.vec()
425 } else {
426 match &name {
427 InterpolableIdent::Literal(ident)
428 if ident.name.eq_ignore_ascii_case("calc")
429 || ident.name.eq_ignore_ascii_case("-webkit-calc")
430 || ident.name.eq_ignore_ascii_case("-moz-calc")
431 || ident.name.eq_ignore_ascii_case("min")
432 || ident.name.eq_ignore_ascii_case("max")
433 || ident.name.eq_ignore_ascii_case("clamp")
434 || ident.name.eq_ignore_ascii_case("sin")
435 || ident.name.eq_ignore_ascii_case("cos")
436 || ident.name.eq_ignore_ascii_case("tan")
437 || ident.name.eq_ignore_ascii_case("asin")
438 || ident.name.eq_ignore_ascii_case("acos")
439 || ident.name.eq_ignore_ascii_case("atan")
440 || ident.name.eq_ignore_ascii_case("sqrt")
441 || ident.name.eq_ignore_ascii_case("exp")
442 || ident.name.eq_ignore_ascii_case("abs")
443 || ident.name.eq_ignore_ascii_case("sign")
444 || ident.name.eq_ignore_ascii_case("hypot")
445 || ident.name.eq_ignore_ascii_case("round")
446 || ident.name.eq_ignore_ascii_case("mod")
447 || ident.name.eq_ignore_ascii_case("rem")
448 || ident.name.eq_ignore_ascii_case("atan2")
449 || ident.name.eq_ignore_ascii_case("pow")
450 || ident.name.eq_ignore_ascii_case("log") =>
451 {
452 let allow_modulo = matches!(self.syntax, Syntax::Scss | Syntax::Sass)
455 && (ident.name.eq_ignore_ascii_case("min")
456 || ident.name.eq_ignore_ascii_case("max"));
457 if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
464 self.parse_calc_args(allow_modulo)?
465 } else {
466 let typed = self.try_parse(|p| {
467 let values = p.parse_calc_args(allow_modulo)?;
468 let TokenWithSpan { token, span } = p.cursor.peek()?;
469 if matches!(token, Token::RParen(..)) {
470 Ok(values)
471 } else {
472 Err(Error {
476 kind: ErrorKind::Unexpected(")", token.symbol()),
477 span: *span,
478 })
479 }
480 });
481 match typed {
482 Ok(values) => values,
483 Err(error) => {
484 let (args, comma_spans) = self.parse_sass_invocation_args()?;
485 if !args.iter().any(|arg| {
490 matches!(arg, ComponentValue::SassKeywordArgument(..))
491 }) {
492 return Err(error);
493 }
494 let mut values = self.vec_with_capacity(args.len() * 2);
495 let mut comma_spans = comma_spans.into_iter();
496 for (i, arg) in args.into_iter().enumerate() {
497 if i > 0
498 && let Some(span) = comma_spans.next()
499 {
500 values.push(ComponentValue::Delimiter(Delimiter {
501 kind: DelimiterKind::Comma,
502 span,
503 }));
504 }
505 values.push(arg);
506 }
507 values
508 }
509 }
510 }
511 }
512 InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("element") => {
513 let id_selector = self.parse().map(ComponentValue::IdSelector)?;
514 self.vec1(id_selector)
515 }
516 InterpolableIdent::Literal(Ident { raw: "boolean" | "if", .. })
517 if self.syntax == Syntax::Less =>
518 {
519 let less_condition = self.parse_less_condition(false)?;
520 let condition = ComponentValue::LessCondition(self.alloc(less_condition));
521 let mut args = self.parse_function_args()?;
522 args.insert(0, condition);
523 args
524 }
525 _ => self.parse_function_args()?,
526 }
527 };
528 let end = self.cursor.expect_r_paren()?.1.end;
529 let span = Span { start: name.span().start, end };
530 Ok(Function { name: FunctionName::Ident(name), args, span })
531 }
532
533 fn parse_calc_args(
537 &mut self,
538 allow_modulo: bool,
539 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
540 let mut values = self.vec_with_capacity(1);
541 loop {
542 match self.cursor.peek()? {
543 TokenWithSpan { token: Token::RParen(..), .. } => break,
544 TokenWithSpan { token: Token::Comma(..), .. } => {
545 values.push(ComponentValue::Delimiter(self.parse()?));
546 }
547 TokenWithSpan { token: Token::DotDotDot(..), .. }
550 if allow_modulo
551 && matches!(self.syntax, Syntax::Scss | Syntax::Sass)
552 && !values.is_empty()
553 && !values
554 .iter()
555 .any(|v| matches!(v, ComponentValue::SassArbitraryArgument(..))) =>
556 {
557 let TokenWithSpan { span: Span { end, .. }, .. } = self.cursor.bump()?;
558 let value = values.pop().unwrap();
559 let span = Span { start: value.span().start, end };
560 values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
561 value: self.alloc(value),
562 span,
563 }));
564 }
565 _ => values.push(self.parse_calc_expr(allow_modulo)?),
566 }
567 }
568 Ok(values)
569 }
570
571 pub(super) fn parse_function_typed_or_raw(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
575 let name_copy = Ident { name: name.name, raw: name.raw, span: name.span };
576 match self.try_parse(|p| p.parse_function(InterpolableIdent::Literal(name))) {
577 Ok(function) => Ok(function),
578 Err(_) => self.parse_raw_function(InterpolableIdent::Literal(name_copy)),
579 }
580 }
581
582 pub(in crate::parser) fn parse_raw_function(
586 &mut self,
587 name: InterpolableIdent<'a>,
588 ) -> PResult<Function<'a>> {
589 self.cursor.expect_l_paren()?;
590 let mut args = self.vec_with_capacity(4);
591 self.parse_raw_function_args_into(&mut args)?;
592 let end = self.cursor.expect_r_paren()?.1.end;
593 let span = Span { start: name.span().start, end };
594 Ok(Function { name: FunctionName::Ident(name), args, span })
595 }
596
597 fn parse_progid_function(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
601 let mut args = self.vec_with_capacity(4);
602 loop {
603 match &self.cursor.peek()?.token {
604 Token::LParen(..)
605 | Token::Semicolon(..)
606 | Token::RBrace(..)
607 | Token::RParen(..)
608 | Token::Eof(..)
609 | Token::Indent(..)
610 | Token::Dedent(..)
611 | Token::Linebreak(..) => break,
612 _ => args.push(ComponentValue::TokenWithSpan(self.cursor.bump()?)),
613 }
614 }
615 self.cursor.expect_l_paren()?;
616 self.parse_raw_function_args_into(&mut args)?;
617 let end = self.cursor.expect_r_paren()?.1.end;
618 let span = Span { start: name.span.start, end };
619 Ok(Function { name: FunctionName::Ident(InterpolableIdent::Literal(name)), args, span })
620 }
621
622 fn parse_raw_function_args_into(
626 &mut self,
627 values: &mut oxc_allocator::Vec<'a, ComponentValue<'a>>,
628 ) -> PResult<()> {
629 let mut pairs: Vec<util::PairedToken> = Vec::with_capacity(1);
630 loop {
631 match &self.cursor.peek()?.token {
632 Token::Eof(..) => break,
633 Token::StrTemplate(..) => {
636 values.push(ComponentValue::InterpolableStr(self.parse()?));
637 continue;
638 }
639 token => {
640 if !util::track_paired_token(token, &mut pairs) {
641 break;
642 }
643 }
644 }
645 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
646 }
647 Ok(())
648 }
649
650 pub(super) fn parse_function_args(
653 &mut self,
654 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
655 let mut values = self.vec_with_capacity(4);
656 loop {
657 match &self.cursor.peek()?.token {
658 Token::RParen(..) | Token::Eof(..) => break,
659 Token::Semicolon(..) => {
660 values.push(self.parse().map(ComponentValue::Delimiter)?);
661 }
662 Token::Exclamation(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
663 values.push(self.parse().map(ComponentValue::ImportantAnnotation)?);
665 }
666 Token::LBrace(..) if self.syntax == Syntax::Less => {
667 values.push(self.parse().map(ComponentValue::LessDetachedRuleset)?);
668 }
669 Token::Dot(..) | Token::NumberSign(..) if self.syntax == Syntax::Less => {
670 if let Ok(mixin) = self.try_parse(Parser::parse_less_anonymous_mixin) {
671 values.push(ComponentValue::LessAnonymousMixin(mixin));
672 } else if let Ok(value) = self.try_parse(ComponentValue::parse) {
673 values.push(value);
674 } else {
675 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
676 }
677 }
678 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
679 self.cursor.bump()?;
680 }
681 Token::Unknown(..) if self.syntax != Syntax::Css => {
685 let span = self.cursor.peek()?.span;
686 return Err(Error { kind: ErrorKind::UnknownToken, span });
687 }
688 _ => {
689 let value = if let Ok(value) = self.try_parse(ComponentValue::parse) {
690 value
691 } else {
692 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
693 continue;
694 };
695 if matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
696 if let Some((_, mut span)) = self.cursor.eat_dot_dot_dot()? {
697 span.start = value.span().start;
698 values.push(ComponentValue::SassArbitraryArgument(
699 SassArbitraryArgument { value: self.alloc(value), span },
700 ));
701 } else if let ComponentValue::SassVariable(sass_var) = value {
702 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
703 let value = self.parse::<ComponentValue>()?;
704 let span =
705 Span { start: sass_var.span.start, end: value.span().end };
706 values.push(ComponentValue::SassKeywordArgument(
707 SassKeywordArgument {
708 name: sass_var,
709 colon_span,
710 value: self.alloc(value),
711 span,
712 },
713 ));
714 } else {
715 values.push(ComponentValue::SassVariable(sass_var));
716 }
717 } else {
718 values.push(value);
719 }
720 } else {
721 values.push(value);
722 }
723 }
724 }
725 }
726 Ok(values)
727 }
728
729 pub(super) fn parse_ratio(&mut self, numerator: Number<'a>) -> PResult<Ratio<'a>> {
732 let (_, solidus_span) = self.cursor.expect_solidus()?;
733 let denominator = self.parse::<Number>()?;
734 if denominator.value <= 0.0 {
735 self.recoverable_errors
736 .push(Error { kind: ErrorKind::InvalidRatioDenominator, span: denominator.span });
737 }
738
739 let span = Span { start: numerator.span.start, end: denominator.span.end };
740 Ok(Ratio { numerator, solidus_span, denominator, span })
741 }
742
743 fn parse_url_modifiers(&mut self) -> PResult<oxc_allocator::Vec<'a, UrlModifier<'a>>> {
748 Ok(match &self.cursor.peek()?.token {
749 Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
750 let mut modifiers = self.vec_with_capacity(1);
751 loop {
752 modifiers.push(self.parse()?);
753 if let Token::RParen(..) = &self.cursor.peek()?.token {
754 break;
755 }
756 }
757 modifiers
758 }
759 _ => self.vec(),
760 })
761 }
762
763 fn parse_src_url(&mut self, name: Ident<'a>) -> PResult<Url<'a>> {
764 self.cursor.expect_l_paren()?;
766 let value = match &self.cursor.peek()?.token {
767 Token::Str(..) | Token::StrTemplate(..) => {
768 Some(UrlValue::Str(self.parse::<InterpolableStr>()?))
769 }
770 _ => None,
771 };
772 let modifiers = self.parse_url_modifiers()?;
773 let end = self.cursor.expect_r_paren()?.1.end;
774 let span = Span { start: name.span.start, end };
775 Ok(Url { name, value, modifiers, span })
776 }
777
778 fn parse_unicode_range(&mut self, prefix_ident: Ident<'a>) -> PResult<UnicodeRange<'a>> {
781 let prefix = prefix_ident.raw.chars().next().unwrap();
782 let (span_start, span_end) = match self.cursor.bump()? {
783 TokenWithSpan { token: Token::Plus(..), span: plus_token_span } => {
784 let start = plus_token_span.start;
785 let mut end = match self.cursor.tokenizer.bump_without_ws_or_comments()? {
786 TokenWithSpan { token: Token::Ident(..) | Token::Question(..), span } => {
787 span.end
788 }
789 TokenWithSpan { token, span } => {
790 return Err(Error {
791 kind: ErrorKind::Unexpected("?", token.symbol()),
792 span,
793 });
794 }
795 };
796 loop {
797 match self.cursor.peek()? {
798 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
799 end = self.cursor.bump()?.span.end;
800 }
801 _ => break,
802 }
803 }
804 (start, end)
805 }
806 TokenWithSpan { token: Token::Dimension(..), span: dimension_token_span } => {
807 let start = dimension_token_span.start;
808 let mut end = dimension_token_span.end;
809 loop {
810 match self.cursor.peek()? {
811 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
812 end = self.cursor.bump()?.span.end;
813 }
814 _ => break,
815 }
816 }
817 (start, end)
818 }
819 TokenWithSpan { token: Token::Number(..), span: number_token_span } => {
820 let start = number_token_span.start;
821 let mut end = number_token_span.end;
822 match &self.cursor.peek()?.token {
823 Token::Question(..) => {
824 end = self.cursor.bump()?.span.end;
825 loop {
826 match self.cursor.peek()? {
827 TokenWithSpan { token: Token::Question(..), span }
828 if span.start == end =>
829 {
830 end = self.cursor.bump()?.span.end;
831 }
832 _ => break,
833 }
834 }
835 }
836 Token::Dimension(..) | Token::Number(..) => {
837 end = self.cursor.bump()?.span.end;
838 }
839 _ => {}
840 }
841 (start, end)
842 }
843 TokenWithSpan { span, .. } => {
844 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
845 }
846 };
847
848 let source = self.source.get(span_start + 1..span_end).ok_or(Error {
849 kind: ErrorKind::InvalidUnicodeRange,
850 span: Span { start: span_start + 1, end: span_end },
851 })?;
852 let span = Span { start: prefix_ident.span.start, end: span_end };
853 let unicode_range = if let Some((left, right)) = source.split_once('-') {
854 if left.len() > 6 || !left.chars().all(|c| c.is_ascii_hexdigit()) {
855 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
856 }
857 if right.len() > 6
858 || !right.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
859 {
860 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
861 }
862 let start = u32::from_str_radix(left, 16)
863 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
864 let end = u32::from_str_radix(&replace_unicode_range_wildcards(right, 'F'), 16)
865 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
866 UnicodeRange { prefix, start, start_raw: left, end, end_raw: Some(right), span }
867 } else {
868 if source.len() > 6
869 || !source.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
870 {
871 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
872 }
873 let start = u32::from_str_radix(&replace_unicode_range_wildcards(source, '0'), 16)
874 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
875 let end = u32::from_str_radix(&replace_unicode_range_wildcards(source, 'F'), 16)
876 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
877 UnicodeRange { prefix, start, start_raw: source, end, end_raw: None, span }
878 };
879 Ok(unicode_range)
884 }
885}
886
887fn replace_unicode_range_wildcards(source: &str, replacement: char) -> String {
888 source.chars().map(|c| if c == '?' { replacement } else { c }).collect()
889}
890
891impl<'a> Parse<'a> for BracketBlock<'a> {
894 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
895 let start = input.cursor.expect_l_bracket()?.1.start;
896 let mut value = input.vec_with_capacity(3);
897 loop {
898 match &input.cursor.peek()?.token {
899 Token::RBracket(..) => break,
900 _ => value.push(input.parse()?),
901 }
902 }
903 let end = input.cursor.expect_r_bracket()?.1.end;
904 Ok(BracketBlock { value, span: Span { start, end } })
905 }
906}
907
908impl<'a> Parse<'a> for ComponentValue<'a> {
913 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
914 match input.syntax {
915 Syntax::Css => input.parse_component_value_atom(),
916 Syntax::Scss | Syntax::Sass => {
917 input.parse_sass_bin_expr(true)
918 }
919 Syntax::Less => input.parse_less_operation(true),
920 }
921 }
922}
923
924impl<'a> Parse<'a> for ComponentValues<'a> {
926 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
928 let first = input.parse::<ComponentValue>()?;
929 let mut span = *first.span();
930
931 let mut values = input.vec_with_capacity(4);
932 values.push(first);
933 loop {
934 match &input.cursor.peek()?.token {
935 Token::Eof(..) => break,
936 Token::Semicolon(..) => {
937 values.push(input.parse().map(ComponentValue::Delimiter)?);
938 }
939 _ => values.push(input.parse()?),
940 }
941 }
942
943 if let Some(value) = values.last() {
944 span.end = value.span().end;
945 }
946 Ok(ComponentValues { values, span })
947 }
948}
949
950impl<'a> Parse<'a> for Delimiter {
952 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
953 use crate::tokenizer::token::*;
954 match input.cursor.bump()? {
955 TokenWithSpan { token: Token::Solidus(..), span } => {
956 Ok(Delimiter { kind: DelimiterKind::Solidus, span })
957 }
958 TokenWithSpan { token: Token::Comma(..), span } => {
959 Ok(Delimiter { kind: DelimiterKind::Comma, span })
960 }
961 TokenWithSpan { token: Token::Semicolon(..), span } => {
962 Ok(Delimiter { kind: DelimiterKind::Semicolon, span })
963 }
964 _ => unreachable!(),
965 }
966 }
967}
968
969impl<'a> Parse<'a> for Dimension<'a> {
971 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
972 let (dimension, span) = input.cursor.expect_dimension()?;
973 input.dimension(dimension, span)
974 }
975}
976
977impl<'a> Parse<'a> for Function<'a> {
981 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
982 let name = input.parse::<FunctionName>()?;
983 match input.cursor.peek()? {
984 TokenWithSpan { token: Token::LParen(..), span } => {
985 util::assert_no_ws_or_comment(name.span(), span)?;
986 match name {
987 FunctionName::Ident(name) => input.parse_function(name),
988 name => {
989 input.cursor.bump()?;
990 let args = input.parse_function_args()?;
991 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
992 let span = Span { start: name.span().start, end };
993 Ok(Function { name, args, span })
994 }
995 }
996 }
997 TokenWithSpan { token, span } => {
998 Err(Error { kind: ErrorKind::Unexpected("(", token.symbol()), span: *span })
999 }
1000 }
1001 }
1002}
1003
1004impl<'a> Parse<'a> for FunctionName<'a> {
1007 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1008 match input.cursor.peek()?.token {
1009 Token::Ident(..) => {
1010 let ident = input.parse::<Ident>()?;
1011 match (&input.cursor.peek()?.token, input.syntax) {
1012 (Token::Dot(..), Syntax::Scss | Syntax::Sass) => {
1013 input.cursor.bump()?;
1014 let member = input.parse::<Ident>()?;
1015 let span = Span { start: ident.span.start, end: member.span.end };
1016 Ok(FunctionName::SassQualifiedName(input.alloc(SassQualifiedName {
1017 module: ident,
1018 member: SassModuleMemberName::Ident(member),
1019 span,
1020 })))
1021 }
1022 _ => Ok(FunctionName::Ident(InterpolableIdent::Literal(ident))),
1023 }
1024 }
1025 Token::Percent(..) if input.syntax == Syntax::Less => {
1026 input.parse().map(FunctionName::LessFormatFunction)
1027 }
1028 Token::Tilde(..) if input.syntax == Syntax::Less => {
1029 input.parse().map(FunctionName::LessListFunction)
1030 }
1031 _ => {
1032 let TokenWithSpan { token, span } = input.cursor.bump()?;
1033 Err(Error { kind: ErrorKind::Unexpected("<ident>", token.symbol()), span })
1034 }
1035 }
1036 }
1037}
1038
1039impl<'a> Parse<'a> for HexColor<'a> {
1041 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1042 let (token, span) = input.cursor.expect_hash()?;
1043 let raw = token.raw;
1044 let value = if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
1045 Ok(HexColor { value, raw, span })
1046 }
1047}
1048
1049impl<'a> Parse<'a> for Ident<'a> {
1051 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1052 let (ident, span) = input.cursor.expect_ident()?;
1053 Ok(input.ident(ident, span))
1054 }
1055}
1056
1057impl<'a> Parse<'a> for InterpolableIdent<'a> {
1060 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1061 if let Token::Placeholder(..) = input.cursor.peek()?.token {
1064 let (placeholder, span) = input.cursor.expect_placeholder()?;
1065 return Ok(InterpolableIdent::Placeholder((placeholder, span).into()));
1066 }
1067 match input.syntax {
1068 Syntax::Css => input.parse().map(InterpolableIdent::Literal),
1069 Syntax::Scss | Syntax::Sass => input.parse_sass_interpolated_ident(),
1070 Syntax::Less => {
1071 if matches!(
1073 input.state.qualified_rule_ctx,
1074 Some(QualifiedRuleContext::DeclarationValue)
1075 ) {
1076 input.parse().map(InterpolableIdent::Literal)
1077 } else {
1078 input.parse_less_interpolated_ident()
1079 }
1080 }
1081 }
1082 }
1083}
1084
1085impl<'a> Parse<'a> for InterpolableStr<'a> {
1087 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1088 match input.cursor.peek()? {
1089 TokenWithSpan { token: Token::Str(..), .. } => {
1090 input.parse().map(InterpolableStr::Literal)
1091 }
1092 TokenWithSpan { token: Token::StrTemplate(..), span } => match input.syntax {
1093 Syntax::Scss | Syntax::Sass => input.parse().map(InterpolableStr::SassInterpolated),
1094 Syntax::Less => input.parse().map(InterpolableStr::LessInterpolated),
1095 Syntax::Css => Err(Error { kind: ErrorKind::UnexpectedTemplateInCss, span: *span }),
1096 },
1097 TokenWithSpan { span, .. } => Err(Error { kind: ErrorKind::ExpectString, span: *span }),
1098 }
1099 }
1100}
1101
1102impl<'a> Parse<'a> for Number<'a> {
1104 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1105 let (number, span) = input.cursor.expect_number()?;
1106 number
1107 .raw
1108 .parse()
1109 .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span })
1110 .map(|value| Self { value, raw: number.raw, span })
1111 }
1112}
1113
1114impl<'a> Parse<'a> for Percentage<'a> {
1116 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1117 let (token, span) = input.cursor.expect_percentage()?;
1118 Ok(Percentage {
1119 value: (token.value, Span { start: span.start, end: span.end - 1 }).try_into()?,
1120 span,
1121 })
1122 }
1123}
1124
1125impl<'a> Parse<'a> for Str<'a> {
1127 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1128 let (str, span) = input.cursor.expect_str()?;
1129 Ok(input.str(str, span))
1130 }
1131}
1132
1133impl<'a> Parse<'a> for Url<'a> {
1138 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1139 let (prefix, prefix_span) = input.cursor.expect_ident()?;
1140 let prefix_name = prefix.name();
1144 let base_name = unvendored(&prefix_name);
1145 if !base_name.eq_ignore_ascii_case("url")
1146 && !base_name.eq_ignore_ascii_case("url-prefix")
1147 && !base_name.eq_ignore_ascii_case("domain")
1148 {
1149 return Err(Error { kind: ErrorKind::ExpectUrl, span: prefix_span });
1150 }
1151 let prefix_start = prefix_span.start;
1152 let name = input.ident(prefix, prefix_span);
1153
1154 match input.cursor.peek()? {
1155 TokenWithSpan { token: Token::LParen(..), span } if prefix_span.end == span.start => {
1156 input.cursor.bump()?;
1157 }
1158 TokenWithSpan { span, .. } => {
1159 return Err(Error { kind: ErrorKind::TryParseError, span: *span });
1163 }
1164 }
1165
1166 if input.cursor.tokenizer.is_start_of_url_string() {
1167 let value = input.parse()?;
1168 let modifiers = input.parse_url_modifiers()?;
1169 let end = input.cursor.expect_r_paren()?.1.end;
1170 let span = Span { start: prefix_start, end };
1171 Ok(Url { name, value: Some(UrlValue::Str(value)), modifiers, span })
1172 } else if let Ok(value) = input.try_parse(UrlRaw::parse) {
1173 let span = Span {
1174 start: prefix_start,
1175 end: value.span.end + 1, };
1177 Ok(Url { name, value: Some(UrlValue::Raw(value)), modifiers: input.vec(), span })
1178 } else {
1179 match input.syntax {
1180 Syntax::Css => {
1181 Err(Error { kind: ErrorKind::InvalidUrl, span: input.cursor.bump()?.span })
1182 }
1183 Syntax::Scss | Syntax::Sass => {
1184 let value = input.parse::<SassInterpolatedUrl>()?;
1185 let span = Span {
1186 start: prefix_start,
1187 end: value.span.end + 1, };
1189 Ok(Url {
1190 name,
1191 value: Some(UrlValue::SassInterpolated(value)),
1192 modifiers: input.vec(),
1193 span,
1194 })
1195 }
1196 Syntax::Less => {
1197 let value = UrlValue::LessEscapedStr(input.parse()?);
1198 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1199 let span = Span { start: prefix_start, end };
1200 Ok(Url { name, value: Some(value), modifiers: input.vec(), span })
1201 }
1202 }
1203 }
1204 }
1205}
1206
1207impl<'a> Parse<'a> for UrlModifier<'a> {
1209 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1210 let ident = input.parse::<InterpolableIdent>()?;
1211 match input.cursor.peek()? {
1212 TokenWithSpan { token: Token::LParen(..), span } if ident.span().end == span.start => {
1213 input.parse_function(ident).map(UrlModifier::Function)
1214 }
1215 _ => Ok(UrlModifier::Ident(ident)),
1216 }
1217 }
1218}
1219
1220impl<'a> Parse<'a> for UrlRaw<'a> {
1222 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1223 let token = input.cursor.tokenizer.scan_url_raw_or_template()?;
1224 match token.url_raw(input.source) {
1225 Some(url) => {
1226 let span = token.span;
1227 let value = if url.escaped {
1228 util::handle_escape_in(url.raw, input.allocator)
1229 } else {
1230 url.raw
1231 };
1232 Ok(UrlRaw { value, raw: url.raw, span })
1233 }
1234 None => Err(Error {
1235 kind: ErrorKind::Unexpected("<url>", token.token.symbol()),
1236 span: token.span,
1237 }),
1238 }
1239 }
1240}