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(rules::$ty$(<$a>)?),
53 )+
54 Supports(Box<'a, rules::SupportsRule<'a>>),
55 UnknownAt(UnknownAtRule<'a>),
56 Style(StyleRule<'a>),
57 Unknown(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::<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::<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::<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::<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;
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 #[cfg(feature = "visitable")]
162 fn test_visits() {
163 assert_visits!(
164 ":root{html:has(&[open]){overflow:hidden}}",
165 StyleRule,
166 SelectorList,
167 CompoundSelector,
168 PseudoClass,
169 StyleRule,
170 SelectorList,
171 CompoundSelector,
172 Tag,
173 HtmlTag,
174 HasPseudoFunction,
175 SelectorList,
176 CompoundSelector,
177 Combinator,
178 Attribute,
179 StyleValue,
180 OverflowStyleValue,
181 OverflowBlockStyleValue
182 );
183 }
184}