css_parse/syntax/
qualified_rule.rs

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