1use super::{Parser, state::ParserState};
2use crate::{
3 Parse, Syntax,
4 ast::*,
5 error::{Error, ErrorKind, PResult},
6 pos::Span,
7 tokenizer::Token,
8};
9
10mod color_profile;
11mod container;
12mod counter_style;
13mod custom_media;
14mod custom_selector;
15mod document;
16mod font_feature_values;
17mod import;
18mod keyframes;
19mod layer;
20mod media;
21mod namespace;
22mod page;
23mod scope;
24mod supports;
25
26impl<'a> Parse<'a> for AtRule<'a> {
34 fn parse(input: &mut Parser<'a>) -> PResult<Self> {
35 let (at_keyword, at_keyword_span) = input.cursor.expect_at_keyword()?;
36
37 let at_rule_name = at_keyword.ident.name();
38 let (prelude, block, end) = if at_rule_name.eq_ignore_ascii_case("media") {
39 let prelude = match input
42 .try_parse_full_prelude(|p| MediaQueryList::parse(p).map(AtRulePrelude::Media))
43 {
44 Ok(prelude) => Some(prelude),
45 Err(_)
46 if matches!(
47 input.cursor.peek()?.token,
48 Token::LBrace(..) | Token::Indent(..)
49 ) =>
50 {
51 None
52 }
53 Err(_) => {
57 let raw = if input.syntax != Syntax::Css {
62 input.try_parse(|p| {
63 let raw = p.parse_raw_at_rule_prelude()?;
64 let has_substitution = matches!(
65 &raw,
66 UnknownAtRulePrelude::TokenSeq(seq)
67 if seq.tokens.iter().any(|t| match &t.token {
68 Token::HashLBrace(..) | Token::StrTemplate(..) => {
69 matches!(p.syntax, Syntax::Scss | Syntax::Sass)
70 }
71 Token::AtKeyword(..) | Token::AtLBraceVar(..) => {
72 p.syntax == Syntax::Less
73 }
74 _ => false,
75 })
76 );
77 if has_substitution {
78 Ok(raw)
79 } else {
80 let span = p.cursor.peek()?.span.clone();
81 Err(Error { kind: ErrorKind::TryParseError, span })
82 }
83 })
84 } else {
85 Err(Error {
86 kind: ErrorKind::TryParseError,
87 span: input.cursor.peek()?.span.clone(),
88 })
89 };
90 match raw {
91 Ok(raw) => Some(AtRulePrelude::Unknown(input.alloc(raw))),
92 Err(_) => Some(AtRulePrelude::Media(input.parse()?)),
93 }
94 }
95 };
96 let block = input.parse::<SimpleBlock>()?;
97 let end = block.span.end;
98 (prelude, Some(block), end)
99 } else if at_rule_name.eq_ignore_ascii_case("keyframes")
100 || at_rule_name.eq_ignore_ascii_case("-webkit-keyframes")
101 || at_rule_name.eq_ignore_ascii_case("-moz-keyframes")
102 || at_rule_name.eq_ignore_ascii_case("-ms-keyframes")
103 || at_rule_name.eq_ignore_ascii_case("-o-keyframes")
104 {
105 let prelude = match &input.cursor.peek()?.token {
109 Token::LBrace(..) => None,
110 _ => {
111 let typed = input.try_parse_full_prelude(KeyframesName::parse);
116 match typed {
117 Ok(name) => Some(AtRulePrelude::Keyframes(name)),
118 Err(_) => input
119 .parse_unknown_at_rule_prelude()?
120 .map(|prelude| AtRulePrelude::Unknown(input.alloc(prelude))),
121 }
122 }
123 };
124 let block = input
125 .with_state(ParserState { in_keyframes_at_rule: true, ..input.state.clone() })
126 .parse::<SimpleBlock>()?;
127 let end = block.span.end;
128 (prelude, Some(block), end)
129 } else if at_rule_name.eq_ignore_ascii_case("import") {
130 let (end, prelude) = match input.syntax {
131 Syntax::Css => {
132 let prelude = input.parse::<ImportPrelude>()?;
133 (prelude.span.end, AtRulePrelude::Import(input.alloc(prelude)))
134 }
135 Syntax::Scss | Syntax::Sass => {
136 let multi_path = input.try_parse(|parser| {
145 let prelude = parser.try_parse_full_prelude(SassImportPrelude::parse)?;
146 if prelude.paths.len() > 1 {
147 Ok(prelude)
148 } else {
149 Err(Error {
150 kind: ErrorKind::TryParseError,
151 span: prelude.span.clone(),
152 })
153 }
154 });
155 if let Ok(prelude) = multi_path {
156 (prelude.span.end, AtRulePrelude::SassImport(prelude))
157 } else if let Ok(prelude) = input.try_parse(ImportPrelude::parse) {
158 (prelude.span.end, AtRulePrelude::Import(input.alloc(prelude)))
159 } else {
160 let prelude = input.parse::<SassImportPrelude>()?;
161 (prelude.span.end, AtRulePrelude::SassImport(prelude))
162 }
163 }
164 Syntax::Less => {
165 if let Ok(prelude) = input.try_parse(ImportPrelude::parse) {
166 (prelude.span.end, AtRulePrelude::Import(input.alloc(prelude)))
167 } else {
168 let prelude = input.parse::<LessImportPrelude>()?;
169 (prelude.span.end, AtRulePrelude::LessImport(input.alloc(prelude)))
170 }
171 }
172 };
173 (Some(prelude), None, end)
174 } else if at_rule_name.eq_ignore_ascii_case("charset") {
175 if input.syntax == Syntax::Less
178 && matches!(input.cursor.peek()?.token, Token::StrTemplate(..))
179 {
180 let prelude = input.parse::<InterpolableStr>()?;
181 let end = prelude.span().end;
182 (
183 Some(AtRulePrelude::Unknown(input.alloc(
184 UnknownAtRulePrelude::ComponentValue(ComponentValue::InterpolableStr(
185 prelude,
186 )),
187 ))),
188 None,
189 end,
190 )
191 } else {
192 let prelude = input.parse::<Str>()?;
193 let end = prelude.span.end;
194 (Some(AtRulePrelude::Charset(prelude)), None, end)
195 }
196 } else if at_rule_name.eq_ignore_ascii_case("font-face") {
197 let block = input.parse::<SimpleBlock>()?;
198 let end = block.span.end;
199 (None, Some(block), end)
200 } else if at_rule_name.eq_ignore_ascii_case("supports") {
201 let prelude = Some(AtRulePrelude::Supports(input.parse()?));
202 let block = input.parse::<SimpleBlock>()?;
203 let end = block.span.end;
204 (prelude, Some(block), end)
205 } else if at_rule_name.eq_ignore_ascii_case("layer") {
206 let prelude = match input.try_parse(LayerNames::parse) {
207 Ok(names) => Some(AtRulePrelude::Layer(names)),
208 Err(_)
210 if input.syntax == Syntax::Less
211 && matches!(input.cursor.peek()?.token, Token::AtKeyword(..)) =>
212 {
213 let raw = input.parse_raw_at_rule_prelude()?;
214 Some(AtRulePrelude::Unknown(input.alloc(raw)))
215 }
216 Err(_) => None,
217 };
218 let block =
219 if matches!(input.cursor.peek()?.token, Token::LBrace(..) | Token::Indent(..)) {
220 Some(input.parse::<SimpleBlock>()?)
221 } else {
222 None
223 };
224 if let Some(block) = &block
225 && matches!(&prelude, Some(AtRulePrelude::Layer(names)) if names.names.len() > 1)
226 {
227 input.recoverable_errors.push(Error {
228 kind: ErrorKind::UnexpectedSimpleBlock,
229 span: block.span.clone(),
230 });
231 }
232 let end = block
233 .as_ref()
234 .map(|block| block.span.end)
235 .or_else(|| prelude.as_ref().map(|prelude| prelude.span().end))
236 .unwrap_or(at_keyword_span.end);
237 (prelude, block, end)
238 } else if at_rule_name.eq_ignore_ascii_case("container") {
239 let prelude =
244 match input.try_parse_full_prelude(|p| p.parse().map(AtRulePrelude::Container)) {
245 Ok(prelude) => prelude,
246 Err(error) => {
247 let is_query_list = input
248 .try_parse(|p| {
249 p.parse::<ContainerPrelude>()?;
250 if matches!(p.cursor.peek()?.token, Token::Comma(..)) {
251 Ok(())
252 } else {
253 let span = p.cursor.peek()?.span.clone();
254 Err(Error { kind: ErrorKind::TryParseError, span })
255 }
256 })
257 .is_ok();
258 let less_variable_name = input.syntax == Syntax::Less
261 && matches!(input.cursor.peek()?.token, Token::AtKeyword(..));
262 if !is_query_list && !less_variable_name {
263 return Err(error);
264 }
265 let prelude = input.parse_raw_at_rule_prelude()?;
266 AtRulePrelude::Unknown(input.alloc(prelude))
267 }
268 };
269 let block = input.parse::<SimpleBlock>()?;
270 let end = block.span.end;
271 (Some(prelude), Some(block), end)
272 } else if at_rule_name.eq_ignore_ascii_case("page") {
273 let prelude = input.try_parse(PageSelectorList::parse).map(AtRulePrelude::Page).ok();
274 let block = input.try_parse(SimpleBlock::parse).ok();
275 let end = block
276 .as_ref()
277 .map(|block| block.span.end)
278 .or_else(|| prelude.as_ref().map(|prelude| prelude.span().end))
279 .unwrap_or(at_keyword_span.end);
280 (prelude, block, end)
281 } else if at_rule_name.eq_ignore_ascii_case("namespace")
282 && input.syntax == Syntax::Less
283 && matches!(input.cursor.peek()?.token, Token::AtKeyword(..))
284 {
285 let raw = input.parse_raw_at_rule_prelude()?;
287 let end = raw.span().end;
288 (Some(AtRulePrelude::Unknown(input.alloc(raw))), None, end)
289 } else if at_rule_name.eq_ignore_ascii_case("namespace") {
290 let namespace = input.parse::<NamespacePrelude>()?;
291 let end = namespace.span.end;
292 (Some(AtRulePrelude::Namespace(input.alloc(namespace))), None, end)
293 } else if at_rule_name.eq_ignore_ascii_case("color-profile") {
294 let prelude = Some(AtRulePrelude::ColorProfile(input.parse()?));
295 let block = input.parse::<SimpleBlock>()?;
296 let end = block.span.end;
297 (prelude, Some(block), end)
298 } else if at_rule_name.eq_ignore_ascii_case("font-feature-values") {
299 let prelude = Some(AtRulePrelude::FontFeatureValues(input.parse()?));
300 let block = input.parse::<SimpleBlock>()?;
301 let end = block.span.end;
302 (prelude, Some(block), end)
303 } else if at_rule_name.eq_ignore_ascii_case("font-palette-values") {
304 let prelude = Some(AtRulePrelude::FontPaletteValues(input.parse_dashed_ident()?));
306 let block = input.parse::<SimpleBlock>()?;
307 let end = block.span.end;
308 (prelude, Some(block), end)
309 } else if at_rule_name.eq_ignore_ascii_case("counter-style") {
310 let prelude = Some(AtRulePrelude::CounterStyle(input.parse_counter_style_prelude()?));
311 let block = input.parse::<SimpleBlock>()?;
312 let end = block.span.end;
313 (prelude, Some(block), end)
314 } else if at_rule_name.eq_ignore_ascii_case("custom-media") {
315 let custom_media = input.parse::<CustomMedia>()?;
316 let end = custom_media.span.end;
317 (Some(AtRulePrelude::CustomMedia(input.alloc(custom_media))), None, end)
318 } else if at_rule_name.eq_ignore_ascii_case("custom-selector") {
319 let custom_selector_prelude = input.parse::<CustomSelectorPrelude>()?;
320 let end = custom_selector_prelude.span.end;
321 (Some(AtRulePrelude::CustomSelector(input.alloc(custom_selector_prelude))), None, end)
322 } else if at_rule_name.eq_ignore_ascii_case("position-try") {
323 let prelude = Some(AtRulePrelude::PositionTry(input.parse_dashed_ident()?));
325 let block = input.parse::<SimpleBlock>()?;
326 let end = block.span.end;
327 (prelude, Some(block), end)
328 } else if at_rule_name.eq_ignore_ascii_case("nest") {
329 let prelude = Some(AtRulePrelude::Nest(input.parse()?));
331 let block = input.parse::<SimpleBlock>()?;
332 let end = block.span.end;
333 (prelude, Some(block), end)
334 } else if at_rule_name.eq_ignore_ascii_case("property") {
335 let prelude = Some(AtRulePrelude::Property(input.parse_dashed_ident()?));
337 let block = input.parse::<SimpleBlock>()?;
338 let end = block.span.end;
339 (prelude, Some(block), end)
340 } else if at_rule_name.eq_ignore_ascii_case("scope") {
341 let prelude = if let Token::LParen(..) | Token::Ident(..) = input.cursor.peek()?.token {
342 let scope = input.parse()?;
343 Some(AtRulePrelude::Scope(input.alloc(scope)))
344 } else {
345 None
346 };
347 let block = input.parse::<SimpleBlock>()?;
348 let end = block.span.end;
349 (prelude, Some(block), end)
350 } else if at_rule_name.eq_ignore_ascii_case("document")
351 || at_rule_name.eq_ignore_ascii_case("-moz-document")
352 {
353 let prelude = match input.try_parse(|p| p.parse().map(AtRulePrelude::Document)) {
354 Ok(prelude) => prelude,
355 Err(error) => {
358 if matches!(
359 input.cursor.peek()?.token,
360 Token::LBrace(..) | Token::Indent(..) | Token::Semicolon(..)
361 ) {
362 return Err(error);
363 }
364 let prelude = input.parse_raw_at_rule_prelude()?;
365 AtRulePrelude::Unknown(input.alloc(prelude))
366 }
367 };
368 let block =
370 if matches!(input.cursor.peek()?.token, Token::LBrace(..) | Token::Indent(..)) {
371 Some(input.parse::<SimpleBlock>()?)
372 } else {
373 None
374 };
375 let end = block.as_ref().map_or(prelude.span().end, |block| block.span.end);
376 (Some(prelude), block, end)
377 } else if at_rule_name.eq_ignore_ascii_case("stylistic")
378 || at_rule_name.eq_ignore_ascii_case("historical-forms")
379 || at_rule_name.eq_ignore_ascii_case("styleset")
380 || at_rule_name.eq_ignore_ascii_case("character-variant")
381 || at_rule_name.eq_ignore_ascii_case("swash")
382 || at_rule_name.eq_ignore_ascii_case("ornaments")
383 || at_rule_name.eq_ignore_ascii_case("annotation")
384 || at_rule_name.eq_ignore_ascii_case("top-left-corner")
385 || at_rule_name.eq_ignore_ascii_case("top-left")
386 || at_rule_name.eq_ignore_ascii_case("top-center")
387 || at_rule_name.eq_ignore_ascii_case("top-right")
388 || at_rule_name.eq_ignore_ascii_case("top-right-corner")
389 || at_rule_name.eq_ignore_ascii_case("bottom-left-corner")
390 || at_rule_name.eq_ignore_ascii_case("bottom-left")
391 || at_rule_name.eq_ignore_ascii_case("bottom-center")
392 || at_rule_name.eq_ignore_ascii_case("bottom-right")
393 || at_rule_name.eq_ignore_ascii_case("bottom-right-corner")
394 || at_rule_name.eq_ignore_ascii_case("left-top")
395 || at_rule_name.eq_ignore_ascii_case("left-middle")
396 || at_rule_name.eq_ignore_ascii_case("left-bottom")
397 || at_rule_name.eq_ignore_ascii_case("right-top")
398 || at_rule_name.eq_ignore_ascii_case("right-middle")
399 || at_rule_name.eq_ignore_ascii_case("right-bottom")
400 || at_rule_name.eq_ignore_ascii_case("viewport")
401 || at_rule_name.eq_ignore_ascii_case("try")
402 || at_rule_name.eq_ignore_ascii_case("starting-style")
403 {
404 let block = input.parse::<SimpleBlock>()?;
405 let end = block.span.end;
406 (None, Some(block), end)
407 } else if at_rule_name == "plugin" && input.syntax == Syntax::Less {
408 let prelude = input.parse::<LessPlugin>()?;
409 let end = prelude.span.end;
410 (Some(AtRulePrelude::LessPlugin(input.alloc(prelude))), None, end)
411 } else if at_rule_name.eq_ignore_ascii_case("function")
412 && matches!(&input.cursor.peek()?.token, Token::Ident(ident) if ident.raw.starts_with("--"))
413 {
414 let prelude = input.parse_raw_at_rule_prelude()?;
420 let block = input
421 .with_state(ParserState { in_css_function_body: true, ..input.state.clone() })
422 .parse::<SimpleBlock>()?;
423 let end = block.span.end;
424 (Some(AtRulePrelude::Unknown(input.alloc(prelude))), Some(block), end)
425 } else if matches!(input.syntax, Syntax::Scss | Syntax::Sass) {
426 use super::state::{
427 SASS_CTX_ALLOW_DIV, SASS_CTX_ALLOW_KEYFRAME_BLOCK, SASS_CTX_IN_FUNCTION,
428 };
429 match &*at_rule_name {
430 "each" => {
431 let prelude = input.parse()?;
432 let block = input.parse::<SimpleBlock>()?;
433 let end = block.span.end;
434 (Some(AtRulePrelude::SassEach(input.alloc(prelude))), Some(block), end)
435 }
436 "while" => {
437 input.eat_sass_line_continuation()?;
438 let prelude = input.parse()?;
439 let block = input.parse::<SimpleBlock>()?;
440 let end = block.span.end;
441 (Some(AtRulePrelude::SassExpr(input.alloc(prelude))), Some(block), end)
442 }
443 "for" => {
444 let prelude = input.parse()?;
445 let block = input.parse::<SimpleBlock>()?;
446 let end = block.span.end;
447 (Some(AtRulePrelude::SassFor(input.alloc(prelude))), Some(block), end)
448 }
449 "mixin" => {
450 let prelude = input.parse()?;
451 let block = input
452 .with_state(ParserState {
453 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_KEYFRAME_BLOCK,
454 ..input.state.clone()
455 })
456 .parse::<SimpleBlock>()?;
457 let end = block.span.end;
458 (Some(AtRulePrelude::SassMixin(input.alloc(prelude))), Some(block), end)
459 }
460 "include" => {
461 let prelude = input.parse::<SassInclude>()?;
462 let block = if matches!(
463 input.cursor.peek()?.token,
464 Token::LBrace(..) | Token::Indent(..)
465 ) {
466 Some(
467 input
468 .with_state(ParserState {
469 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_KEYFRAME_BLOCK,
470 ..input.state.clone()
471 })
472 .parse::<SimpleBlock>()?,
473 )
474 } else {
475 None
476 };
477 let end =
478 block.as_ref().map(|block| block.span.end).unwrap_or(prelude.span.end);
479 (Some(AtRulePrelude::SassInclude(input.alloc(prelude))), block, end)
480 }
481 "content" => {
482 if matches!(input.cursor.peek()?.token, Token::LParen(..)) {
483 let prelude = input.parse::<SassContent>()?;
484 let end = prelude.span.end;
485 (Some(AtRulePrelude::SassContent(prelude)), None, end)
486 } else {
487 (None, None, input.cursor.tokenizer.current_offset())
488 }
489 }
490 "use" => {
491 let prelude = input.parse::<SassUse>()?;
492 let end = prelude.span.end;
493 (Some(AtRulePrelude::SassUse(input.alloc(prelude))), None, end)
494 }
495 "function" => {
496 let prelude = input.parse::<SassFunction>()?;
497 let block = input
498 .with_state(ParserState {
499 sass_ctx: input.state.sass_ctx | SASS_CTX_IN_FUNCTION,
500 ..input.state.clone()
501 })
502 .parse::<SimpleBlock>()?;
503 let end = block.span.end;
504 (Some(AtRulePrelude::SassFunction(input.alloc(prelude))), Some(block), end)
505 }
506 "return" => {
507 input.eat_sass_line_continuation()?;
508 let expr = input
509 .with_state(ParserState {
510 sass_ctx: input.state.sass_ctx | SASS_CTX_ALLOW_DIV,
511 ..input.state.clone()
512 })
513 .parse_maybe_sass_list(true)?;
514 let end = expr.span().end;
515 if input.state.sass_ctx & SASS_CTX_IN_FUNCTION == 0 {
516 input.recoverable_errors.push(Error {
517 kind: ErrorKind::ReturnOutsideFunction,
518 span: Span { start: at_keyword_span.start, end },
519 });
520 }
521 (Some(AtRulePrelude::SassExpr(input.alloc(expr))), None, end)
522 }
523 "extend" => {
524 let prelude = input.parse::<SassExtend>()?;
525 let end = prelude.span.end;
526 (Some(AtRulePrelude::SassExtend(input.alloc(prelude))), None, end)
527 }
528 "warn" | "error" | "debug" => {
529 input.eat_sass_line_continuation()?;
530 let expr = input.parse_maybe_sass_list(true)?;
531 let end = expr.span().end;
532 (Some(AtRulePrelude::SassExpr(input.alloc(expr))), None, end)
533 }
534 "forward" => {
535 let prelude = input.parse::<SassForward>()?;
536 let end = prelude.span.end;
537 (Some(AtRulePrelude::SassForward(input.alloc(prelude))), None, end)
538 }
539 "at-root" => {
540 let prelude = if !matches!(
541 input.cursor.peek()?.token,
542 Token::LBrace(..)
543 | Token::Indent(..)
544 | Token::Linebreak(..)
545 | Token::Dedent(..)
546 | Token::Eof(..)
547 ) {
548 Some(AtRulePrelude::SassAtRoot(input.parse()?))
549 } else {
550 None
551 };
552 let block = input
555 .with_state(ParserState {
556 in_keyframes_at_rule: false,
557 ..input.state.clone()
558 })
559 .parse::<SimpleBlock>()?;
560 let end = block.span.end;
561 (prelude, Some(block), end)
562 }
563 _ => {
564 let (prelude, block, end) = input.parse_unknown_at_rule()?;
565 (
566 prelude.map(|prelude| AtRulePrelude::Unknown(input.alloc(prelude))),
567 block,
568 end.unwrap_or(at_keyword_span.end),
569 )
570 }
571 }
572 } else {
573 let (prelude, block, end) = input.parse_unknown_at_rule()?;
574 (
575 prelude.map(|prelude| AtRulePrelude::Unknown(input.alloc(prelude))),
576 block,
577 end.unwrap_or(at_keyword_span.end),
578 )
579 };
580
581 let span = Span { start: at_keyword_span.start, end };
582 Ok(AtRule {
583 name: input.ident(
584 at_keyword.ident,
585 Span { start: at_keyword_span.start + 1, end: at_keyword_span.end },
586 ),
587 prelude,
588 block,
589 span,
590 })
591 }
592}
593
594impl<'a> Parser<'a> {
595 fn try_parse_full_prelude<T>(&mut self, f: impl FnOnce(&mut Self) -> PResult<T>) -> PResult<T> {
600 self.try_parse(|p| {
601 let value = f(p)?;
602 match &p.cursor.peek()?.token {
603 Token::LBrace(..)
604 | Token::Indent(..)
605 | Token::Semicolon(..)
606 | Token::Dedent(..)
607 | Token::Linebreak(..)
608 | Token::Eof(..) => Ok(value),
609 _ => {
610 let span = p.cursor.peek()?.span.clone();
611 Err(Error { kind: ErrorKind::TryParseError, span })
612 }
613 }
614 })
615 }
616
617 fn parse_raw_at_rule_prelude(&mut self) -> PResult<UnknownAtRulePrelude<'a>> {
622 let start = self.cursor.tokenizer.current_offset();
623 let mut tokens = self.vec();
624 let mut pairs: Vec<crate::util::PairedToken> = Vec::new();
625 loop {
626 match &self.cursor.peek()?.token {
627 Token::Semicolon(..)
628 | Token::Dedent(..)
629 | Token::Linebreak(..)
630 | Token::Indent(..)
631 | Token::Eof(..) => break,
632 Token::LBrace(..) if pairs.is_empty() => break,
633 Token::StrTemplate(..) => {
637 self.consume_str_template_tokens_into(&mut tokens)?;
638 continue;
639 }
640 token => {
641 if !crate::util::track_paired_token(token, &mut pairs) {
642 break;
643 }
644 }
645 }
646 tokens.push(self.cursor.bump()?);
647 }
648 let span = Span {
649 start: tokens.first().map_or(start, |token| token.span.start),
650 end: tokens.last().map_or(start, |token| token.span.end),
651 };
652 Ok(UnknownAtRulePrelude::TokenSeq(TokenSeq { tokens, span }))
653 }
654
655 pub(super) fn parse_unknown_at_rule(
659 &mut self,
660 ) -> PResult<(Option<UnknownAtRulePrelude<'a>>, Option<SimpleBlock<'a>>, Option<usize>)> {
661 let prelude = self.parse_unknown_at_rule_prelude()?;
662 let block = match &self.cursor.peek()?.token {
663 Token::LBrace(..) | Token::Indent(..) => {
667 let sass_ctx = if matches!(self.syntax, Syntax::Scss | Syntax::Sass) {
668 self.state.sass_ctx | super::state::SASS_CTX_ALLOW_KEYFRAME_BLOCK
669 } else {
670 self.state.sass_ctx
671 };
672 Some(
673 self.with_state(ParserState { sass_ctx, ..self.state.clone() })
674 .parse::<SimpleBlock>()?,
675 )
676 }
677 _ => None,
678 };
679 let end = block
680 .as_ref()
681 .map(|block| block.span.end)
682 .or_else(|| prelude.as_ref().map(|prelude| prelude.span().end));
683 Ok((prelude, block, end))
684 }
685
686 fn parse_unknown_at_rule_prelude(&mut self) -> PResult<Option<UnknownAtRulePrelude<'a>>> {
689 if let Ok(prelude) = self.try_parse(|parser| {
690 let mut tokens = parser.vec();
691 loop {
692 match &parser.cursor.peek()?.token {
693 Token::LBrace(..)
694 | Token::RBrace(..)
695 | Token::Semicolon(..)
696 | Token::Indent(..)
697 | Token::Dedent(..)
698 | Token::Linebreak(..)
699 | Token::Eof(..) => break,
700 Token::StrTemplate(..) | Token::HashLBrace(..) => {
701 return Err(Error {
702 kind: ErrorKind::TryParseError,
703 span: parser.cursor.bump()?.span,
704 });
705 }
706 _ => tokens.push(parser.cursor.bump()?),
707 }
708 }
709 if let Some((first, last)) = tokens.first().zip(tokens.last()) {
710 let span = Span { start: first.span.start, end: last.span.end };
711 Ok(Some(UnknownAtRulePrelude::TokenSeq(TokenSeq { tokens, span })))
712 } else {
713 Ok(None)
714 }
715 }) {
716 return Ok(prelude);
717 }
718
719 Ok(Some(UnknownAtRulePrelude::ComponentValue(match self.syntax {
720 Syntax::Css => self.parse()?,
721 Syntax::Scss | Syntax::Sass => {
722 self.parse_maybe_sass_list(true)?
723 }
724 Syntax::Less => self.parse_maybe_less_list(true)?,
725 })))
726 }
727}