1use crate::{
2 CssAtomSet, CssMetadata, SelectorList, StyleValue, UnknownAtRule, UnknownQualifiedRule, diagnostics::CssDiagnostic,
3 rules,
4};
5use css_parse::{
6 Box, Cursor, DeclarationGroup, Diagnostic, Parse, Parser, QualifiedRule, Result as ParserResult, RuleVariants,
7};
8use csskit_derives::*;
9use csskit_proc_macro::node;
10
11#[node]
18#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
20#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
21#[derive(csskit_derives::NodeWithMetadata)]
22#[metadata(node_kinds = StyleRule)]
23pub struct StyleRule<'a> {
24 #[metadata(block)]
25 pub rule: QualifiedRule<'a, SelectorList<'a>, StyleValue<'a>, NestedGroupRule<'a>, CssMetadata>,
26}
27
28macro_rules! apply_rules {
30 ($macro: ident) => {
31 $macro! {
32 Container(ContainerRule<'a>): "container",
33 Layer(LayerRule<'a>): "layer",
34 Media(MediaRule<'a>): "media",
35 Scope(ScopeRule<'a>): "scope",
36 }
37 };
38}
39
40macro_rules! nested_group_rule {
41 ( $(
42 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
43 )+ ) => {
44 #[node]
46 #[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
48 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
49 #[derive(csskit_derives::NodeWithMetadata)]
50 pub enum NestedGroupRule<'a> {
51 $(
52 $name(Box<'a, rules::$ty$(<$a>)?>),
53 )+
54 Supports(Box<'a, rules::SupportsRule<'a>>),
55 UnknownAt(Box<'a, UnknownAtRule<'a>>),
56 Style(Box<'a, StyleRule<'a>>),
57 Unknown(Box<'a, UnknownQualifiedRule<'a>>),
58 Declarations(DeclarationGroup<'a, StyleValue<'a>, CssMetadata>),
59 }
60 }
61}
62apply_rules!(nested_group_rule);
63
64impl<'a> RuleVariants<'a> for NestedGroupRule<'a> {
65 type DeclarationValue = StyleValue<'a>;
66 type Metadata = CssMetadata;
67
68 fn parse_at_rule<I>(p: &mut Parser<'a, I>, name: Cursor) -> ParserResult<Self>
69 where
70 I: Iterator<Item = Cursor> + Clone,
71 {
72 macro_rules! parse_rule {
73 ( $(
74 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
75 )+ ) => {
76 match p.to_atom::<CssAtomSet>(name) {
77 $(CssAtomSet::$name => p.parse::<Box<rules::$ty>>().map(Self::$name),)+
78 CssAtomSet::Supports => p.parse::<rules::SupportsRule>().map(|r| Self::Supports(Box::new_in(p.alloc(), r))),
79 _ => Err(Diagnostic::new(name.into(), Diagnostic::unexpected_at_rule))?,
80 }
81 }
82 }
83 apply_rules!(parse_rule)
84 }
85
86 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
87 where
88 I: Iterator<Item = Cursor> + Clone,
89 {
90 p.parse::<Box<UnknownAtRule>>().map(Self::UnknownAt)
91 }
92
93 fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
94 where
95 I: Iterator<Item = Cursor> + Clone,
96 {
97 p.parse::<Box<StyleRule>>().map(Self::Style)
98 }
99
100 fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
101 where
102 I: Iterator<Item = Cursor> + Clone,
103 {
104 p.parse::<Box<UnknownQualifiedRule>>().map(Self::Unknown)
105 }
106
107 fn is_unknown(&self) -> bool {
108 matches!(self, Self::UnknownAt(_) | Self::Unknown(_))
109 }
110
111 fn from_declaration_group(
112 group: css_parse::DeclarationGroup<'a, Self::DeclarationValue, Self::Metadata>,
113 ) -> Option<Self> {
114 Some(Self::Declarations(group))
115 }
116}
117
118impl<'a> Parse<'a> for NestedGroupRule<'a> {
119 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
120 where
121 I: Iterator<Item = Cursor> + Clone,
122 {
123 Self::parse_rule_variants(p)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::CssAtomSet;
131 use css_parse::{assert_parse, assert_parse_span};
132
133 #[cfg(feature = "visitable")]
134 use crate::assert_visits;
135
136 #[test]
137 fn test_writes() {
138 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{}");
139 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body,body{}");
140 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{width:1px;}");
141 assert_parse!(CssAtomSet::ATOMS, StyleRule, "body{opacity:0;}");
142 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo *{}");
143 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":nth-child(1){opacity:0;}");
144 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:(baz);}");
145 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{width: calc(1px + (var(--foo)) + 1px);}");
146 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".foo{--bar:1}");
147 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{--custom:{width:0;height:0;};}");
148 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{a;b{}}");
150 assert_parse!(CssAtomSet::ATOMS, StyleRule, ":root{$(var)-size: 100%;}");
152 assert_parse!(CssAtomSet::ATOMS, StyleRule, ".md{--:ra( ;86)}");
153 assert_parse!(CssAtomSet::ATOMS, StyleRule, "a{@supports (color:red){color:red;}}");
154 assert_parse!(CssAtomSet::ATOMS, StyleRule, "a{@supports selector(a){color:red;b{color:blue;}}}");
155 assert_parse!(CssAtomSet::ATOMS, StyleRule, "a{@container (width>0){color:red;}}");
156 assert_parse!(CssAtomSet::ATOMS, StyleRule, "a{@layer foo{color:red;}}");
157 assert_parse!(CssAtomSet::ATOMS, StyleRule, "a{@scope(.card) to (img){color:red;}}");
158 }
159
160 #[test]
161 fn test_spans() {
162 assert_parse_span!(
163 CssAtomSet::ATOMS,
164 StyleRule,
165 r#"
166 body{} a{}
167 ^^^^^^
168 "#
169 );
170 assert_parse_span!(
171 CssAtomSet::ATOMS,
172 StyleRule,
173 r#"
174 ul li:nth-of-type(2n) {} a{}
175 ^^^^^^^^^^^^^^^^^^^^^^^^
176 "#
177 );
178 assert_parse_span!(
179 CssAtomSet::ATOMS,
180 StyleRule,
181 r#"
182 :nth-child(-2n+1){opacity:0;}
183 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
184 "#
185 );
186 }
187
188 #[test]
189 #[cfg(feature = "visitable")]
190 fn test_visits() {
191 assert_visits!(
192 ":root{html:has(&[open]){overflow:hidden}}",
193 StyleRule,
194 SelectorList,
195 CompoundSelector,
196 PseudoClass,
197 StyleRule,
198 SelectorList,
199 CompoundSelector,
200 Tag,
201 HtmlTag,
202 HasPseudoFunction,
203 SelectorList,
204 CompoundSelector,
205 Combinator,
206 Attribute,
207 StyleValue,
208 OverflowStyleValue,
209 OverflowBlockStyleValue
210 );
211 }
212}