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