Skip to main content

css_parse/syntax/
declaration.rs

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