css_ast/selector/
pseudo_element.rs1use crate::{MozPseudoElement, MsPseudoElement, OPseudoElement, WebkitPseudoElement, diagnostics};
2use css_parse::{Build, KindSet, Parse, Parser, Result as ParserResult, T, keyword_set, pseudo_class};
3use csskit_derives::{ToCursors, ToSpan, Visitable};
4
5macro_rules! apply_pseudo_element {
6 ($macro: ident) => {
7 $macro! {
8 After: "after",
9 Backdrop: "backdrop",
10 Before: "before",
11 Checkmark: "checkmark",
12 Column: "column",
13 Cue: "cue",
14 DetailsContent: "details-content",
15 FileSelectorButton: "file-selector-button",
16 FirstLetter: "first-letter",
17 FirstLine: "first-line",
18 GrammarError: "grammar-error",
19 Marker: "marker",
20 PickerIcon: "picker-icon",
21 Placeholder: "placeholder",
22 ScrollMarker: "scroll-marker",
23 ScrollMarkerGroup: "scroll-marker-group",
24 Selection: "selection",
25 SpellingError: "spelling-error",
26 TargetText: "target-text",
27 ViewTransition: "view-transition",
28 }
29 };
30}
31
32macro_rules! define_pseudo_element {
33 ( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
34 #[derive(ToSpan, ToCursors, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
35 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "kebab-case"))]
36 #[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
37 #[visit(self)]
38 pub enum PseudoElement {
39 $($(#[$meta])* $ident(T![::], T![Ident]),)+
40 Webkit(WebkitPseudoElement),
41 Moz(MozPseudoElement),
42 Ms(MsPseudoElement),
43 O(OPseudoElement),
44 }
45 };
46}
47apply_pseudo_element!(define_pseudo_element);
48
49macro_rules! define_pseudo_element_keyword {
50 ( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
51 keyword_set!(pub enum PseudoElementKeyword {
52 $($ident: $str,)+
53 });
54 };
55}
56apply_pseudo_element!(define_pseudo_element_keyword);
57
58impl<'a> Parse<'a> for PseudoElement {
59 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
60 let checkpoint = p.checkpoint();
61 let skip = p.set_skip(KindSet::NONE);
62 let colons = p.parse::<T![::]>();
63 let keyword = p.parse::<PseudoElementKeyword>();
64 p.set_skip(skip);
65 let colons = colons?;
66 macro_rules! match_keyword {
67 ( $($(#[$meta:meta])* $ident: ident: $str: tt $(,)*)+ ) => {
68 match keyword {
69 $(Ok(PseudoElementKeyword::$ident(_)) => Ok(Self::$ident(colons, <T![Ident]>::build(p, keyword?.into()))),)+
70 Err(_) => {
71 p.rewind(checkpoint);
72 let c = p.peek_n(2);
73 if let Ok(psuedo) = p.try_parse::<WebkitPseudoElement>() {
74 return Ok(Self::Webkit(psuedo));
75 }
76 if let Ok(psuedo) = p.try_parse::<MozPseudoElement>() {
77 return Ok(Self::Moz(psuedo));
78 }
79 if let Ok(psuedo) = p.try_parse::<MsPseudoElement>() {
80 return Ok(Self::Ms(psuedo));
81 }
82 if let Ok(psuedo) = p.try_parse::<OPseudoElement>() {
83 return Ok(Self::O(psuedo));
84 }
85 Err(diagnostics::UnexpectedPseudoElement(p.to_source_cursor(c).to_string(), c))?
86 }
87 }
88 }
89 }
90 apply_pseudo_element!(match_keyword)
91 }
92}
93
94pseudo_class!(
95 #[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
96 #[derive(Visitable)]
97 #[visit(self)]
98 pub enum LegacyPseudoElement {
99 After: "after",
100 Before: "before",
101 FirstLetter: "first-letter",
102 FirstLine: "first-line",
103 }
104);
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use css_parse::assert_parse;
110
111 #[test]
112 fn size_test() {
113 assert_eq!(std::mem::size_of::<PseudoElement>(), 44);
114 assert_eq!(std::mem::size_of::<LegacyPseudoElement>(), 28);
115 }
116
117 #[test]
118 fn test_writes() {
119 assert_parse!(PseudoElement, "::after");
120 assert_parse!(PseudoElement, "::first-letter");
121 assert_parse!(PseudoElement, "::view-transition");
122 assert_parse!(LegacyPseudoElement, ":after");
123 }
124
125 #[cfg(feature = "css_feature_data")]
126 #[test]
127 fn test_feature_data() {
128 use crate::assert_feature_id;
129 assert_feature_id!("::after", PseudoElement, "css.selectors.after");
130 assert_feature_id!("::view-transition", PseudoElement, "css.selectors.view-transition");
131 assert_feature_id!("::spelling-error", PseudoElement, "css.selectors.spelling-error");
132 assert_feature_id!(":after", LegacyPseudoElement, "css.selectors.after");
133 assert_feature_id!(":before", LegacyPseudoElement, "css.selectors.before");
134 }
135}