1use crate::{
2 StyleValue, Visit, VisitMut, Visitable as VisitableTrait, VisitableMut, selector::ComplexSelector, stylesheet::Rule,
3};
4use bumpalo::collections::Vec;
5use css_parse::{
6 AtRule, Build, ComponentValues, ConditionKeyword, Cursor, Declaration, FeatureConditionList, Parse, Parser,
7 Result as ParserResult, RuleList, T, atkeyword_set, diagnostics, function_set,
8};
9use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
10
11atkeyword_set!(pub struct AtSupportsKeyword "supports");
12
13#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
46#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.property"))]
47#[visit]
48pub struct SupportsRule<'a>(pub AtRule<AtSupportsKeyword, SupportsCondition<'a>, SupportsRuleBlock<'a>>);
49
50#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
52pub struct SupportsRuleBlock<'a>(RuleList<'a, Rule<'a>>);
53
54#[derive(ToSpan, ToCursors, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(tag = "type", content = "value"))]
56pub enum SupportsCondition<'a> {
57 Is(SupportsFeature<'a>),
58 Not(ConditionKeyword, SupportsFeature<'a>),
59 And(Vec<'a, (SupportsFeature<'a>, Option<ConditionKeyword>)>),
60 Or(Vec<'a, (SupportsFeature<'a>, Option<ConditionKeyword>)>),
61}
62
63impl<'a> FeatureConditionList<'a> for SupportsCondition<'a> {
64 type FeatureCondition = SupportsFeature<'a>;
65 fn build_is(feature: SupportsFeature<'a>) -> Self {
66 Self::Is(feature)
67 }
68 fn build_not(keyword: ConditionKeyword, feature: SupportsFeature<'a>) -> Self {
69 Self::Not(keyword, feature)
70 }
71 fn build_and(feature: Vec<'a, (SupportsFeature<'a>, Option<ConditionKeyword>)>) -> Self {
72 Self::And(feature)
73 }
74 fn build_or(feature: Vec<'a, (SupportsFeature<'a>, Option<ConditionKeyword>)>) -> Self {
75 Self::Or(feature)
76 }
77}
78
79impl<'a> Parse<'a> for SupportsCondition<'a> {
80 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
81 if p.peek::<T![Function]>() || p.peek::<T!['(']>() {
82 return Ok(Self::Is(p.parse::<SupportsFeature>()?));
83 }
84 Self::parse_condition(p)
85 }
86}
87
88impl<'a> VisitableTrait for SupportsCondition<'a> {
89 fn accept<V: Visit>(&self, v: &mut V) {
90 match self {
91 Self::Is(feature) => feature.accept(v),
92 Self::Not(_, feature) => feature.accept(v),
93 Self::And(features) => {
94 for (feature, _) in features {
95 feature.accept(v);
96 }
97 }
98 Self::Or(features) => {
99 for (feature, _) in features {
100 feature.accept(v);
101 }
102 }
103 }
104 }
105}
106
107impl<'a> VisitableMut for SupportsCondition<'a> {
108 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
109 match self {
110 Self::Is(feature) => feature.accept_mut(v),
111 Self::Not(_, feature) => feature.accept_mut(v),
112 Self::And(features) => {
113 for (feature, _) in features {
114 feature.accept_mut(v);
115 }
116 }
117 Self::Or(features) => {
118 for (feature, _) in features {
119 feature.accept_mut(v);
120 }
121 }
122 }
123 }
124}
125
126#[allow(clippy::large_enum_variant)] #[derive(ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
129pub enum SupportsFeature<'a> {
130 FontTech(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
131 FontFormat(Option<T!['(']>, T![Function], ComponentValues<'a>, T![')'], Option<T![')']>),
132 Selector(Option<T!['(']>, T![Function], ComplexSelector<'a>, T![')'], Option<T![')']>),
133 Property(T!['('], Declaration<'a, StyleValue<'a>>, Option<T![')']>),
134}
135
136function_set!(
137 pub enum SupportsFeatureKeyword {
138 FontTech: "font-tech",
139 FontFormat: "font-format",
140 Selector: "selector"
141 }
142);
143
144impl<'a> Parse<'a> for SupportsFeature<'a> {
145 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
146 let open = p.parse_if_peek::<T!['(']>()?;
147 if p.peek::<T![Function]>() {
148 let keyword = p.parse::<SupportsFeatureKeyword>()?;
149 let c: Cursor = keyword.into();
150 let function = <T![Function]>::build(p, c);
151 match keyword {
152 SupportsFeatureKeyword::Selector(_) => {
153 let selector = p.parse::<ComplexSelector>()?;
154 let close = p.parse::<T![')']>()?;
156 let open_close = if open.is_some() { Some(p.parse::<T![')']>()?) } else { None };
157 Ok(Self::Selector(open, function, selector, close, open_close))
158 }
159 SupportsFeatureKeyword::FontTech(_) => {
160 todo!();
161 }
162 SupportsFeatureKeyword::FontFormat(_) => {
163 todo!();
164 }
165 }
166 } else if let Some(open) = open {
167 let property = p.parse::<Declaration<'a, StyleValue<'a>>>()?;
168 let close = p.parse_if_peek::<T![')']>()?;
169 Ok(Self::Property(open, property, close))
170 } else {
171 Err(diagnostics::Unexpected(p.next()))?
172 }
173 }
174}
175
176impl<'a> VisitableTrait for SupportsFeature<'a> {
177 fn accept<V: Visit>(&self, v: &mut V) {
178 match self {
179 Self::FontTech(_, _, _, _, _) => todo!(),
180 Self::FontFormat(_, _, _, _, _) => todo!(),
181 Self::Selector(_, _, selector, _, _) => selector.accept(v),
182 Self::Property(_, property, _) => property.accept(v),
183 }
184 }
185}
186
187impl<'a> VisitableMut for SupportsFeature<'a> {
188 fn accept_mut<V: VisitMut>(&mut self, v: &mut V) {
189 match self {
190 Self::FontTech(_, _, _, _, _) => todo!(),
191 Self::FontFormat(_, _, _, _, _) => todo!(),
192 Self::Selector(_, _, selector, _, _) => selector.accept_mut(v),
193 Self::Property(_, property, _) => property.accept_mut(v),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use css_parse::assert_parse;
202
203 #[test]
204 fn size_test() {
205 assert_eq!(std::mem::size_of::<SupportsRule>(), 512);
206 assert_eq!(std::mem::size_of::<SupportsCondition>(), 416);
207 assert_eq!(std::mem::size_of::<SupportsRuleBlock>(), 64);
208 }
209
210 #[test]
211 fn test_writes() {
212 assert_parse!(SupportsRule, "@supports(color:black){}");
213 assert_parse!(SupportsRule, "@supports(width:1px){body{width:1px}}");
214 }
225}