1use crate::{
2 CssAtomSet, CssDiagnostic, CssMetadata, SelectorList, StyleValue, UnknownAtRule, UnknownQualifiedRule, rules,
3};
4use css_parse::{
5 BadDeclaration, Cursor, Diagnostic, NodeWithMetadata, Parse, Parser, QualifiedRule, Result as ParserResult,
6 RuleVariants,
7};
8use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
9
10#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
19pub struct StyleRule<'a> {
20 pub rule: QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>, CssMetadata>,
21}
22
23impl<'a> NodeWithMetadata<CssMetadata> for StyleRule<'a> {
24 fn metadata(&self) -> CssMetadata {
25 self.rule.metadata()
26 }
27}
28
29impl<'a> NodeWithMetadata<CssMetadata> for NestedGroupRule<'a> {
30 fn metadata(&self) -> CssMetadata {
31 match self {
32 Self::Container(r) => r.metadata(),
33 Self::Layer(r) => r.metadata(),
34 Self::Media(r) => r.metadata(),
35 Self::Scope(r) => r.metadata(),
36 Self::Supports(r) => r.metadata(),
37 Self::UnknownAt(r) => r.metadata(),
38 Self::Style(r) => r.metadata(),
39 Self::Unknown(r) => r.metadata(),
40 Self::BadDeclaration(r) => r.metadata(),
41 }
42 }
43}
44
45macro_rules! apply_rules {
47 ($macro: ident) => {
48 $macro! {
49 Container(ContainerRule<'a>): "container",
50 Layer(LayerRule<'a>): "layer",
51 Media(MediaRule<'a>): "media",
52 Scope(ScopeRule): "scope",
53 Supports(SupportsRule<'a>): "supports",
54 }
55 };
56}
57
58macro_rules! nested_group_rule {
59 ( $(
60 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
61 )+ ) => {
62 #[allow(clippy::large_enum_variant)] #[derive(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(untagged))]
67 pub enum NestedGroupRule<'a> {
68 $(
69 $name(rules::$ty$(<$a>)?),
70 )+
71 UnknownAt(UnknownAtRule<'a>),
72 Style(StyleRule<'a>),
73 Unknown(UnknownQualifiedRule<'a>),
74 BadDeclaration(BadDeclaration<'a>),
75 }
76 }
77}
78apply_rules!(nested_group_rule);
79
80impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
81 fn parse_at_rule<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
82 where
83 I: Iterator<Item = Cursor> + Clone,
84 {
85 macro_rules! parse_rule {
86 ( $(
87 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
88 )+ ) => {
89 match p.to_atom::<CssAtomSet>(name) {
90 $(CssAtomSet::$name => p.parse::<rules::$ty>().map(Self::$name),)+
91 _ => Err(Diagnostic::new(name.into(), Diagnostic::unexpected_at_rule))?,
92 }
93 }
94 }
95 apply_rules!(parse_rule)
96 }
97
98 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
99 where
100 I: Iterator<Item = Cursor> + Clone,
101 {
102 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
103 }
104
105 fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
106 where
107 I: Iterator<Item = Cursor> + Clone,
108 {
109 p.parse::<StyleRule>().map(Self::Style)
110 }
111
112 fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
113 where
114 I: Iterator<Item = Cursor> + Clone,
115 {
116 p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
117 }
118
119 fn bad_declaration(b: BadDeclaration<'a>) -> Option<Self> {
120 Some(Self::BadDeclaration(b))
121 }
122}
123
124impl<'a> Parse<'a> for NestedGroupRule<'a> {
125 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
126 where
127 I: Iterator<Item = Cursor> + Clone,
128 {
129 Self::parse_rule_variants(p)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::CssAtomSet;
137 use css_parse::assert_parse;
138
139 #[test]
140 fn size_test() {
141 assert_eq!(std::mem::size_of::<StyleRule>(), 176);
142 }
143
144 #[test]
145 fn test_writes() {
146 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{}");
147 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body,body{}");
148 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{width:1px;}");
149 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{opacity:0;}");
150 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo *{}");
151 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":nth-child(1){opacity:0;}");
152 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:(baz);}");
153 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
154 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:1}");
155 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{--custom:{width:0;height:0;};}");
156 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{a;b{}}");
158 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{$(var)-size: 100%;}");
160 }
161}