1use crate::{
2 AppliesTo, BoxPortion, BoxSide, CssAtomSet, CssMetadata, DeclarationKind, DeclarationMetadata, Inherits, NodeKinds,
3 PropertyGroup, PropertyKind, VendorPrefixes, values,
4};
5use css_lexer::Kind;
6use css_parse::{
7 AtomSet, ComponentValues, Cursor, Declaration, DeclarationValue, Diagnostic, KindSet, NodeWithMetadata, Parser,
8 Peek, Result as ParserResult, SemanticEq as SemanticEqTrait, State, T,
9};
10use csskit_derives::{Parse, SemanticEq, ToCursors, ToSpan};
11use std::{fmt::Debug, hash::Hash};
12
13include!(concat!(env!("OUT_DIR"), "/css_apply_properties.rs"));
15
16#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
18#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
19#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
20pub struct Custom<'a>(pub ComponentValues<'a>);
21
22#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
24#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
25#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
26pub struct Computed<'a>(pub ComponentValues<'a>);
27
28impl<'a> Peek<'a> for Computed<'a> {
29 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
30 where
31 I: Iterator<Item = Cursor> + Clone,
32 {
33 <T![Function]>::peek(p, c)
34 && matches!(
35 p.to_atom::<CssAtomSet>(c),
36 CssAtomSet::Var
37 | CssAtomSet::Calc
38 | CssAtomSet::Min
39 | CssAtomSet::Max
40 | CssAtomSet::Clamp
41 | CssAtomSet::Round
42 | CssAtomSet::Mod
43 | CssAtomSet::Rem
44 | CssAtomSet::Sin
45 | CssAtomSet::Cos
46 | CssAtomSet::Tan
47 | CssAtomSet::Asin
48 | CssAtomSet::Atan
49 | CssAtomSet::Atan2
50 | CssAtomSet::Pow
51 | CssAtomSet::Sqrt
52 | CssAtomSet::Hypot
53 | CssAtomSet::Log
54 | CssAtomSet::Exp
55 | CssAtomSet::Abs
56 | CssAtomSet::Sign
57 )
58 }
59}
60
61#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
63#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
64#[parse(state = State::Nested, stop = KindSet::RIGHT_CURLY_OR_SEMICOLON)]
65pub struct Unknown<'a>(pub ComponentValues<'a>);
66
67macro_rules! style_value {
68 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
69 #[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
71 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
72 pub enum StyleValue<'a> {
73 #[cfg_attr(feature = "visitable", visit(skip))]
74 Initial(T![Ident]),
75 #[cfg_attr(feature = "visitable", visit(skip))]
76 Inherit(T![Ident]),
77 #[cfg_attr(feature = "visitable", visit(skip))]
78 Unset(T![Ident]),
79 #[cfg_attr(feature = "visitable", visit(skip))]
80 Revert(T![Ident]),
81 #[cfg_attr(feature = "visitable", visit(skip))]
82 RevertLayer(T![Ident]),
83 #[cfg_attr(feature = "serde", serde(untagged))]
84 Custom(Custom<'a>),
85 #[cfg_attr(feature = "serde", serde(untagged))]
86 Computed(Computed<'a>),
87 #[cfg_attr(feature = "serde", serde(untagged))]
88 Unknown(Unknown<'a>),
89 $(
90 #[cfg_attr(feature = "serde", serde(untagged))]
91 $name(values::$ty$(<$a>)?),
92 )+
93 }
94 }
95}
96
97apply_properties!(style_value);
98
99impl<'a> NodeWithMetadata<CssMetadata> for StyleValue<'a> {
100 fn metadata(&self) -> CssMetadata {
101 macro_rules! metadata {
102 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
103 match self {
104 Self::Initial(_) |
105 Self::Inherit(_)|
106 Self::Unset(_)|
107 Self::Revert(_)|
108 Self::RevertLayer(_) => {
109 CssMetadata {
110 declaration_kinds: DeclarationKind::CssWideKeywords,
111 ..Default::default()
112 }
113 }
114 Self::Custom(_) => {
115 CssMetadata {
116 declaration_kinds: DeclarationKind::Custom,
117 ..Default::default()
118 }
119 }
120 Self::Computed(_) => {
121 CssMetadata {
122 declaration_kinds: DeclarationKind::Computed,
123 ..Default::default()
124 }
125 },
126 Self::Unknown(_) => {
127 CssMetadata {
128 node_kinds: NodeKinds::Unknown,
129 ..Default::default()
130 }
131 },
132 $(
133 Self::$name(_) => {
134 let mut declaration_kinds = DeclarationKind::none();
135 if values::$ty::is_shorthand() {
136 declaration_kinds |= DeclarationKind::Shorthands;
137 } else {
138 declaration_kinds |= DeclarationKind::Longhands;
139 }
140 CssMetadata {
141 property_groups: values::$ty::property_group(),
142 applies_to: values::$ty::applies_to(),
143 box_sides: values::$ty::box_side(),
144 box_portions: values::$ty::box_portion(),
145 declaration_kinds,
146 unitless_zero_resolves: values::$ty::unitless_zero_resolves(),
147 ..Default::default()
148 }
149 }
150 )+
151 }
152 };
153 }
154 apply_properties!(metadata)
155 }
156}
157
158impl<'a> StyleValue<'a> {
159 pub fn initial_by_name(property_name: CssAtomSet) -> Option<&'static str> {
163 macro_rules! get_initial_by_name {
164 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
165 match property_name {
166 $(
167 CssAtomSet::$name => Some(values::$ty::initial()),
168 )+
169 _ => None,
170 }
171 };
172 }
173 apply_properties!(get_initial_by_name)
174 }
175
176 pub fn inherits_by_name(property_name: CssAtomSet) -> Option<Inherits> {
178 macro_rules! get_inherits_by_name {
179 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
180 match property_name {
181 $(
182 CssAtomSet::$name => Some(values::$ty::inherits()),
183 )+
184 _ => None,
185 }
186 };
187 }
188 apply_properties!(get_inherits_by_name)
189 }
190
191 pub fn applies_to_by_name(property_name: CssAtomSet) -> Option<AppliesTo> {
193 macro_rules! get_applies_to_by_name {
194 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
195 match property_name {
196 $(
197 CssAtomSet::$name => Some(values::$ty::applies_to()),
198 )+
199 _ => None,
200 }
201 };
202 }
203 apply_properties!(get_applies_to_by_name)
204 }
205
206 pub fn property_group_by_name(property_name: CssAtomSet) -> Option<PropertyGroup> {
208 macro_rules! get_property_group_by_name {
209 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
210 match property_name {
211 $(
212 CssAtomSet::$name => Some(values::$ty::property_group()),
213 )+
214 _ => None,
215 }
216 };
217 }
218 apply_properties!(get_property_group_by_name)
219 }
220
221 pub fn box_side_by_name(property_name: CssAtomSet) -> Option<BoxSide> {
223 macro_rules! get_box_side_by_name {
224 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
225 match property_name {
226 $(
227 CssAtomSet::$name => Some(values::$ty::box_side()),
228 )+
229 _ => None,
230 }
231 };
232 }
233 apply_properties!(get_box_side_by_name)
234 }
235
236 pub fn box_portion_by_name(property_name: CssAtomSet) -> Option<BoxPortion> {
238 macro_rules! get_box_portion_by_name {
239 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
240 match property_name {
241 $(
242 CssAtomSet::$name => Some(values::$ty::box_portion()),
243 )+
244 _ => None,
245 }
246 };
247 }
248 apply_properties!(get_box_portion_by_name)
249 }
250
251 pub fn shorthand_group_by_name(property_name: CssAtomSet) -> CssAtomSet {
255 macro_rules! get_shorthand_group_by_name {
256 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
257 match property_name {
258 $(
259 CssAtomSet::$name => values::$ty::shorthand_group(),
260 )+
261 _ => CssAtomSet::_None,
262 }
263 };
264 }
265 apply_properties!(get_shorthand_group_by_name)
266 }
267
268 pub fn longhands_by_name(property_name: CssAtomSet) -> Option<&'static [CssAtomSet]> {
272 macro_rules! get_longhands_by_name {
273 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
274 match property_name {
275 $(
276 CssAtomSet::$name => values::$ty::longhands(),
277 )+
278 _ => None,
279 }
280 };
281 }
282 apply_properties!(get_longhands_by_name)
283 }
284
285 pub fn is_shorthand_by_name(property_name: CssAtomSet) -> bool {
287 macro_rules! get_is_shorthand_by_name {
288 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
289 match property_name {
290 $(
291 CssAtomSet::$name => values::$ty::is_shorthand(),
292 )+
293 _ => false,
294 }
295 };
296 }
297 apply_properties!(get_is_shorthand_by_name)
298 }
299}
300
301impl<'a> DeclarationValue<'a, CssMetadata> for StyleValue<'a> {
302 type ComputedValue = Computed<'a>;
303
304 fn declaration_metadata(decl: &Declaration<'a, Self, CssMetadata>) -> CssMetadata {
305 let mut meta = decl.value.metadata();
306 meta.node_kinds |= NodeKinds::Declaration;
308 if decl.important.is_some() {
309 meta.declaration_kinds |= DeclarationKind::Important;
310 }
311 if decl.name.is_dashed_ident() {
313 meta.node_kinds |= NodeKinds::Custom;
314 }
315 if decl.value.is_unknown() {
317 meta.node_kinds |= NodeKinds::Unknown;
318 }
319 let cursor: Cursor = decl.name.into();
321 meta.vendor_prefixes = CssAtomSet::from_bits(cursor.atom_bits()).try_into().unwrap_or(VendorPrefixes::none());
322 meta.property_kinds |= PropertyKind::Name;
324 meta
325 }
326
327 fn valid_declaration_name<I>(p: &Parser<'a, I>, c: Cursor) -> bool
328 where
329 I: Iterator<Item = Cursor> + Clone,
330 {
331 let atom = p.to_atom::<CssAtomSet>(c);
332 c.token().is_dashed_ident()
333 || crate::property_atoms::CSS_PROPERTY_ATOMS.contains(&atom)
334 || CSS_VENDOR_PROPERTY_ATOMS.contains(&atom)
335 }
336
337 fn is_unknown(&self) -> bool {
338 matches!(self, Self::Unknown(_))
339 }
340
341 fn is_custom(&self) -> bool {
342 matches!(self, Self::Custom(_))
343 }
344
345 fn is_initial(&self) -> bool {
346 matches!(self, Self::Initial(_))
347 }
348
349 fn is_inherit(&self) -> bool {
350 matches!(self, Self::Inherit(_))
351 }
352
353 fn is_unset(&self) -> bool {
354 matches!(self, Self::Unset(_))
355 }
356
357 fn is_revert(&self) -> bool {
358 matches!(self, Self::Revert(_))
359 }
360
361 fn is_revert_layer(&self) -> bool {
362 matches!(self, Self::RevertLayer(_))
363 }
364
365 fn needs_computing(&self) -> bool {
366 matches!(self, Self::Computed(_))
367 }
368
369 fn parse_custom_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
370 where
371 I: Iterator<Item = Cursor> + Clone,
372 {
373 p.parse::<Custom>().map(Self::Custom)
374 }
375
376 fn parse_computed_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
377 where
378 I: Iterator<Item = Cursor> + Clone,
379 {
380 p.parse::<Computed>().map(Self::Computed)
381 }
382
383 fn parse_specified_declaration_value<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
384 where
385 I: Iterator<Item = Cursor> + Clone,
386 {
387 let c = p.peek_n(1);
388 if c == Kind::Ident {
389 match p.to_atom::<CssAtomSet>(c) {
390 CssAtomSet::Initial => return Ok(Self::Initial(p.parse::<T![Ident]>()?)),
391 CssAtomSet::Inherit => return Ok(Self::Inherit(p.parse::<T![Ident]>()?)),
392 CssAtomSet::Unset => return Ok(Self::Unset(p.parse::<T![Ident]>()?)),
393 CssAtomSet::Revert => return Ok(Self::Revert(p.parse::<T![Ident]>()?)),
394 CssAtomSet::RevertLayer => return Ok(Self::RevertLayer(p.parse::<T![Ident]>()?)),
395 _ => {}
396 }
397 }
398 macro_rules! parse_declaration_value {
399 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $atom: ident,)+ ) => {
400 match p.to_atom::<CssAtomSet>(name) {
401 $(CssAtomSet::$atom => p.parse::<values::$ty>().map(Self::$name),)+
402 _ => Err(Diagnostic::new(name, Diagnostic::unexpected))?,
403 }
404 }
405 }
406 apply_properties!(parse_declaration_value)
407 }
408
409 fn parse_unknown_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
410 where
411 I: Iterator<Item = Cursor> + Clone,
412 {
413 p.parse::<Unknown>().map(Self::Unknown)
414 }
415}
416
417impl<'a> SemanticEqTrait for crate::StyleValue<'a> {
418 fn semantic_eq(&self, other: &Self) -> bool {
419 macro_rules! semantic_eq {
420 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
421 match (self, other) {
422 (Self::Initial(_), Self::Initial(_)) => true,
423 (Self::Inherit(_), Self::Inherit(_)) => true,
424 (Self::Unset(_), Self::Unset(_)) => true,
425 (Self::Revert(_), Self::Revert(_)) => true,
426 (Self::RevertLayer(_), Self::RevertLayer(_)) => true,
427 (Self::Custom(a), Self::Custom(b)) => a.semantic_eq(b),
428 (Self::Computed(a), Self::Computed(b)) => a.semantic_eq(b),
429 (Self::Unknown(a), Self::Unknown(b)) => a.semantic_eq(b),
430 $((Self::$name(a), Self::$name(b)) => a.semantic_eq(b),)+
431 (_, _) => false,
432 }
433 };
434 }
435 apply_properties!(semantic_eq)
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::{CssAtomSet, CssMetadata};
443 use bumpalo::Bump;
444 use css_lexer::Lexer;
445 use css_parse::{Declaration, Parser, assert_parse};
446
447 type Property<'a> = Declaration<'a, StyleValue<'a>, CssMetadata>;
448
449 #[test]
450 fn size_test() {
451 assert_eq!(std::mem::size_of::<Property>(), 328);
452 assert_eq!(std::mem::size_of::<StyleValue>(), 256);
453 }
454
455 #[test]
456 fn test_writes() {
457 assert_parse!(CssAtomSet::ATOMS, Property, "width:inherit", Property { value: StyleValue::Inherit(_), .. });
458 assert_parse!(
459 CssAtomSet::ATOMS,
460 Property,
461 "width:inherit!important",
462 Property { value: StyleValue::Inherit(_), important: Some(_), .. }
463 );
464 assert_parse!(
465 CssAtomSet::ATOMS,
466 Property,
467 "width:revert;",
468 Property { value: StyleValue::Revert(_), semicolon: Some(_), .. }
469 );
470 assert_parse!(CssAtomSet::ATOMS, Property, "width:var(--a)", Property { value: StyleValue::Computed(_), .. });
471
472 assert_parse!(CssAtomSet::ATOMS, Property, "float:none!important");
473 assert_parse!(CssAtomSet::ATOMS, Property, "width:1px");
474 assert_parse!(CssAtomSet::ATOMS, Property, "width:min(1px, 2px)");
475 assert_parse!(CssAtomSet::ATOMS, Property, "border:1px solid var(--red)");
476 assert_parse!(CssAtomSet::ATOMS, Property, "dunno:like whatever");
478 assert_parse!(CssAtomSet::ATOMS, Property, "rotate:1.21gw");
479 assert_parse!(CssAtomSet::ATOMS, Property, "_background:black");
480 assert_parse!(CssAtomSet::ATOMS, Property, "--custom:{foo:{bar};baz:(bing);}");
481 }
482
483 #[test]
484 fn test_property_validation() {
485 let bump = Bump::new();
486
487 let input = "width:1px";
488 let lexer = Lexer::new(&CssAtomSet::ATOMS, input);
489 let mut p = Parser::new(&bump, input, lexer);
490 let decl = p.parse::<Property>().unwrap();
491 assert!(!decl.value.is_unknown(), "width should be recognized as a known property");
492
493 let input = "notarealproperty:value";
494 let lexer = Lexer::new(&CssAtomSet::ATOMS, input);
495 let mut p = Parser::new(&bump, input, lexer);
496 let decl = p.parse::<Property>().unwrap();
497 assert!(decl.value.is_unknown(), "notarealproperty should be parsed as unknown");
498
499 let input = "-webkit-filter:blur(4px)";
500 let lexer = Lexer::new(&CssAtomSet::ATOMS, input);
501 let mut p = Parser::new(&bump, input, lexer);
502 let decl = p.parse::<Property>().unwrap();
503 assert!(!decl.value.is_unknown(), "-webkit-filter should be recognized as a known property");
504
505 let input = "--custom:value";
506 let lexer = Lexer::new(&CssAtomSet::ATOMS, input);
507 let mut p = Parser::new(&bump, input, lexer);
508 let decl = p.parse::<Property>().unwrap();
509 assert!(decl.value.is_custom(), "--custom should be parsed as custom property");
510 }
511}