css_parse/syntax/
qualified_rule.rs

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