Skip to main content

css_parse/syntax/
qualified_rule.rs

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