css_parse/syntax/
qualified_rule.rs

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