1use crate::{StyleValue, selector::SelectorList};
2use css_parse::{
3 Cursor, Parse, Parser, QualifiedRule, Result as ParserResult, RuleVariants, atkeyword_set, syntax::BadDeclaration,
4};
5use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
6use csskit_proc_macro::visit;
7
8use super::{UnknownAtRule, UnknownQualifiedRule, rules};
9
10#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[visit]
19pub struct StyleRule<'a>(pub QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>>);
20
21macro_rules! apply_rules {
23 ($macro: ident) => {
24 $macro! {
25 Container(ContainerRule<'a>): "container",
26 Layer(LayerRule<'a>): "layer",
27 Media(MediaRule<'a>): "media",
28 Scope(ScopeRule): "scope",
29 Supports(SupportsRule<'a>): "supports",
30 }
31 };
32}
33
34macro_rules! nested_group_rule {
35 ( $(
36 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
37 )+ ) => {
38 #[allow(clippy::large_enum_variant)] #[derive(ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
42 pub enum NestedGroupRule<'a> {
43 $(
44 $name(rules::$ty$(<$a>)?),
45 )+
46 UnknownAt(UnknownAtRule<'a>),
47 Style(StyleRule<'a>),
48 Unknown(UnknownQualifiedRule<'a>),
49 BadDeclaration(BadDeclaration<'a>),
50 }
51 }
52}
53apply_rules!(nested_group_rule);
54
55macro_rules! define_atkeyword_set {
56 ( $(
57 $name:ident($ty:ty): $str:tt,
58 )+ ) => {
59 atkeyword_set!(
60 enum AtRuleKeywords {
61 $($name: $str),+
62 }
63 );
64 }
65}
66
67apply_rules!(define_atkeyword_set);
68
69impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
70 fn parse_at_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
71 let kw = p.parse::<AtRuleKeywords>()?;
72 macro_rules! parse_rule {
73 ( $(
74 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
75 )+ ) => {
76 match kw {
77 $(AtRuleKeywords::$name(_) => p.parse::<rules::$ty>().map(Self::$name),)+
78 }
79 }
80 }
81 apply_rules!(parse_rule)
82 }
83
84 fn parse_unknown_at_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
85 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
86 }
87
88 fn parse_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
89 p.parse::<StyleRule>().map(Self::Style)
90 }
91
92 fn parse_unknown_qualified_rule(p: &mut Parser<'a>, _name: Cursor) -> ParserResult<Self> {
93 p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
94 }
95
96 fn bad_declaration(b: BadDeclaration<'a>) -> Option<Self> {
97 Some(Self::BadDeclaration(b))
98 }
99}
100
101impl<'a> Parse<'a> for NestedGroupRule<'a> {
102 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
103 Self::parse_rule_variants(p)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use css_parse::assert_parse;
111
112 #[test]
113 fn size_test() {
114 assert_eq!(std::mem::size_of::<StyleRule>(), 128);
115 }
116
117 #[test]
118 fn test_writes() {
119 assert_parse!(StyleRule, "body{}");
120 assert_parse!(StyleRule, "body,body{}");
121 assert_parse!(StyleRule, "body{width:1px;}");
122 assert_parse!(StyleRule, "body{opacity:0;}");
123 assert_parse!(StyleRule, ".foo *{}", ".foo *{}");
124 assert_parse!(StyleRule, ":nth-child(1){opacity:0;}");
125 assert_parse!(StyleRule, ".foo{--bar:(baz);}");
126 assert_parse!(StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
127 assert_parse!(StyleRule, ".foo{--bar:1}");
128 assert_parse!(StyleRule, ":root{--custom:{width:0;height:0;};}");
129 assert_parse!(StyleRule, ":root{a;b{}}");
131 assert_parse!(StyleRule, ":root{$(var)-size: 100%;}");
133 }
134}