css_ast/selector/
pseudo_class.rs

1use crate::diagnostics;
2use css_parse::{Build, Parse, Parser, Result as ParserResult, T, keyword_set};
3use csskit_derives::{ToCursors, ToSpan, Visitable};
4
5use super::{moz::MozPseudoClass, ms::MsPseudoClass, o::OPseudoClass, webkit::WebkitPseudoClass};
6
7macro_rules! apply_pseudo_class {
8	($macro: ident) => {
9		$macro! {
10			Active: "active",
11			AnyLink: "any-link",
12			Autofill: "autofill",
13			Blank: "blank",
14			Buffering: "buffering",
15			Checked: "checked",
16			Current: "current",
17			Default: "default",
18			Defined: "defined",
19			Disabled: "disabled",
20			Empty: "empty",
21			Enabled: "enabled",
22			First: "first",
23			FirstChild: "first-child",
24			FirstOfType: "first-of-type",
25			Focus: "focus",
26			FocusVisible: "focus-visible",
27			FocusWithin: "focus-within",
28			Fullscreen: "fullscreen",
29			Future: "future",
30			HasSlotted: "has-slotted",
31			Host: "host",
32			Heading: "heading",
33			Hover: "hover",
34			InRange: "in-range",
35			Indeterminate: "indeterminate",
36			Invalid: "invalid",
37			LastChild: "last-child",
38			LastOfType: "last-of-type",
39			Left: "left",
40			Link: "link",
41			LocalLink: "local-link",
42			Modal: "modal",
43			Muted: "muted",
44			OnlyChild: "only-child",
45			OnlyOfType: "only-of-type",
46			Open: "open",
47			Optional: "optional",
48			OutOfRange: "out-of-range",
49			Past: "past",
50			Paused: "paused",
51			PictureInPicture: "picture-in-picture",
52			PlaceholderShown: "placeholder-shown",
53			Playing: "playing",
54			PopoverOpen: "popover-open",
55			ReadOnly: "read-only",
56			ReadWrite: "read-write",
57			Required: "required",
58			Right: "right",
59			Root: "root",
60			Scope: "scope",
61			Seeking: "seeking",
62			Stalled: "stalled",
63			Target: "target",
64			TargetCurrent: "target-current",
65			TargetWithin: "target-within",
66			UserInvalid: "user-invalid",
67			Valid: "valid",
68			Visited: "visited",
69			VolumeLocked: "volume-locked",
70		}
71	};
72}
73
74macro_rules! define_pseudo_class {
75	( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
76		#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77		#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "kebab-case"))]
78		#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
79		#[visit(self)]
80		pub enum PseudoClass {
81			$($(#[$meta])* $ident(T![:], T![Ident]),)+
82			Webkit(WebkitPseudoClass),
83			Moz(MozPseudoClass),
84			Ms(MsPseudoClass),
85			O(OPseudoClass),
86		}
87	};
88}
89apply_pseudo_class!(define_pseudo_class);
90
91macro_rules! define_pseudo_class_keyword {
92	( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
93		keyword_set!(
94			enum PseudoClassKeyword {
95				$($ident: $str,)+
96			}
97		);
98	}
99}
100apply_pseudo_class!(define_pseudo_class_keyword);
101
102impl<'a> Parse<'a> for PseudoClass {
103	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
104		let checkpoint = p.checkpoint();
105		let colon = p.parse::<T![:]>()?;
106		let keyword = p.parse::<PseudoClassKeyword>();
107		macro_rules! match_keyword {
108			( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
109				match keyword {
110					$(Ok(PseudoClassKeyword::$ident(c)) => Ok(Self::$ident(colon, <T![Ident]>::build(p, c.into()))),)+
111					Err(_) => {
112						p.rewind(checkpoint);
113						let c = p.peek_n(2);
114						if let Ok(psuedo) = p.try_parse::<WebkitPseudoClass>() {
115							return Ok(Self::Webkit(psuedo));
116						}
117						if let Ok(psuedo) = p.try_parse::<MozPseudoClass>() {
118							return Ok(Self::Moz(psuedo));
119						}
120						if let Ok(psuedo) = p.try_parse::<MsPseudoClass>() {
121							return Ok(Self::Ms(psuedo));
122						}
123						if let Ok(psuedo) = p.try_parse::<OPseudoClass>() {
124							return Ok(Self::O(psuedo));
125						}
126						Err(diagnostics::UnexpectedPseudoClass(p.parse_str(c).into(), c))?
127					}
128				}
129			};
130		}
131		apply_pseudo_class!(match_keyword)
132	}
133}
134
135#[cfg(test)]
136mod tests {
137	use super::*;
138	use css_parse::assert_parse;
139
140	#[test]
141	fn size_test() {
142		assert_eq!(std::mem::size_of::<PseudoClass>(), 32);
143	}
144
145	#[test]
146	fn test_writes() {
147		assert_parse!(PseudoClass, ":target");
148		assert_parse!(PseudoClass, ":scope");
149		assert_parse!(PseudoClass, ":valid");
150	}
151
152	#[cfg(feature = "css_feature_data")]
153	#[test]
154	fn test_feature_data() {
155		use crate::assert_feature_id;
156		assert_feature_id!(":hover", PseudoClass, "css.selectors.hover");
157		assert_feature_id!(":future", PseudoClass, "css.selectors.future");
158		assert_feature_id!(":volume-locked", PseudoClass, "css.selectors.volume-locked");
159	}
160}