Skip to main content

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