Skip to main content

css_ast/selector/
functional_pseudo_class.rs

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		// Nested :has() is invalid CSS - :has() cannot contain :has()
416		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}