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}