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