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