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> {
12 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
13 let sass_unquoted_path = input.syntax == Syntax::Sass
17 && matches!(input.cursor.peek()?, TokenWithSpan { token: Token::Ident(..), span }
18 if input.source.as_bytes().get(span.end) != Some(&b'('));
19 let href = match &input.cursor.peek()?.token {
20 Token::Str(..) | Token::StrTemplate(..) => input.parse().map(ImportPreludeHref::Str)?,
21 Token::Ident(..) if sass_unquoted_path => {
22 let start = input.cursor.peek()?.span.start;
23 let mut end = start;
24 while matches!(
25 &input.cursor.peek()?.token,
26 Token::Ident(..) | Token::Dot(..) | Token::Minus(..) | Token::Solidus(..)
27 ) && input.cursor.peek()?.span.start == end
28 {
29 end = input.cursor.bump()?.span.end;
30 }
31 let raw = unsafe { input.source.get_unchecked(start..end) };
32 let span = Span { start, end };
33 ImportPreludeHref::Str(InterpolableStr::Literal(Str { value: raw, raw, span }))
34 }
35 _ => match input.try_parse(Url::parse) {
36 Ok(url) => ImportPreludeHref::Url(url),
37 Err(error) if matches!(input.syntax, Syntax::Scss | Syntax::Sass) => {
41 let (function_name, function_name_span) = input.cursor.expect_ident()?;
42 let function_name = input.ident(function_name, function_name_span);
43 if !function_name.name.eq_ignore_ascii_case("url") {
44 return Err(error);
45 }
46 input
47 .parse_function(InterpolableIdent::Literal(function_name))
48 .map(ImportPreludeHref::Function)
49 .map_err(|_| error)?
50 }
51 Err(error) => return Err(error),
52 },
53 };
54 let mut span = href.span().clone();
55
56 let structured = input.try_parse(Self::parse_structured_tail);
57 let (layer, supports, media, modifiers) = match structured {
58 Ok((layer, supports, media)) => (layer, supports, media, None),
59 Err(_) => {
63 let start = input.cursor.peek()?.span.start;
64 let values = input.parse_declaration_value_tokens(true)?;
65 if let Some(ComponentValue::TokenWithSpan(TokenWithSpan {
68 token: Token::Comma(..),
69 span,
70 })) = values.last()
71 {
72 return Err(Error { kind: ErrorKind::ExpectRule, span: span.clone() });
73 }
74 let end = values.last().map_or(start, |value| value.span().end);
75 (None, None, None, Some(ComponentValues { values, span: Span { start, end } }))
76 }
77 };
78 if let Some(layer) = &layer {
79 span.end = layer.span().end;
80 }
81 if let Some(supports) = &supports {
82 span.end = supports.span().end;
83 }
84 if let Some(media) = &media {
85 span.end = media.span.end;
86 }
87 if let Some(modifiers) = &modifiers
88 && modifiers.span.end > modifiers.span.start
89 {
90 span.end = modifiers.span.end;
91 }
92
93 Ok(ImportPrelude { href, layer, supports, media, modifiers, span })
94 }
95}
96
97impl<'a> ImportPrelude<'a> {
98 #[allow(clippy::type_complexity)]
102 fn parse_structured_tail(
103 input: &mut Parser<'a>,
104 ) -> PResult<(
105 Option<ImportPreludeLayer<'a>>,
106 Option<ImportPreludeSupports<'a>>,
107 Option<MediaQueryList<'a>>,
108 )> {
109 let layer = match &input.cursor.peek()?.token {
110 Token::Ident(ident) if ident.name().eq_ignore_ascii_case("layer") => {
111 let ident = input.parse::<Ident>()?;
112 let layer = match input.cursor.peek()? {
113 TokenWithSpan { token: Token::LParen(..), span }
114 if span.start == ident.span.end =>
115 {
116 input.cursor.bump()?;
117 let layer_name = input.parse().map(ComponentValue::LayerName)?;
118 let args = input.vec1(layer_name);
119 let end = input.cursor.expect_r_paren()?.1.end;
120 let span = Span { start: ident.span.start, end };
121 ImportPreludeLayer::WithName(Function {
122 name: FunctionName::Ident(InterpolableIdent::Literal(ident)),
123 args,
124 span,
125 })
126 }
127 _ => ImportPreludeLayer::Empty(ident),
128 };
129 Some(layer)
130 }
131 _ => None,
132 };
133
134 let supports = input.try_parse(|parser| {
135 let (ident, span) = parser.cursor.expect_ident()?;
137 if !ident.name().eq_ignore_ascii_case("supports") {
138 return Err(Error { kind: ErrorKind::TryParseError, span });
139 }
140
141 parser.cursor.expect_l_paren_without_ws_or_comments()?;
142
143 let kind = if let Ok(supports_condition) = parser.try_parse(SupportsCondition::parse) {
144 ImportPreludeSupportsKind::SupportsCondition(supports_condition)
145 } else {
146 parser.parse().map(ImportPreludeSupportsKind::Declaration)?
147 };
148 let (_, Span { end, .. }) = parser.cursor.expect_r_paren()?;
149 Ok(ImportPreludeSupports { kind, span: Span { start: span.start, end } })
150 });
151 let media = if at_import_prelude_end(&input.cursor.peek()?.token) {
154 None
155 } else {
156 Some(input.parse::<MediaQueryList>()?)
157 };
158
159 if at_import_prelude_end(&input.cursor.peek()?.token) {
161 Ok((layer, supports.ok(), media))
162 } else {
163 let span = input.cursor.peek()?.span.clone();
164 Err(Error { kind: ErrorKind::TryParseError, span })
165 }
166 }
167}
168
169fn at_import_prelude_end(token: &Token) -> bool {
171 matches!(
172 token,
173 Token::Semicolon(..)
174 | Token::Eof(..)
175 | Token::RBrace(..)
176 | Token::Dedent(..)
177 | Token::Linebreak(..)
178 )
179}