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}