Skip to main content

css_parse/syntax/
declaration.rs

1use super::prelude::*;
2use crate::{BangImportant, Box, 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<Box<'a, BangImportant>>,
39	pub semicolon: Option<token_macros::Semicolon>,
40	#[cfg_attr(feature = "serde", serde(skip))]
41	_phantom: PhantomData<(&'a (), M)>,
42}
43
44impl<'a, V, M> Declaration<'a, V, M>
45where
46	V: DeclarationValue<'a, M>,
47	M: NodeMetadata,
48{
49	pub fn is_unknown(&self) -> bool {
50		self.value.is_unknown()
51	}
52}
53
54impl<'a, V, M> NodeWithMetadata<M> for Declaration<'a, V, M>
55where
56	V: DeclarationValue<'a, M>,
57	M: NodeMetadata,
58{
59	fn self_metadata(&self) -> M {
60		V::declaration_metadata(self)
61	}
62
63	fn metadata(&self) -> M {
64		V::declaration_metadata(self)
65	}
66}
67
68impl<'a, V, M> Peek<'a> for Declaration<'a, V, M>
69where
70	V: DeclarationValue<'a, M>,
71	M: NodeMetadata,
72{
73	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident]);
74
75	#[inline(always)]
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::<Box<'a, 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, source_text: &str) -> bool {
168		// Semicolon is not semantically relevant!
169		self.name.semantic_eq(&other.name, source_text)
170			&& self.value.semantic_eq(&other.value, source_text)
171			&& self.important.semantic_eq(&other.important, source_text)
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		fn parse_specified_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
193		where
194			Iter: Iterator<Item = crate::Cursor> + Clone,
195		{
196			p.parse::<T![Ident]>().map(Self)
197		}
198	}
199
200	impl ToCursors for Decl {
201		fn to_cursors(&self, s: &mut impl CursorSink) {
202			s.append(self.0.into())
203		}
204	}
205
206	impl ToSpan for Decl {
207		fn to_span(&self) -> Span {
208			self.0.to_span()
209		}
210	}
211
212	impl SemanticEq for Decl {
213		fn semantic_eq(&self, other: &Self, source_text: &str) -> bool {
214			self.0.semantic_eq(&other.0, source_text)
215		}
216	}
217
218	#[test]
219	fn test_writes() {
220		assert_parse!(EmptyAtomSet::ATOMS, Declaration<Decl, ()>, "color:black;");
221	}
222}