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