1use super::prelude::*;
2use crate::selector::ComplexSelector;
3use crate::{FontFormat, FontTech};
4use css_parse::Box;
5
6#[node]
38#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
40#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
41#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.property"))]
42#[derive(csskit_derives::NodeWithMetadata)]
43#[metadata(node_kinds = AtRule, used_at_rules = Supports)]
44pub struct SupportsRule<'a> {
45 #[cfg_attr(feature = "visitable", visit(skip))]
46 #[atom(CssAtomSet::Supports)]
47 pub name: T![AtKeyword],
48 pub prelude: SupportsCondition<'a>,
49 #[metadata(block)]
50 pub block: SupportsRuleBlock<'a>,
51}
52
53#[node]
54#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
56#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
57#[derive(csskit_derives::NodeWithMetadata)]
58pub struct SupportsRuleBlock<'a>(pub Block<'a, StyleValue<'a>, Rule<'a>, CssMetadata>);
59
60#[node]
61#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
63#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
64#[derive(csskit_derives::NodeWithMetadata)]
65pub enum SupportsCondition<'a> {
66 Is(SupportsFeature<'a>),
67 Not(#[atom(CssAtomSet::Not)] T![Ident], SupportsFeature<'a>),
68 #[peek(skip)]
69 And(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
70 #[peek(skip)]
71 Or(Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>),
72}
73
74impl<'a> FeatureConditionList<'a> for SupportsCondition<'a> {
75 type FeatureCondition = SupportsFeature<'a>;
76 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
77 where
78 I: Iterator<Item = Cursor> + Clone,
79 {
80 p.equals_atom(c, &CssAtomSet::Not)
81 }
82 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
83 where
84 I: Iterator<Item = Cursor> + Clone,
85 {
86 p.equals_atom(c, &CssAtomSet::And)
87 }
88 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
89 where
90 I: Iterator<Item = Cursor> + Clone,
91 {
92 p.equals_atom(c, &CssAtomSet::Or)
93 }
94 fn build_is(feature: SupportsFeature<'a>) -> Self {
95 Self::Is(feature)
96 }
97 fn build_not(keyword: T![Ident], feature: SupportsFeature<'a>) -> Self {
98 Self::Not(keyword, feature)
99 }
100 fn build_and(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
101 Self::And(feature)
102 }
103 fn build_or(feature: Vec<'a, (SupportsFeature<'a>, Option<T![Ident]>)>) -> Self {
104 Self::Or(feature)
105 }
106}
107
108impl<'a> Parse<'a> for SupportsCondition<'a> {
109 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
110 where
111 I: Iterator<Item = Cursor> + Clone,
112 {
113 Self::parse_condition(p)
114 }
115}
116
117#[node]
118#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
119#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
120#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
121#[derive(csskit_derives::NodeWithMetadata)]
122pub enum SupportsFeature<'a> {
123 FontTech(
124 #[cfg_attr(feature = "visitable", visit(skip))] T![Function],
125 FontTech,
126 #[cfg_attr(feature = "visitable", visit(skip))]
127 #[semantic_eq(skip)]
128 T![')'],
129 ),
130 FontFormat(
131 #[cfg_attr(feature = "visitable", visit(skip))] T![Function],
132 FontFormat,
133 #[cfg_attr(feature = "visitable", visit(skip))]
134 #[semantic_eq(skip)]
135 T![')'],
136 ),
137 Selector(
138 #[cfg_attr(feature = "visitable", visit(skip))] T![Function],
139 ComplexSelector<'a>,
140 #[cfg_attr(feature = "visitable", visit(skip))]
141 #[semantic_eq(skip)]
142 T![')'],
143 ),
144 Property(
145 #[cfg_attr(feature = "visitable", visit(skip))]
146 #[semantic_eq(skip)]
147 T!['('],
148 Box<'a, Declaration<'a, StyleValue<'a>, CssMetadata>>,
149 #[cfg_attr(feature = "visitable", visit(skip))]
150 #[semantic_eq(skip)]
151 Option<T![')']>,
152 ),
153 Condition(
156 #[cfg_attr(feature = "visitable", visit(skip))]
157 #[semantic_eq(skip)]
158 T!['('],
159 Box<'a, SupportsCondition<'a>>,
160 #[cfg_attr(feature = "visitable", visit(skip))]
161 #[semantic_eq(skip)]
162 T![')'],
163 ),
164}
165
166impl<'a> Peek<'a> for SupportsFeature<'a> {
167 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftParen, Kind::Function]);
168
169 #[inline(always)]
170 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
171 where
172 I: Iterator<Item = Cursor> + Clone,
173 {
174 <T!['(']>::peek(p, c)
175 || (<T![Function]>::peek(p, c)
176 && matches!(
177 p.to_atom::<CssAtomSet>(c),
178 CssAtomSet::Selector | CssAtomSet::FontTech | CssAtomSet::FontFormat
179 ))
180 }
181}
182impl<'a> Parse<'a> for SupportsFeature<'a> {
183 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
184 where
185 I: Iterator<Item = Cursor> + Clone,
186 {
187 if let Some(open) = p.parse_if_peek::<T!['(']>()? {
188 let is_declaration = p.peek_n(1) == Kind::Ident && p.peek_n(2) == Kind::Colon;
189 if is_declaration {
190 let property = p.parse::<Declaration<'a, StyleValue<'a>, CssMetadata>>()?;
191 let close = p.parse_if_peek::<T![')']>()?;
192 return Ok(Self::Property(open, Box::new_in(p.alloc(), property), close));
193 }
194 let condition = p.parse::<SupportsCondition>()?;
195 let close = p.parse::<T![')']>()?;
196 return Ok(Self::Condition(open, Box::new_in(p.alloc(), condition), close));
197 }
198 if p.peek::<T![Function]>() {
199 let function = p.parse::<T![Function]>()?;
200 return match p.to_atom::<CssAtomSet>(function.into()) {
201 CssAtomSet::Selector => {
202 let selector = p.parse::<ComplexSelector>()?;
203 let close = p.parse::<T![')']>()?;
204 Ok(Self::Selector(function, selector, close))
205 }
206 CssAtomSet::FontTech => {
207 let tech = p.parse::<FontTech>()?;
208 let close = p.parse::<T![')']>()?;
209 Ok(Self::FontTech(function, tech, close))
210 }
211 CssAtomSet::FontFormat => {
212 let format = p.parse::<FontFormat>()?;
213 let close = p.parse::<T![')']>()?;
214 Ok(Self::FontFormat(function, format, close))
215 }
216 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected_function))?,
217 };
218 }
219 Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
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 test_writes() {
231 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(color:black){}");
232 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(width:1px){body{width:1px}}");
233 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports not (width:1--foo){}");
234 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(width: 1--foo) or (width: 1foo) {\n\n}");
235 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(width: 1--foo) and (width: 1foo) {\n\n}");
236 assert_parse!(
237 CssAtomSet::ATOMS,
238 SupportsRule,
239 "@supports(width: 100vw) {\n\tbody {\n\t\twidth: 100vw;\n\t}\n}"
240 );
241 assert_parse!(
242 CssAtomSet::ATOMS,
243 SupportsRule,
244 "@supports not ((text-align-last: justify) or (-moz-text-align-last: justify)) {\n\n}"
245 );
246 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports((position:-webkit-sticky)or (position:sticky)) {}");
247 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports selector(h2 > p) {\n\n}");
248 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(selector(h2 > p)) {}");
249 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports not selector(h2 > p) {\n\n}");
250 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports not (selector(h2 > p)) {}");
251 assert_parse!(
252 CssAtomSet::ATOMS,
253 SupportsRule,
254 "@supports (selector(::-moz-meter-bar) or selector(::-webkit-meter-bar)) {\n\n}"
255 );
256 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports font-tech(color-COLRv1) {\n\n}");
257 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(font-tech(variations)) {}");
258 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports font-format(woff2) {\n\n}");
259 assert_parse!(CssAtomSet::ATOMS, SupportsRule, "@supports(font-format(\"woff2-variations\")) {}");
260 }
261}