css_parse/syntax/
declaration.rs

1use crate::{
2	BangImportant, Cursor, CursorSink, DeclarationValue, Kind, NodeMetadata, NodeWithMetadata, Parse, Parser, Peek,
3	Result, SemanticEq, Span, T, ToCursors, ToSpan, token_macros,
4};
5use std::marker::PhantomData;
6
7/// This is a generic type that can be used for AST nodes representing a [Declaration][1], aka "property". This is
8/// defined as:
9///
10/// ```md
11/// <property-id>
12///  │├─ <ident> ─┤│
13///
14/// <declaration>
15///  │├─ <property-id> ─ ":" ─ <V> ──╮─────────────────────────────╭──╮───────╭┤│
16///                                  ╰─ "!" ─ <ident "important"> ─╯  ╰─ ";" ─╯
17/// ```
18///
19/// An ident is parsed first, as the property name, followed by a `:`. After this the given `<V>` will be parsed as the
20/// style value. Parsing may continue to a `!important`, or the optional trailing semi `;`, if either are present.
21///
22/// The grammar of `<V>` isn't defined here - it'll be dependant on the property name. Consequently, `<V>` must
23/// implement the [DeclarationValue] trait, which must provide the
24/// `parse_declaration_value(&mut Parser<'a>, Cursor) -> Result<Self>` method - the [Cursor] given to said method
25/// represents the Ident of the property name, so it can be reasoned about in order to dispatch to the right
26/// declaration value parsing step.
27///
28/// [1]: https://drafts.csswg.org/css-syntax-3/#consume-a-declaration
29#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
31pub struct Declaration<'a, V, M>
32where
33	V: DeclarationValue<'a, M>,
34	M: NodeMetadata,
35{
36	pub name: token_macros::Ident,
37	pub colon: token_macros::Colon,
38	pub value: V,
39	pub important: Option<BangImportant>,
40	pub semicolon: Option<token_macros::Semicolon>,
41	#[cfg_attr(feature = "serde", serde(skip))]
42	_phantom: PhantomData<&'a M>,
43}
44
45impl<'a, V, M> Declaration<'a, V, M>
46where
47	V: DeclarationValue<'a, M>,
48	M: NodeMetadata,
49{
50	pub fn is_unknown(&self) -> bool {
51		self.value.is_unknown()
52	}
53}
54
55impl<'a, V, M> NodeWithMetadata<M> for Declaration<'a, V, M>
56where
57	V: DeclarationValue<'a, M>,
58	M: NodeMetadata,
59{
60	fn self_metadata(&self) -> M {
61		// Declaration's self_metadata should return the declaration-specific metadata
62		// (includes !important, property info, etc.) for selector matching.
63		DeclarationValue::declaration_metadata(self)
64	}
65
66	fn metadata(&self) -> M {
67		DeclarationValue::declaration_metadata(self)
68	}
69}
70
71impl<'a, V, M> Peek<'a> for Declaration<'a, V, M>
72where
73	V: DeclarationValue<'a, M>,
74	M: NodeMetadata,
75{
76	fn peek<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
77	where
78		Iter: Iterator<Item = crate::Cursor> + Clone,
79	{
80		// A declaration must be an Ident followed by a Colon (with any number of whitespace inbetween). If that is not the
81		// case then it definitely cannot be parsed as a Declaration.
82		//
83		// https://drafts.csswg.org/css-syntax-3/#consume-a-blocks-contents
84		// ... "If the next non-whitespace token isn’t a <colon-token>, you can similarly immediately stop parsing as a
85		// declaration." ... "(That is, font+ ... is guaranteed to not be a property"...
86		if c != Kind::Ident || p.peek_n(2) != Kind::Colon {
87			return false;
88		}
89
90		// https://drafts.csswg.org/css-syntax-3/#consume-a-blocks-contents
91		// ... "If the first two non-whitespace tokens are a custom property name and a colon, it’s definitely a custom
92		// property and won’t ever produce a valid rule" ... "(That is, --foo:hover {...} is guaranteed to be a custom
93		// property, not a rule.)".
94		if c.token().is_dashed_ident() {
95			return true;
96		}
97
98		// If the third token is a `Colon` then it's likely a Pseudo Element selector. Colons are not valid value tokens
99		// inside of a declaration at current, however this is _technically_ a non-standard affordance that may be removed
100		// in future.
101		if p.peek_n(3) == Kind::Colon {
102			return false;
103		}
104
105		// https://drafts.csswg.org/css-syntax-3/#consume-a-blocks-contents
106		// ... "If the first three non-whitespace tokens are a valid property name, a colon, and anything other than a
107		// <{-token>, and then while parsing the declaration's value you encounter a <{-token>, you can immediately stop
108		// parsing as a declaration and reparse as a rule instead.
109		// (That is, font:bar {... is guaranteed to be an invalid property.)"
110		if p.peek_n(4) == Kind::LeftCurly || p.peek_n(5) == Kind::LeftCurly {
111			return false;
112		}
113
114		// All early checks have been exhausted, so the next step is to parse the Declaration to see if it is valid.
115		true
116	}
117}
118
119impl<'a, V, M> Parse<'a> for Declaration<'a, V, M>
120where
121	V: DeclarationValue<'a, M>,
122	M: NodeMetadata,
123{
124	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
125	where
126		Iter: Iterator<Item = crate::Cursor> + Clone,
127	{
128		let name = p.parse::<T![Ident]>()?;
129		let colon = p.parse::<T![:]>()?;
130		let c: Cursor = name.into();
131		let value = <V>::parse_declaration_value(p, c)?;
132		let important = p.parse_if_peek::<BangImportant>()?;
133		let semicolon = p.parse_if_peek::<T![;]>()?;
134		Ok(Self { name, colon, value, important, semicolon, _phantom: PhantomData })
135	}
136}
137
138impl<'a, V, M> ToCursors for Declaration<'a, V, M>
139where
140	V: DeclarationValue<'a, M> + ToCursors,
141	M: NodeMetadata,
142{
143	fn to_cursors(&self, s: &mut impl CursorSink) {
144		ToCursors::to_cursors(&self.name, s);
145		ToCursors::to_cursors(&self.colon, s);
146		ToCursors::to_cursors(&self.value, s);
147		ToCursors::to_cursors(&self.important, s);
148		ToCursors::to_cursors(&self.semicolon, s);
149	}
150}
151
152impl<'a, V, M> ToSpan for Declaration<'a, V, M>
153where
154	V: DeclarationValue<'a, M> + ToSpan,
155	M: NodeMetadata,
156{
157	fn to_span(&self) -> Span {
158		self.name.to_span() + self.value.to_span() + self.important.to_span() + self.semicolon.to_span()
159	}
160}
161
162impl<'a, V, M> SemanticEq for Declaration<'a, V, M>
163where
164	V: DeclarationValue<'a, M>,
165	M: NodeMetadata,
166{
167	fn semantic_eq(&self, other: &Self) -> bool {
168		// Semicolon is not semantically relevant!
169		self.name.semantic_eq(&other.name)
170			&& self.value.semantic_eq(&other.value)
171			&& self.important.semantic_eq(&other.important)
172	}
173}
174
175#[cfg(test)]
176mod tests {
177	use super::*;
178	use crate::EmptyAtomSet;
179	use crate::SemanticEq;
180	use crate::test_helpers::*;
181
182	#[derive(Debug)]
183	struct Decl(T![Ident]);
184
185	impl<M: NodeMetadata> NodeWithMetadata<M> for Decl {
186		fn metadata(&self) -> M {
187			M::default()
188		}
189	}
190
191	impl<'a, M: NodeMetadata> DeclarationValue<'a, M> for Decl {
192		type ComputedValue = T![Eof];
193
194		fn is_initial(&self) -> bool {
195			false
196		}
197
198		fn is_inherit(&self) -> bool {
199			false
200		}
201
202		fn is_unset(&self) -> bool {
203			false
204		}
205
206		fn is_revert(&self) -> bool {
207			false
208		}
209
210		fn is_revert_layer(&self) -> bool {
211			false
212		}
213
214		fn needs_computing(&self) -> bool {
215			false
216		}
217
218		fn parse_specified_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
219		where
220			Iter: Iterator<Item = crate::Cursor> + Clone,
221		{
222			p.parse::<T![Ident]>().map(Self)
223		}
224	}
225
226	impl ToCursors for Decl {
227		fn to_cursors(&self, s: &mut impl CursorSink) {
228			s.append(self.0.into())
229		}
230	}
231
232	impl ToSpan for Decl {
233		fn to_span(&self) -> Span {
234			self.0.to_span()
235		}
236	}
237
238	impl SemanticEq for Decl {
239		fn semantic_eq(&self, other: &Self) -> bool {
240			self.0.semantic_eq(&other.0)
241		}
242	}
243
244	#[test]
245	fn size_test() {
246		assert_eq!(std::mem::size_of::<Declaration<Decl, ()>>(), 80);
247	}
248
249	#[test]
250	fn test_writes() {
251		assert_parse!(EmptyAtomSet::ATOMS, Declaration<Decl, ()>, "color:black;");
252	}
253}