1use super::prelude::*;
2
3use super::{moz::MozPseudoClass, ms::MsPseudoClass, o::OPseudoClass, webkit::WebkitPseudoClass};
4
5macro_rules! apply_pseudo_class {
6 ($macro: ident) => {
7 $macro! {
8 Active: CssAtomSet::Active,
9 ActiveViewTransition: CssAtomSet::ActiveViewTransition,
10 AnimatedImage: CssAtomSet::AnimatedImage,
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 FirstOfPage: CssAtomSet::FirstOfPage,
25 FirstOfType: CssAtomSet::FirstOfType,
26 Focus: CssAtomSet::Focus,
27 FocusVisible: CssAtomSet::FocusVisible,
28 FocusWithin: CssAtomSet::FocusWithin,
29 Fullscreen: CssAtomSet::Fullscreen,
30 Future: CssAtomSet::Future,
31 HasSlotted: CssAtomSet::HasSlotted,
32 Heading: CssAtomSet::Heading,
33 HighValue: CssAtomSet::HighValue,
34 Host: CssAtomSet::Host,
35 Hover: CssAtomSet::Hover,
36 InRange: CssAtomSet::InRange,
37 Indeterminate: CssAtomSet::Indeterminate,
38 InterestSource: CssAtomSet::InterestSource,
39 InterestTarget: CssAtomSet::InterestTarget,
40 Invalid: CssAtomSet::Invalid,
41 LastChild: CssAtomSet::LastChild,
42 LastOfPage: CssAtomSet::LastOfPage,
43 LastOfType: CssAtomSet::LastOfType,
44 Left: CssAtomSet::Left,
45 Link: CssAtomSet::Link,
46 LocalLink: CssAtomSet::LocalLink,
47 LowValue: CssAtomSet::LowValue,
48 Modal: CssAtomSet::Modal,
49 Muted: CssAtomSet::Muted,
50 NavSource: CssAtomSet::NavSource,
51 OnlyChild: CssAtomSet::OnlyChild,
52 OnlyOfType: CssAtomSet::OnlyOfType,
53 Open: CssAtomSet::Open,
54 OptimalValue: CssAtomSet::OptimalValue,
55 Optional: CssAtomSet::Optional,
56 OutOfRange: CssAtomSet::OutOfRange,
57 Past: CssAtomSet::Past,
58 Paused: CssAtomSet::Paused,
59 PictureInPicture: CssAtomSet::PictureInPicture,
60 PlaceholderShown: CssAtomSet::PlaceholderShown,
61 Playing: CssAtomSet::Playing,
62 PopoverOpen: CssAtomSet::PopoverOpen,
63 ReadOnly: CssAtomSet::ReadOnly,
64 ReadWrite: CssAtomSet::ReadWrite,
65 Required: CssAtomSet::Required,
66 Right: CssAtomSet::Right,
67 Root: CssAtomSet::Root,
68 Scope: CssAtomSet::Scope,
69 Seeking: CssAtomSet::Seeking,
70 Snapped: CssAtomSet::Snapped,
71 SnappedBlock: CssAtomSet::SnappedBlock,
72 SnappedInline: CssAtomSet::SnappedInline,
73 SnappedX: CssAtomSet::SnappedX,
74 SnappedY: CssAtomSet::SnappedY,
75 Stalled: CssAtomSet::Stalled,
76 Target: CssAtomSet::Target,
77 TargetAfter: CssAtomSet::TargetAfter,
78 TargetBefore: CssAtomSet::TargetBefore,
79 TargetCurrent: CssAtomSet::TargetCurrent,
80 Unchecked: CssAtomSet::Unchecked,
81 UserInvalid: CssAtomSet::UserInvalid,
82 UserValid: CssAtomSet::UserValid,
83 Valid: CssAtomSet::Valid,
84 Visited: CssAtomSet::Visited,
85 VolumeLocked: CssAtomSet::VolumeLocked,
86 }
87 };
88}
89
90macro_rules! define_pseudo_class {
91 ( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
92 #[node]
93 #[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, 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(self))]
96#[derive(csskit_derives::NodeWithMetadata)]
97 #[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
98 pub enum PseudoClass {
99 $($(#[$meta])* $ident(#[semantic_eq(skip)] T![:], T![Ident]),)+
100 Webkit(WebkitPseudoClass),
101 Moz(MozPseudoClass),
102 Ms(MsPseudoClass),
103 O(OPseudoClass),
104 }
105 };
106}
107apply_pseudo_class!(define_pseudo_class);
108
109impl<'a> Parse<'a> for PseudoClass {
110 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
111 where
112 I: Iterator<Item = Cursor> + Clone,
113 {
114 let c = p.peek_n(2);
115 macro_rules! match_keyword {
116 ( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
117 match p.to_atom::<CssAtomSet>(c) {
118 $($pat => {
119 let colon = p.parse::<T![:]>()?;
120 let ident = p.parse::<T![Ident]>()?;
121 Ok(Self::$ident(colon, ident))
122 })+
123 _ => {
124 if let Ok(psuedo) = p.try_parse::<WebkitPseudoClass>() {
125 return Ok(Self::Webkit(psuedo));
126 }
127 if let Ok(psuedo) = p.try_parse::<MozPseudoClass>() {
128 return Ok(Self::Moz(psuedo));
129 }
130 if let Ok(psuedo) = p.try_parse::<MsPseudoClass>() {
131 return Ok(Self::Ms(psuedo));
132 }
133 if let Ok(psuedo) = p.try_parse::<OPseudoClass>() {
134 return Ok(Self::O(psuedo));
135 }
136 Err(Diagnostic::new(c, Diagnostic::unexpected_pseudo_class))?
137 }
138 }
139 };
140 }
141 apply_pseudo_class!(match_keyword)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::CssAtomSet;
149 use css_parse::assert_parse;
150
151 #[test]
152 fn test_writes() {
153 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":target");
154 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":scope");
155 assert_parse!(CssAtomSet::ATOMS, PseudoClass, ":valid");
156 }
157
158 #[test]
159 fn unknown_pseudo_class_names_only_the_bad_token() {
160 let source = ":nonsense";
161 let arena = css_parse::Arena::default();
162 let lexer = css_lexer::Lexer::new(&CssAtomSet::ATOMS, source);
163 let result = css_parse::Parser::new(&arena, source, lexer).parse_entirely::<PseudoClass>();
164 let error = result.errors.first().expect("a diagnostic");
165 let meta = error.meta(source);
166 assert_eq!(meta.message, "Unexpected pseudo selector ':nonsense'");
167 }
168
169 #[cfg(feature = "css_feature_data")]
170 #[test]
171 fn test_feature_data() {
172 use crate::assert_feature_id;
173 assert_feature_id!(":hover", PseudoClass, "css.selectors.hover");
174 assert_feature_id!(":future", PseudoClass, "css.selectors.future");
175 assert_feature_id!(":volume-locked", PseudoClass, "css.selectors.volume-locked");
176 }
177}