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(..) if self.syntax == Syntax::Css => {
417 self.parse().map(ComponentValue::PostcssSimpleVar)
418 }
419 Token::LParen(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
420 match self.try_parse(SassParenthesizedExpression::parse) {
421 Ok(expr) => Ok(ComponentValue::SassParenthesizedExpression(expr)),
422 Err(err) => self.parse().map(ComponentValue::SassMap).map_err(|_| err),
423 }
424 }
425 Token::HashLBrace(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
426 let ident = self.parse_sass_interpolated_ident()?;
427 match self.cursor.peek()? {
428 TokenWithSpan { token: Token::LParen(..), span }
429 if span.start == ident.span().end =>
430 {
431 self.parse_function(ident).map(ComponentValue::Function)
432 }
433 _ => Ok(ComponentValue::InterpolableIdent(ident)),
434 }
435 }
436 Token::StrTemplate(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => self
437 .parse()
438 .map(InterpolableStr::SassInterpolated)
439 .map(ComponentValue::InterpolableStr),
440 Token::Ampersand(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
441 self.parse().map(ComponentValue::SassParentSelector)
442 }
443 Token::LBrace(..)
444 if self.syntax == Syntax::Scss
445 && matches!(
446 self.state.qualified_rule_ctx,
447 Some(QualifiedRuleContext::DeclarationValue)
448 ) =>
449 {
450 self.parse().map(ComponentValue::SassNestingDeclaration)
451 }
452 Token::Indent(..)
453 if self.syntax == Syntax::Sass
454 && matches!(
455 self.state.qualified_rule_ctx,
456 Some(QualifiedRuleContext::DeclarationValue)
457 ) =>
458 {
459 self.parse().map(ComponentValue::SassNestingDeclaration)
460 }
461 Token::AtKeyword(..) if self.syntax == Syntax::Less => {
462 self.parse_less_maybe_variable_or_with_lookups()
463 }
464 Token::Dot(..) if self.syntax == Syntax::Less => {
465 self.parse_less_maybe_mixin_call_or_with_lookups()
466 }
467 Token::Dot(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
473 let dot = self.cursor.bump()?;
474 match self.cursor.peek()? {
475 TokenWithSpan { token: Token::Ident(..), span }
476 if span.start == dot.span.end =>
477 {
478 Ok(ComponentValue::TokenWithSpan(dot))
479 }
480 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: dot.span }),
481 }
482 }
483 Token::StrTemplate(..) if self.syntax == Syntax::Less => self
484 .parse()
485 .map(InterpolableStr::LessInterpolated)
486 .map(ComponentValue::InterpolableStr),
487 Token::At(..) if self.syntax == Syntax::Less => {
488 self.parse().map(ComponentValue::LessVariableVariable)
489 }
490 Token::DollarVar(..) if self.syntax == Syntax::Less => {
491 self.parse().map(ComponentValue::LessPropertyVariable)
492 }
493 Token::Tilde(..) if self.syntax == Syntax::Less => {
494 if let Ok(list_function_call) = self.try_parse(Function::parse) {
495 Ok(ComponentValue::Function(list_function_call))
496 } else if let Ok(less_escaped_str) = self.try_parse(LessEscapedStr::parse) {
497 Ok(ComponentValue::LessEscapedStr(less_escaped_str))
498 } else {
499 self.parse().map(ComponentValue::LessJavaScriptSnippet)
500 }
501 }
502 Token::Percent(..) if self.syntax == Syntax::Less => self
503 .try_parse(Function::parse)
504 .map(ComponentValue::Function)
505 .or_else(|_| self.parse().map(ComponentValue::LessPercentKeyword)),
506 Token::BacktickCode(..) if self.syntax == Syntax::Less => {
507 self.parse().map(ComponentValue::LessJavaScriptSnippet)
508 }
509 Token::Placeholder(..) => {
510 let (placeholder, span) = self.cursor.expect_placeholder()?;
511 Ok(ComponentValue::Placeholder((placeholder, span).into()))
512 }
513 _ => Err(Error { kind: ErrorKind::ExpectComponentValue, span: token_with_span.span }),
514 }
515 }
516
517 pub(super) fn parse_dashed_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
519 let ident = self.parse()?;
520 if let InterpolableIdent::Literal(ident) = &ident
521 && !ident.name.starts_with("--")
522 {
523 self.recoverable_errors
524 .push(Error { kind: ErrorKind::ExpectDashedIdent, span: ident.span });
525 }
526 Ok(ident)
527 }
528
529 pub(super) fn parse_function(&mut self, name: InterpolableIdent<'a>) -> PResult<Function<'a>> {
531 self.cursor.expect_l_paren()?;
532 let args = if let Token::RParen(..) = &self.cursor.peek()?.token {
533 self.vec()
534 } else {
535 match &name {
536 InterpolableIdent::Literal(ident)
537 if ident.name.eq_ignore_ascii_case("calc")
538 || ident.name.eq_ignore_ascii_case("-webkit-calc")
539 || ident.name.eq_ignore_ascii_case("-moz-calc")
540 || ident.name.eq_ignore_ascii_case("min")
541 || ident.name.eq_ignore_ascii_case("max")
542 || ident.name.eq_ignore_ascii_case("clamp")
543 || ident.name.eq_ignore_ascii_case("sin")
544 || ident.name.eq_ignore_ascii_case("cos")
545 || ident.name.eq_ignore_ascii_case("tan")
546 || ident.name.eq_ignore_ascii_case("asin")
547 || ident.name.eq_ignore_ascii_case("acos")
548 || ident.name.eq_ignore_ascii_case("atan")
549 || ident.name.eq_ignore_ascii_case("sqrt")
550 || ident.name.eq_ignore_ascii_case("exp")
551 || ident.name.eq_ignore_ascii_case("abs")
552 || ident.name.eq_ignore_ascii_case("sign")
553 || ident.name.eq_ignore_ascii_case("hypot")
554 || ident.name.eq_ignore_ascii_case("round")
555 || ident.name.eq_ignore_ascii_case("mod")
556 || ident.name.eq_ignore_ascii_case("rem")
557 || ident.name.eq_ignore_ascii_case("atan2")
558 || ident.name.eq_ignore_ascii_case("pow")
559 || ident.name.eq_ignore_ascii_case("log") =>
560 {
561 let allow_modulo = matches!(self.syntax, Syntax::Scss | Syntax::Sass)
564 && (ident.name.eq_ignore_ascii_case("min")
565 || ident.name.eq_ignore_ascii_case("max"));
566 if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
573 self.parse_calc_args(allow_modulo)?
574 } else {
575 let typed = self.try_parse(|p| {
576 let values = p.parse_calc_args(allow_modulo)?;
577 let TokenWithSpan { token, span } = p.cursor.peek()?;
578 if matches!(token, Token::RParen(..)) {
579 Ok(values)
580 } else {
581 Err(Error {
585 kind: ErrorKind::Unexpected(")", token.symbol()),
586 span: *span,
587 })
588 }
589 });
590 match typed {
591 Ok(values) => values,
592 Err(error) => {
593 let (args, comma_spans) = self.parse_sass_invocation_args()?;
594 if !args.iter().any(|arg| {
599 matches!(arg, ComponentValue::SassKeywordArgument(..))
600 }) {
601 return Err(error);
602 }
603 let mut values = self.vec_with_capacity(args.len() * 2);
604 let mut comma_spans = comma_spans.into_iter();
605 for (i, arg) in args.into_iter().enumerate() {
606 if i > 0
607 && let Some(span) = comma_spans.next()
608 {
609 values.push(ComponentValue::Delimiter(Delimiter {
610 kind: DelimiterKind::Comma,
611 span,
612 }));
613 }
614 values.push(arg);
615 }
616 values
617 }
618 }
619 }
620 }
621 InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("element") => {
622 let id_selector = self.parse().map(ComponentValue::IdSelector)?;
623 self.vec1(id_selector)
624 }
625 InterpolableIdent::Literal(Ident { raw: "boolean" | "if", .. })
626 if self.syntax == Syntax::Less =>
627 {
628 let less_condition = self.parse_less_condition(false)?;
629 let condition = ComponentValue::LessCondition(self.alloc(less_condition));
630 let mut args = self.parse_function_args()?;
631 args.insert(0, condition);
632 args
633 }
634 _ => self.parse_function_args()?,
635 }
636 };
637 let end = self.cursor.expect_r_paren()?.1.end;
638 let span = Span { start: name.span().start, end };
639 Ok(Function { name: FunctionName::Ident(name), args, span })
640 }
641
642 fn parse_calc_args(
646 &mut self,
647 allow_modulo: bool,
648 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
649 let mut values = self.vec_with_capacity(1);
650 loop {
651 match self.cursor.peek()? {
652 TokenWithSpan { token: Token::RParen(..), .. } => break,
653 TokenWithSpan { token: Token::Comma(..), .. } => {
654 values.push(ComponentValue::Delimiter(self.parse()?));
655 }
656 TokenWithSpan { token: Token::DotDotDot(..), .. }
659 if allow_modulo
660 && matches!(self.syntax, Syntax::Scss | Syntax::Sass)
661 && !values.is_empty()
662 && !values
663 .iter()
664 .any(|v| matches!(v, ComponentValue::SassArbitraryArgument(..))) =>
665 {
666 let TokenWithSpan { span: Span { end, .. }, .. } = self.cursor.bump()?;
667 let value = values.pop().unwrap();
668 let span = Span { start: value.span().start, end };
669 values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
670 value: self.alloc(value),
671 span,
672 }));
673 }
674 _ => values.push(self.parse_calc_expr(allow_modulo)?),
675 }
676 }
677 Ok(values)
678 }
679
680 pub(super) fn parse_function_typed_or_raw(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
684 let name_copy = Ident { name: name.name, raw: name.raw, span: name.span };
685 match self.try_parse(|p| p.parse_function(InterpolableIdent::Literal(name))) {
686 Ok(function) => Ok(function),
687 Err(_) => self.parse_raw_function(InterpolableIdent::Literal(name_copy)),
688 }
689 }
690
691 pub(in crate::parser) fn parse_raw_function(
695 &mut self,
696 name: InterpolableIdent<'a>,
697 ) -> PResult<Function<'a>> {
698 self.cursor.expect_l_paren()?;
699 let mut args = self.vec_with_capacity(4);
700 self.parse_raw_function_args_into(&mut args)?;
701 let end = self.cursor.expect_r_paren()?.1.end;
702 let span = Span { start: name.span().start, end };
703 Ok(Function { name: FunctionName::Ident(name), args, span })
704 }
705
706 fn parse_progid_function(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
710 let mut args = self.vec_with_capacity(4);
711 let mut pairs: Vec<util::PairedToken> = Vec::new();
715 loop {
716 match &self.cursor.peek()?.token {
717 Token::LParen(..) if pairs.is_empty() => break,
718 Token::Semicolon(..)
719 | Token::RBrace(..)
720 | Token::RParen(..)
721 | Token::Indent(..)
722 | Token::Dedent(..)
723 | Token::Linebreak(..)
724 if pairs.is_empty() =>
725 {
726 break;
727 }
728 Token::Eof(..) => break,
729 token => {
730 if !util::track_paired_token(token, &mut pairs) {
731 break;
732 }
733 args.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
734 }
735 }
736 }
737 self.cursor.expect_l_paren()?;
738 self.parse_raw_function_args_into(&mut args)?;
739 let end = self.cursor.expect_r_paren()?.1.end;
740 let span = Span { start: name.span.start, end };
741 Ok(Function { name: FunctionName::Ident(InterpolableIdent::Literal(name)), args, span })
742 }
743
744 fn parse_raw_function_args_into(
748 &mut self,
749 values: &mut oxc_allocator::Vec<'a, ComponentValue<'a>>,
750 ) -> PResult<()> {
751 let mut pairs: Vec<util::PairedToken> = Vec::with_capacity(1);
752 loop {
753 match &self.cursor.peek()?.token {
754 Token::Eof(..) => break,
755 Token::StrTemplate(..) => {
758 values.push(ComponentValue::InterpolableStr(self.parse()?));
759 continue;
760 }
761 token => {
762 if !util::track_paired_token(token, &mut pairs) {
763 break;
764 }
765 }
766 }
767 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
768 }
769 Ok(())
770 }
771
772 pub(super) fn parse_function_args(
775 &mut self,
776 ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
777 let mut values = self.vec_with_capacity(4);
778 loop {
779 match &self.cursor.peek()?.token {
780 Token::RParen(..) | Token::Eof(..) => break,
781 Token::Semicolon(..) => {
782 values.push(self.parse().map(ComponentValue::Delimiter)?);
783 }
784 Token::Exclamation(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
785 values.push(self.parse().map(ComponentValue::ImportantAnnotation)?);
787 }
788 Token::LBrace(..) if self.syntax == Syntax::Less => {
789 values.push(self.parse().map(ComponentValue::LessDetachedRuleset)?);
790 }
791 Token::Dot(..) | Token::NumberSign(..) if self.syntax == Syntax::Less => {
792 if let Ok(mixin) = self.try_parse(Parser::parse_less_anonymous_mixin) {
793 values.push(ComponentValue::LessAnonymousMixin(mixin));
794 } else if let Ok(value) = self.try_parse(ComponentValue::parse) {
795 values.push(value);
796 } else {
797 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
798 }
799 }
800 Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
801 self.cursor.bump()?;
802 }
803 Token::Unknown(..) if self.syntax != Syntax::Css => {
807 let span = self.cursor.peek()?.span;
808 return Err(Error { kind: ErrorKind::UnknownToken, span });
809 }
810 _ => {
811 let value = if let Ok(value) = self.try_parse(ComponentValue::parse) {
812 value
813 } else {
814 values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
815 continue;
816 };
817 if matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
818 if let Some((_, mut span)) = self.cursor.eat_dot_dot_dot()? {
819 span.start = value.span().start;
820 values.push(ComponentValue::SassArbitraryArgument(
821 SassArbitraryArgument { value: self.alloc(value), span },
822 ));
823 } else if let ComponentValue::SassVariable(sass_var) = value {
824 if let Some((_, colon_span)) = self.cursor.eat_colon()? {
825 let value = self.parse::<ComponentValue>()?;
826 let span =
827 Span { start: sass_var.span.start, end: value.span().end };
828 values.push(ComponentValue::SassKeywordArgument(
829 SassKeywordArgument {
830 name: sass_var,
831 colon_span,
832 value: self.alloc(value),
833 span,
834 },
835 ));
836 } else {
837 values.push(ComponentValue::SassVariable(sass_var));
838 }
839 } else {
840 values.push(value);
841 }
842 } else {
843 values.push(value);
844 }
845 }
846 }
847 }
848 Ok(values)
849 }
850
851 pub(super) fn parse_ratio(&mut self, numerator: Number<'a>) -> PResult<Ratio<'a>> {
854 let (_, solidus_span) = self.cursor.expect_solidus()?;
855 let denominator = self.parse::<Number>()?;
856 if denominator.value <= 0.0 {
857 self.recoverable_errors
858 .push(Error { kind: ErrorKind::InvalidRatioDenominator, span: denominator.span });
859 }
860
861 let span = Span { start: numerator.span.start, end: denominator.span.end };
862 Ok(Ratio { numerator, solidus_span, denominator, span })
863 }
864
865 fn parse_url_modifiers(&mut self) -> PResult<oxc_allocator::Vec<'a, UrlModifier<'a>>> {
870 Ok(match &self.cursor.peek()?.token {
871 Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
872 let mut modifiers = self.vec_with_capacity(1);
873 loop {
874 modifiers.push(self.parse()?);
875 if let Token::RParen(..) = &self.cursor.peek()?.token {
876 break;
877 }
878 }
879 modifiers
880 }
881 _ => self.vec(),
882 })
883 }
884
885 fn parse_src_url(&mut self, name: Ident<'a>) -> PResult<Url<'a>> {
886 self.cursor.expect_l_paren()?;
888 let value = match &self.cursor.peek()?.token {
889 Token::Str(..) | Token::StrTemplate(..) => {
890 Some(UrlValue::Str(self.parse::<InterpolableStr>()?))
891 }
892 _ => None,
893 };
894 let modifiers = self.parse_url_modifiers()?;
895 let end = self.cursor.expect_r_paren()?.1.end;
896 let span = Span { start: name.span.start, end };
897 Ok(Url { name, value, modifiers, span })
898 }
899
900 fn parse_unicode_range(&mut self, prefix_ident: Ident<'a>) -> PResult<UnicodeRange<'a>> {
903 let prefix = prefix_ident.raw.chars().next().unwrap();
904 let (span_start, span_end) = match self.cursor.bump()? {
905 TokenWithSpan { token: Token::Plus(..), span: plus_token_span } => {
906 let start = plus_token_span.start;
907 let mut end = match self.cursor.tokenizer.bump_without_ws_or_comments()? {
908 TokenWithSpan { token: Token::Ident(..) | Token::Question(..), span } => {
909 span.end
910 }
911 TokenWithSpan { token, span } => {
912 return Err(Error {
913 kind: ErrorKind::Unexpected("?", token.symbol()),
914 span,
915 });
916 }
917 };
918 loop {
919 match self.cursor.peek()? {
920 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
921 end = self.cursor.bump()?.span.end;
922 }
923 _ => break,
924 }
925 }
926 (start, end)
927 }
928 TokenWithSpan { token: Token::Dimension(..), span: dimension_token_span } => {
929 let start = dimension_token_span.start;
930 let mut end = dimension_token_span.end;
931 loop {
932 match self.cursor.peek()? {
933 TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
934 end = self.cursor.bump()?.span.end;
935 }
936 _ => break,
937 }
938 }
939 (start, end)
940 }
941 TokenWithSpan { token: Token::Number(..), span: number_token_span } => {
942 let start = number_token_span.start;
943 let mut end = number_token_span.end;
944 match &self.cursor.peek()?.token {
945 Token::Question(..) => {
946 end = self.cursor.bump()?.span.end;
947 loop {
948 match self.cursor.peek()? {
949 TokenWithSpan { token: Token::Question(..), span }
950 if span.start == end =>
951 {
952 end = self.cursor.bump()?.span.end;
953 }
954 _ => break,
955 }
956 }
957 }
958 Token::Dimension(..) | Token::Number(..) => {
959 end = self.cursor.bump()?.span.end;
960 }
961 _ => {}
962 }
963 (start, end)
964 }
965 TokenWithSpan { span, .. } => {
966 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
967 }
968 };
969
970 let source = self.source.get(span_start + 1..span_end).ok_or(Error {
971 kind: ErrorKind::InvalidUnicodeRange,
972 span: Span { start: span_start + 1, end: span_end },
973 })?;
974 let span = Span { start: prefix_ident.span.start, end: span_end };
975 let unicode_range = if let Some((left, right)) = source.split_once('-') {
976 if left.len() > 6 || !left.chars().all(|c| c.is_ascii_hexdigit()) {
977 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
978 }
979 if right.len() > 6
980 || !right.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(left, 16)
985 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
986 let end = u32::from_str_radix(&replace_unicode_range_wildcards(right, 'F'), 16)
987 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
988 UnicodeRange { prefix, start, start_raw: left, end, end_raw: Some(right), span }
989 } else {
990 if source.len() > 6
991 || !source.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
992 {
993 return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
994 }
995 let start = u32::from_str_radix(&replace_unicode_range_wildcards(source, '0'), 16)
996 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
997 let end = u32::from_str_radix(&replace_unicode_range_wildcards(source, 'F'), 16)
998 .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span })?;
999 UnicodeRange { prefix, start, start_raw: source, end, end_raw: None, span }
1000 };
1001 Ok(unicode_range)
1006 }
1007}
1008
1009fn replace_unicode_range_wildcards(source: &str, replacement: char) -> String {
1010 source.chars().map(|c| if c == '?' { replacement } else { c }).collect()
1011}
1012
1013impl<'a> Parse<'a> for BracketBlock<'a> {
1016 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1017 let start = input.cursor.expect_l_bracket()?.1.start;
1018 let mut value = input.vec_with_capacity(3);
1019 loop {
1020 match &input.cursor.peek()?.token {
1021 Token::RBracket(..) => break,
1022 _ => value.push(input.parse()?),
1023 }
1024 }
1025 let end = input.cursor.expect_r_bracket()?.1.end;
1026 Ok(BracketBlock { value, span: Span { start, end } })
1027 }
1028}
1029
1030impl<'a> Parse<'a> for ComponentValue<'a> {
1035 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1036 match input.syntax {
1037 Syntax::Css => input.parse_component_value_atom(),
1038 Syntax::Scss | Syntax::Sass => {
1039 input.parse_sass_bin_expr(true)
1040 }
1041 Syntax::Less => input.parse_less_operation(true),
1042 }
1043 }
1044}
1045
1046impl<'a> Parse<'a> for ComponentValues<'a> {
1048 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1050 let first = input.parse::<ComponentValue>()?;
1051 let mut span = *first.span();
1052
1053 let mut values = input.vec_with_capacity(4);
1054 values.push(first);
1055 loop {
1056 match &input.cursor.peek()?.token {
1057 Token::Eof(..) => break,
1058 Token::Semicolon(..) => {
1059 values.push(input.parse().map(ComponentValue::Delimiter)?);
1060 }
1061 _ => values.push(input.parse()?),
1062 }
1063 }
1064
1065 if let Some(value) = values.last() {
1066 span.end = value.span().end;
1067 }
1068 Ok(ComponentValues { values, span })
1069 }
1070}
1071
1072impl<'a> Parse<'a> for Delimiter {
1074 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1075 use crate::tokenizer::token::*;
1076 match input.cursor.bump()? {
1077 TokenWithSpan { token: Token::Solidus(..), span } => {
1078 Ok(Delimiter { kind: DelimiterKind::Solidus, span })
1079 }
1080 TokenWithSpan { token: Token::Comma(..), span } => {
1081 Ok(Delimiter { kind: DelimiterKind::Comma, span })
1082 }
1083 TokenWithSpan { token: Token::Semicolon(..), span } => {
1084 Ok(Delimiter { kind: DelimiterKind::Semicolon, span })
1085 }
1086 _ => unreachable!(),
1087 }
1088 }
1089}
1090
1091impl<'a> Parse<'a> for Dimension<'a> {
1093 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1094 let (dimension, span) = input.cursor.expect_dimension()?;
1095 input.dimension(dimension, span)
1096 }
1097}
1098
1099impl<'a> Parse<'a> for Function<'a> {
1103 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1104 let name = input.parse::<FunctionName>()?;
1105 match input.cursor.peek()? {
1106 TokenWithSpan { token: Token::LParen(..), span } => {
1107 util::assert_no_ws_or_comment(name.span(), span)?;
1108 match name {
1109 FunctionName::Ident(name) => input.parse_function(name),
1110 name => {
1111 input.cursor.bump()?;
1112 let args = input.parse_function_args()?;
1113 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1114 let span = Span { start: name.span().start, end };
1115 Ok(Function { name, args, span })
1116 }
1117 }
1118 }
1119 TokenWithSpan { token, span } => {
1120 Err(Error { kind: ErrorKind::Unexpected("(", token.symbol()), span: *span })
1121 }
1122 }
1123 }
1124}
1125
1126impl<'a> Parse<'a> for FunctionName<'a> {
1129 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1130 match input.cursor.peek()?.token {
1131 Token::Ident(..) => {
1132 let ident = input.parse::<Ident>()?;
1133 match (&input.cursor.peek()?.token, input.syntax) {
1134 (Token::Dot(..), Syntax::Scss | Syntax::Sass) => {
1135 input.cursor.bump()?;
1136 let member = input.parse::<Ident>()?;
1137 let span = Span { start: ident.span.start, end: member.span.end };
1138 Ok(FunctionName::SassQualifiedName(input.alloc(SassQualifiedName {
1139 module: ident,
1140 member: SassModuleMemberName::Ident(member),
1141 span,
1142 })))
1143 }
1144 _ => Ok(FunctionName::Ident(InterpolableIdent::Literal(ident))),
1145 }
1146 }
1147 Token::Percent(..) if input.syntax == Syntax::Less => {
1148 input.parse().map(FunctionName::LessFormatFunction)
1149 }
1150 Token::Tilde(..) if input.syntax == Syntax::Less => {
1151 input.parse().map(FunctionName::LessListFunction)
1152 }
1153 _ => {
1154 let TokenWithSpan { token, span } = input.cursor.bump()?;
1155 Err(Error { kind: ErrorKind::Unexpected("<ident>", token.symbol()), span })
1156 }
1157 }
1158 }
1159}
1160
1161impl<'a> Parse<'a> for HexColor<'a> {
1163 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1164 let (token, span) = input.cursor.expect_hash()?;
1165 let raw = token.raw;
1166 let value = if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
1167 Ok(HexColor { value, raw, span })
1168 }
1169}
1170
1171impl<'a> Parse<'a> for Ident<'a> {
1173 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1174 let (ident, span) = input.cursor.expect_ident()?;
1175 Ok(input.ident(ident, span))
1176 }
1177}
1178
1179impl<'a> Parse<'a> for InterpolableIdent<'a> {
1182 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1183 if let Token::Placeholder(..) = input.cursor.peek()?.token {
1186 let (placeholder, span) = input.cursor.expect_placeholder()?;
1187 return Ok(InterpolableIdent::Placeholder((placeholder, span).into()));
1188 }
1189 match input.syntax {
1190 Syntax::Css => input.parse().map(InterpolableIdent::Literal),
1191 Syntax::Scss | Syntax::Sass => input.parse_sass_interpolated_ident(),
1192 Syntax::Less => {
1193 if matches!(
1195 input.state.qualified_rule_ctx,
1196 Some(QualifiedRuleContext::DeclarationValue)
1197 ) {
1198 input.parse().map(InterpolableIdent::Literal)
1199 } else {
1200 input.parse_less_interpolated_ident()
1201 }
1202 }
1203 }
1204 }
1205}
1206
1207impl<'a> Parse<'a> for InterpolableStr<'a> {
1209 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1210 match input.cursor.peek()? {
1211 TokenWithSpan { token: Token::Str(..), .. } => {
1212 input.parse().map(InterpolableStr::Literal)
1213 }
1214 TokenWithSpan { token: Token::StrTemplate(..), span } => match input.syntax {
1215 Syntax::Scss | Syntax::Sass => input.parse().map(InterpolableStr::SassInterpolated),
1216 Syntax::Less => input.parse().map(InterpolableStr::LessInterpolated),
1217 Syntax::Css => Err(Error { kind: ErrorKind::UnexpectedTemplateInCss, span: *span }),
1218 },
1219 TokenWithSpan { span, .. } => Err(Error { kind: ErrorKind::ExpectString, span: *span }),
1220 }
1221 }
1222}
1223
1224impl<'a> Parse<'a> for Number<'a> {
1226 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1227 let (number, span) = input.cursor.expect_number()?;
1228 number
1229 .raw
1230 .parse()
1231 .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span })
1232 .map(|value| Self { value, raw: number.raw, span })
1233 }
1234}
1235
1236impl<'a> Parse<'a> for Percentage<'a> {
1238 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1239 let (token, span) = input.cursor.expect_percentage()?;
1240 Ok(Percentage {
1241 value: (token.value, Span { start: span.start, end: span.end - 1 }).try_into()?,
1242 span,
1243 })
1244 }
1245}
1246
1247impl<'a> Parse<'a> for Str<'a> {
1249 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1250 let (str, span) = input.cursor.expect_str()?;
1251 Ok(input.str(str, span))
1252 }
1253}
1254
1255impl<'a> Parse<'a> for Url<'a> {
1260 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1261 let (prefix, prefix_span) = input.cursor.expect_ident()?;
1262 let prefix_name = prefix.name();
1266 let base_name = unvendored(&prefix_name);
1267 if !base_name.eq_ignore_ascii_case("url")
1268 && !base_name.eq_ignore_ascii_case("url-prefix")
1269 && !base_name.eq_ignore_ascii_case("domain")
1270 {
1271 return Err(Error { kind: ErrorKind::ExpectUrl, span: prefix_span });
1272 }
1273 let prefix_start = prefix_span.start;
1274 let name = input.ident(prefix, prefix_span);
1275
1276 match input.cursor.peek()? {
1277 TokenWithSpan { token: Token::LParen(..), span } if prefix_span.end == span.start => {
1278 input.cursor.bump()?;
1279 }
1280 TokenWithSpan { span, .. } => {
1281 return Err(Error { kind: ErrorKind::TryParseError, span: *span });
1285 }
1286 }
1287
1288 if input.cursor.tokenizer.is_start_of_url_string() {
1289 let value = input.parse()?;
1290 let modifiers = input.parse_url_modifiers()?;
1291 let end = input.cursor.expect_r_paren()?.1.end;
1292 let span = Span { start: prefix_start, end };
1293 Ok(Url { name, value: Some(UrlValue::Str(value)), modifiers, span })
1294 } else if let Ok(value) = input.try_parse(UrlRaw::parse) {
1295 let span = Span {
1296 start: prefix_start,
1297 end: value.span.end + 1, };
1299 Ok(Url { name, value: Some(UrlValue::Raw(value)), modifiers: input.vec(), span })
1300 } else {
1301 match input.syntax {
1302 Syntax::Css => {
1303 Err(Error { kind: ErrorKind::InvalidUrl, span: input.cursor.bump()?.span })
1304 }
1305 Syntax::Scss | Syntax::Sass => {
1306 let value = input.parse::<SassInterpolatedUrl>()?;
1307 let span = Span {
1308 start: prefix_start,
1309 end: value.span.end + 1, };
1311 Ok(Url {
1312 name,
1313 value: Some(UrlValue::SassInterpolated(value)),
1314 modifiers: input.vec(),
1315 span,
1316 })
1317 }
1318 Syntax::Less => {
1319 let value = UrlValue::LessEscapedStr(input.parse()?);
1320 let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1321 let span = Span { start: prefix_start, end };
1322 Ok(Url { name, value: Some(value), modifiers: input.vec(), span })
1323 }
1324 }
1325 }
1326 }
1327}
1328
1329impl<'a> Parse<'a> for UrlModifier<'a> {
1331 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1332 let ident = input.parse::<InterpolableIdent>()?;
1333 match input.cursor.peek()? {
1334 TokenWithSpan { token: Token::LParen(..), span } if ident.span().end == span.start => {
1335 input.parse_function(ident).map(UrlModifier::Function)
1336 }
1337 _ => Ok(UrlModifier::Ident(ident)),
1338 }
1339 }
1340}
1341
1342impl<'a> Parse<'a> for UrlRaw<'a> {
1344 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1345 let token = input.cursor.tokenizer.scan_url_raw_or_template()?;
1346 match token.url_raw(input.source) {
1347 Some(url) => {
1348 let span = token.span;
1349 let value = if url.escaped {
1350 util::handle_escape_in(url.raw, input.allocator)
1351 } else {
1352 url.raw
1353 };
1354 Ok(UrlRaw { value, raw: url.raw, span })
1355 }
1356 None => Err(Error {
1357 kind: ErrorKind::Unexpected("<url>", token.token.symbol()),
1358 span: token.span,
1359 }),
1360 }
1361 }
1362}