css_ast/selector/
pseudo_element.rs

1use 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}