Skip to main content

oxc_css_parser/parser/
value.rs

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
14/// Strip one leading `-vendor-` prefix (`-moz-calc` -> `calc`); returns the
15/// name unchanged when there is none.
16fn unvendored(name: &str) -> &str {
17    name.strip_prefix('-').and_then(|rest| rest.split_once('-')).map_or(name, |(_, base)| base)
18}
19
20/// dart-sass "special functions" whose contents may be raw text rather than
21/// values, but which are worth a typed parse first (so plain `element(#id)`
22/// or `-webkit-calc(1px + 2px)` keep their structured AST): `element(...)`
23/// and `type(...)`, plus `calc(...)`/`url(...)` under an unrecognized vendor
24/// prefix. (`expression(...)` and `progid:...(...)` are always raw, and an
25/// unvendored `calc`/`url` is parsed as a real calculation/URL.)
26fn 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(
43        &mut self,
44        precedence: u8,
45        allow_modulo: bool,
46    ) -> PResult<ComponentValue<'a>> {
47        let mut left = if precedence >= PRECEDENCE_MULTIPLY {
48            if self.cursor.eat_l_paren()?.is_some() {
49                let expr = self.parse_calc_expr(allow_modulo)?;
50                self.cursor.expect_r_paren()?;
51                expr
52            } else if matches!(self.syntax, Syntax::Scss | Syntax::Sass)
53                && matches!(&self.cursor.peek()?.token, Token::Minus(..) | Token::Plus(..))
54                && {
55                    let span = &self.cursor.peek()?.span;
56                    self.source.as_bytes().get(span.end) == Some(&b'(')
57                }
58            {
59                // SassScript allows a unary sign glued to a parenthesized
60                // operand inside a calculation (`round(-(1) + 2)`); a spaced
61                // `calc(+ 1px)` stays invalid, as in dart-sass.
62                let op = match &self.cursor.peek()?.token {
63                    Token::Minus(..) => SassUnaryOperator {
64                        kind: SassUnaryOperatorKind::Minus,
65                        span: self.cursor.bump()?.span,
66                    },
67                    _ => SassUnaryOperator {
68                        kind: SassUnaryOperatorKind::Plus,
69                        span: self.cursor.bump()?.span,
70                    },
71                };
72                let expr = self.parse_calc_expr_recursively(PRECEDENCE_MULTIPLY, allow_modulo)?;
73                let span = Span { start: op.span.start, end: expr.span().end };
74                ComponentValue::SassUnaryExpression(SassUnaryExpression {
75                    expr: self.alloc(expr),
76                    op,
77                    span,
78                })
79            } else if self.syntax == Syntax::Less {
80                if matches!(self.cursor.peek()?.token, Token::Minus(..)) {
81                    ComponentValue::LessNegativeValue(self.parse()?)
82                } else {
83                    self.parse_component_value_atom()?
84                }
85            } else {
86                self.parse_component_value_atom()?
87            }
88        } else {
89            self.parse_calc_expr_recursively(precedence + 1, allow_modulo)?
90        };
91
92        loop {
93            let operator = match &self.cursor.peek()?.token {
94                Token::Asterisk(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
95                    kind: CalcOperatorKind::Multiply,
96                    span: self.cursor.bump()?.span,
97                },
98                Token::Solidus(..) if precedence == PRECEDENCE_MULTIPLY => CalcOperator {
99                    kind: CalcOperatorKind::Division,
100                    span: self.cursor.bump()?.span,
101                },
102                // Sass modulo (`%`) shares multiplicative precedence, but only the
103                // legacy SassScript `min`/`max` accept it (`allow_modulo`); true
104                // calculations (`calc`, `clamp`, `sin`, ...) reject it, as does CSS.
105                Token::Percent(..) if precedence == PRECEDENCE_MULTIPLY && allow_modulo => {
106                    CalcOperator { kind: CalcOperatorKind::Modulo, span: self.cursor.bump()?.span }
107                }
108                Token::Plus(..) if precedence == PRECEDENCE_PLUS => {
109                    CalcOperator { kind: CalcOperatorKind::Plus, span: self.cursor.bump()?.span }
110                }
111                Token::Minus(..) if precedence == PRECEDENCE_PLUS => {
112                    CalcOperator { kind: CalcOperatorKind::Minus, span: self.cursor.bump()?.span }
113                }
114                _ => break,
115            };
116
117            let right = self.parse_calc_expr_recursively(precedence + 1, allow_modulo)?;
118            let span = Span { start: left.span().start, end: right.span().end };
119            left = ComponentValue::Calc(Calc {
120                left: self.alloc(left),
121                op: operator,
122                right: self.alloc(right),
123                span,
124            });
125        }
126
127        Ok(left)
128    }
129
130    pub(super) fn parse_component_value_atom(&mut self) -> PResult<ComponentValue<'a>> {
131        let token_with_span = self.cursor.peek()?;
132        match &token_with_span.token {
133            Token::Ident(token) => {
134                if unvendored(&token.name()).eq_ignore_ascii_case("url") {
135                    match self.try_parse(Url::parse) {
136                        Ok(url) => return Ok(ComponentValue::Url(self.alloc(url))),
137                        Err(Error { kind: ErrorKind::TryParseError, .. }) => {}
138                        Err(error) => {
139                            // Not a `<url-token>` (quotes, parens, or raw
140                            // whitespace inside). Reference compilers accept
141                            // these as a function call with raw-ish contents
142                            // (`url(fn("s"))`, multi-line data: URIs), so fall
143                            // back to a function parse, keeping the original
144                            // error if even that shape doesn't fit.
145                            let (function_name, function_name_span) = self.cursor.expect_ident()?;
146                            let function_name = self.ident(function_name, function_name_span);
147                            return self
148                                .parse_function_typed_or_raw(function_name)
149                                .map(ComponentValue::Function)
150                                .map_err(|_| error);
151                        }
152                    }
153                }
154                let ident = self.parse::<InterpolableIdent>()?;
155                let ident_end = ident.span().end;
156                match self.cursor.peek()? {
157                    TokenWithSpan { token: Token::LParen(..), span } if span.start == ident_end => {
158                        return match ident {
159                            InterpolableIdent::Literal(ident)
160                                if ident.name.eq_ignore_ascii_case("src") =>
161                            {
162                                self.parse_src_url(ident)
163                                    .map(|url| ComponentValue::Url(self.alloc(url)))
164                            }
165                            InterpolableIdent::Literal(ident)
166                                if unvendored(ident.name).eq_ignore_ascii_case("expression") =>
167                            {
168                                // IE `expression(...)` (any vendor prefix):
169                                // contents are script, not CSS values.
170                                self.parse_raw_function(InterpolableIdent::Literal(ident))
171                                    .map(ComponentValue::Function)
172                            }
173                            InterpolableIdent::Literal(ident)
174                                if is_special_typed_or_raw_function(ident.name) =>
175                            {
176                                self.parse_function_typed_or_raw(ident)
177                                    .map(ComponentValue::Function)
178                            }
179                            ident => self.parse_function(ident).map(ComponentValue::Function),
180                        };
181                    }
182                    // IE filter syntax `-c-progid:d.e(...)` — everything to
183                    // the matching `)` is raw. (An unprefixed `progid:` at the
184                    // start of a value takes the whole-value raw path in
185                    // `Declaration::parse` instead.)
186                    TokenWithSpan { token: Token::Colon(..), span }
187                        if span.start == ident_end
188                            && matches!(
189                                &ident,
190                                InterpolableIdent::Literal(id)
191                                    if unvendored(id.name).eq_ignore_ascii_case("progid")
192                            ) =>
193                    {
194                        if let InterpolableIdent::Literal(ident) = ident {
195                            return self.parse_progid_function(ident).map(ComponentValue::Function);
196                        }
197                        unreachable!("guard matched a literal ident");
198                    }
199                    TokenWithSpan { token: Token::Dot(..), span }
200                        if matches!(self.syntax, Syntax::Scss | Syntax::Sass)
201                            && span.start == ident_end =>
202                    {
203                        if let InterpolableIdent::Literal(module) = ident {
204                            let name = self.parse_sass_qualified_name(module)?;
205                            return if let SassQualifiedName {
206                                member: SassModuleMemberName::Ident(..),
207                                ..
208                            } = name
209                            {
210                                let (_, lparen_span) = self.cursor.expect_l_paren()?;
211                                util::assert_no_ws_or_comment(&name.span, &lparen_span)?;
212                                let args = self.parse_function_args()?;
213                                let (_, Span { end, .. }) = self.cursor.expect_r_paren()?;
214                                let span = Span { start: name.span.start, end };
215                                Ok(ComponentValue::Function(Function {
216                                    name: FunctionName::SassQualifiedName(self.alloc(name)),
217                                    args,
218                                    span,
219                                }))
220                            } else {
221                                Ok(ComponentValue::SassQualifiedName(self.alloc(name)))
222                            };
223                        }
224                    }
225                    _ => {}
226                }
227                match ident {
228                    InterpolableIdent::Literal(ident) if ident.raw.eq_ignore_ascii_case("u") => {
229                        match self.cursor.peek()? {
230                            TokenWithSpan { token: Token::Plus(..), span }
231                                if span.start == ident_end =>
232                            {
233                                self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
234                            }
235                            TokenWithSpan { token: Token::Number(token), span }
236                                if token.raw.starts_with('+') && span.start == ident_end =>
237                            {
238                                self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
239                            }
240                            TokenWithSpan { token: Token::Dimension(token), span }
241                                if token.value.raw.starts_with('+') && span.start == ident_end =>
242                            {
243                                self.parse_unicode_range(ident).map(ComponentValue::UnicodeRange)
244                            }
245                            _ => Ok(ComponentValue::InterpolableIdent(InterpolableIdent::Literal(
246                                ident,
247                            ))),
248                        }
249                    }
250                    _ => Ok(ComponentValue::InterpolableIdent(ident)),
251                }
252            }
253            Token::Solidus(..) | Token::Comma(..) => self.parse().map(ComponentValue::Delimiter),
254            Token::Number(..) => self.parse().map(ComponentValue::Number),
255            Token::Dimension(..) => self.parse().map(ComponentValue::Dimension),
256            Token::Percentage(..) => self.parse().map(ComponentValue::Percentage),
257            Token::Hash(..) => {
258                if self.syntax == Syntax::Less {
259                    self.parse_maybe_hex_color_or_less_mixin_call()
260                } else {
261                    self.parse().map(ComponentValue::HexColor)
262                }
263            }
264            Token::Str(..) => {
265                self.parse().map(InterpolableStr::Literal).map(ComponentValue::InterpolableStr)
266            }
267            Token::LBracket(..) => self.parse().map(ComponentValue::BracketBlock),
268            Token::DollarVar(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
269                self.parse().map(ComponentValue::SassVariable)
270            }
271            Token::DollarVar(..)
272                if self.syntax == Syntax::Css && self.options.allow_postcss_simple_vars =>
273            {
274                self.parse().map(ComponentValue::PostcssSimpleVar)
275            }
276            Token::LParen(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
277                match self.try_parse(SassParenthesizedExpression::parse) {
278                    Ok(expr) => Ok(ComponentValue::SassParenthesizedExpression(expr)),
279                    Err(err) => self.parse().map(ComponentValue::SassMap).map_err(|_| err),
280                }
281            }
282            Token::HashLBrace(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
283                let ident = self.parse_sass_interpolated_ident()?;
284                match self.cursor.peek()? {
285                    TokenWithSpan { token: Token::LParen(..), span }
286                        if span.start == ident.span().end =>
287                    {
288                        self.parse_function(ident).map(ComponentValue::Function)
289                    }
290                    _ => Ok(ComponentValue::InterpolableIdent(ident)),
291                }
292            }
293            Token::StrTemplate(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => self
294                .parse()
295                .map(InterpolableStr::SassInterpolated)
296                .map(ComponentValue::InterpolableStr),
297            Token::Ampersand(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
298                self.parse().map(ComponentValue::SassParentSelector)
299            }
300            Token::LBrace(..)
301                if self.syntax == Syntax::Scss
302                    && matches!(
303                        self.state.qualified_rule_ctx,
304                        Some(QualifiedRuleContext::DeclarationValue)
305                    ) =>
306            {
307                self.parse().map(ComponentValue::SassNestingDeclaration)
308            }
309            Token::Indent(..)
310                if self.syntax == Syntax::Sass
311                    && matches!(
312                        self.state.qualified_rule_ctx,
313                        Some(QualifiedRuleContext::DeclarationValue)
314                    ) =>
315            {
316                self.parse().map(ComponentValue::SassNestingDeclaration)
317            }
318            Token::AtKeyword(..) if self.syntax == Syntax::Less => {
319                self.parse_less_maybe_variable_or_with_lookups()
320            }
321            Token::Dot(..) if self.syntax == Syntax::Less => {
322                self.parse_less_maybe_mixin_call_or_with_lookups()
323            }
324            Token::StrTemplate(..) if self.syntax == Syntax::Less => self
325                .parse()
326                .map(InterpolableStr::LessInterpolated)
327                .map(ComponentValue::InterpolableStr),
328            Token::At(..) if self.syntax == Syntax::Less => {
329                self.parse().map(ComponentValue::LessVariableVariable)
330            }
331            Token::DollarVar(..) if self.syntax == Syntax::Less => {
332                self.parse().map(ComponentValue::LessPropertyVariable)
333            }
334            Token::Tilde(..) if self.syntax == Syntax::Less => {
335                if let Ok(list_function_call) = self.try_parse(Function::parse) {
336                    Ok(ComponentValue::Function(list_function_call))
337                } else if let Ok(less_escaped_str) = self.try_parse(LessEscapedStr::parse) {
338                    Ok(ComponentValue::LessEscapedStr(less_escaped_str))
339                } else {
340                    self.parse().map(ComponentValue::LessJavaScriptSnippet)
341                }
342            }
343            Token::Percent(..) if self.syntax == Syntax::Less => self
344                .try_parse(Function::parse)
345                .map(ComponentValue::Function)
346                .or_else(|_| self.parse().map(ComponentValue::LessPercentKeyword)),
347            Token::BacktickCode(..) if self.syntax == Syntax::Less => {
348                self.parse().map(ComponentValue::LessJavaScriptSnippet)
349            }
350            Token::Placeholder(..) => {
351                let (placeholder, span) = self.cursor.expect_placeholder()?;
352                Ok(ComponentValue::Placeholder((placeholder, span).into()))
353            }
354            _ => Err(Error {
355                kind: ErrorKind::ExpectComponentValue,
356                span: token_with_span.span.clone(),
357            }),
358        }
359    }
360
361    pub(super) fn parse_dashed_ident(&mut self) -> PResult<InterpolableIdent<'a>> {
362        let ident = self.parse()?;
363        match &ident {
364            InterpolableIdent::Literal(ident) if !ident.name.starts_with("--") => {
365                self.recoverable_errors
366                    .push(Error { kind: ErrorKind::ExpectDashedIdent, span: ident.span.clone() });
367            }
368            _ => {}
369        }
370        Ok(ident)
371    }
372
373    pub(super) fn parse_function(&mut self, name: InterpolableIdent<'a>) -> PResult<Function<'a>> {
374        self.cursor.expect_l_paren()?;
375        let args = if let Token::RParen(..) = &self.cursor.peek()?.token {
376            self.vec()
377        } else {
378            match &name {
379                InterpolableIdent::Literal(ident)
380                    if ident.name.eq_ignore_ascii_case("calc")
381                        || ident.name.eq_ignore_ascii_case("-webkit-calc")
382                        || ident.name.eq_ignore_ascii_case("-moz-calc")
383                        || ident.name.eq_ignore_ascii_case("min")
384                        || ident.name.eq_ignore_ascii_case("max")
385                        || ident.name.eq_ignore_ascii_case("clamp")
386                        || ident.name.eq_ignore_ascii_case("sin")
387                        || ident.name.eq_ignore_ascii_case("cos")
388                        || ident.name.eq_ignore_ascii_case("tan")
389                        || ident.name.eq_ignore_ascii_case("asin")
390                        || ident.name.eq_ignore_ascii_case("acos")
391                        || ident.name.eq_ignore_ascii_case("atan")
392                        || ident.name.eq_ignore_ascii_case("sqrt")
393                        || ident.name.eq_ignore_ascii_case("exp")
394                        || ident.name.eq_ignore_ascii_case("abs")
395                        || ident.name.eq_ignore_ascii_case("sign")
396                        || ident.name.eq_ignore_ascii_case("hypot")
397                        || ident.name.eq_ignore_ascii_case("round")
398                        || ident.name.eq_ignore_ascii_case("mod")
399                        || ident.name.eq_ignore_ascii_case("rem")
400                        || ident.name.eq_ignore_ascii_case("atan2")
401                        || ident.name.eq_ignore_ascii_case("pow")
402                        || ident.name.eq_ignore_ascii_case("log") =>
403                {
404                    // Only the legacy SassScript `min`/`max` accept the Sass `%` modulo
405                    // operator; true calculations (`calc`, `clamp`, `sin`, ...) reject it.
406                    let allow_modulo = matches!(self.syntax, Syntax::Scss | Syntax::Sass)
407                        && (ident.name.eq_ignore_ascii_case("min")
408                            || ident.name.eq_ignore_ascii_case("max"));
409                    // The calc grammar doesn't cover SassScript uses of these
410                    // names (`abs(\$number: -3)`, `max(1 2 3...)`,
411                    // `round(-(1) + 2)`); Scss/Sass fall back to a strict
412                    // SassScript call — but only there, so invalid calc stays
413                    // invalid. Other syntaxes have no fallback, so they skip
414                    // the rollback snapshot entirely.
415                    if !matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
416                        self.parse_calc_args(allow_modulo)?
417                    } else {
418                        let typed = self.try_parse(|p| {
419                            let values = p.parse_calc_args(allow_modulo)?;
420                            if matches!(&p.cursor.peek()?.token, Token::RParen(..)) {
421                                Ok(values)
422                            } else {
423                                let span = p.cursor.peek()?.span.clone();
424                                Err(Error { kind: ErrorKind::TryParseError, span })
425                            }
426                        });
427                        match typed {
428                            Ok(values) => values,
429                            Err(error) => {
430                                let (args, comma_spans) = self.parse_sass_invocation_args()?;
431                                // Only a keyword argument justifies the fallback
432                                // (`abs(\$number: -3)` is a SassScript call); plain
433                                // expressions the calc grammar rejected
434                                // (`calc(1px % 2px)`, double spreads) stay invalid.
435                                if !args.iter().any(|arg| {
436                                    matches!(arg, ComponentValue::SassKeywordArgument(..))
437                                }) {
438                                    return Err(error);
439                                }
440                                let mut values = self.vec_with_capacity(args.len() * 2);
441                                let mut comma_spans = comma_spans.into_iter();
442                                for (i, arg) in args.into_iter().enumerate() {
443                                    if i > 0
444                                        && let Some(span) = comma_spans.next()
445                                    {
446                                        values.push(ComponentValue::Delimiter(Delimiter {
447                                            kind: DelimiterKind::Comma,
448                                            span,
449                                        }));
450                                    }
451                                    values.push(arg);
452                                }
453                                values
454                            }
455                        }
456                    }
457                }
458                InterpolableIdent::Literal(ident) if ident.name.eq_ignore_ascii_case("element") => {
459                    let id_selector = self.parse().map(ComponentValue::IdSelector)?;
460                    self.vec1(id_selector)
461                }
462                InterpolableIdent::Literal(Ident { raw: "boolean" | "if", .. })
463                    if self.syntax == Syntax::Less =>
464                {
465                    let less_condition = self.parse_less_condition(false)?;
466                    let condition = ComponentValue::LessCondition(self.alloc(less_condition));
467                    let mut args = self.parse_function_args()?;
468                    args.insert(0, condition);
469                    args
470                }
471                _ => self.parse_function_args()?,
472            }
473        };
474        let end = self.cursor.expect_r_paren()?.1.end;
475        let span = Span { start: name.span().start, end };
476        Ok(Function { name: FunctionName::Ident(name), args, span })
477    }
478
479    /// The `calc()`-family argument list: comma-delimited calc expressions,
480    /// with the SassScript spread (`max(1 2 3...)`) wrapping the preceding
481    /// value. Stops before the closing `)`.
482    fn parse_calc_args(
483        &mut self,
484        allow_modulo: bool,
485    ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
486        let mut values = self.vec_with_capacity(1);
487        loop {
488            match self.cursor.peek()? {
489                TokenWithSpan { token: Token::RParen(..), .. } => break,
490                TokenWithSpan { token: Token::Comma(..), .. } => {
491                    values.push(ComponentValue::Delimiter(self.parse()?));
492                }
493                // a spread is SassScript, so only the legacy `min`/`max`
494                // accept it (`clamp(1px 2px 3px...)` errors), and only once
495                TokenWithSpan { token: Token::DotDotDot(..), .. }
496                    if allow_modulo
497                        && matches!(self.syntax, Syntax::Scss | Syntax::Sass)
498                        && !values.is_empty()
499                        && !values
500                            .iter()
501                            .any(|v| matches!(v, ComponentValue::SassArbitraryArgument(..))) =>
502                {
503                    let TokenWithSpan { span: Span { end, .. }, .. } = self.cursor.bump()?;
504                    let value = values.pop().unwrap();
505                    let span = Span { start: value.span().start, end };
506                    values.push(ComponentValue::SassArbitraryArgument(SassArbitraryArgument {
507                        value: self.alloc(value),
508                        span,
509                    }));
510                }
511                _ => values.push(self.parse_calc_expr(allow_modulo)?),
512            }
513        }
514        Ok(values)
515    }
516
517    /// Parse a function with the typed grammar; when its arguments don't fit
518    /// (dart-sass special functions carry raw text: `element(/**/ c)`,
519    /// `-c-calc(@#$)`, `url(fn("s"))`), re-parse the contents as raw tokens.
520    pub(super) fn parse_function_typed_or_raw(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
521        let name_copy = Ident { name: name.name, raw: name.raw, span: name.span.clone() };
522        match self.try_parse(|p| p.parse_function(InterpolableIdent::Literal(name))) {
523            Ok(function) => Ok(function),
524            Err(_) => self.parse_raw_function(InterpolableIdent::Literal(name_copy)),
525        }
526    }
527
528    /// Parse `name(<raw contents>)` where the contents are preserved tokens
529    /// balanced to the matching `)` (IE `expression(...)`, unknown
530    /// `@supports` functions, and friends).
531    pub(in crate::parser) fn parse_raw_function(
532        &mut self,
533        name: InterpolableIdent<'a>,
534    ) -> PResult<Function<'a>> {
535        self.cursor.expect_l_paren()?;
536        let mut args = self.vec_with_capacity(4);
537        self.parse_raw_function_args_into(&mut args)?;
538        let end = self.cursor.expect_r_paren()?.1.end;
539        let span = Span { start: name.span().start, end };
540        Ok(Function { name: FunctionName::Ident(name), args, span })
541    }
542
543    /// IE filter syntax `progid:DXImageTransform.Microsoft.f(...)`, optionally
544    /// vendor prefixed: the `:dotted.path` prefix and the parenthesized
545    /// contents are all preserved tokens.
546    fn parse_progid_function(&mut self, name: Ident<'a>) -> PResult<Function<'a>> {
547        let mut args = self.vec_with_capacity(4);
548        loop {
549            match &self.cursor.peek()?.token {
550                Token::LParen(..)
551                | Token::Semicolon(..)
552                | Token::RBrace(..)
553                | Token::RParen(..)
554                | Token::Eof(..)
555                | Token::Indent(..)
556                | Token::Dedent(..)
557                | Token::Linebreak(..) => break,
558                _ => args.push(ComponentValue::TokenWithSpan(self.cursor.bump()?)),
559            }
560        }
561        self.cursor.expect_l_paren()?;
562        self.parse_raw_function_args_into(&mut args)?;
563        let end = self.cursor.expect_r_paren()?.1.end;
564        let span = Span { start: name.span.start, end };
565        Ok(Function { name: FunctionName::Ident(InterpolableIdent::Literal(name)), args, span })
566    }
567
568    /// Consume function contents as preserved tokens, balancing pairs, until
569    /// the function's own `)`. Semicolons and stray delimiters are legal here
570    /// (`expression(a;b)`, `url(data:...;base64,...)`).
571    fn parse_raw_function_args_into(
572        &mut self,
573        values: &mut oxc_allocator::Vec<'a, ComponentValue<'a>>,
574    ) -> PResult<()> {
575        let mut pairs: Vec<util::PairedToken> = Vec::with_capacity(1);
576        loop {
577            match &self.cursor.peek()?.token {
578                Token::Eof(..) => break,
579                // Interpolated strings must be parsed structurally so the
580                // tokenizer resumes the string after each `#{...}`.
581                Token::StrTemplate(..) => {
582                    values.push(ComponentValue::InterpolableStr(self.parse()?));
583                    continue;
584                }
585                token => {
586                    if !util::track_paired_token(token, &mut pairs) {
587                        break;
588                    }
589                }
590            }
591            values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
592        }
593        Ok(())
594    }
595
596    pub(super) fn parse_function_args(
597        &mut self,
598    ) -> PResult<oxc_allocator::Vec<'a, ComponentValue<'a>>> {
599        let mut values = self.vec_with_capacity(4);
600        loop {
601            match &self.cursor.peek()?.token {
602                Token::RParen(..) | Token::Eof(..) => break,
603                Token::Semicolon(..) => {
604                    values.push(self.parse().map(ComponentValue::Delimiter)?);
605                }
606                Token::Exclamation(..) if matches!(self.syntax, Syntax::Scss | Syntax::Sass) => {
607                    // while this syntax is weird, Bootstrap is actually using it
608                    values.push(self.parse().map(ComponentValue::ImportantAnnotation)?);
609                }
610                Token::LBrace(..) if self.syntax == Syntax::Less => {
611                    values.push(self.parse().map(ComponentValue::LessDetachedRuleset)?);
612                }
613                Token::Dot(..) | Token::NumberSign(..) if self.syntax == Syntax::Less => {
614                    if let Ok(mixin) = self.try_parse(Parser::parse_less_anonymous_mixin) {
615                        values.push(ComponentValue::LessAnonymousMixin(mixin));
616                    } else if let Ok(value) = self.try_parse(ComponentValue::parse) {
617                        values.push(value);
618                    } else {
619                        values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
620                    }
621                }
622                Token::Indent(..) | Token::Dedent(..) | Token::Linebreak(..) => {
623                    self.cursor.bump()?;
624                }
625                // A stray delimiter is a plain token in CSS, but the
626                // preprocessor dialects give it real syntax and their
627                // reference compilers reject it in function arguments.
628                Token::Unknown(..) if self.syntax != Syntax::Css => {
629                    let span = self.cursor.peek()?.span.clone();
630                    return Err(Error { kind: ErrorKind::UnknownToken, span });
631                }
632                _ => {
633                    let value = if let Ok(value) = self.try_parse(ComponentValue::parse) {
634                        value
635                    } else {
636                        values.push(ComponentValue::TokenWithSpan(self.cursor.bump()?));
637                        continue;
638                    };
639                    if matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
640                        if let Some((_, mut span)) = self.cursor.eat_dot_dot_dot()? {
641                            span.start = value.span().start;
642                            values.push(ComponentValue::SassArbitraryArgument(
643                                SassArbitraryArgument { value: self.alloc(value), span },
644                            ));
645                        } else if let ComponentValue::SassVariable(sass_var) = value {
646                            if let Some((_, colon_span)) = self.cursor.eat_colon()? {
647                                let value = self.parse::<ComponentValue>()?;
648                                let span =
649                                    Span { start: sass_var.span.start, end: value.span().end };
650                                values.push(ComponentValue::SassKeywordArgument(
651                                    SassKeywordArgument {
652                                        name: sass_var,
653                                        colon_span,
654                                        value: self.alloc(value),
655                                        span,
656                                    },
657                                ));
658                            } else {
659                                values.push(ComponentValue::SassVariable(sass_var));
660                            }
661                        } else {
662                            values.push(value);
663                        }
664                    } else {
665                        values.push(value);
666                    }
667                }
668            }
669        }
670        Ok(values)
671    }
672
673    pub(super) fn parse_ratio(&mut self, numerator: Number<'a>) -> PResult<Ratio<'a>> {
674        let (_, solidus_span) = self.cursor.expect_solidus()?;
675        let denominator = self.parse::<Number>()?;
676        if denominator.value <= 0.0 {
677            self.recoverable_errors.push(Error {
678                kind: ErrorKind::InvalidRatioDenominator,
679                span: denominator.span.clone(),
680            });
681        }
682
683        let span = Span { start: numerator.span.start, end: denominator.span.end };
684        Ok(Ratio { numerator, solidus_span, denominator, span })
685    }
686
687    fn parse_src_url(&mut self, name: Ident<'a>) -> PResult<Url<'a>> {
688        // caller of `parse_src_url` should make sure there're no whitespaces before paren
689        self.cursor.expect_l_paren()?;
690        let value = match &self.cursor.peek()?.token {
691            Token::Str(..) | Token::StrTemplate(..) => {
692                Some(UrlValue::Str(self.parse::<InterpolableStr>()?))
693            }
694            _ => None,
695        };
696        let modifiers = match &self.cursor.peek()?.token {
697            Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
698                let mut modifiers = self.vec_with_capacity(1);
699                loop {
700                    modifiers.push(self.parse()?);
701                    if let Token::RParen(..) = &self.cursor.peek()?.token {
702                        break;
703                    }
704                }
705                modifiers
706            }
707            _ => self.vec(),
708        };
709        let end = self.cursor.expect_r_paren()?.1.end;
710        let span = Span { start: name.span.start, end };
711        Ok(Url { name, value, modifiers, span })
712    }
713
714    fn parse_unicode_range(&mut self, prefix_ident: Ident<'a>) -> PResult<UnicodeRange<'a>> {
715        let prefix = prefix_ident.raw.chars().next().unwrap();
716        let (span_start, span_end) = match self.cursor.bump()? {
717            TokenWithSpan { token: Token::Plus(..), span: plus_token_span } => {
718                let start = plus_token_span.start;
719                let mut end = match self.cursor.tokenizer.bump_without_ws_or_comments()? {
720                    TokenWithSpan { token: Token::Ident(..) | Token::Question(..), span } => {
721                        span.end
722                    }
723                    TokenWithSpan { token, span } => {
724                        return Err(Error {
725                            kind: ErrorKind::Unexpected("?", token.symbol()),
726                            span,
727                        });
728                    }
729                };
730                loop {
731                    match self.cursor.peek()? {
732                        TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
733                            end = self.cursor.bump()?.span.end;
734                        }
735                        _ => break,
736                    }
737                }
738                (start, end)
739            }
740            TokenWithSpan { token: Token::Dimension(..), span: dimension_token_span } => {
741                let start = dimension_token_span.start;
742                let mut end = dimension_token_span.end;
743                loop {
744                    match self.cursor.peek()? {
745                        TokenWithSpan { token: Token::Question(..), span } if span.start == end => {
746                            end = self.cursor.bump()?.span.end;
747                        }
748                        _ => break,
749                    }
750                }
751                (start, end)
752            }
753            TokenWithSpan { token: Token::Number(..), span: number_token_span } => {
754                let start = number_token_span.start;
755                let mut end = number_token_span.end;
756                match &self.cursor.peek()?.token {
757                    Token::Question(..) => {
758                        end = self.cursor.bump()?.span.end;
759                        loop {
760                            match self.cursor.peek()? {
761                                TokenWithSpan { token: Token::Question(..), span }
762                                    if span.start == end =>
763                                {
764                                    end = self.cursor.bump()?.span.end;
765                                }
766                                _ => break,
767                            }
768                        }
769                    }
770                    Token::Dimension(..) | Token::Number(..) => {
771                        end = self.cursor.bump()?.span.end;
772                    }
773                    _ => {}
774                }
775                (start, end)
776            }
777            TokenWithSpan { span, .. } => {
778                return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
779            }
780        };
781
782        let source = self.source.get(span_start + 1..span_end).ok_or(Error {
783            kind: ErrorKind::InvalidUnicodeRange,
784            span: Span { start: span_start + 1, end: span_end },
785        })?;
786        let span = Span { start: prefix_ident.span.start, end: span_end };
787        let unicode_range = if let Some((left, right)) = source.split_once('-') {
788            if left.len() > 6 || !left.chars().all(|c| c.is_ascii_hexdigit()) {
789                return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
790            }
791            if right.len() > 6
792                || !right.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
793            {
794                return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
795            }
796            let start = u32::from_str_radix(left, 16)
797                .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span: span.clone() })?;
798            let end = u32::from_str_radix(&replace_unicode_range_wildcards(right, 'F'), 16)
799                .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span: span.clone() })?;
800            UnicodeRange { prefix, start, start_raw: left, end, end_raw: Some(right), span }
801        } else {
802            if source.len() > 6
803                || !source.trim_end_matches('?').chars().all(|c| c.is_ascii_hexdigit())
804            {
805                return Err(Error { kind: ErrorKind::InvalidUnicodeRange, span });
806            }
807            let start = u32::from_str_radix(&replace_unicode_range_wildcards(source, '0'), 16)
808                .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span: span.clone() })?;
809            let end = u32::from_str_radix(&replace_unicode_range_wildcards(source, 'F'), 16)
810                .map_err(|_| Error { kind: ErrorKind::InvalidUnicodeRange, span: span.clone() })?;
811            UnicodeRange { prefix, start, start_raw: source, end, end_raw: None, span }
812        };
813        // Value-level checks (end > U+10FFFF, start > end) are deliberately
814        // NOT errors: reference compilers pass such ranges through and
815        // browsers clamp/ignore them at used-value time (`U+??????`,
816        // `U+123456`, `U+1A2B3C-10FFFF` all appear in real-world corpora).
817        Ok(unicode_range)
818    }
819}
820
821fn replace_unicode_range_wildcards(source: &str, replacement: char) -> String {
822    source.chars().map(|c| if c == '?' { replacement } else { c }).collect()
823}
824
825impl<'a> Parse<'a> for BracketBlock<'a> {
826    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
827        let start = input.cursor.expect_l_bracket()?.1.start;
828        let mut value = input.vec_with_capacity(3);
829        loop {
830            match &input.cursor.peek()?.token {
831                Token::RBracket(..) => break,
832                _ => value.push(input.parse()?),
833            }
834        }
835        let end = input.cursor.expect_r_bracket()?.1.end;
836        Ok(BracketBlock { value, span: Span { start, end } })
837    }
838}
839
840impl<'a> Parse<'a> for ComponentValue<'a> {
841    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
842        match input.syntax {
843            Syntax::Css => input.parse_component_value_atom(),
844            Syntax::Scss | Syntax::Sass => {
845                input.parse_sass_bin_expr(/* allow_comparison */ true)
846            }
847            Syntax::Less => input.parse_less_operation(/* allow_mixin_call */ true),
848        }
849    }
850}
851
852impl<'a> Parse<'a> for ComponentValues<'a> {
853    /// This is for public-use only. For internal code of oxc-css-parser, **DO NOT** use.
854    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
855        let first = input.parse::<ComponentValue>()?;
856        let mut span = first.span().clone();
857
858        let mut values = input.vec_with_capacity(4);
859        values.push(first);
860        loop {
861            match &input.cursor.peek()?.token {
862                Token::Eof(..) => break,
863                Token::Semicolon(..) => {
864                    values.push(input.parse().map(ComponentValue::Delimiter)?);
865                }
866                _ => values.push(input.parse()?),
867            }
868        }
869
870        if let Some(value) = values.last() {
871            span.end = value.span().end;
872        }
873        Ok(ComponentValues { values, span })
874    }
875}
876
877impl<'a> Parse<'a> for Delimiter {
878    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
879        use crate::tokenizer::token::*;
880        match input.cursor.bump()? {
881            TokenWithSpan { token: Token::Solidus(..), span } => {
882                Ok(Delimiter { kind: DelimiterKind::Solidus, span })
883            }
884            TokenWithSpan { token: Token::Comma(..), span } => {
885                Ok(Delimiter { kind: DelimiterKind::Comma, span })
886            }
887            TokenWithSpan { token: Token::Semicolon(..), span } => {
888                Ok(Delimiter { kind: DelimiterKind::Semicolon, span })
889            }
890            _ => unreachable!(),
891        }
892    }
893}
894
895impl<'a> Parse<'a> for Dimension<'a> {
896    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
897        let (dimension, span) = input.cursor.expect_dimension()?;
898        input.dimension(dimension, span)
899    }
900}
901
902impl<'a> Parse<'a> for Function<'a> {
903    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
904        let name = input.parse::<FunctionName>()?;
905        match input.cursor.peek()? {
906            TokenWithSpan { token: Token::LParen(..), span } => {
907                util::assert_no_ws_or_comment(name.span(), span)?;
908                match name {
909                    FunctionName::Ident(name) => input.parse_function(name),
910                    name => {
911                        input.cursor.bump()?;
912                        let args = input.parse_function_args()?;
913                        let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
914                        let span = Span { start: name.span().start, end };
915                        Ok(Function { name, args, span })
916                    }
917                }
918            }
919            TokenWithSpan { token, span } => {
920                Err(Error { kind: ErrorKind::Unexpected("(", token.symbol()), span: span.clone() })
921            }
922        }
923    }
924}
925
926impl<'a> Parse<'a> for FunctionName<'a> {
927    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
928        match input.cursor.peek()?.token {
929            Token::Ident(..) => {
930                let ident = input.parse::<Ident>()?;
931                match (&input.cursor.peek()?.token, input.syntax) {
932                    (Token::Dot(..), Syntax::Scss | Syntax::Sass) => {
933                        input.cursor.bump()?;
934                        let member = input.parse::<Ident>()?;
935                        let span = Span { start: ident.span.start, end: member.span.end };
936                        Ok(FunctionName::SassQualifiedName(input.alloc(SassQualifiedName {
937                            module: ident,
938                            member: SassModuleMemberName::Ident(member),
939                            span,
940                        })))
941                    }
942                    _ => Ok(FunctionName::Ident(InterpolableIdent::Literal(ident))),
943                }
944            }
945            Token::Percent(..) if input.syntax == Syntax::Less => {
946                input.parse().map(FunctionName::LessFormatFunction)
947            }
948            Token::Tilde(..) if input.syntax == Syntax::Less => {
949                input.parse().map(FunctionName::LessListFunction)
950            }
951            _ => {
952                let TokenWithSpan { token, span } = input.cursor.bump()?;
953                Err(Error { kind: ErrorKind::Unexpected("<ident>", token.symbol()), span })
954            }
955        }
956    }
957}
958
959impl<'a> Parse<'a> for HexColor<'a> {
960    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
961        let (token, span) = input.cursor.expect_hash()?;
962        let raw = token.raw;
963        let value = if token.escaped { util::handle_escape_in(raw, input.allocator) } else { raw };
964        Ok(HexColor { value, raw, span })
965    }
966}
967
968impl<'a> Parse<'a> for Ident<'a> {
969    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
970        let (ident, span) = input.cursor.expect_ident()?;
971        Ok(input.ident(ident, span))
972    }
973}
974
975impl<'a> Parse<'a> for InterpolableIdent<'a> {
976    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
977        // A css-in-js placeholder stands in for an interpolated ident anywhere one
978        // is expected (id selector `#${x}`, attribute value `[a=${x}]`, ...).
979        if let Token::Placeholder(..) = input.cursor.peek()?.token {
980            let (placeholder, span) = input.cursor.expect_placeholder()?;
981            return Ok(InterpolableIdent::Placeholder((placeholder, span).into()));
982        }
983        match input.syntax {
984            Syntax::Css => input.parse().map(InterpolableIdent::Literal),
985            Syntax::Scss | Syntax::Sass => input.parse_sass_interpolated_ident(),
986            Syntax::Less => {
987                // Less variable interpolation is disallowed in declaration value
988                if matches!(
989                    input.state.qualified_rule_ctx,
990                    Some(QualifiedRuleContext::DeclarationValue)
991                ) {
992                    input.parse().map(InterpolableIdent::Literal)
993                } else {
994                    input.parse_less_interpolated_ident()
995                }
996            }
997        }
998    }
999}
1000
1001impl<'a> Parse<'a> for InterpolableStr<'a> {
1002    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1003        match input.cursor.peek()? {
1004            TokenWithSpan { token: Token::Str(..), .. } => {
1005                input.parse().map(InterpolableStr::Literal)
1006            }
1007            TokenWithSpan { token: Token::StrTemplate(..), span } => match input.syntax {
1008                Syntax::Scss | Syntax::Sass => input.parse().map(InterpolableStr::SassInterpolated),
1009                Syntax::Less => input.parse().map(InterpolableStr::LessInterpolated),
1010                Syntax::Css => {
1011                    Err(Error { kind: ErrorKind::UnexpectedTemplateInCss, span: span.clone() })
1012                }
1013            },
1014            TokenWithSpan { span, .. } => {
1015                Err(Error { kind: ErrorKind::ExpectString, span: span.clone() })
1016            }
1017        }
1018    }
1019}
1020
1021impl<'a> Parse<'a> for Number<'a> {
1022    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1023        let (number, span) = input.cursor.expect_number()?;
1024        number
1025            .raw
1026            .parse()
1027            .map_err(|_| Error { kind: ErrorKind::InvalidNumber, span: span.clone() })
1028            .map(|value| Self { value, raw: number.raw, span })
1029    }
1030}
1031
1032impl<'a> Parse<'a> for Percentage<'a> {
1033    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1034        let (token, span) = input.cursor.expect_percentage()?;
1035        Ok(Percentage {
1036            value: (token.value, Span { start: span.start, end: span.end - 1 }).try_into()?,
1037            span,
1038        })
1039    }
1040}
1041
1042impl<'a> Parse<'a> for Str<'a> {
1043    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1044        let (str, span) = input.cursor.expect_str()?;
1045        Ok(input.str(str, span))
1046    }
1047}
1048
1049impl<'a> Parse<'a> for Url<'a> {
1050    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1051        let (prefix, prefix_span) = input.cursor.expect_ident()?;
1052        // `url-prefix(...)` and `domain(...)` (Gecko `@document` matchers)
1053        // take the same unquoted-URL contents as `url(...)` — token-level
1054        // scanning would mis-lex `//` in `https://` as a comment.
1055        let prefix_name = prefix.name();
1056        let base_name = unvendored(&prefix_name);
1057        if !base_name.eq_ignore_ascii_case("url")
1058            && !base_name.eq_ignore_ascii_case("url-prefix")
1059            && !base_name.eq_ignore_ascii_case("domain")
1060        {
1061            return Err(Error { kind: ErrorKind::ExpectUrl, span: prefix_span });
1062        }
1063        let prefix_start = prefix_span.start;
1064        let name = input.ident(prefix, prefix_span.clone());
1065
1066        match input.cursor.peek()? {
1067            TokenWithSpan { token: Token::LParen(..), span } if prefix_span.end == span.start => {
1068                input.cursor.bump()?;
1069            }
1070            TokenWithSpan { span, .. } => {
1071                return Err(Error { kind: ErrorKind::TryParseError, span: span.clone() });
1072            }
1073        }
1074
1075        if input.cursor.tokenizer.is_start_of_url_string() {
1076            let value = input.parse()?;
1077            let modifiers = match &input.cursor.peek()?.token {
1078                Token::Ident(..) | Token::HashLBrace(..) | Token::AtLBraceVar(..) => {
1079                    let mut modifiers = input.vec_with_capacity(1);
1080                    loop {
1081                        modifiers.push(input.parse()?);
1082                        if let Token::RParen(..) = &input.cursor.peek()?.token {
1083                            break;
1084                        }
1085                    }
1086                    modifiers
1087                }
1088                _ => input.vec(),
1089            };
1090            let end = input.cursor.expect_r_paren()?.1.end;
1091            let span = Span { start: prefix_start, end };
1092            Ok(Url { name, value: Some(UrlValue::Str(value)), modifiers, span })
1093        } else if let Ok(value) = input.try_parse(UrlRaw::parse) {
1094            let span = Span {
1095                start: prefix_start,
1096                end: value.span.end + 1, // `)` is consumed, but span excludes it
1097            };
1098            Ok(Url { name, value: Some(UrlValue::Raw(value)), modifiers: input.vec(), span })
1099        } else {
1100            match input.syntax {
1101                Syntax::Css => {
1102                    Err(Error { kind: ErrorKind::InvalidUrl, span: input.cursor.bump()?.span })
1103                }
1104                Syntax::Scss | Syntax::Sass => {
1105                    let value = input.parse::<SassInterpolatedUrl>()?;
1106                    let span = Span {
1107                        start: prefix_start,
1108                        end: value.span.end + 1, // `)` is consumed, but span excludes it
1109                    };
1110                    Ok(Url {
1111                        name,
1112                        value: Some(UrlValue::SassInterpolated(value)),
1113                        modifiers: input.vec(),
1114                        span,
1115                    })
1116                }
1117                Syntax::Less => {
1118                    let value = UrlValue::LessEscapedStr(input.parse()?);
1119                    let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
1120                    let span = Span { start: prefix_start, end };
1121                    Ok(Url { name, value: Some(value), modifiers: input.vec(), span })
1122                }
1123            }
1124        }
1125    }
1126}
1127
1128impl<'a> Parse<'a> for UrlModifier<'a> {
1129    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1130        let ident = input.parse::<InterpolableIdent>()?;
1131        match input.cursor.peek()? {
1132            TokenWithSpan { token: Token::LParen(..), span } if ident.span().end == span.start => {
1133                input.parse_function(ident).map(UrlModifier::Function)
1134            }
1135            _ => Ok(UrlModifier::Ident(ident)),
1136        }
1137    }
1138}
1139
1140impl<'a> Parse<'a> for UrlRaw<'a> {
1141    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
1142        match input.cursor.tokenizer.scan_url_raw_or_template()? {
1143            TokenWithSpan { token: Token::UrlRaw(url), span } => {
1144                let value = if url.escaped {
1145                    util::handle_escape_in(url.raw, input.allocator)
1146                } else {
1147                    url.raw
1148                };
1149                Ok(UrlRaw { value, raw: url.raw, span })
1150            }
1151            TokenWithSpan { token, span } => {
1152                Err(Error { kind: ErrorKind::Unexpected("<url>", token.symbol()), span })
1153            }
1154        }
1155    }
1156}