css_ast/selector/
pseudo_element.rs

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