Skip to main content

css_ast/selector/
pseudo_element.rs

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