Skip to main content

oxc_css_parser/parser/at_rule/
scope.rs

1use super::Parser;
2use crate::{
3    Parse,
4    ast::*,
5    error::{Error, ErrorKind, PResult},
6    pos::Span,
7    tokenizer::{Token, TokenWithSpan},
8};
9
10// to ( <scope-end> ) where <scope-end> = <selector-list>
11impl<'a> Parse<'a> for ScopeEnd<'a> {
12    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
13        let token = input.cursor.bump()?;
14        let to_span = if token.is_ident_name_eq_ignore_ascii_case(input.source, "to") {
15            token.span
16        } else {
17            return Err(Error { kind: ErrorKind::ExpectScopeTo, span: token.span });
18        };
19
20        let (_, lparen_span) = input.cursor.expect_l_paren()?;
21        // An empty scope root/limit (`@scope ()`, `to ()`) is accepted:
22        // <scope-start>/<scope-end> are forgiving selector lists, and
23        // lightningcss emits this form when every selector is dropped.
24        let selector = if let Token::RParen(..) = input.cursor.peek()?.token {
25            None
26        } else {
27            Some(input.parse()?)
28        };
29        let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
30
31        let span = Span { start: to_span.start, end };
32        Ok(ScopeEnd { to_span, lparen_span, selector, span })
33    }
34}
35
36// https://drafts.csswg.org/css-cascade-6/#scope-syntax
37//
38// @scope [ ( <scope-start> ) ]? [ to ( <scope-end> ) ]? { <block-contents> }
39// <scope-start> = <selector-list>
40// <scope-end>   = <selector-list>
41impl<'a> Parse<'a> for ScopePrelude<'a> {
42    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
43        let start = if let Token::LParen(..) = input.cursor.peek()?.token {
44            Some(input.parse::<ScopeStart>()?)
45        } else {
46            None
47        };
48        let end = if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "to") {
49            Some(input.parse::<ScopeEnd>()?)
50        } else {
51            None
52        };
53
54        match (start, end) {
55            (Some(start), Some(end)) => {
56                let span = Span { start: start.span.start, end: end.span.end };
57                Ok(ScopePrelude::Both(ScopeStartWithEnd { start, end, span }))
58            }
59            (Some(start), None) => Ok(ScopePrelude::StartOnly(start)),
60            (None, Some(end)) => Ok(ScopePrelude::EndOnly(end)),
61            (None, None) => {
62                let TokenWithSpan { token, span } = input.cursor.bump()?;
63                Err(Error { kind: ErrorKind::Unexpected("(", token.symbol()), span })
64            }
65        }
66    }
67}
68
69// ( <scope-start> ) where <scope-start> = <selector-list>
70impl<'a> Parse<'a> for ScopeStart<'a> {
71    fn parse(input: &mut Parser<'a>) -> PResult<Self> {
72        let (_, Span { start, .. }) = input.cursor.expect_l_paren()?;
73        // See `ScopeEnd`: an empty `@scope ()` root is accepted.
74        let selector = if let Token::RParen(..) = input.cursor.peek()?.token {
75            None
76        } else {
77            Some(input.parse()?)
78        };
79        let (_, Span { end, .. }) = input.cursor.expect_r_paren()?;
80
81        Ok(ScopeStart { selector, span: Span { start, end } })
82    }
83}