Skip to main content

css_parse/syntax/
qualified_rule.rs

1use crate::{
2	BadDeclaration, Block, CursorSink, DeclarationValue, Diagnostic, Kind, KindSet, NodeMetadata, NodeWithMetadata,
3	Parse, Parser, Peek, Result, SemanticEq, Span, State, T, ToCursors, ToSpan,
4};
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize))]
8#[cfg_attr(
9	feature = "serde",
10	serde(bound(serialize = "P: serde::Serialize, D: serde::Serialize, R: serde::Serialize"))
11)]
12pub struct QualifiedRule<'a, P, D, R, M>
13where
14	// TODO: P: NodeWithMetadata<M>,
15	D: DeclarationValue<'a, M>,
16	M: NodeMetadata,
17{
18	pub prelude: P,
19	pub block: Block<'a, D, R, M>,
20	#[cfg_attr(feature = "serde", serde(skip))]
21	meta: M,
22}
23
24impl<'a, P, D, R, M> NodeWithMetadata<M> for QualifiedRule<'a, P, D, R, M>
25where
26	D: DeclarationValue<'a, M>,
27	M: NodeMetadata,
28{
29	fn metadata(&self) -> M {
30		self.meta
31	}
32}
33
34impl<'a, P, D, R, M> Peek<'a> for QualifiedRule<'a, P, D, R, M>
35where
36	P: Peek<'a>,
37	D: DeclarationValue<'a, M>,
38	M: NodeMetadata,
39{
40	const PEEK_KINDSET: KindSet = P::PEEK_KINDSET;
41}
42
43// https://drafts.csswg.org/css-syntax-3/#consume-a-qualified-rule
44/// A QualifiedRule represents a block with a prelude which may contain other rules.
45/// Examples of QualifiedRules are StyleRule, KeyframeRule (no s!).
46impl<'a, P, D, R, M> Parse<'a> for QualifiedRule<'a, P, D, R, M>
47where
48	D: DeclarationValue<'a, M>,
49	P: Parse<'a>,
50	R: Parse<'a> + NodeWithMetadata<M> + crate::RuleVariants<'a, DeclarationValue = D, Metadata = M>,
51	M: NodeMetadata,
52{
53	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
54	where
55		Iter: Iterator<Item = crate::Cursor> + Clone,
56	{
57		let c = p.peek_n(1);
58		// Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
59
60		// Process input:
61
62		// <EOF-token>
63		// stop token (if passed)
64		//   This is a parse error. Return nothing.
65		if p.at_end() {
66			Err(Diagnostic::new(p.peek_n(1), Diagnostic::unexpected_end))?
67		}
68
69		// <}-token>
70		//   This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
71		if p.is(State::Nested) && <T!['}']>::peek(p, c) {
72			Err(Diagnostic::new(c, Diagnostic::unexpected_close_curly))?;
73		}
74
75		// <{-token>
76		//	If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose value starts with "--" followed by a <colon-token>, then:
77		let checkpoint = p.checkpoint();
78		if <T![DashedIdent]>::peek(p, c) {
79			p.parse::<T![DashedIdent]>().ok();
80			if <T![:]>::peek(p, p.peek_n(1)) {
81				// If nested is true, consume the remnants of a bad declaration from input, with nested set to true, and return nothing.
82				if p.is(State::Nested) {
83					p.rewind(checkpoint.clone());
84					let start = p.peek_n(1);
85					p.parse::<BadDeclaration>()?;
86					let end = p.peek_n(0);
87					Err(Diagnostic::new(start, Diagnostic::bad_declaration).with_end_cursor(end))?
88				// If nested is false, consume a block from input, and return nothing.
89				} else {
90					// QualifiedRules must be able to consume a block from their input when encountering
91					// a custom property like declaration that doesn't end but opens a `{` block. This
92					// is implemented as parsing the existing block as that' simplifies downstream logic
93					// but consumers of this trait can instead opt to implement an optimised version of
94					// this which doesn't build up an AST and just throws away tokens.
95					p.parse::<Block<'a, D, R, M>>()?;
96					let start = p.peek_n(1);
97					p.parse::<BadDeclaration>()?;
98					let end = p.peek_n(0);
99					Err(Diagnostic::new(start, Diagnostic::bad_declaration).with_end_cursor(end))?
100				}
101			}
102			p.rewind(checkpoint);
103		}
104
105		// Set the StopOn Curly to signify to prelude parsers that they shouldn't consume beyond the curly
106		let old_stop = p.set_stop(KindSet::new(&[Kind::LeftCurly]));
107		let prelude = p.parse::<P>();
108		p.set_stop(old_stop);
109		let prelude = prelude?;
110
111		// Otherwise, consume a block from input, and let child rules be the result.
112		// If the first item of child rules is a list of declarations,
113		// remove it from child rules and assign it to rule’s declarations.
114		// If any remaining items of child rules are lists of declarations,
115		// replace them with nested declarations rules containing the list as its sole child.
116		// Assign child rules to rule’s child rules.
117		let block = p.parse::<Block<'a, D, R, M>>()?;
118		let meta = block.metadata();
119		Ok(Self { prelude, block, meta })
120	}
121}
122
123impl<'a, P, D, R, M> ToCursors for QualifiedRule<'a, P, D, R, M>
124where
125	D: DeclarationValue<'a, M> + ToCursors,
126	P: ToCursors,
127	R: ToCursors,
128	M: NodeMetadata,
129{
130	fn to_cursors(&self, s: &mut impl CursorSink) {
131		ToCursors::to_cursors(&self.prelude, s);
132		ToCursors::to_cursors(&self.block, s);
133	}
134}
135
136impl<'a, P, D, R, M> ToSpan for QualifiedRule<'a, P, D, R, M>
137where
138	D: DeclarationValue<'a, M> + ToSpan,
139	P: ToSpan,
140	R: ToSpan,
141	M: NodeMetadata,
142{
143	fn to_span(&self) -> Span {
144		self.prelude.to_span() + self.block.to_span()
145	}
146}
147
148impl<'a, P, D, R, M> SemanticEq for QualifiedRule<'a, P, D, R, M>
149where
150	D: DeclarationValue<'a, M> + SemanticEq,
151	P: SemanticEq,
152	R: SemanticEq,
153	M: NodeMetadata,
154{
155	fn semantic_eq(&self, other: &Self) -> bool {
156		self.prelude.semantic_eq(&other.prelude) && self.block.semantic_eq(&other.block)
157	}
158}
159
160#[cfg(test)]
161mod tests {
162	use super::*;
163	use crate::{Cursor, EmptyAtomSet, test_helpers::*};
164
165	#[derive(Debug)]
166	struct Decl(T![Ident]);
167
168	impl NodeWithMetadata<()> for Decl {
169		fn metadata(&self) {}
170	}
171
172	impl<'a> DeclarationValue<'a, ()> for Decl {
173		type ComputedValue = T![Eof];
174
175		fn is_initial(&self) -> bool {
176			false
177		}
178
179		fn is_inherit(&self) -> bool {
180			false
181		}
182
183		fn is_unset(&self) -> bool {
184			false
185		}
186
187		fn is_revert(&self) -> bool {
188			false
189		}
190
191		fn is_revert_layer(&self) -> bool {
192			false
193		}
194
195		fn needs_computing(&self) -> bool {
196			false
197		}
198
199		fn parse_specified_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _: Cursor) -> Result<Self>
200		where
201			Iter: Iterator<Item = crate::Cursor> + Clone,
202		{
203			p.parse::<T![Ident]>().map(Self)
204		}
205	}
206
207	impl ToCursors for Decl {
208		fn to_cursors(&self, s: &mut impl CursorSink) {
209			ToCursors::to_cursors(&self.0, s);
210		}
211	}
212
213	impl ToSpan for Decl {
214		fn to_span(&self) -> Span {
215			self.0.to_span()
216		}
217	}
218
219	impl SemanticEq for Decl {
220		fn semantic_eq(&self, other: &Self) -> bool {
221			self.0.semantic_eq(&other.0)
222		}
223	}
224
225	#[derive(Debug)]
226	struct Rule(T![Ident]);
227
228	impl<'a> Parse<'a> for Rule {
229		fn parse<I>(p: &mut Parser<'a, I>) -> Result<Self>
230		where
231			I: Iterator<Item = Cursor> + Clone,
232		{
233			Ok(Self(p.parse::<T![Ident]>()?))
234		}
235	}
236
237	impl ToCursors for Rule {
238		fn to_cursors(&self, s: &mut impl CursorSink) {
239			ToCursors::to_cursors(&self.0, s);
240		}
241	}
242
243	impl ToSpan for Rule {
244		fn to_span(&self) -> Span {
245			self.0.to_span()
246		}
247	}
248
249	impl NodeWithMetadata<()> for Rule {
250		fn metadata(&self) {}
251	}
252
253	impl<'a> crate::RuleVariants<'a> for Rule {
254		type DeclarationValue = Decl;
255		type Metadata = ();
256	}
257
258	#[test]
259	fn size_test() {
260		assert_eq!(std::mem::size_of::<QualifiedRule<T![Ident], Decl, Rule, ()>>(), 112);
261	}
262
263	#[test]
264	fn test_writes() {
265		assert_parse!(EmptyAtomSet::ATOMS, QualifiedRule<T![Ident], Decl, Rule, ()>, "body{color:black}");
266	}
267}