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