oxc_css_parser/parser/at_rule/
import.rs1use super::Parser;
2use crate::{
3 Parse, Syntax,
4 ast::*,
5 error::{Error, ErrorKind, PResult},
6 pos::Span,
7 tokenizer::{Token, TokenWithSpan},
8};
9
10impl<'a> Parse<'a> for ImportPrelude<'a> {
18 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
19 let sass_unquoted_path = input.syntax == Syntax::Sass
23 && matches!(input.cursor.peek()?, TokenWithSpan { token: Token::Ident(..), span }
24 if input.source.as_bytes().get(span.end) != Some(&b'('));
25 let href = match &input.cursor.peek()?.token {
26 Token::Str(..) | Token::StrTemplate(..) => input.parse().map(ImportPreludeHref::Str)?,
27 Token::Ident(..) if sass_unquoted_path => {
28 let start = input.cursor.peek()?.span.start;
29 let mut end = start;
30 while matches!(
31 &input.cursor.peek()?.token,
32 Token::Ident(..) | Token::Dot(..) | Token::Minus(..) | Token::Solidus(..)
33 ) && input.cursor.peek()?.span.start == end
34 {
35 end = input.cursor.bump()?.span.end;
36 }
37 let raw = unsafe { input.source.get_unchecked(start..end) };
38 let span = Span { start, end };
39 ImportPreludeHref::Str(InterpolableStr::Literal(Str { value: raw, raw, span }))
40 }
41 _ => match input.try_parse(Url::parse) {
42 Ok(url) => ImportPreludeHref::Url(url),
43 Err(mut error) => {
44 if matches!(error.kind, ErrorKind::TryParseError) {
48 error.kind = ErrorKind::ExpectUrl;
49 }
50 if !matches!(input.syntax, Syntax::Scss | Syntax::Sass) {
55 return Err(error);
56 }
57 let (function_name, function_name_span) = input.cursor.expect_ident()?;
58 let function_name = input.ident(function_name, function_name_span);
59 if !function_name.name.eq_ignore_ascii_case("url") {
60 return Err(error);
61 }
62 input
63 .parse_function(InterpolableIdent::Literal(function_name))
64 .map(ImportPreludeHref::Function)
65 .map_err(|_| error)?
66 }
67 },
68 };
69 let mut span = *href.span();
70
71 let structured = input.try_parse(Self::parse_structured_tail);
72 let (layer, supports, media, modifiers) = match structured {
73 Ok((layer, supports, media)) => (layer, supports, media, None),
74 Err(_) => {
81 let start = input.cursor.peek()?.span.start;
82 let values = input.parse_declaration_value_tokens(true)?;
83 if let Some(ComponentValue::TokenWithSpan(TokenWithSpan {
86 token: Token::Comma(..),
87 span,
88 })) = values.last()
89 {
90 return Err(Error { kind: ErrorKind::ExpectRule, span: *span });
91 }
92 let end = values.last().map_or(start, |value| value.span().end);
93 (None, None, None, Some(ComponentValues { values, span: Span { start, end } }))
94 }
95 };
96 if let Some(layer) = &layer {
97 span.end = layer.span().end;
98 }
99 if let Some(supports) = &supports {
100 span.end = supports.span().end;
101 }
102 if let Some(media) = &media {
103 span.end = media.span.end;
104 }
105 if let Some(modifiers) = &modifiers
106 && modifiers.span.end > modifiers.span.start
107 {
108 span.end = modifiers.span.end;
109 }
110
111 Ok(ImportPrelude { href, layer, supports, media, modifiers, span })
112 }
113}
114
115impl<'a> ImportPrelude<'a> {
116 #[allow(clippy::type_complexity)]
120 fn parse_structured_tail(
121 input: &mut Parser<'a>,
122 ) -> PResult<(
123 Option<ImportPreludeLayer<'a>>,
124 Option<ImportPreludeSupports<'a>>,
125 Option<MediaQueryList<'a>>,
126 )> {
127 let layer =
128 if input.cursor.peek()?.is_ident_name_eq_ignore_ascii_case(input.source, "layer") {
129 let ident = input.parse::<Ident>()?;
130 let layer = match input.cursor.peek()? {
131 TokenWithSpan { token: Token::LParen(..), span }
132 if span.start == ident.span.end =>
133 {
134 input.cursor.bump()?;
135 let layer_name = input.parse().map(ComponentValue::LayerName)?;
136 let args = input.vec1(layer_name);
137 let end = input.cursor.expect_r_paren()?.1.end;
138 let span = Span { start: ident.span.start, end };
139 ImportPreludeLayer::WithName(Function {
140 name: FunctionName::Ident(InterpolableIdent::Literal(ident)),
141 args,
142 span,
143 })
144 }
145 _ => ImportPreludeLayer::Empty(ident),
146 };
147 Some(layer)
148 } else {
149 None
150 };
151
152 let supports = input.try_parse(|parser| {
153 let (ident, span) = parser.cursor.expect_ident()?;
155 if !ident.name().eq_ignore_ascii_case("supports") {
156 return Err(Error { kind: ErrorKind::TryParseError, span });
157 }
158
159 parser.cursor.expect_l_paren_without_ws_or_comments()?;
160
161 let kind = if let Ok(supports_condition) = parser.try_parse(SupportsCondition::parse) {
162 ImportPreludeSupportsKind::SupportsCondition(supports_condition)
163 } else {
164 parser.parse().map(ImportPreludeSupportsKind::Declaration)?
165 };
166 let (_, Span { end, .. }) = parser.cursor.expect_r_paren()?;
167 Ok(ImportPreludeSupports { kind, span: Span { start: span.start, end } })
168 });
169 let media = if at_import_prelude_end(&input.cursor.peek()?.token) {
172 None
173 } else {
174 Some(input.parse::<MediaQueryList>()?)
175 };
176
177 if at_import_prelude_end(&input.cursor.peek()?.token) {
179 Ok((layer, supports.ok(), media))
180 } else {
181 let span = input.cursor.peek()?.span;
182 Err(Error { kind: ErrorKind::TryParseError, span })
183 }
184 }
185}
186
187fn at_import_prelude_end(token: &Token) -> bool {
189 matches!(
190 token,
191 Token::Semicolon(..)
192 | Token::Eof(..)
193 | Token::RBrace(..)
194 | Token::Dedent(..)
195 | Token::Linebreak(..)
196 )
197}