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 if precedence == PRECEDENCE_PLUS
110 && matches!(self.syntax, Syntax::Scss | Syntax::Sass)
111 && matches!(left, ComponentValue::Function(..))
112 {
113 let left_end = left.span().end;
114 let split_op = |raw: &str, start: usize| CalcOperator {
115 kind: if raw.starts_with('+') {
116 CalcOperatorKind::Plus
117 } else {
118 CalcOperatorKind::Minus
119 },
120 span: Span { start, end: start + 1 },
121 };
122 match self.cursor.peek()? {
123 token @ TokenWithSpan { token: Token::Number(..), span }
124 if token
125 .number_raw(self.source)
126 .is_some_and(|raw| raw.starts_with('+') || raw.starts_with('-'))
127 && span.start == left_end =>
128 {
129 let (number, number_span) = self.cursor.expect_number()?;
130 let op = split_op(number.raw, number_span.start);
131 let right = {
132 let span = Span { start: number_span.start + 1, end: number_span.end };
133 let raw = unsafe { number.raw.get_unchecked(1..number.raw.len()) };
134 Number::try_from((crate::token::Number { raw }, span))
135 .map(ComponentValue::Number)?
136 };
137 let right = self.parse_calc_mul_tail(right, allow_modulo)?;
138 let span = Span { start: left.span().start, end: right.span().end };
139 left = ComponentValue::Calc(Calc {
140 left: self.alloc(left),
141 op,
142 right: self.alloc(right),
143 span,
144 });
145 }
146 token @ TokenWithSpan { token: Token::Dimension(..), span }
147 if token
148 .dimension_value_raw(self.source)
149 .is_some_and(|raw| raw.starts_with('+') || raw.starts_with('-'))
150 && span.start == left_end =>
151 {
152 let (dimension, dimension_span) = self.cursor.expect_dimension()?;
153 let op = split_op(dimension.value.raw, dimension_span.start);
154 let right = self
155 .dimension(
156 crate::token::Dimension {
157 value: crate::token::Number {
158 raw: unsafe {
159 dimension
160 .value
161 .raw
162 .get_unchecked(1..dimension.value.raw.len())
163 },
164 },
165 unit: dimension.unit,
166 },
167 Span { start: dimension_span.start + 1, end: dimension_span.end },
168 )
169 .map(ComponentValue::Dimension)?;
170 let right = self.parse_calc_mul_tail(right, allow_modulo)?;
171 let span = Span { start: left.span().start, end: right.span().end };
172 left = ComponentValue::Calc(Calc {
173 left: self.alloc(left),
174 op,
175 right: self.alloc(right),
176 span,
177 });
178 }
179 _ => {}
180 }
181 }
182
183 loop {
184 let operator = match &self.cursor.peek()?.token {
185 Token::Asterisk(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
186 kind: CalcOperatorKind::Multiply,
187 span: self.cursor.bump()?.span,
188 },
189 Token::Solidus(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
190 kind: CalcOperatorKind::Division,
191 span: self.cursor.bump()?.span,
192 },
193 Token::Percent(..) if precedence == PRECEDENCE_MULTIPLY && allow_modulo => {
197 CalcOperator { kind: CalcOperatorKind::Modulo, span: self.cursor.bump()?.span }
198 }
199 Token::Plus(..) if precedence == PRECEDENCE_PLUS => {
200 CalcOperator { kind: CalcOperatorKind::Plus, span: self.cursor.bump()?.span }
201 }
202 Token::Minus(..) if precedence == PRECEDENCE_PLUS => {
203 CalcOperator { kind: CalcOperatorKind::Minus, span: self.cursor.bump()?.span }
204 }
205 _ => break,
206 };
207
208 let right = self.parse_calc_expr_recursively(precedence + 1, allow_modulo)?;
209 let span = Span { start: left.span().start, end: right.span().end };
210 left = ComponentValue::Calc(Calc {
211 left: self.alloc(left),
212 op: operator,
213 right: self.alloc(right),
214 span,
215 });
216 }
217
218 Ok(left)
219 }
220
221 fn parse_calc_mul_tail(
225 &mut self,
226 mut left: ComponentValue<'a>,
227 allow_modulo: bool,
228 ) -> PResult<ComponentValue<'a>> {
229 loop {
230 let kind = match &self.cursor.peek()?.token {
231 Token::Asterisk(..) => CalcOperatorKind::Multiply,
232 Token::Solidus(..) => CalcOperatorKind::Division,
233 Token::Percent(..) if allow_modulo => CalcOperatorKind::Modulo,
234 _ => return Ok(left),
235 };
236 let op = CalcOperator { kind, span: self.cursor.bump()?.span };
237 let right = self.parse_calc_expr_recursively(PRECEDENCE_MULTIPLY + 1, allow_modulo)?;
238 let span = Span { start: left.span().start, end: right.span().end };
239 left = ComponentValue::Calc(Calc {
240 left: self.alloc(left),
241 op,
242 right: self.alloc(right),
243 span,
244 });
245 }
246 }
247
248 pub(super) fn parse_component_value_atom(&mut self) -> PResult<ComponentValue<'a>> {
252 let token_with_span = self.cursor.peek()?;
253 match &token_with_span.token {
254 Token::Ident(..) => {
255 let ident = token_with_span.ident(self.source).unwrap();
256 if unvendored(&ident.name()).eq_ignore_ascii_case("url") {
257 match self.try_parse(Url::parse) {
258 Ok(url) => return Ok(ComponentValue::Url(self.alloc(url))),
259 Err(Error { kind: ErrorKind::TryParseError, .. }) => {}
260 Err(error) => {
261 let (function_name, function_name_span) = self.cursor.expect_ident()?;
268 let function_name = self.ident(function_name, function_name_span);
269 return self
270 .parse_function_typed_or_raw(function_name)
271 .map(ComponentValue::Function)
272 .map_err(|_| error);
273 }
274 }
275 }
276 let ident = self.parse::<InterpolableIdent>()?;
277 let ident_end = ident.span().end;
278 match self.cursor.peek()? {
279 TokenWithSpan { token: Token::LParen(..), span } if span.start == ident_end => {
280 return match ident {
281 InterpolableIdent::Literal(ident)
282 if ident.name.eq_ignore_ascii_case("src") =>
283 {
284 self.parse_src_url(ident)
285 .map(|url| ComponentValue::Url(self.alloc(url)))
286 }
287 InterpolableIdent::Literal(ident)
288 if unvendored(ident.name).eq_ignore_ascii_case("expression") =>
289 {
290 self.parse_raw_function(InterpolableIdent::Literal(ident))
293 .map(ComponentValue::Function)
294 }
295 InterpolableIdent::Literal(ident)
296 if is_special_typed_or_raw_function(ident.name) =>
297 {
298 self.parse_function_typed_or_raw(ident)
299 .map(ComponentValue::Function)
300 }
301 ident => self.parse_function(ident).map(ComponentValue::Function),
302 };
303 }
304 TokenWithSpan { token: Token::Colon(..), span }
309 if span.start == ident_end
310 && matches!(
311 &ident,
312 InterpolableIdent::Literal(id)
313 if unvendored(id.name).eq_ignore_ascii_case("progid")
314 ) =>
315 {
316 if let InterpolableIdent::Literal(ident) = ident {
317 return self.parse_progid_function(ident).map(ComponentValue::Function);
318 }
319 unreachable!("guard matched a literal ident");
320 }
321 TokenWithSpan { token: Token::Dot(..), span }
322 if matches!(self.syntax, Syntax::Scss | Syntax::Sass)
323 && span.start == ident_end =>
324 {
325 if let InterpolableIdent::Literal(module) = &ident {
326 let module =
327 Ident { name: module.name, raw: module.raw, span: module.span };
328 let qualified = self.try_parse(|parser| {
331 let name = parser.parse_sass_qualified_name(module)?;
332 if let SassQualifiedName {
333 member: SassModuleMemberName::Ident(..),
334 ..
335 } = name
336 {
337 let (_, lparen_span) = parser.cursor.expect_l_paren()?;
338 util::assert_no_ws_or_comment(&name.span, &lparen_span)?;
339 let args = parser.parse_function_args()?;
340 let (_, Span { end, .. }) = parser.cursor.expect_r_paren()?;
341 let span = Span { start: name.span.start, end };
342 Ok(ComponentValue::Function(Function {
343 name: FunctionName::SassQualifiedName(parser.alloc(name)),
344 args,
345 span,
346 }))
347 } else {
348 Ok(ComponentValue::SassQualifiedName(parser.alloc(name)))
349 }
350 });
351 return match qualified {
352 Ok(value) => Ok(value),
353 Err(_) => Ok(ComponentValue::InterpolableIdent(ident)),
361 };
362 }
363 }
364 _ => {}
365 }
366 match ident {
367 InterpolableIdent::Literal(ident) if ident.raw.eq_ignore_ascii_case("u") => {
368 match self.cursor.peek()? {
369 TokenWithSpan { token: Token::Plus(..), span }
370 if span.start == ident_end =>
371 {
372 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
373 }
374 token @ TokenWithSpan { token: Token::Number(..), span }
375 if token
376 .number_raw(self.source)
377 .is_some_and(|raw| raw.starts_with('+'))
378 && span.start == ident_end =>
379 {
380 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
381 }
382 token @ TokenWithSpan { token: Token::Dimension(..), span }
383 if token
384 .dimension_value_raw(self.source)
385 .is_some_and(|raw| raw.starts_with('+'))
386 && span.start == ident_end =>
387 {
388 self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
389 }
390 _ => Ok(ComponentValue::InterpolableIdent(InterpolableIdent::Literal(
391 ident,
392 ))),
393 }
394 }
395 _ => Ok(ComponentValue::InterpolableIdent(ident)),
396 }
397 }
398 Token::Solidus(..) | Token::Comma(..) => self.parse().map(ComponentValue::Delimiter),
399 Token::Number(..) => self.parse().map(ComponentValue::Number),
400 Token::Dimension(..) => self.parse().map(ComponentValue::Dimension),
401 Token::Percentage(..) => self.parse().map(ComponentValue::Percentage),
402 Token::Hash(..) => {
403 if self.syntax == Syntax::Less {
404 self.parse_maybe_hex_color_or_less_mixin_call()
405 } else {
406 self.parse().map(ComponentValue::HexColor)
407 }
408 }
409 Token::Str(..) => {
410 self.parse().map(InterpolableStr::Literal).map(ComponentValue::InterpolableStr)
411 }
412 Token::LBracket(..) => self.parse().map(ComponentValue::BracketBlock),
413 Token::DollarVar(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
414 self.parse().map(ComponentValue::SassVariable)
415 }
416 Token::DollarVar(..)
417 if self.syntax == Syntax::Css && self.options.allow_postcss_simple_vars =>
418 {
419 self.parse().map(ComponentValue::PostcssSimpleVar)
420 }
421 Token::LParen(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
422 match self.try_parse(SassParenthesizedExpression::parse) {
423 Ok(expr) => Ok(ComponentValue::SassParenthesizedExpression(expr)),
424 Err(err) => self.parse().map(ComponentValue::SassMap).map_err(|_| err),
425 }
426 }
427 Token::HashLBrace(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
428 let ident = self.parse_sass_interpolated_ident()?;
429 match self.cursor.peek()? {
430 TokenWithSpan { token: Token::LParen(..), span }
431 if span.start == ident.span().end =>
432 {
433 self.parse_function(ident).map(ComponentValue::Function)
434 }
435 _ => Ok(ComponentValue::InterpolableIdent(ident)),
436 }
437 }
438 Token::StrTemplate(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => self
439 .parse()
440 .map(InterpolableStr::SassInterpolated)
441 .map(ComponentValue::InterpolableStr),
442 Token::Ampersand(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
443 self.parse().map(ComponentValue::SassParentSelector)
444 }
445 Token::LBrace(..)
446 if self.syntax == Syntax::Scss
447 && matches!(
448 self.state.qualified_rule_ctx,
449 Some(QualifiedRuleContext::DeclarationValue)
450 ) =>
451 {
452 self.parse().map(ComponentValue::SassNestingDeclaration)
453 }
454 Token::Indent(..)
455 if self.syntax == Syntax::Sass
456 && matches!(
457 self.state.qualified_rule_ctx,
458 Some(QualifiedRuleContext::DeclarationValue)
459 ) =>
460 {
461 self.parse().map(ComponentValue::SassNestingDeclaration)
462 }
463 Token::AtKeyword(..) if self.syntax == Syntax::Less => {
464 self.parse_less_maybe_variable_or_with_lookups()
465 }
466 Token::Dot(..) if self.syntax == Syntax::Less => {
467 self.parse_less_maybe_mixin_call_or_with_lookups()
468 }
469 Token::Dot(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
475 let dot = self.cursor.bump()?;
476 match self.cursor.peek()? {
477 TokenWithSpan { token: Token::Ident(..), span }
478 if span.start == dot.span.end =>
479 {
480 Ok(ComponentValue::TokenWithSpan(dot))
481 }
482 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: dot.span }),
483 }
484 }
485 Token::StrTemplate(..) if self.syntax == Syntax::Less => self
486 .parse()
487 .map(InterpolableStr::LessInterpolated)
488 .map(ComponentValue::InterpolableStr),
489 Token::At(..) if self.syntax == Syntax::Less => {
490 self.parse().map(ComponentValue::LessVariableVariable)
491 }
492 Token::DollarVar(..) if self.syntax == Syntax::Less => {
493 self.parse().map(ComponentValue::LessPropertyVariable)
494 }
495 Token::Tilde(..) if self.syntax == Syntax::Less => {
496 if let Ok(list_function_call) = self.try_parse(Function::parse) {
497 Ok(ComponentValue::Function(list_function_call))
498 } else if let Ok(less_escaped_str) = self.try_parse(LessEscapedStr::parse) {
499 Ok(ComponentValue::LessEscapedStr(less_escaped_str))
500 } else {
501 self.parse().map(ComponentValue::LessJavaScriptSnippet)
502 }
503 }
504 Token::Percent(..) if self.syntax == Syntax::Less => self
505 .try_parse(Function::parse)
506 .map(ComponentValue::Function)
507 .or_else(|_| self.parse().map(ComponentValue::LessPercentKeyword)),
508 Token::BacktickCode(..) if self.syntax == Syntax::Less => {
509 self.parse().map(ComponentValue::LessJavaScriptSnippet)
510 }
511 Token::Placeholder(..) => {
512 let (placeholder, span) = self.cursor.expect_placeholder()?;
513 Ok(ComponentValue::Placeholder((placeholder, span).into()))
514 }
515 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: token_with_span.span }),
516 }
517 }
518
519 pub(super) fn parse_dashed_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
521 let ident = self.parse()?;
522 if let InterpolableIdent::Literal(ident) = &ident
523 && !ident.name.starts_with("--")
524 {
525 self.recoverable_errors
526 .push(Error { kind: ErrorKind::ExpectDashedIdent, span: ident.span });
527 }
528 Ok(ident)
529 }
530
531 pub(super) fn parse_function(&mut self, name: InterpolableIdent<'a>) -> PResult<Function<'a>> {
533 self.cursor.expect_l_paren()?;
534 let args = if let Token::RParen(..) = &self.cursor.peek()?.token {
535 self.vec()
536 } else {
537 match &name {
538 InterpolableIdent::Literal(ident)
539 if ident.name.eq_ignore_ascii_case("calc")
540 || ident.name.eq_ignore_ascii_case("-webkit-calc")
541 || ident.name.eq_ignore_ascii_case("-moz-calc")
542 || ident.name.eq_ignore_ascii_case("min")
543 || ident.name.eq_ignore_ascii_case("max")
544 || ident.name.eq_ignore_ascii_case("clamp")
545 || ident.name.eq_ignore_ascii_case("sin")
546 || ident.name.eq_ignore_ascii_case("cos")
547 || ident.name.eq_ignore_ascii_case("tan")
548 || ident.name.eq_ignore_ascii_case("asin")
549 || ident.name.eq_ignore_ascii_case("acos")
550 || ident.name.eq_ignore_ascii_case("atan")
551 || ident.name.eq_ignore_ascii_case("sqrt")
552 || ident.name.eq_ignore_ascii_case("exp")
553 || ident.name.eq_ignore_ascii_case("abs")
554 || ident.name.eq_ignore_ascii_case("sign")
555 || ident.name.eq_ignore_ascii_case("hypot")
556 || ident.name.eq_ignore_ascii_case("round")
557 || ident.name.eq_ignore_ascii_case("mod")
558 || ident.name.eq_ignore_ascii_case("rem")
559 || ident.name.eq_ignore_ascii_case("atan2")
560 || ident.name.eq_ignore_ascii_case("pow")
561 || ident.name.eq_ignore_ascii_case("log") =>
562 {
563 let allow_modulo = matches!(self.syntax, Syntax::Scss | Syntax::Sass)
566 && (ident.name.eq_ignore_ascii_case("min")
567 || ident.name.eq_ignore_ascii_case("max"));
568 if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
575 self.parse_calc_args(allow_modulo)?
576 } else {
577 let typed = self.try_parse(|p| {
578 let values = p.parse_calc_args(allow_modulo)?;
579 let TokenWithSpan { token, span } = p.cursor.peek()?;
580 if matches!(token, Token::RParen(..)) {
581 Ok(values)
582 } else {
583 Err(Error {
587 kind: ErrorKind::Unexpected(")", token.symbol()),
588 span: *span,
589 })
590 }
591 });
592 match typed {
593 Ok(values) => values,
594 Err(error) => {
595 let (args, comma_spans) = self.parse_sass_invocation_args()?;
596 if !args.iter().any(|arg| {
601 matches!(arg, ComponentValue::SassKeywordArgument(..))
602 }) {
603 return Err(error);
604 }
605 let mut values = self.vec_with_capacity(args.len() * 2);
606 let mut comma_spans = comma_spans.into_iter();
607 for (i, arg) in args.into_iter().enumerate() {
608 if i > 0
609 && let Some(span) = comma_spans.next()
610 {
611 values.push(ComponentValue::Delimiter(Delimiter {
612 kind: DelimiterKind::Comma,
613 span,
614 }));
615 }
616 values.push(arg);
617 }
618 values
619 }
620 }
621 }
622 }
623 InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("element") => {
624 let id_selector = self.parse().map(ComponentValue::IdSelector)?;
625 self.vec1(id_selector)
626 }
627 InterpolableIdent::Literal(Ident { raw: "boolean" | "if", .. })
628 if self.syntax == Syntax::Less =>
629 {
630 let less_condition = self.parse_less_condition(false)?;
631 let condition = ComponentValue::LessCondition(self.alloc(less_condition));
632 let mut args = self.parse_function_args()?;
633 args.insert(0, condition);
634 args
635 }
636 _ => self.parse_function_args()?,
637 }
638 };
639 let end = self.cursor.expect_r_paren()?.1.end;
640 let span = Span { start: name.span().start, end };
641 Ok(Function { name: FunctionName::Ident(name), args, span })
642 }
643
644 fn parse_calc_args(
648 &mut self,
649 allow_modulo: bool,
650 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
651 let mut values = self.vec_with_capacity(1);
652 loop {
653 match self.cursor.peek()? {
654 TokenWithSpan { token: Token::RParen(..), .. } => break,
655 TokenWithSpan { token: Token::Comma(..), .. } => {
656 values.push(ComponentValue::Delimiter(self.parse()?));
657 }
658 TokenWithSpan { token: Token::DotDotDot(..), .. }
661 if allow_modulo
662 && matches!(self.syntax, Syntax::Scss | Syntax::Sass)
663 && !values.is_empty()
664 && !values
665 .iter()
666 .any(|v| matches!(v, ComponentValue::SassArbitraryArgument(..))) =>
667 {
668 let TokenWithSpan { span: Span { end, .. }, .. } = self.cursor.bump()?;
669 let value = values.pop().unwrap();
670 let span = Span { start: value.span().start, end };
671 values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
672 value: self.alloc(value),
673 span,
674 }));
675 }
676 _ => values.push(self.parse_calc_expr(allow_modulo)?),
677 }
678 }
679 Ok(values)
680 }
681
682 pub(super) fn parse_function_typed_or_raw(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
686 let name_copy = Ident { name: name.name, raw: name.raw, span: name.span };
687 match self.try_parse(|p| p.parse_function(InterpolableIdent::Literal(name))) {
688 Ok(function) => Ok(function),
689 Err(_) => self.parse_raw_function(InterpolableIdent::Literal(name_copy)),
690 }
691 }
692
693 pub(in crate::parser) fn parse_raw_function(
697 &mut self,
698 name: InterpolableIdent<'a>,
699 ) -> PResult<Function<'a>> {
700 self.cursor.expect_l_paren()?;
701 let mut args = self.vec_with_capacity(4);
702 self.parse_raw_function_args_into(&mut args)?;
703 let end = self.cursor.expect_r_paren()?.1.end;
704 let span = Span { start: name.span().start, end };
705 Ok(Function { name: FunctionName::Ident(name), args, span })
706 }
707
708 fn parse_progid_function(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
712 let mut args = self.vec_with_capacity(4);
713 loop {
714 match &self.cursor.peek()?.token {
715 Token::LParen(..)
716 | Token::Semicolon(..)
717 | Token::RBrace(..)
718 | Token::RParen(..)
719 | Token::Eof(..)
720 | Token::Indent(..)
721 | Token::Dedent(..)
722 | Token::Linebreak(..) => break,
723 _ => args.push(ComponentValue::TokenWithSpan(self.cursor.bump()?)),
724 }
725 }
726 self.cursor.expect_l_paren()?;
727 self.parse_raw_function_args_into(&mut args)?;
728 let end = self.cursor.expect_r_paren()?.1.end;
729 let span = Span { start: name.span.start, end };
730 Ok(Function { name: FunctionName::Ident(InterpolableIdent::Literal(name)), args, span })
731 }
732
733 fn parse_raw_function_args_into(
737 &mut self,
738 values: &mut oxc_allocator::Vec<'a, ComponentValue<'a>>,
739 ) -> PResult<()> {
740 let mut pairs: Vec<util::PairedToken> = Vec::with_capacity(1);
741 loop {
742 match &self.cursor.peek()?.token {
743 Token::Eof(..) => break,
744 Token::StrTemplate(..) => {
747 values.push(ComponentValue::InterpolableStr(self.parse()?));
748 continue;
749 }
750 token => {
751 if !util::track_paired_token(token, &mut pairs) {
752 break;
753 }
754 }
755 }
756 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
757 }
758 Ok(())
759 }
760
761 pub(super) fn parse_function_args(
764 &mut self,
765 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
766 let mut values = self.vec_with_capacity(4);
767 loop {
768 match &self.cursor.peek()?.token {
769 Token::RParen(..) | Token::Eof(..) => break,
770 Token::Semicolon(..) => {
771 values.push(self.parse().map(ComponentValue::Delimiter)?);
772 }
773 Token::Exclamation(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
774 values.push(self.parse().map(ComponentValue::ImportantAnnotation)?);
776 }
777 Token::LBrace(..) if self.syntax == Syntax::Less => {
778 values.push(self.parse().map(ComponentValue::LessDetachedRuleset)?);
779 }
780 Token::Dot(..) | Token::NumberSign(..) if self.syntax == Syntax::Less => {
781 if let Ok(mixin) = self.try_parse(Parser::parse_less_anonymous_mixin) {
782 values.push(ComponentValue::LessAnonymousMixin(mixin));
783 } else if let Ok(value) = self.try_parse(ComponentValue::parse) {
784 values.push(value);
785 } else {
786 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
787 }
788 }
789 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
790 self.cursor.bump()?;
791 }
792 Token::Unknown(..) if self.syntax != Syntax::Css => {
796 let span = self.cursor.peek()?.span;
797 return Err(Error { kind: ErrorKind::UnknownToken, span });
798 }
799 _ => {
800 let value = if let Ok(value) = self.try_parse(ComponentValue::parse) {
801 value
802 } else {
803 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
804 continue;
805 };
806 if matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
807 if let Some((_, mut span)) = self.cursor.eat_dot_dot_dot()? {
808 span.start = value.span().start;
809 values.push(ComponentValue::SassArbitraryArgument(
810 SassArbitraryArgument { value: self.alloc(value), span },
811 ));
812 } else if let ComponentValue::SassVariable(sass_var) = value {
813 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
814 let value = self.parse::<ComponentValue>()?;
815 let span =
816 Span { start: sass_var.span.start, end: value.span().end };
817 values.push(ComponentValue::SassKeywordArgument(
818 SassKeywordArgument {
819 name: sass_var,
820 colon_span,
821 value: self.alloc(value),
822 span,
823 },
824 ));
825 } else {
826 values.push(ComponentValue::SassVariable(sass_var));
827 }
828 } else {
829 values.push(value);
830 }
831 } else {
832 values.push(value);
833 }
834 }
835 }
836 }
837 Ok(values)
838 }
839
840 pub(super) fn parse_ratio(&mut self, numerator: Number<'a>) -> PResult<Ratio<'a>> {
843 let (_, solidus_span) = self.cursor.expect_solidus()?;
844 let denominator = self.parse::<Number>()?;
845 if denominator.value <= 0.0 {
846 self.recoverable_errors
847 .push(Error { kind: ErrorKind::InvalidRatioDenominator, span: denominator.span });
848 }
849
850 let span = Span { start: numerator.span.start, end: denominator.span.end };
851 Ok(Ratio { numerator, solidus_span, denominator, span })
852 }
853
854 fn parse_url_modifiers(&mut self) -> PResult<oxc_allocator::Vec<'a, UrlModifier<'a>>> {
859 Ok(match &self.cursor.peek()?.token {
860 Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
861 let mut modifiers = self.vec_with_capacity(1);
862 loop {
863 modifiers.push(self.parse()?);
864 if let Token::RParen(..) = &self.cursor.peek()?.token {
865 break;
866 }
867 }
868 modifiers
869 }
870 _ => self.vec(),
871 })
872 }
873
874 fn parse_src_url(&mut self, name: Ident<'a>) -> PResult<Url<'a>> {
875 self.cursor.expect_l_paren()?;
877 let value = match &self.cursor.peek()?.token {
878 Token::Str(..) | Token::StrTemplate(..) => {
879 Some(UrlValue::Str(self.parse::<InterpolableStr>()?))
880 }
881 _ => None,
882 };
883 let modifiers = self.parse_url_modifiers()?;
884 let end = self.cursor.expect_r_paren()?.1.end;
885 let span = Span { start: name.span.start, end };
886 Ok(Url { name, value, modifiers, span })
887 }
888
889 fn parse_unicode_range(&mut self, prefix_ident: Ident<'a>) -> PResult<UnicodeRange<'a>> {
892 let prefix = prefix_ident.raw.chars().next().unwrap();
893 let (span_start, span_end) = match self.cursor.bump()? {
894 TokenWithSpan { token: Token::Plus(..), span: plus_token_span } => {
895 let start = plus_token_span.start;
896 let mut end = match self.cursor.tokenizer.bump_without_ws_or_comments()? {
897 TokenWithSpan { token: Token::Ident(..) | Token::Question(..), span } => {
898 span.end
899 }
900 TokenWithSpan { token, span } => {
901 return Err(Error {
902 kind: ErrorKind::Unexpected("?", token.symbol()),
903 span,
904 });
905 }
906 };
907 loop {
908 match self.cursor.peek()? {
909 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
910 end = self.cursor.bump()?.span.end;
911 }
912 _ => break,
913 }
914 }
915 (start, end)
916 }
917 TokenWithSpan { token: Token::Dimension(..), span: dimension_token_span } => {
918 let start = dimension_token_span.start;
919 let mut end = dimension_token_span.end;
920 loop {
921 match self.cursor.peek()? {
922 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
923 end = self.cursor.bump()?.span.end;
924 }
925 _ => break,
926 }
927 }
928 (start, end)
929 }
930 TokenWithSpan { token: Token::Number(..), span: number_token_span } => {
931 let start = number_token_span.start;
932 let mut end = number_token_span.end;
933 match &self.cursor.peek()?.token {
934 Token::Question(..) => {
935 end = self.cursor.bump()?.span.end;
936 loop {
937 match self.cursor.peek()? {
938 TokenWithSpan { token: Token::Question(..), span }
939 if span.start == end =>
940 {
941 end = self.cursor.bump()?.span.end;
942 }
943 _ => break,
944 }
945 }
946 }
947 Token::Dimension(..) | Token::Number(..) => {
948 end = self.cursor.bump()?.span.end;
949 }
950 _ => {}
951 }
952 (start, end)
953 }
954 TokenWithSpan { span, .. } => {
955 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
956 }
957 };
958
959 let source = self.source.get(span_start + 1..span_end).ok_or(Error {
960 kind: ErrorKind::InvalidUnicodeRange,
961 span: Span { start: span_start + 1, end: span_end },
962 })?;
963 let span = Span { start: prefix_ident.span.start, end: span_end };
964 let unicode_range = if let Some((left, right)) = source.split_once('-') {
965 if left.len() > 6 || !left.chars().all(|c| c.is_ascii_hexdigit()) {
966 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
967 }
968 if right.len() > 6
969 || !right.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
970 {
971 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
972 }
973 let start = u32::from_str_radix(left, 16)
974 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
975 let end = u32::from_str_radix(&replace_unicode_range_wildcards(right, 'F'), 16)
976 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
977 UnicodeRange { prefix, start, start_raw: left, end, end_raw: Some(right), span }
978 } else {
979 if source.len() > 6
980 || !source.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
981 {
982 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
983 }
984 let start = u32::from_str_radix(&replace_unicode_range_wildcards(source, '0'), 16)
985 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
986 let end = u32::from_str_radix(&replace_unicode_range_wildcards(source, 'F'), 16)
987 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
988 UnicodeRange { prefix, start, start_raw: source, end, end_raw: None, span }
989 };
990 Ok(unicode_range)
995 }
996}
997
998fn replace_unicode_range_wildcards(source: &str, replacement: char) -> String {
999 source.chars().map(|c| if c == '?' { replacement } else { c }).collect()
1000}
1001
1002impl<'a> Parse<'a> for BracketBlock<'a> {
1005 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1006 let start = input.cursor.expect_l_bracket()?.1.start;
1007 let mut value = input.vec_with_capacity(3);
1008 loop {
1009 match &input.cursor.peek()?.token {
1010 Token::RBracket(..) => break,
1011 _ => value.push(input.parse()?),
1012 }
1013 }
1014 let end = input.cursor.expect_r_bracket()?.1.end;
1015 Ok(BracketBlock { value, span: Span { start, end } })
1016 }
1017}
1018
1019impl<'a> Parse<'a> for ComponentValue<'a> {
1024 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1025 match input.syntax {
1026 Syntax::Css => input.parse_component_value_atom(),
1027 Syntax::Scss | Syntax::Sass => {
1028 input.parse_sass_bin_expr(true)
1029 }
1030 Syntax::Less => input.parse_less_operation(true),
1031 }
1032 }
1033}
1034
1035impl<'a> Parse<'a> for ComponentValues<'a> {
1037 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1039 let first = input.parse::<ComponentValue>()?;
1040 let mut span = *first.span();
1041
1042 let mut values = input.vec_with_capacity(4);
1043 values.push(first);
1044 loop {
1045 match &input.cursor.peek()?.token {
1046 Token::Eof(..) => break,
1047 Token::Semicolon(..) => {
1048 values.push(input.parse().map(ComponentValue::Delimiter)?);
1049 }
1050 _ => values.push(input.parse()?),
1051 }
1052 }
1053
1054 if let Some(value) = values.last() {
1055 span.end = value.span().end;
1056 }
1057 Ok(ComponentValues { values, span })
1058 }
1059}
1060
1061impl<'a> Parse<'a> for Delimiter {
1063 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1064 use crate::tokenizer::token::*;
1065 match input.cursor.bump()? {
1066 TokenWithSpan { token: Token::Solidus(..), span } => {
1067 Ok(Delimiter { kind: DelimiterKind::Solidus, span })
1068 }
1069 TokenWithSpan { token: Token::Comma(..), span } => {
1070 Ok(Delimiter { kind: DelimiterKind::Comma, span })
1071 }
1072 TokenWithSpan { token: Token::Semicolon(..), span } => {
1073 Ok(Delimiter { kind: DelimiterKind::Semicolon, span })
1074 }
1075 _ => unreachable!(),
1076 }
1077 }
1078}
1079
1080impl<'a> Parse<'a> for Dimension<'a> {
1082 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1083 let (dimension, span) = input.cursor.expect_dimension()?;
1084 input.dimension(dimension, span)
1085 }
1086}
1087
1088impl<'a> Parse<'a> for Function<'a> {
1092 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1093 let name = input.parse::<FunctionName>()?;
1094 match input.cursor.peek()? {
1095 TokenWithSpan { token: Token::LParen(..), span } => {
1096 util::assert_no_ws_or_comment(name.span(), span)?;
1097 match name {
1098 FunctionName::Ident(name) => input.parse_function(name),
1099 name => {
1100 input.cursor.bump()?;
1101 let args = input.parse_function_args()?;
1102 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1103 let span = Span { start: name.span().start, end };
1104 Ok(Function { name, args, span })
1105 }
1106 }
1107 }
1108 TokenWithSpan { token, span } => {
1109 Err(Error { kind: ErrorKind::Unexpected("(", token.symbol()), span: *span })
1110 }
1111 }
1112 }
1113}
1114
1115impl<'a> Parse<'a> for FunctionName<'a> {
1118 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1119 match input.cursor.peek()?.token {
1120 Token::Ident(..) => {
1121 let ident = input.parse::<Ident>()?;
1122 match (&input.cursor.peek()?.token, input.syntax) {
1123 (Token::Dot(..), Syntax::Scss | Syntax::Sass) => {
1124 input.cursor.bump()?;
1125 let member = input.parse::<Ident>()?;
1126 let span = Span { start: ident.span.start, end: member.span.end };
1127 Ok(FunctionName::SassQualifiedName(input.alloc(SassQualifiedName {
1128 module: ident,
1129 member: SassModuleMemberName::Ident(member),
1130 span,
1131 })))
1132 }
1133 _ => Ok(FunctionName::Ident(InterpolableIdent::Literal(ident))),
1134 }
1135 }
1136 Token::Percent(..) if input.syntax == Syntax::Less => {
1137 input.parse().map(FunctionName::LessFormatFunction)
1138 }
1139 Token::Tilde(..) if input.syntax == Syntax::Less => {
1140 input.parse().map(FunctionName::LessListFunction)
1141 }
1142 _ => {
1143 let TokenWithSpan { token, span } = input.cursor.bump()?;
1144 Err(Error { kind: ErrorKind::Unexpected("<ident>", token.symbol()), span })
1145 }
1146 }
1147 }
1148}
1149
1150impl<'a> Parse<'a> for HexColor<'a> {
1152 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1153 let (token, span) = input.cursor.expect_hash()?;
1154 let raw = token.raw;
1155 let value = if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
1156 Ok(HexColor { value, raw, span })
1157 }
1158}
1159
1160impl<'a> Parse<'a> for Ident<'a> {
1162 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1163 let (ident, span) = input.cursor.expect_ident()?;
1164 Ok(input.ident(ident, span))
1165 }
1166}
1167
1168impl<'a> Parse<'a> for InterpolableIdent<'a> {
1171 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1172 if let Token::Placeholder(..) = input.cursor.peek()?.token {
1175 let (placeholder, span) = input.cursor.expect_placeholder()?;
1176 return Ok(InterpolableIdent::Placeholder((placeholder, span).into()));
1177 }
1178 match input.syntax {
1179 Syntax::Css => input.parse().map(InterpolableIdent::Literal),
1180 Syntax::Scss | Syntax::Sass => input.parse_sass_interpolated_ident(),
1181 Syntax::Less => {
1182 if matches!(
1184 input.state.qualified_rule_ctx,
1185 Some(QualifiedRuleContext::DeclarationValue)
1186 ) {
1187 input.parse().map(InterpolableIdent::Literal)
1188 } else {
1189 input.parse_less_interpolated_ident()
1190 }
1191 }
1192 }
1193 }
1194}
1195
1196impl<'a> Parse<'a> for InterpolableStr<'a> {
1198 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1199 match input.cursor.peek()? {
1200 TokenWithSpan { token: Token::Str(..), .. } => {
1201 input.parse().map(InterpolableStr::Literal)
1202 }
1203 TokenWithSpan { token: Token::StrTemplate(..), span } => match input.syntax {
1204 Syntax::Scss | Syntax::Sass => input.parse().map(InterpolableStr::SassInterpolated),
1205 Syntax::Less => input.parse().map(InterpolableStr::LessInterpolated),
1206 Syntax::Css => Err(Error { kind: ErrorKind::UnexpectedTemplateInCss, span: *span }),
1207 },
1208 TokenWithSpan { span, .. } => Err(Error { kind: ErrorKind::ExpectString, span: *span }),
1209 }
1210 }
1211}
1212
1213impl<'a> Parse<'a> for Number<'a> {
1215 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1216 let (number, span) = input.cursor.expect_number()?;
1217 number
1218 .raw
1219 .parse()
1220 .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span })
1221 .map(|value| Self { value, raw: number.raw, span })
1222 }
1223}
1224
1225impl<'a> Parse<'a> for Percentage<'a> {
1227 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1228 let (token, span) = input.cursor.expect_percentage()?;
1229 Ok(Percentage {
1230 value: (token.value, Span { start: span.start, end: span.end - 1 }).try_into()?,
1231 span,
1232 })
1233 }
1234}
1235
1236impl<'a> Parse<'a> for Str<'a> {
1238 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1239 let (str, span) = input.cursor.expect_str()?;
1240 Ok(input.str(str, span))
1241 }
1242}
1243
1244impl<'a> Parse<'a> for Url<'a> {
1249 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1250 let (prefix, prefix_span) = input.cursor.expect_ident()?;
1251 let prefix_name = prefix.name();
1255 let base_name = unvendored(&prefix_name);
1256 if !base_name.eq_ignore_ascii_case("url")
1257 && !base_name.eq_ignore_ascii_case("url-prefix")
1258 && !base_name.eq_ignore_ascii_case("domain")
1259 {
1260 return Err(Error { kind: ErrorKind::ExpectUrl, span: prefix_span });
1261 }
1262 let prefix_start = prefix_span.start;
1263 let name = input.ident(prefix, prefix_span);
1264
1265 match input.cursor.peek()? {
1266 TokenWithSpan { token: Token::LParen(..), span } if prefix_span.end == span.start => {
1267 input.cursor.bump()?;
1268 }
1269 TokenWithSpan { span, .. } => {
1270 return Err(Error { kind: ErrorKind::TryParseError, span: *span });
1274 }
1275 }
1276
1277 if input.cursor.tokenizer.is_start_of_url_string() {
1278 let value = input.parse()?;
1279 let modifiers = input.parse_url_modifiers()?;
1280 let end = input.cursor.expect_r_paren()?.1.end;
1281 let span = Span { start: prefix_start, end };
1282 Ok(Url { name, value: Some(UrlValue::Str(value)), modifiers, span })
1283 } else if let Ok(value) = input.try_parse(UrlRaw::parse) {
1284 let span = Span {
1285 start: prefix_start,
1286 end: value.span.end + 1, };
1288 Ok(Url { name, value: Some(UrlValue::Raw(value)), modifiers: input.vec(), span })
1289 } else {
1290 match input.syntax {
1291 Syntax::Css => {
1292 Err(Error { kind: ErrorKind::InvalidUrl, span: input.cursor.bump()?.span })
1293 }
1294 Syntax::Scss | Syntax::Sass => {
1295 let value = input.parse::<SassInterpolatedUrl>()?;
1296 let span = Span {
1297 start: prefix_start,
1298 end: value.span.end + 1, };
1300 Ok(Url {
1301 name,
1302 value: Some(UrlValue::SassInterpolated(value)),
1303 modifiers: input.vec(),
1304 span,
1305 })
1306 }
1307 Syntax::Less => {
1308 let value = UrlValue::LessEscapedStr(input.parse()?);
1309 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1310 let span = Span { start: prefix_start, end };
1311 Ok(Url { name, value: Some(value), modifiers: input.vec(), span })
1312 }
1313 }
1314 }
1315 }
1316}
1317
1318impl<'a> Parse<'a> for UrlModifier<'a> {
1320 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1321 let ident = input.parse::<InterpolableIdent>()?;
1322 match input.cursor.peek()? {
1323 TokenWithSpan { token: Token::LParen(..), span } if ident.span().end == span.start => {
1324 input.parse_function(ident).map(UrlModifier::Function)
1325 }
1326 _ => Ok(UrlModifier::Ident(ident)),
1327 }
1328 }
1329}
1330
1331impl<'a> Parse<'a> for UrlRaw<'a> {
1333 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1334 let token = input.cursor.tokenizer.scan_url_raw_or_template()?;
1335 match token.url_raw(input.source) {
1336 Some(url) => {
1337 let span = token.span;
1338 let value = if url.escaped {
1339 util::handle_escape_in(url.raw, input.allocator)
1340 } else {
1341 url.raw
1342 };
1343 Ok(UrlRaw { value, raw: url.raw, span })
1344 }
1345 None => Err(Error {
1346 kind: ErrorKind::Unexpected("<url>", token.token.symbol()),
1347 span: token.span,
1348 }),
1349 }
1350 }
1351}