1use super::prelude::*;
2use crate::selector::ComplexSelector;
3
4#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
37#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
38#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.property"))]
39pub struct SupportsRule<'a> {
40 #[cfg_attr(feature = "visitable", visit(skip))]
41 #[atom(CssAtomSet::Supports)]
42 pub name: T![AtKeyword],
43 pub prelude: SupportsCondition<'a>,
44 pub block: SupportsRuleBlock<'a>,
45}
46
47impl<'a> NodeWithMetadata<CssMetadata> for SupportsRule<'a> {
48 fn metadata(&self) -> CssMetadata {
49 let mut meta = self.block.0.metadata();
50 meta.used_at_rules |= AtRuleId::Supports;
51 meta
52 }
53}
54
55#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
57#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
58pub struct SupportsRuleBlock<'a>(pub RuleList<'a, Rule<'a>, CssMetadata>);
59
60#[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
62pub enum SupportsCondition<'a> {
63 Is(SupportsFeature<'a>),
64 Not(T![Ident], SupportsFeature<'a>),
65 And(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
66 Or(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
67}
68
69impl<'a> FeatureConditionList<'a> for SupportsCondition<'a> {
70 type FeatureCondition = SupportsFeature<'a>;
71 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
72 where
73 I: Iterator<Item = Cursor> + Clone,
74 {
75 p.equals_atom(c, &CssAtomSet::Not)
76 }
77 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
78 where
79 I: Iterator<Item = Cursor> + Clone,
80 {
81 p.equals_atom(c, &CssAtomSet::And)
82 }
83 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
84 where
85 I: Iterator<Item = Cursor> + Clone,
86 {
87 p.equals_atom(c, &CssAtomSet::Or)
88 }
89 fn build_is(feature: SupportsFeature<'a>) -> Self {
90 Self::Is(feature)
91 }
92 fn build_not(keyword: T![Ident], feature: SupportsFeature<'a>) -> Self {
93 Self::Not(keyword, feature)
94 }
95 fn build_and(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
96 Self::And(feature)
97 }
98 fn build_or(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
99 Self::Or(feature)
100 }
101}
102
103impl<'a> Parse<'a> for SupportsCondition<'a> {
104 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
105 where
106 I: Iterator<Item = Cursor> + Clone,
107 {
108 if p.peek::<T![Function]>() || p.peek::<T!['(']>() {
109 return Ok(Self::Is(p.parse::<SupportsFeature>()?));
110 }
111 Self::parse_condition(p)
112 }
113}
114
115#[cfg(feature = "visitable")]
116impl<'a> VisitableTrait for SupportsCondition<'a> {
117 fn accept<V: Visit>(&self, v: &mut V) {
118 match self {
119 Self::Is(feature) => feature.accept(v),
120 Self::Not(_, feature) => feature.accept(v),
121 Self::And(features) => {
122 for (feature, _) in features {
123 feature.accept(v);
124 }
125 }
126 Self::Or(features) => {
127 for (feature, _) in features {
128 feature.accept(v);
129 }
130 }
131 }
132 }
133}
134
135#[cfg(feature = "visitable")]
136impl<'a> VisitableMut for SupportsCondition<'a> {
137 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
138 match self {
139 Self::Is(feature) => feature.accept_mut(v),
140 Self::Not(_, feature) => feature.accept_mut(v),
141 Self::And(features) => {
142 for (feature, _) in features {
143 feature.accept_mut(v);
144 }
145 }
146 Self::Or(features) => {
147 for (feature, _) in features {
148 feature.accept_mut(v);
149 }
150 }
151 }
152 }
153}
154
155#[allow(clippy::large_enum_variant)] #[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
157#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
158pub enum SupportsFeature<'a> {
159 FontTech(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
160 FontFormat(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
161 Selector(Option<T!['(']>, T![Function], ComplexSelector<'a>, T![')'], Option<T![')']>),
162 Property(T!['('], Declaration<'a, StyleValue<'a>, CssMetadata>, Option<T![')']>),
163}
164
165impl<'a> Parse<'a> for SupportsFeature<'a> {
166 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
167 where
168 I: Iterator<Item = Cursor> + Clone,
169 {
170 let open = p.parse_if_peek::<T!['(']>()?;
171 if p.peek::<T![Function]>() {
172 let function = p.parse::<T![Function]>()?;
173 match p.to_atom::<CssAtomSet>(function.into()) {
174 CssAtomSet::Selector => {
175 let selector = p.parse::<ComplexSelector>()?;
176 let close = p.parse::<T![')']>()?;
178 let open_close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
179 Ok(Self::Selector(open, function, selector, close, open_close))
180 }
181 CssAtomSet::FontTech => {
182 todo!();
183 }
184 CssAtomSet::FontFormat => {
185 todo!();
186 }
187 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected_function))?,
188 }
189 } else if let Some(open) = open {
190 let property = p.parse::<Declaration<'a, StyleValue<'a>, CssMetadata>>()?;
191 let close = p.parse_if_peek::<T![')']>()?;
192 Ok(Self::Property(open, property, close))
193 } else {
194 Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
195 }
196 }
197}
198
199#[cfg(feature = "visitable")]
200impl<'a> VisitableTrait for SupportsFeature<'a> {
201 fn accept<V: Visit>(&self, v: &mut V) {
202 match self {
203 Self::FontTech(_, _, _, _, _) => todo!(),
204 Self::FontFormat(_, _, _, _, _) => todo!(),
205 Self::Selector(_, _, selector, _, _) => selector.accept(v),
206 Self::Property(_, property, _) => property.accept(v),
207 }
208 }
209}
210
211#[cfg(feature = "visitable")]
212impl<'a> VisitableMut for SupportsFeature<'a> {
213 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
214 match self {
215 Self::FontTech(_, _, _, _, _) => todo!(),
216 Self::FontFormat(_, _, _, _, _) => todo!(),
217 Self::Selector(_, _, selector, _, _) => selector.accept_mut(v),
218 Self::Property(_, property, _) => property.accept_mut(v),
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::CssAtomSet;
227 use css_parse::assert_parse;
228
229 #[test]
230 fn size_test() {
231 assert_eq!(std::mem::size_of::<SupportsRule>(), 520);
232 assert_eq!(std::mem::size_of::<SupportsCondition>(), 416);
233 assert_eq!(std::mem::size_of::<SupportsRuleBlock>(), 88);
234 }
235
236 #[test]
237 fn test_writes() {
238 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(color:black){}");
239 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(width:1px){body{width:1px}}");
240 }
251}