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#[derive(csskit_derives::NodeWithMetadata)]
40		pub enum PseudoElement {
41			$($(#[$meta])* $ident(T![::], T![Ident]),)+
42			Webkit(WebkitPseudoElement),
43			Moz(MozPseudoElement),
44			Ms(MsPseudoElement),
45			O(OPseudoElement),
46		}
47	};
48}
49apply_pseudo_element!(define_pseudo_element);
50
51impl<'a> Peek<'a> for PseudoElement {
52	fn peek<I>(p: &Parser<'a, I>, _: css_lexer::Cursor) -> bool
53	where
54		I: Iterator<Item = Cursor> + Clone,
55	{
56		p.peek::<T![::]>() && p.peek_n(3) == Kind::Ident
57	}
58}
59
60impl<'a> Parse<'a> for PseudoElement {
61	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
62	where
63		I: Iterator<Item = Cursor> + Clone,
64	{
65		let c = p.peek_n(3);
66		macro_rules! match_keyword {
67			( $($(#[$meta:meta])* $ident: ident: $pat: pat $(,)*)+ ) => {
68				match p.to_atom::<CssAtomSet>(c) {
69					$($pat => {
70						let skip = p.set_skip(KindSet::NONE);
71						let colons = p.parse::<T![::]>();
72						let ident = p.parse::<T![Ident]>();
73						p.set_skip(skip);
74						Ok(Self::$ident(colons?, ident?))
75					})+
76					_ => {
77						if let Ok(psuedo) = p.try_parse::<WebkitPseudoElement>() {
78							return Ok(Self::Webkit(psuedo));
79						}
80						if let Ok(psuedo) = p.try_parse::<MozPseudoElement>() {
81							return Ok(Self::Moz(psuedo));
82						}
83						if let Ok(psuedo) = p.try_parse::<MsPseudoElement>() {
84							return Ok(Self::Ms(psuedo));
85						}
86						if let Ok(psuedo) = p.try_parse::<OPseudoElement>() {
87							return Ok(Self::O(psuedo));
88						}
89						Err(Diagnostic::new(c, Diagnostic::unexpected_pseudo_element))?
90					}
91				}
92			}
93		}
94		apply_pseudo_element!(match_keyword)
95	}
96}
97
98pseudo_class!(
99	#[derive(ToSpan, ToCursors, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
100	#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
101	#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
102#[derive(csskit_derives::NodeWithMetadata)]
103	#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.selectors"))]
104	pub enum LegacyPseudoElement {
105		After: CssAtomSet::After,
106		Before: CssAtomSet::Before,
107		FirstLetter: CssAtomSet::FirstLetter,
108		FirstLine: CssAtomSet::FirstLine,
109	}
110);
111
112#[cfg(test)]
113mod tests {
114	use super::*;
115	use crate::CssAtomSet;
116	use css_parse::assert_parse;
117
118	#[test]
119	fn size_test() {
120		assert_eq!(std::mem::size_of::<PseudoElement>(), 44);
121		assert_eq!(std::mem::size_of::<LegacyPseudoElement>(), 28);
122	}
123
124	#[test]
125	fn test_writes() {
126		assert_parse!(CssAtomSet::ATOMS, PseudoElement, "::after");
127		assert_parse!(CssAtomSet::ATOMS, PseudoElement, "::first-letter");
128		assert_parse!(CssAtomSet::ATOMS, PseudoElement, "::view-transition");
129		assert_parse!(CssAtomSet::ATOMS, LegacyPseudoElement, ":after");
130	}
131
132	#[cfg(feature = "css_feature_data")]
133	#[test]
134	fn test_feature_data() {
135		use crate::assert_feature_id;
136		assert_feature_id!("::after", PseudoElement, "css.selectors.after");
137		assert_feature_id!("::view-transition", PseudoElement, "css.selectors.view-transition");
138		assert_feature_id!("::spelling-error", PseudoElement, "css.selectors.spelling-error");
139		assert_feature_id!(":after", LegacyPseudoElement, "css.selectors.after");
140		assert_feature_id!(":before", LegacyPseudoElement, "css.selectors.before");
141	}
142}