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 c.token().is_dashed_ident() || crate::property_atoms::CSS_PROPERTY_ATOMS.contains(&p.to_atom::<CssAtomSet>(c))
332 }
333
334 fn is_unknown(&self) -> bool {
335 matches!(self, Self::Unknown(_))
336 }
337
338 fn is_custom(&self) -> bool {
339 matches!(self, Self::Custom(_))
340 }
341
342 fn is_initial(&self) -> bool {
343 matches!(self, Self::Initial(_))
344 }
345
346 fn is_inherit(&self) -> bool {
347 matches!(self, Self::Inherit(_))
348 }
349
350 fn is_unset(&self) -> bool {
351 matches!(self, Self::Unset(_))
352 }
353
354 fn is_revert(&self) -> bool {
355 matches!(self, Self::Revert(_))
356 }
357
358 fn is_revert_layer(&self) -> bool {
359 matches!(self, Self::RevertLayer(_))
360 }
361
362 fn needs_computing(&self) -> bool {
363 matches!(self, Self::Computed(_))
364 }
365
366 fn parse_custom_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
367 where
368 I: Iterator<Item = Cursor> + Clone,
369 {
370 p.parse::<Custom>().map(Self::Custom)
371 }
372
373 fn parse_computed_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
374 where
375 I: Iterator<Item = Cursor> + Clone,
376 {
377 p.parse::<Computed>().map(Self::Computed)
378 }
379
380 fn parse_specified_declaration_value<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
381 where
382 I: Iterator<Item = Cursor> + Clone,
383 {
384 let c = p.peek_n(1);
385 if c == Kind::Ident {
386 match p.to_atom::<CssAtomSet>(c) {
387 CssAtomSet::Initial => return Ok(Self::Initial(p.parse::<T![Ident]>()?)),
388 CssAtomSet::Inherit => return Ok(Self::Inherit(p.parse::<T![Ident]>()?)),
389 CssAtomSet::Unset => return Ok(Self::Unset(p.parse::<T![Ident]>()?)),
390 CssAtomSet::Revert => return Ok(Self::Revert(p.parse::<T![Ident]>()?)),
391 CssAtomSet::RevertLayer => return Ok(Self::RevertLayer(p.parse::<T![Ident]>()?)),
392 _ => {}
393 }
394 }
395 macro_rules! parse_declaration_value {
396 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $atom: ident,)+ ) => {
397 match p.to_atom::<CssAtomSet>(name) {
398 $(CssAtomSet::$atom => p.parse::<values::$ty>().map(Self::$name),)+
399 _ => Err(Diagnostic::new(name, Diagnostic::unexpected))?,
400 }
401 }
402 }
403 apply_properties!(parse_declaration_value)
404 }
405
406 fn parse_unknown_declaration_value<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
407 where
408 I: Iterator<Item = Cursor> + Clone,
409 {
410 p.parse::<Unknown>().map(Self::Unknown)
411 }
412}
413
414impl<'a> SemanticEqTrait for crate::StyleValue<'a> {
415 fn semantic_eq(&self, other: &Self) -> bool {
416 macro_rules! semantic_eq {
417 ( $( $name: ident: $ty: ident$(<$a: lifetime>)? = $str: tt,)+ ) => {
418 match (self, other) {
419 (Self::Initial(_), Self::Initial(_)) => true,
420 (Self::Inherit(_), Self::Inherit(_)) => true,
421 (Self::Unset(_), Self::Unset(_)) => true,
422 (Self::Revert(_), Self::Revert(_)) => true,
423 (Self::RevertLayer(_), Self::RevertLayer(_)) => true,
424 (Self::Custom(a), Self::Custom(b)) => a.semantic_eq(b),
425 (Self::Computed(a), Self::Computed(b)) => a.semantic_eq(b),
426 (Self::Unknown(a), Self::Unknown(b)) => a.semantic_eq(b),
427 $((Self::$name(a), Self::$name(b)) => a.semantic_eq(b),)+
428 (_, _) => false,
429 }
430 };
431 }
432 apply_properties!(semantic_eq)
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::{CssAtomSet, CssMetadata};
440 use bumpalo::Bump;
441 use css_lexer::Lexer;
442 use css_parse::{Declaration, Parser, assert_parse};
443
444 type Property<'a> = Declaration<'a, StyleValue<'a>, CssMetadata>;
445
446 #[test]
447 fn size_test() {
448 assert_eq!(std::mem::size_of::<Property>(), 488);
449 assert_eq!(std::mem::size_of::<StyleValue>(), 416);
450 }
451
452 #[test]
453 fn test_writes() {
454 assert_parse!(CssAtomSet::ATOMS, Property, "width:inherit", Property { value: StyleValue::Inherit(_), .. });
455 assert_parse!(
456 CssAtomSet::ATOMS,
457 Property,
458 "width:inherit!important",
459 Property { value: StyleValue::Inherit(_), important: Some(_), .. }
460 );
461 assert_parse!(
462 CssAtomSet::ATOMS,
463 Property,
464 "width:revert;",
465 Property { value: StyleValue::Revert(_), semicolon: Some(_), .. }
466 );
467 assert_parse!(CssAtomSet::ATOMS, Property, "width:var(--a)", Property { value: StyleValue::Computed(_), .. });
468
469 assert_parse!(CssAtomSet::ATOMS, Property, "float:none!important");
470 assert_parse!(CssAtomSet::ATOMS, Property, "width:1px");
471 assert_parse!(CssAtomSet::ATOMS, Property, "width:min(1px, 2px)");
472 assert_parse!(CssAtomSet::ATOMS, Property, "border:1px solid var(--red)");
473 assert_parse!(CssAtomSet::ATOMS, Property, "dunno:like whatever");
475 assert_parse!(CssAtomSet::ATOMS, Property, "rotate:1.21gw");
476 assert_parse!(CssAtomSet::ATOMS, Property, "_background:black");
477 assert_parse!(CssAtomSet::ATOMS, Property, "--custom:{foo:{bar};baz:(bing);}");
478 }
479
480 #[test]
481 fn test_property_validation() {
482 let bump = Bump::new();
483
484 let input = "width:1px";
485 let lexer = Lexer::new(&CssAtomSet::ATOMS, input);
486 let mut p = Parser::new(&bump, input, lexer);
487 let decl = p.parse::<Property>().unwrap();
488 assert!(!decl.value.is_unknown(), "width should be recognized as a known property");
489
490 let input = "notarealproperty:value";
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(), "notarealproperty should be parsed as unknown");
495
496 let input = "--custom: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_custom(), "--custom should be parsed as custom property");
501 }
502}