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