1use super::{Parser, state::ParserState};
2use crate::{
3 Parse, Syntax, arena_box,
4 ast::*,
5 bump,
6 error::{Error, ErrorKind, PResult},
7 expect, peek,
8 pos::{Span, Spanned},
9 tokenizer::Token,
10};
11
12mod color_profile;
13mod container;
14mod counter_style;
15mod custom_media;
16mod custom_selector;
17mod document;
18mod font_feature_values;
19mod import;
20mod keyframes;
21mod layer;
22mod media;
23mod namespace;
24mod page;
25mod scope;
26mod supports;
27
28impl<'a> Parse<'a> for AtRule<'a> {
29 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
30 let (at_keyword, at_keyword_span) = expect!(input, AtKeyword);
31
32 let at_rule_name = at_keyword.ident.name();
33 let (prelude, block, end) = if at_rule_name.eq_ignore_ascii_case("media") {
34 let prelude = input.try_parse(MediaQueryList::parse).ok().map(AtRulePrelude::Media);
35 let block = input.parse::<SimpleBlock>()?;
36 let end = block.span.end;
37 (prelude, Some(block), end)
38 } else if at_rule_name.eq_ignore_ascii_case("keyframes")
39 || at_rule_name.eq_ignore_ascii_case("-webkit-keyframes")
40 || at_rule_name.eq_ignore_ascii_case("-moz-keyframes")
41 || at_rule_name.eq_ignore_ascii_case("-ms-keyframes")
42 || at_rule_name.eq_ignore_ascii_case("-o-keyframes")
43 {
44 let prelude = match &peek!(input).token {
48 Token::LBrace(..) => None,
49 _ => Some(AtRulePrelude::Keyframes(input.parse()?)),
50 };
51 let block = input
52 .with_state(ParserState { in_keyframes_at_rule: true, ..input.state.clone() })
53 .parse::<SimpleBlock>()?;
54 let end = block.span.end;
55 (prelude, Some(block), end)
56 } else if at_rule_name.eq_ignore_ascii_case("import") {
57 let (end, prelude) = match input.syntax {
58 Syntax::Css => {
59 let prelude = input.parse::<ImportPrelude>()?;
60 (prelude.span.end, AtRulePrelude::Import(arena_box!(input, prelude)))
61 }
62 Syntax::Scss | Syntax::Sass => {
63 if let Ok(prelude) = input.try_parse(ImportPrelude::parse) {
64 (prelude.span.end, AtRulePrelude::Import(arena_box!(input, prelude)))
65 } else {
66 let prelude = input.parse::<SassImportPrelude>()?;
67 (prelude.span.end, AtRulePrelude::SassImport(prelude))
68 }
69 }
70 Syntax::Less => {
71 if let Ok(prelude) = input.try_parse(ImportPrelude::parse) {
72 (prelude.span.end, AtRulePrelude::Import(arena_box!(input, prelude)))
73 } else {
74 let prelude = input.parse::<LessImportPrelude>()?;
75 (prelude.span.end, AtRulePrelude::LessImport(arena_box!(input, prelude)))
76 }
77 }
78 };
79 (Some(prelude), None, end)
80 } else if at_rule_name.eq_ignore_ascii_case("charset") {
81 let prelude = input.parse::<Str>()?;
83 let end = prelude.span.end;
84 (Some(AtRulePrelude::Charset(prelude)), None, end)
85 } else if at_rule_name.eq_ignore_ascii_case("font-face") {
86 let block = input.parse::<SimpleBlock>()?;
87 let end = block.span.end;
88 (None, Some(block), end)
89 } else if at_rule_name.eq_ignore_ascii_case("supports") {
90 let prelude = Some(AtRulePrelude::Supports(input.parse()?));
91 let block = input.parse::<SimpleBlock>()?;
92 let end = block.span.end;
93 (prelude, Some(block), end)
94 } else if at_rule_name.eq_ignore_ascii_case("layer") {
95 let prelude = input.try_parse(LayerNames::parse).ok();
96 let block = if matches!(peek!(input).token, Token::LBrace(..) | Token::Indent(..)) {
97 Some(input.parse::<SimpleBlock>()?)
98 } else {
99 None
100 };
101 if let Some(block) = &block
102 && prelude.as_ref().is_some_and(|prelude| prelude.names.len() > 1)
103 {
104 input.recoverable_errors.push(Error {
105 kind: ErrorKind::UnexpectedSimpleBlock,
106 span: block.span.clone(),
107 });
108 }
109 let end = block
110 .as_ref()
111 .map(|block| block.span.end)
112 .or_else(|| prelude.as_ref().map(|prelude| prelude.span.end))
113 .unwrap_or(at_keyword_span.end);
114 (prelude.map(AtRulePrelude::Layer), block, end)
115 } else if at_rule_name.eq_ignore_ascii_case("container") {
116 let prelude = Some(AtRulePrelude::Container(input.parse()?));
117 let block = input.parse::<SimpleBlock>()?;
118 let end = block.span.end;
119 (prelude, Some(block), end)
120 } else if at_rule_name.eq_ignore_ascii_case("page") {
121 let prelude = input.try_parse(PageSelectorList::parse).map(AtRulePrelude::Page).ok();
122 let block = input.try_parse(SimpleBlock::parse).ok();
123 let end = block
124 .as_ref()
125 .map(|block| block.span.end)
126 .or_else(|| prelude.as_ref().map(|prelude| prelude.span().end))
127 .unwrap_or(at_keyword_span.end);
128 (prelude, block, end)
129 } else if at_rule_name.eq_ignore_ascii_case("namespace") {
130 let namespace = input.parse::<NamespacePrelude>()?;
131 let end = namespace.span.end;
132 (Some(AtRulePrelude::Namespace(arena_box!(input, namespace))), None, end)
133 } else if at_rule_name.eq_ignore_ascii_case("color-profile") {
134 let prelude = Some(AtRulePrelude::ColorProfile(input.parse()?));
135 let block = input.parse::<SimpleBlock>()?;
136 let end = block.span.end;
137 (prelude, Some(block), end)
138 } else if at_rule_name.eq_ignore_ascii_case("font-feature-values") {
139 let prelude = Some(AtRulePrelude::FontFeatureValues(input.parse()?));
140 let block = input.parse::<SimpleBlock>()?;
141 let end = block.span.end;
142 (prelude, Some(block), end)
143 } else if at_rule_name.eq_ignore_ascii_case("font-palette-values") {
144 let prelude = Some(AtRulePrelude::FontPaletteValues(input.parse_dashed_ident()?));
146 let block = input.parse::<SimpleBlock>()?;
147 let end = block.span.end;
148 (prelude, Some(block), end)
149 } else if at_rule_name.eq_ignore_ascii_case("counter-style") {
150 let prelude = Some(AtRulePrelude::CounterStyle(input.parse_counter_style_prelude()?));
151 let block = input.parse::<SimpleBlock>()?;
152 let end = block.span.end;
153 (prelude, Some(block), end)
154 } else if at_rule_name.eq_ignore_ascii_case("custom-media") {
155 let custom_media = input.parse::<CustomMedia>()?;
156 let end = custom_media.span.end;
157 (Some(AtRulePrelude::CustomMedia(arena_box!(input, custom_media))), None, end)
158 } else if at_rule_name.eq_ignore_ascii_case("custom-selector") {
159 let custom_selector_prelude = input.parse::<CustomSelectorPrelude>()?;
160 let end = custom_selector_prelude.span.end;
161 (
162 Some(AtRulePrelude::CustomSelector(arena_box!(input, custom_selector_prelude))),
163 None,
164 end,
165 )
166 } else if at_rule_name.eq_ignore_ascii_case("position-try") {
167 let prelude = Some(AtRulePrelude::PositionTry(input.parse_dashed_ident()?));
169 let block = input.parse::<SimpleBlock>()?;
170 let end = block.span.end;
171 (prelude, Some(block), end)
172 } else if at_rule_name.eq_ignore_ascii_case("nest") {
173 let prelude = Some(AtRulePrelude::Nest(input.parse()?));
175 let block = input.parse::<SimpleBlock>()?;
176 let end = block.span.end;
177 (prelude, Some(block), end)
178 } else if at_rule_name.eq_ignore_ascii_case("property") {
179 let prelude = Some(AtRulePrelude::Property(input.parse_dashed_ident()?));
181 let block = input.parse::<SimpleBlock>()?;
182 let end = block.span.end;
183 (prelude, Some(block), end)
184 } else if at_rule_name.eq_ignore_ascii_case("scope") {
185 let prelude = if let Token::LParen(..) | Token::Ident(..) = peek!(input).token {
186 Some(AtRulePrelude::Scope(arena_box!(input, input.parse()?)))
187 } else {
188 None
189 };
190 let block = input.parse::<SimpleBlock>()?;
191 let end = block.span.end;
192 (prelude, Some(block), end)
193 } else if at_rule_name.eq_ignore_ascii_case("document")
194 || at_rule_name.eq_ignore_ascii_case("-moz-document")
195 {
196 let prelude = Some(AtRulePrelude::Document(input.parse()?));
197 let block = input.parse::<SimpleBlock>()?;
198 let end = block.span.end;
199 (prelude, Some(block), end)
200 } else if at_rule_name.eq_ignore_ascii_case("stylistic")
201 || at_rule_name.eq_ignore_ascii_case("historical-forms")
202 || at_rule_name.eq_ignore_ascii_case("styleset")
203 || at_rule_name.eq_ignore_ascii_case("character-variant")
204 || at_rule_name.eq_ignore_ascii_case("swash")
205 || at_rule_name.eq_ignore_ascii_case("ornaments")
206 || at_rule_name.eq_ignore_ascii_case("annotation")
207 || at_rule_name.eq_ignore_ascii_case("top-left-corner")
208 || at_rule_name.eq_ignore_ascii_case("top-left")
209 || at_rule_name.eq_ignore_ascii_case("top-center")
210 || at_rule_name.eq_ignore_ascii_case("top-right")
211 || at_rule_name.eq_ignore_ascii_case("top-right-corner")
212 || at_rule_name.eq_ignore_ascii_case("bottom-left-corner")
213 || at_rule_name.eq_ignore_ascii_case("bottom-left")
214 || at_rule_name.eq_ignore_ascii_case("bottom-center")
215 || at_rule_name.eq_ignore_ascii_case("bottom-right")
216 || at_rule_name.eq_ignore_ascii_case("bottom-right-corner")
217 || at_rule_name.eq_ignore_ascii_case("left-top")
218 || at_rule_name.eq_ignore_ascii_case("left-middle")
219 || at_rule_name.eq_ignore_ascii_case("left-bottom")
220 || at_rule_name.eq_ignore_ascii_case("right-top")
221 || at_rule_name.eq_ignore_ascii_case("right-middle")
222 || at_rule_name.eq_ignore_ascii_case("right-bottom")
223 || at_rule_name.eq_ignore_ascii_case("viewport")
224 || at_rule_name.eq_ignore_ascii_case("try")
225 || at_rule_name.eq_ignore_ascii_case("starting-style")
226 {
227 let block = input.parse::<SimpleBlock>()?;
228 let end = block.span.end;
229 (None, Some(block), end)
230 } else if at_rule_name == "plugin" && input.syntax == Syntax::Less {
231 let prelude = input.parse::<LessPlugin>()?;
232 let end = prelude.span.end;
233 (Some(AtRulePrelude::LessPlugin(arena_box!(input, prelude))), None, end)
234 } else if matches!(input.syntax, Syntax::Scss | Syntax::Sass) {
235 use super::state::{
236 SASS_CTX_ALLOW_DIV, SASS_CTX_ALLOW_KEYFRAME_BLOCK, SASS_CTX_IN_FUNCTION,
237 };
238 match &*at_rule_name {
239 "each" => {
240 let prelude = input.parse()?;
241 let block = input.parse::<SimpleBlock>()?;
242 let end = block.span.end;
243 (Some(AtRulePrelude::SassEach(arena_box!(input, prelude))), Some(block), end)
244 }
245 "while" => {
246 let prelude = input.parse()?;
247 let block = input.parse::<SimpleBlock>()?;
248 let end = block.span.end;
249 (Some(AtRulePrelude::SassExpr(arena_box!(input, prelude))), Some(block), end)
250 }
251 "for" => {
252 let prelude = input.parse()?;
253 let block = input.parse::<SimpleBlock>()?;
254 let end = block.span.end;
255 (Some(AtRulePrelude::SassFor(arena_box!(input, prelude))), Some(block), end)
256 }
257 "mixin" => {
258 let prelude = input.parse()?;
259 let block = input
260 .with_state(ParserState {
261 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_KEYFRAME_BLOCK,
262 ..input.state.clone()
263 })
264 .parse::<SimpleBlock>()?;
265 let end = block.span.end;
266 (Some(AtRulePrelude::SassMixin(arena_box!(input, prelude))), Some(block), end)
267 }
268 "include" => {
269 let prelude = input.parse::<SassInclude>()?;
270 let block =
271 if matches!(peek!(input).token, Token::LBrace(..) | Token::Indent(..)) {
272 Some(
273 input
274 .with_state(ParserState {
275 sass_ctx: input.state.sass_ctx
276 | SASS_CTX_ALLOW_KEYFRAME_BLOCK,
277 ..input.state.clone()
278 })
279 .parse::<SimpleBlock>()?,
280 )
281 } else {
282 None
283 };
284 let end =
285 block.as_ref().map(|block| block.span.end).unwrap_or(prelude.span.end);
286 (Some(AtRulePrelude::SassInclude(arena_box!(input, prelude))), block, end)
287 }
288 "content" => {
289 if matches!(peek!(input).token, Token::LParen(..)) {
290 let prelude = input.parse::<SassContent>()?;
291 let end = prelude.span.end;
292 (Some(AtRulePrelude::SassContent(prelude)), None, end)
293 } else {
294 (None, None, input.tokenizer.current_offset())
295 }
296 }
297 "use" => {
298 let prelude = input.parse::<SassUse>()?;
299 let end = prelude.span.end;
300 (Some(AtRulePrelude::SassUse(arena_box!(input, prelude))), None, end)
301 }
302 "function" => {
303 let prelude = input.parse::<SassFunction>()?;
304 let block = input
305 .with_state(ParserState {
306 sass_ctx: input.state.sass_ctx | SASS_CTX_IN_FUNCTION,
307 ..input.state.clone()
308 })
309 .parse::<SimpleBlock>()?;
310 let end = block.span.end;
311 (
312 Some(AtRulePrelude::SassFunction(arena_box!(input, prelude))),
313 Some(block),
314 end,
315 )
316 }
317 "return" => {
318 let expr = input
319 .with_state(ParserState {
320 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV,
321 ..input.state.clone()
322 })
323 .parse_maybe_sass_list(true)?;
324 let end = expr.span().end;
325 if input.state.sass_ctx & SASS_CTX_IN_FUNCTION == 0 {
326 input.recoverable_errors.push(Error {
327 kind: ErrorKind::ReturnOutsideFunction,
328 span: Span { start: at_keyword_span.start, end },
329 });
330 }
331 (Some(AtRulePrelude::SassExpr(arena_box!(input, expr))), None, end)
332 }
333 "extend" => {
334 let prelude = input.parse::<SassExtend>()?;
335 let end = prelude.span.end;
336 (Some(AtRulePrelude::SassExtend(arena_box!(input, prelude))), None, end)
337 }
338 "warn" | "error" | "debug" => {
339 let expr = input.parse_maybe_sass_list(true)?;
340 let end = expr.span().end;
341 (Some(AtRulePrelude::SassExpr(arena_box!(input, expr))), None, end)
342 }
343 "forward" => {
344 let prelude = input.parse::<SassForward>()?;
345 let end = prelude.span.end;
346 (Some(AtRulePrelude::SassForward(arena_box!(input, prelude))), None, end)
347 }
348 "at-root" => {
349 let prelude =
350 if !matches!(peek!(input).token, Token::LBrace(..) | Token::Indent(..)) {
351 Some(AtRulePrelude::SassAtRoot(input.parse()?))
352 } else {
353 None
354 };
355 let block = input.parse::<SimpleBlock>()?;
356 let end = block.span.end;
357 (prelude, Some(block), end)
358 }
359 _ => {
360 let (prelude, block, end) = input.parse_unknown_at_rule()?;
361 (
362 prelude.map(|prelude| AtRulePrelude::Unknown(arena_box!(input, prelude))),
363 block,
364 end.unwrap_or(at_keyword_span.end),
365 )
366 }
367 }
368 } else {
369 let (prelude, block, end) = input.parse_unknown_at_rule()?;
370 (
371 prelude.map(|prelude| AtRulePrelude::Unknown(arena_box!(input, prelude))),
372 block,
373 end.unwrap_or(at_keyword_span.end),
374 )
375 };
376
377 let span = Span { start: at_keyword_span.start, end };
378 Ok(AtRule {
379 name: input.ident(
380 at_keyword.ident,
381 Span { start: at_keyword_span.start + 1, end: at_keyword_span.end },
382 ),
383 prelude,
384 block,
385 span,
386 })
387 }
388}
389
390impl<'a> Parser<'a> {
391 pub(super) fn parse_unknown_at_rule(
392 &mut self,
393 ) -> PResult<(Option<UnknownAtRulePrelude<'a>>, Option<SimpleBlock<'a>>, Option<usize>)> {
394 let prelude = self.parse_unknown_at_rule_prelude()?;
395 let block = match &peek!(self).token {
396 Token::LBrace(..) | Token::Indent(..) => Some(self.parse::<SimpleBlock>()?),
397 _ => None,
398 };
399 let end = block
400 .as_ref()
401 .map(|block| block.span.end)
402 .or_else(|| prelude.as_ref().map(|prelude| prelude.span().end));
403 Ok((prelude, block, end))
404 }
405
406 fn parse_unknown_at_rule_prelude(&mut self) -> PResult<Option<UnknownAtRulePrelude<'a>>> {
407 if let Ok(prelude) = self.try_parse(|parser| {
408 let mut tokens = parser.vec();
409 loop {
410 match &peek!(parser).token {
411 Token::LBrace(..)
412 | Token::RBrace(..)
413 | Token::Semicolon(..)
414 | Token::Indent(..)
415 | Token::Dedent(..)
416 | Token::Linebreak(..)
417 | Token::Eof(..) => break,
418 Token::StrTemplate(..) | Token::HashLBrace(..) => {
419 return Err(Error {
420 kind: ErrorKind::TryParseError,
421 span: bump!(parser).span,
422 });
423 }
424 _ => tokens.push(bump!(parser)),
425 }
426 }
427 if let Some((first, last)) = tokens.first().zip(tokens.last()) {
428 let span = Span { start: first.span().start, end: last.span().end };
429 Ok(Some(UnknownAtRulePrelude::TokenSeq(TokenSeq { tokens, span })))
430 } else {
431 Ok(None)
432 }
433 }) {
434 return Ok(prelude);
435 }
436
437 Ok(Some(UnknownAtRulePrelude::ComponentValue(match self.syntax {
438 Syntax::Css => self.parse()?,
439 Syntax::Scss | Syntax::Sass => {
440 self.parse_maybe_sass_list(true)?
441 }
442 Syntax::Less => self.parse_maybe_less_list(true)?,
443 })))
444 }
445}