css_ast/properties/
mod.rs

1use crate::{CssAtomSet, CssMetadata, DeclarationKind, DeclarationMetadata, values};
2use css_lexer::Kind;
3use css_parse::{
4	ComponentValues, Cursor, Declaration, DeclarationValue, Diagnostic, KindSet, NodeWithMetadata, Parser, Peek,
5	Result as ParserResult, SemanticEq as SemanticEqTrait, State, T,
6};
7use csskit_derives::{Parse, SemanticEq, ToCursors, ToSpan};
8use std::{fmt::Debug, hash::Hash};
9
10// The build.rs generates a list of CSS properties from the value mods
11include!(concat!(env!("OUT_DIR"), "/css_apply_properties.rs"));
12
13#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
15#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
16#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
17pub struct Custom<'a>(pub ComponentValues<'a>);
18
19#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
23pub struct Computed<'a>(pub ComponentValues<'a>);
24
25impl<'a> Peek<'a> for Computed<'a> {
26	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
27	where
28		I: Iterator<Item = Cursor> + Clone,
29	{
30		<T![Function]>::peek(p, c)
31			&& matches!(
32				p.to_atom::<CssAtomSet>(c),
33				CssAtomSet::Var
34					| CssAtomSet::Calc
35					| CssAtomSet::Min
36					| CssAtomSet::Max
37					| CssAtomSet::Clamp
38					| CssAtomSet::Round
39					| CssAtomSet::Mod
40					| CssAtomSet::Rem
41					| CssAtomSet::Sin
42					| CssAtomSet::Cos
43					| CssAtomSet::Tan
44					| CssAtomSet::Asin
45					| CssAtomSet::Atan
46					| CssAtomSet::Atan2
47					| CssAtomSet::Pow
48					| CssAtomSet::Sqrt
49					| CssAtomSet::Hypot
50					| CssAtomSet::Log
51					| CssAtomSet::Exp
52					| CssAtomSet::Abs
53					| CssAtomSet::Sign
54			)
55	}
56}
57
58#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
60#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
61#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
62pub struct Unknown<'a>(pub ComponentValues<'a>);
63
64macro_rules! style_value {
65	( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
66		#[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
67		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
68		#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
69		pub enum StyleValue<'a> {
70			#[cfg_attr(feature = "visitable", visit(skip))]
71			Initial(T![Ident]),
72			#[cfg_attr(feature = "visitable", visit(skip))]
73			Inherit(T![Ident]),
74			#[cfg_attr(feature = "visitable", visit(skip))]
75			Unset(T![Ident]),
76			#[cfg_attr(feature = "visitable", visit(skip))]
77			Revert(T![Ident]),
78			#[cfg_attr(feature = "visitable", visit(skip))]
79			RevertLayer(T![Ident]),
80			#[cfg_attr(feature = "serde", serde(untagged))]
81			Custom(Custom<'a>),
82			#[cfg_attr(feature = "serde", serde(untagged))]
83			Computed(Computed<'a>),
84			#[cfg_attr(feature = "serde", serde(untagged))]
85			Unknown(Unknown<'a>),
86			$(
87				#[cfg_attr(feature = "serde", serde(untagged))]
88				$name(values::$ty$(<$a>)?),
89			)+
90		}
91	}
92}
93
94apply_properties!(style_value);
95
96impl<'a> NodeWithMetadata<CssMetadata> for StyleValue<'a> {
97	fn metadata(&self) -> CssMetadata {
98		macro_rules! metadata {
99			( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
100				match self {
101					Self::Initial(_) |
102					Self::Inherit(_)|
103					Self::Unset(_)|
104					Self::Revert(_)|
105					Self::RevertLayer(_) => {
106						CssMetadata {
107							declaration_kinds: DeclarationKind::CssWideKeywords,
108							..Default::default()
109						}
110					}
111					Self::Custom(_) => {
112						CssMetadata {
113							declaration_kinds: DeclarationKind::Custom,
114							..Default::default()
115						}
116					}
117					Self::Computed(_) => {
118						CssMetadata {
119							declaration_kinds: DeclarationKind::Computed,
120							..Default::default()
121						}
122					},
123					Self::Unknown(_) => {
124						CssMetadata {
125							declaration_kinds: DeclarationKind::Unknown,
126							..Default::default()
127						}
128					},
129					$(
130					Self::$name(_) => {
131						CssMetadata {
132							property_groups: values::$ty::property_group(),
133							applies_to: values::$ty::applies_to(),
134							box_sides: values::$ty::box_side(),
135							box_portions: values::$ty::box_portion(),
136							..Default::default()
137						}
138					}
139					)+
140				}
141			};
142		}
143		apply_properties!(metadata)
144	}
145}
146
147impl<'a> DeclarationValue<'a, CssMetadata> for StyleValue<'a> {
148	type ComputedValue = Computed<'a>;
149
150	fn declaration_metadata(decl: &Declaration<'a, Self, CssMetadata>) -> CssMetadata {
151		let mut meta = decl.value.metadata();
152		if decl.important.is_some() {
153			meta.declaration_kinds |= DeclarationKind::Important;
154		}
155		meta
156	}
157
158	fn valid_declaration_name<I>(p: &Parser<'a, I>, c: Cursor) -> bool
159	where
160		I: Iterator<Item = Cursor> + Clone,
161	{
162		macro_rules! match_name {
163			( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
164				match p.to_atom::<CssAtomSet>(c) {
165					$(CssAtomSet::$name => true,)+
166					_ => false,
167				}
168			}
169		}
170		apply_properties!(match_name)
171	}
172
173	fn is_unknown(&self) -> bool {
174		matches!(self, Self::Unknown(_))
175	}
176
177	fn is_initial(&self) -> bool {
178		matches!(self, Self::Initial(_))
179	}
180
181	fn is_inherit(&self) -> bool {
182		matches!(self, Self::Inherit(_))
183	}
184
185	fn is_unset(&self) -> bool {
186		matches!(self, Self::Unset(_))
187	}
188
189	fn is_revert(&self) -> bool {
190		matches!(self, Self::Revert(_))
191	}
192
193	fn is_revert_layer(&self) -> bool {
194		matches!(self, Self::RevertLayer(_))
195	}
196
197	fn needs_computing(&self) -> bool {
198		matches!(self, Self::Computed(_))
199	}
200
201	fn parse_custom_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
202	where
203		I: Iterator<Item = Cursor> + Clone,
204	{
205		p.parse::<Custom>().map(Self::Custom)
206	}
207
208	fn parse_computed_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
209	where
210		I: Iterator<Item = Cursor> + Clone,
211	{
212		p.parse::<Computed>().map(Self::Computed)
213	}
214
215	fn parse_specified_declaration_value<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
216	where
217		I: Iterator<Item = Cursor> + Clone,
218	{
219		let c = p.peek_n(1);
220		if c == Kind::Ident {
221			match p.to_atom::<CssAtomSet>(c) {
222				CssAtomSet::Initial => return Ok(Self::Initial(p.parse::<T![Ident]>()?)),
223				CssAtomSet::Inherit => return Ok(Self::Inherit(p.parse::<T![Ident]>()?)),
224				CssAtomSet::Unset => return Ok(Self::Unset(p.parse::<T![Ident]>()?)),
225				CssAtomSet::Revert => return Ok(Self::Revert(p.parse::<T![Ident]>()?)),
226				CssAtomSet::RevertLayer => return Ok(Self::RevertLayer(p.parse::<T![Ident]>()?)),
227				_ => {}
228			}
229		}
230		macro_rules! parse_declaration_value {
231			( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $atom: ident,)+ ) => {
232				match p.to_atom::<CssAtomSet>(name) {
233					$(CssAtomSet::$atom => p.parse::<values::$ty>().map(Self::$name),)+
234					_ => Err(Diagnostic::new(name, Diagnostic::unexpected))?,
235				}
236			}
237		}
238		apply_properties!(parse_declaration_value)
239	}
240
241	fn parse_unknown_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
242	where
243		I: Iterator<Item = Cursor> + Clone,
244	{
245		p.parse::<Unknown>().map(Self::Unknown)
246	}
247}
248
249impl<'a> SemanticEqTrait for crate::StyleValue<'a> {
250	fn semantic_eq(&self, other: &Self) -> bool {
251		macro_rules! semantic_eq {
252			( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
253				match (self, other) {
254					(Self::Initial(_), Self::Initial(_)) => true,
255					(Self::Inherit(_), Self::Inherit(_)) => true,
256					(Self::Unset(_), Self::Unset(_)) => true,
257					(Self::Revert(_), Self::Revert(_)) => true,
258					(Self::RevertLayer(_), Self::RevertLayer(_)) => true,
259					(Self::Custom(a), Self::Custom(b)) => a.semantic_eq(b),
260					(Self::Computed(a), Self::Computed(b)) => a.semantic_eq(b),
261					(Self::Unknown(a), Self::Unknown(b)) => a.semantic_eq(b),
262					$((Self::$name(a), Self::$name(b)) => a.semantic_eq(b),)+
263					(_, _) => false,
264				}
265			};
266		}
267		apply_properties!(semantic_eq)
268	}
269}
270
271#[cfg(test)]
272mod tests {
273	use super::*;
274	use crate::{CssAtomSet, CssMetadata};
275	use css_parse::{Declaration, assert_parse};
276
277	type Property<'a> = Declaration<'a, StyleValue<'a>, CssMetadata>;
278
279	#[test]
280	fn size_test() {
281		assert_eq!(std::mem::size_of::<Property>(), 368);
282		assert_eq!(std::mem::size_of::<StyleValue>(), 296);
283	}
284
285	#[test]
286	fn test_writes() {
287		assert_parse!(CssAtomSet::ATOMS, Property, "width:inherit", Property { value: StyleValue::Inherit(_), .. });
288		assert_parse!(
289			CssAtomSet::ATOMS,
290			Property,
291			"width:inherit!important",
292			Property { value: StyleValue::Inherit(_), important: Some(_), .. }
293		);
294		assert_parse!(
295			CssAtomSet::ATOMS,
296			Property,
297			"width:revert;",
298			Property { value: StyleValue::Revert(_), semicolon: Some(_), .. }
299		);
300		assert_parse!(CssAtomSet::ATOMS, Property, "width:var(--a)", Property { value: StyleValue::Computed(_), .. });
301
302		assert_parse!(CssAtomSet::ATOMS, Property, "float:none!important");
303		assert_parse!(CssAtomSet::ATOMS, Property, "width:1px");
304		assert_parse!(CssAtomSet::ATOMS, Property, "width:min(1px, 2px)");
305		assert_parse!(CssAtomSet::ATOMS, Property, "border:1px solid var(--red)");
306		// Should still parse unknown properties
307		assert_parse!(CssAtomSet::ATOMS, Property, "dunno:like whatever");
308		assert_parse!(CssAtomSet::ATOMS, Property, "rotate:1.21gw");
309		assert_parse!(CssAtomSet::ATOMS, Property, "_background:black");
310		assert_parse!(CssAtomSet::ATOMS, Property, "--custom:{foo:{bar};baz:(bing);}");
311	}
312}