1use crate::CssAtomSet;
2use css_lexer::Kind;
3use css_parse::{CommaSeparated, Cursor, Diagnostic, Parse, Parser, Peek, Result as ParserResult, State, T};
4use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
5
6use super::{ForgivingSelector, Nth, RelativeSelector, SelectorList};
7
8macro_rules! apply_functional_pseudo_class {
9 ($macro: ident) => {
10 $macro! {
11 Dir(DirPseudoFunction) CssAtomSet::Dir,
12 Has(HasPseudoFunction<'a>) CssAtomSet::Has,
13 Heading(HeadingPseudoFunction<'a>) CssAtomSet::Heading,
14 Host(HostPseudoFunction<'a>) CssAtomSet::Host,
15 HostContext(HostContextPseudoFunction<'a>) CssAtomSet::HostContext,
16 Is(IsPseudoFunction<'a>) CssAtomSet::Is,
17 Lang(LangPseudoFunction<'a>) CssAtomSet::Lang,
18 Not(NotPseudoFunction<'a>) CssAtomSet::Not,
19 NthChild(NthChildPseudoFunction) CssAtomSet::NthChild,
20 NthCol(NthColPseudoFunction) CssAtomSet::NthCol,
21 NthLastChild(NthLastChildPseudoFunction) CssAtomSet::NthLastChild,
22 NthLastCol(NthLastColPseudoFunction) CssAtomSet::NthLastCol,
23 NthLastOfType(NthLastOfTypePseudoFunction) CssAtomSet::NthLastOfType,
24 NthOfType(NthOfTypePseudoFunction) CssAtomSet::NthOfType,
25 State(StatePseudoFunction) CssAtomSet::State,
26 Where(WherePseudoFunction<'a>) CssAtomSet::Where,
27 }
28 };
29}
30
31macro_rules! define_functional_pseudo_class {
32 ( $($ident: ident($ty: ty) $pat: pat $(,)*)+ ) => {
33 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
34 #[derive( ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35 #[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
36 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
37 pub enum FunctionalPseudoClass<'a> {
38 $($ident($ty),)+
39 }
40 }
41}
42apply_functional_pseudo_class!(define_functional_pseudo_class);
43
44impl<'a> Peek<'a> for FunctionalPseudoClass<'a> {
45 fn peek<I>(p: &Parser<'a, I>, c: css_lexer::Cursor) -> bool
46 where
47 I: Iterator<Item = Cursor> + Clone,
48 {
49 <T![:]>::peek(p, c) && p.peek_n(2) == Kind::Function
50 }
51}
52
53impl<'a> Parse<'a> for FunctionalPseudoClass<'a> {
54 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
55 where
56 I: Iterator<Item = Cursor> + Clone,
57 {
58 macro_rules! match_keyword {
59 ( $($ident: ident($ty: ty) $pat: pat $(,)*)+ ) => {
60 match p.to_atom::<CssAtomSet>(p.peek_n(2)) {
61 CssAtomSet::Has if p.is(State::DisallowRelativeSelector) => {
62 Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
63 }
64 $($pat => p.parse::<$ty>().map(Self::$ident),)+
65 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
66 }
67 }
68 }
69 apply_functional_pseudo_class!(match_keyword)
70 }
71}
72
73#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
75#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
76#[derive(csskit_derives::NodeWithMetadata)]
77pub struct DirPseudoFunction {
78 pub colon: T![:],
79 #[atom(CssAtomSet::Dir)]
80 pub function: T![Function],
81 pub value: DirValue,
82 pub close: Option<T![')']>,
83}
84
85#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
87#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
88#[derive(csskit_derives::NodeWithMetadata)]
89pub enum DirValue {
90 #[atom(CssAtomSet::Rtl)]
91 Rtl(T![Ident]),
92 #[atom(CssAtomSet::Ltr)]
93 Ltr(T![Ident]),
94}
95
96#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
98#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
99#[derive(csskit_derives::NodeWithMetadata)]
100pub struct HasPseudoFunction<'a> {
101 #[cfg_attr(feature = "visitable", visit(skip))]
102 pub colon: T![:],
103 #[cfg_attr(feature = "visitable", visit(skip))]
104 #[atom(CssAtomSet::Has)]
105 pub function: T![Function],
106 #[parse(state = State::DisallowRelativeSelector)]
107 pub value: RelativeSelector<'a>,
108 #[cfg_attr(feature = "visitable", visit(skip))]
109 pub close: Option<T![')']>,
110}
111
112#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
114#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
115#[derive(csskit_derives::NodeWithMetadata)]
116pub struct HostPseudoFunction<'a> {
117 #[cfg_attr(feature = "visitable", visit(skip))]
118 pub colon: T![:],
119 #[cfg_attr(feature = "visitable", visit(skip))]
120 #[atom(CssAtomSet::Host)]
121 pub function: T![Function],
122 pub value: SelectorList<'a>,
123 #[cfg_attr(feature = "visitable", visit(skip))]
124 pub close: Option<T![')']>,
125}
126
127#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
129#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
130#[derive(csskit_derives::NodeWithMetadata)]
131pub struct HostContextPseudoFunction<'a> {
132 #[cfg_attr(feature = "visitable", visit(skip))]
133 pub colon: T![:],
134 #[cfg_attr(feature = "visitable", visit(skip))]
135 #[atom(CssAtomSet::HostContext)]
136 pub function: T![Function],
137 pub value: SelectorList<'a>,
138 #[cfg_attr(feature = "visitable", visit(skip))]
139 pub close: Option<T![')']>,
140}
141
142#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
144#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
145#[derive(csskit_derives::NodeWithMetadata)]
146pub struct IsPseudoFunction<'a> {
147 #[cfg_attr(feature = "visitable", visit(skip))]
148 pub colon: T![:],
149 #[cfg_attr(feature = "visitable", visit(skip))]
150 #[atom(CssAtomSet::Is)]
151 pub function: T![Function],
152 pub value: ForgivingSelector<'a>,
153 #[cfg_attr(feature = "visitable", visit(skip))]
154 pub close: Option<T![')']>,
155}
156
157#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
159#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
160#[derive(csskit_derives::NodeWithMetadata)]
161pub struct LangPseudoFunction<'a> {
162 pub colon: T![:],
163 #[atom(CssAtomSet::Lang)]
164 pub function: T![Function],
165 pub value: LangValues<'a>,
166 pub close: Option<T![')']>,
167}
168
169#[derive(ToSpan, Parse, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
171pub struct LangValues<'a>(pub CommaSeparated<'a, LangValue>);
172
173#[derive(Parse, ToSpan, Peek, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
175pub enum LangValue {
176 Ident(T![Ident]),
177 String(T![String]),
178}
179
180#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
182#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
183#[derive(csskit_derives::NodeWithMetadata)]
184pub struct NotPseudoFunction<'a> {
185 #[cfg_attr(feature = "visitable", visit(skip))]
186 pub colon: T![:],
187 #[cfg_attr(feature = "visitable", visit(skip))]
188 #[atom(CssAtomSet::Not)]
189 pub function: T![Function],
190 pub value: SelectorList<'a>,
191 #[cfg_attr(feature = "visitable", visit(skip))]
192 pub close: Option<T![')']>,
193}
194
195#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
197#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
198#[derive(csskit_derives::NodeWithMetadata)]
199pub struct NthChildPseudoFunction {
200 #[cfg_attr(feature = "visitable", visit(skip))]
201 pub colon: T![:],
202 #[cfg_attr(feature = "visitable", visit(skip))]
203 #[atom(CssAtomSet::NthChild)]
204 pub function: T![Function],
205 pub value: Nth,
206 #[cfg_attr(feature = "visitable", visit(skip))]
207 pub close: Option<T![')']>,
208}
209
210#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
211#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
212#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
213#[derive(csskit_derives::NodeWithMetadata)]
214pub struct NthColPseudoFunction {
215 #[cfg_attr(feature = "visitable", visit(skip))]
216 pub colon: T![:],
217 #[cfg_attr(feature = "visitable", visit(skip))]
218 #[atom(CssAtomSet::NthCol)]
219 pub function: T![Function],
220 pub value: Nth,
221 #[cfg_attr(feature = "visitable", visit(skip))]
222 pub close: Option<T![')']>,
223}
224
225#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
226#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
227#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
228#[derive(csskit_derives::NodeWithMetadata)]
229pub struct NthLastChildPseudoFunction {
230 #[cfg_attr(feature = "visitable", visit(skip))]
231 pub colon: T![:],
232 #[cfg_attr(feature = "visitable", visit(skip))]
233 #[atom(CssAtomSet::NthLastChild)]
234 pub function: T![Function],
235 pub value: Nth,
236 #[cfg_attr(feature = "visitable", visit(skip))]
237 pub close: Option<T![')']>,
238}
239
240#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
241#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
242#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
243#[derive(csskit_derives::NodeWithMetadata)]
244pub struct NthLastColPseudoFunction {
245 #[cfg_attr(feature = "visitable", visit(skip))]
246 pub colon: T![:],
247 #[cfg_attr(feature = "visitable", visit(skip))]
248 #[atom(CssAtomSet::NthLastCol)]
249 pub function: T![Function],
250 pub value: Nth,
251 #[cfg_attr(feature = "visitable", visit(skip))]
252 pub close: Option<T![')']>,
253}
254
255#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
257#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
258#[derive(csskit_derives::NodeWithMetadata)]
259pub struct NthLastOfTypePseudoFunction {
260 #[cfg_attr(feature = "visitable", visit(skip))]
261 pub colon: T![:],
262 #[cfg_attr(feature = "visitable", visit(skip))]
263 #[atom(CssAtomSet::NthLastOfType)]
264 pub function: T![Function],
265 pub value: Nth,
266 #[cfg_attr(feature = "visitable", visit(skip))]
267 pub close: Option<T![')']>,
268}
269
270#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
271#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
272#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
273#[derive(csskit_derives::NodeWithMetadata)]
274pub struct NthOfTypePseudoFunction {
275 #[cfg_attr(feature = "visitable", visit(skip))]
276 pub colon: T![:],
277 #[cfg_attr(feature = "visitable", visit(skip))]
278 #[atom(CssAtomSet::NthOfType)]
279 pub function: T![Function],
280 pub value: Nth,
281 #[cfg_attr(feature = "visitable", visit(skip))]
282 pub close: Option<T![')']>,
283}
284
285#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
286#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
287#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
288#[derive(csskit_derives::NodeWithMetadata)]
289pub struct WherePseudoFunction<'a> {
290 #[cfg_attr(feature = "visitable", visit(skip))]
291 pub colon: T![:],
292 #[cfg_attr(feature = "visitable", visit(skip))]
293 #[atom(CssAtomSet::Where)]
294 pub function: T![Function],
295 pub value: ForgivingSelector<'a>,
296 #[cfg_attr(feature = "visitable", visit(skip))]
297 pub close: Option<T![')']>,
298}
299
300#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
302#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
303#[derive(csskit_derives::NodeWithMetadata)]
304pub struct StatePseudoFunction {
305 pub colon: T![:],
306 #[atom(CssAtomSet::State)]
307 pub function: T![Function],
308 pub value: T![Ident],
309 pub close: Option<T![')']>,
310}
311
312#[derive(Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
314#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
315#[derive(csskit_derives::NodeWithMetadata)]
316pub struct HeadingPseudoFunction<'a> {
317 pub colon: T![:],
318 #[atom(CssAtomSet::Heading)]
319 pub function: T![Function],
320 pub value: CommaSeparated<'a, Nth>,
321 pub close: Option<T![')']>,
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::selector::SelectorList;
328 use css_parse::{assert_parse, assert_parse_error};
329
330 #[test]
331 fn size_test() {
332 assert_eq!(std::mem::size_of::<FunctionalPseudoClass>(), 104);
333 assert_eq!(std::mem::size_of::<DirValue>(), 16);
334 }
335
336 #[test]
337 fn test_has_parses() {
338 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(.foo)");
339 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(> .foo)");
340 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(+ .sibling)");
341 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(:is(.a,.b))");
342 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(:not(.foo))");
343 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":has(:where(.foo))");
344 }
345
346 #[test]
347 fn test_nested_has_disallowed() {
348 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(:has(.foo))");
350 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(:has(:has(.foo)))");
351 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(.bar :has(.foo))");
352 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(:is(:has(.foo)))");
353 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(:not(:has(.foo)))");
354 assert_parse_error!(CssAtomSet::ATOMS, SelectorList, ":has(:where(:has(.foo)))");
355 }
356}