1use crate::{CssAtomSet, CssDiagnostic};
2use css_parse::{Cursor, Diagnostic, Parse, Parser, Result as ParserResult, T};
3use csskit_derives::{Peek, SemanticEq, ToCursors, ToSpan};
4
5use super::{moz::MozPseudoClass, ms::MsPseudoClass, o::OPseudoClass, webkit::WebkitPseudoClass};
6
7macro_rules! apply_pseudo_class {
8 ($macro: ident) => {
9 $macro! {
10 Active: CssAtomSet::Active,
11 AnyLink: CssAtomSet::AnyLink,
12 Autofill: CssAtomSet::Autofill,
13 Blank: CssAtomSet::Blank,
14 Buffering: CssAtomSet::Buffering,
15 Checked: CssAtomSet::Checked,
16 Current: CssAtomSet::Current,
17 Default: CssAtomSet::Default,
18 Defined: CssAtomSet::Defined,
19 Disabled: CssAtomSet::Disabled,
20 Empty: CssAtomSet::Empty,
21 Enabled: CssAtomSet::Enabled,
22 First: CssAtomSet::First,
23 FirstChild: CssAtomSet::FirstChild,
24 FirstOfType: CssAtomSet::FirstOfType,
25 Focus: CssAtomSet::Focus,
26 FocusVisible: CssAtomSet::FocusVisible,
27 FocusWithin: CssAtomSet::FocusWithin,
28 Fullscreen: CssAtomSet::Fullscreen,
29 Future: CssAtomSet::Future,
30 HasSlotted: CssAtomSet::HasSlotted,
31 Host: CssAtomSet::Host,
32 Heading: CssAtomSet::Heading,
33 Hover: CssAtomSet::Hover,
34 InRange: CssAtomSet::InRange,
35 Indeterminate: CssAtomSet::Indeterminate,
36 Invalid: CssAtomSet::Invalid,
37 LastChild: CssAtomSet::LastChild,
38 LastOfType: CssAtomSet::LastOfType,
39 Left: CssAtomSet::Left,
40 Link: CssAtomSet::Link,
41 LocalLink: CssAtomSet::LocalLink,
42 Modal: CssAtomSet::Modal,
43 Muted: CssAtomSet::Muted,
44 OnlyChild: CssAtomSet::OnlyChild,
45 OnlyOfType: CssAtomSet::OnlyOfType,
46 Open: CssAtomSet::Open,
47 Optional: CssAtomSet::Optional,
48 OutOfRange: CssAtomSet::OutOfRange,
49 Past: CssAtomSet::Past,
50 Paused: CssAtomSet::Paused,
51 PictureInPicture: CssAtomSet::PictureInPicture,
52 PlaceholderShown: CssAtomSet::PlaceholderShown,
53 Playing: CssAtomSet::Playing,
54 PopoverOpen: CssAtomSet::PopoverOpen,
55 ReadOnly: CssAtomSet::ReadOnly,
56 ReadWrite: CssAtomSet::ReadWrite,
57 Required: CssAtomSet::Required,
58 Right: CssAtomSet::Right,
59 Root: CssAtomSet::Root,
60 Scope: CssAtomSet::Scope,
61 Seeking: CssAtomSet::Seeking,
62 Stalled: CssAtomSet::Stalled,
63 Target: CssAtomSet::Target,
64 TargetCurrent: CssAtomSet::TargetCurrent,
65 TargetWithin: CssAtomSet::TargetWithin,
66 UserInvalid: CssAtomSet::UserInvalid,
67 Valid: CssAtomSet::Valid,
68 Visited: CssAtomSet::Visited,
69 VolumeLocked: CssAtomSet::VolumeLocked,
70 }
71 };
72}
73
74macro_rules! define_pseudo_class {
75 ( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
76 #[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
78 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
79#[derive(csskit_derives::NodeWithMetadata)]
80 #[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
81 pub enum PseudoClass {
82 $($(#[$meta])* $ident(T![:], T![Ident]),)+
83 Webkit(WebkitPseudoClass),
84 Moz(MozPseudoClass),
85 Ms(MsPseudoClass),
86 O(OPseudoClass),
87 }
88 };
89}
90apply_pseudo_class!(define_pseudo_class);
91
92impl<'a> Parse<'a> for PseudoClass {
93 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
94 where
95 I: Iterator<Item = Cursor> + Clone,
96 {
97 let c = p.peek_n(2);
98 macro_rules! match_keyword {
99 ( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
100 match p.to_atom::<CssAtomSet>(c) {
101 $($pat => {
102 let colon = p.parse::<T![:]>()?;
103 let ident = p.parse::<T![Ident]>()?;
104 Ok(Self::$ident(colon, ident))
105 })+
106 _ => {
107 if let Ok(psuedo) = p.try_parse::<WebkitPseudoClass>() {
108 return Ok(Self::Webkit(psuedo));
109 }
110 if let Ok(psuedo) = p.try_parse::<MozPseudoClass>() {
111 return Ok(Self::Moz(psuedo));
112 }
113 if let Ok(psuedo) = p.try_parse::<MsPseudoClass>() {
114 return Ok(Self::Ms(psuedo));
115 }
116 if let Ok(psuedo) = p.try_parse::<OPseudoClass>() {
117 return Ok(Self::O(psuedo));
118 }
119 Err(Diagnostic::new(c, Diagnostic::unexpected_pseudo_class))?
120 }
121 }
122 };
123 }
124 apply_pseudo_class!(match_keyword)
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::CssAtomSet;
132 use css_parse::assert_parse;
133
134 #[test]
135 fn size_test() {
136 assert_eq!(std::mem::size_of::<PseudoClass>(), 32);
137 }
138
139 #[test]
140 fn test_writes() {
141 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":target");
142 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":scope");
143 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":valid");
144 }
145
146 #[cfg(feature = "css_feature_data")]
147 #[test]
148 fn test_feature_data() {
149 use crate::assert_feature_id;
150 assert_feature_id!(":hover", PseudoClass, "css.selectors.hover");
151 assert_feature_id!(":future", PseudoClass, "css.selectors.future");
152 assert_feature_id!(":volume-locked", PseudoClass, "css.selectors.volume-locked");
153 }
154}