1use crate::{
2 CssAtomSet, CssDiagnostic, CssMetadata, NodeKinds, SelectorList, StyleValue, UnknownAtRule, UnknownQualifiedRule,
3 rules,
4};
5use css_parse::{
6 Cursor, DeclarationGroup, Diagnostic, NodeMetadata, NodeWithMetadata, Parse, Parser, QualifiedRule,
7 Result as ParserResult, RuleVariants,
8};
9use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
10
11#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
19#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
20pub struct StyleRule<'a> {
21 pub rule: QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>, CssMetadata>,
22}
23
24impl<'a> NodeWithMetadata<CssMetadata> for StyleRule<'a> {
25 fn self_metadata(&self) -> CssMetadata {
26 let child_meta = self.rule.metadata();
27 let is_empty = child_meta.declaration_kinds.is_none() && !child_meta.has_rules();
28 let mut node_kinds = NodeKinds::StyleRule;
29 if is_empty {
30 node_kinds |= NodeKinds::EmptyBlock;
31 }
32 CssMetadata { node_kinds, ..Default::default() }
33 }
34
35 fn metadata(&self) -> CssMetadata {
36 self.rule.metadata().merge(self.self_metadata())
37 }
38}
39
40macro_rules! apply_rules {
42 ($macro: ident) => {
43 $macro! {
44 Container(ContainerRule<'a>): "container",
45 Layer(LayerRule<'a>): "layer",
46 Media(MediaRule<'a>): "media",
47 Scope(ScopeRule): "scope",
48 Supports(SupportsRule<'a>): "supports",
49 }
50 };
51}
52
53macro_rules! nested_group_rule {
54 ( $(
55 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
56 )+ ) => {
57 #[allow(clippy::large_enum_variant)] #[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
60 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
61 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
62 #[derive(csskit_derives::NodeWithMetadata)]
63 #[metadata(delegate)]
64 pub enum NestedGroupRule<'a> {
65 $(
66 $name(rules::$ty$(<$a>)?),
67 )+
68 UnknownAt(UnknownAtRule<'a>),
69 Style(StyleRule<'a>),
70 Unknown(UnknownQualifiedRule<'a>),
71 Declarations(DeclarationGroup<'a, StyleValue<'a>, CssMetadata>),
72 }
73 }
74}
75apply_rules!(nested_group_rule);
76
77impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
78 type DeclarationValue = StyleValue<'a>;
79 type Metadata = CssMetadata;
80
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 is_unknown(&self) -> bool {
120 matches!(self, Self::UnknownAt(_) | Self::Unknown(_))
121 }
122
123 fn from_declaration_group(
124 group: css_parse::DeclarationGroup<'a, Self::DeclarationValue, Self::Metadata>,
125 ) -> Option<Self> {
126 Some(Self::Declarations(group))
127 }
128}
129
130impl<'a> Parse<'a> for NestedGroupRule<'a> {
131 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
132 where
133 I: Iterator<Item = Cursor> + Clone,
134 {
135 Self::parse_rule_variants(p)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::CssAtomSet;
143 use css_parse::assert_parse;
144
145 #[cfg(feature = "visitable")]
146 use crate::assert_visits;
147
148 #[test]
149 fn size_test() {
150 assert_eq!(std::mem::size_of::<StyleRule>(), 192);
151 }
152
153 #[test]
154 fn test_writes() {
155 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{}");
156 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body,body{}");
157 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{width:1px;}");
158 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{opacity:0;}");
159 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo *{}");
160 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":nth-child(1){opacity:0;}");
161 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:(baz);}");
162 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
163 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:1}");
164 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{--custom:{width:0;height:0;};}");
165 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{a;b{}}");
167 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{$(var)-size: 100%;}");
169 }
170
171 #[test]
172 #[cfg(feature = "visitable")]
173 fn test_visits() {
174 assert_visits!(
175 ":root{html:has(&[open]){overflow:hidden}}",
176 StyleRule,
177 SelectorList,
178 CompoundSelector,
179 PseudoClass,
180 StyleRule,
181 SelectorList,
182 CompoundSelector,
183 Tag,
184 HtmlTag,
185 HasPseudoFunction,
186 SelectorList,
187 CompoundSelector,
188 Combinator,
189 Attribute,
190 StyleValue,
191 OverflowStyleValue,
192 OverflowBlockStyleValue
193 );
194 }
195}