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