css_parse/syntax/
at_rule.rs

1use crate::{
2	Cursor, CursorSink, Parse, Parser, Peek, Result as ParserResult, Span, T, ToCursors, ToSpan, token_macros,
3};
4use std::marker::PhantomData;
5
6/// This struct provides the generic [`<at-rule>` grammar][1]. It will [consume an at-rule][2]. This is defined as:
7///
8/// ```md
9/// <at-rule>
10///  │├─ <AT> ─ <P> ─ <B> ─╭───────╮┤│
11///                        ╰─ ";" ─╯
12/// ```
13///
14/// `<AT>` must implement [Peek],  [Parse], and `Into<token_macros::AtKeyword>`. This helps enforce that this is an
15/// at-rule, that the first token has to be a specific AtKeyword.
16///
17/// `<P>` - the prelude - must implement [Parse], [ToCursors], and [ToSpan]. To make the prelude optional simply use an
18/// [Option]. To enforce no prelude the [NoPreludeAllowed][crate::NoPreludeAllowed] type can be used. Non-optional
19/// types are considered required.
20///
21/// `<B>` - the block - must implement [Parse], [ToCursors], and [ToSpan]. To make the block optional simply use an
22/// [Option]. To enforce no block the [NoBlockAllowed][crate::NoBlockAllowed] type can be used. Non-optional types are
23/// considered required. Ideally the block should implement one of [Block][crate::Block],
24/// [DeclarationList][crate::DeclarationList], or [DeclarationRuleList][crate::DeclarationRuleList].
25///
26/// A generic AtRule could be `AtRule<T![AtKeyword], ComponentValues<'a>, SimpleBlock>`.
27///
28/// To specify extra restrictions on the value of the at-keyword, use [atkeyword_set][crate::atkeyword_set].
29///
30/// # Example
31///
32/// ```
33/// use css_parse::*;
34///
35/// /// A grammar like `@test foo {}`
36/// #[derive(Debug)]
37/// pub struct TestAtRule<'a>(AtRule<T![AtKeyword], T![Ident], SimpleBlock<'a>>);
38///
39/// impl<'a> Parse<'a> for TestAtRule<'a> {
40///     fn parse(p: &mut Parser<'a>) -> Result<Self> {
41///         Ok(Self(p.parse::<AtRule<T![AtKeyword], T![Ident], SimpleBlock<'a>>>()?))
42///     }
43/// }
44///
45/// impl ToCursors for TestAtRule<'_> {
46///     fn to_cursors(&self, s: &mut impl CursorSink) {
47///         self.0.to_cursors(s);
48///     }
49/// }
50///
51/// assert_parse!(TestAtRule, "@test foo{}");
52/// ```
53///
54///
55/// [1]: https://drafts.csswg.org/css-syntax-3/#at-rule-diagram
56/// [2]: https://drafts.csswg.org/css-syntax-3/#consume-an-at-rule
57#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
59pub struct AtRule<AT, P, B>
60where
61	AT: Into<token_macros::AtKeyword>,
62{
63	pub name: token_macros::AtKeyword,
64	pub prelude: P,
65	pub block: B,
66	pub semicolon: Option<token_macros::Semicolon>,
67	#[cfg_attr(feature = "serde", serde(skip))]
68	_phantom: PhantomData<AT>,
69}
70
71impl<'a, AT, P, B> Peek<'a> for AtRule<AT, P, B>
72where
73	AT: Peek<'a> + Into<token_macros::AtKeyword>,
74{
75	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
76		<AT>::peek(p, c)
77	}
78}
79
80impl<'a, AT, P, B> Parse<'a> for AtRule<AT, P, B>
81where
82	AT: Parse<'a> + Into<token_macros::AtKeyword>,
83	P: Parse<'a>,
84	B: Parse<'a>,
85{
86	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
87		let name = p.parse::<AT>()?.into();
88		let prelude = p.parse::<P>()?;
89		let block = p.parse::<B>()?;
90		let semicolon = p.parse_if_peek::<T![;]>()?;
91		Ok(Self { name, prelude, block, semicolon, _phantom: PhantomData })
92	}
93}
94
95impl<AT, P, B> ToCursors for AtRule<AT, P, B>
96where
97	AT: Into<token_macros::AtKeyword>,
98	P: ToCursors,
99	B: ToCursors,
100{
101	fn to_cursors(&self, s: &mut impl CursorSink) {
102		ToCursors::to_cursors(&self.name, s);
103		ToCursors::to_cursors(&self.prelude, s);
104		ToCursors::to_cursors(&self.block, s);
105		ToCursors::to_cursors(&self.semicolon, s);
106	}
107}
108
109impl<AT, P, B> ToSpan for AtRule<AT, P, B>
110where
111	AT: Into<token_macros::AtKeyword>,
112	P: ToSpan,
113	B: ToSpan,
114{
115	fn to_span(&self) -> Span {
116		self.name.to_span()
117			+ if let Some(semi) = self.semicolon {
118				semi.to_span()
119			} else {
120				self.prelude.to_span() + self.block.to_span()
121			}
122	}
123}
124
125#[cfg(test)]
126mod tests {
127	use super::*;
128	use crate::{SimpleBlock, test_helpers::*};
129
130	#[test]
131	fn size_test() {
132		assert_eq!(std::mem::size_of::<AtRule<T![AtKeyword], T![Ident], T![Ident]>>(), 52);
133	}
134
135	#[test]
136	fn test_writes() {
137		assert_parse!(AtRule < T![AtKeyword], Option<T![Ident]>, SimpleBlock>, "@foo{}");
138		assert_parse!(AtRule < T![AtKeyword], Option<T![Ident]>, SimpleBlock>, "@foo prelude{}");
139		assert_parse!(AtRule < T![AtKeyword], T![Ident], SimpleBlock>, "@foo prelude{}");
140	}
141
142	#[test]
143	fn test_errors() {
144		assert_parse_error!(AtRule < T![AtKeyword], T![Ident], SimpleBlock>, "@foo{}");
145	}
146}