1use crate::CssMetadata;
2use bumpalo::collections::Vec;
3use css_parse::{
4 CompoundSelector as CompoundSelectorTrait, Cursor, NodeMetadata, NodeWithMetadata, Parse, Parser,
5 Result as ParserResult, SelectorComponent as SelectorComponentTrait, T, syntax::CommaSeparated,
6};
7use csskit_derives::{IntoCursor, Parse, Peek, SemanticEq, ToCursors, ToSpan};
8
9mod attribute;
10mod class;
11mod combinator;
12mod functional_pseudo_class;
13mod functional_pseudo_element;
14mod moz;
15mod ms;
16mod namespace;
17mod nth;
18mod o;
19mod pseudo_class;
20mod pseudo_element;
21mod tag;
22mod webkit;
23
24pub use attribute::*;
25pub use class::*;
26pub use combinator::*;
27pub use functional_pseudo_class::*;
28pub use functional_pseudo_element::*;
29pub use moz::*;
30pub use ms::*;
31pub use namespace::*;
32pub use nth::*;
33pub use o::*;
34pub use pseudo_class::*;
35pub use pseudo_element::*;
36pub use tag::*;
37pub use webkit::*;
38
39#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
49#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
50pub struct SelectorList<'a>(pub CommaSeparated<'a, CompoundSelector<'a>>);
51
52impl<'a> NodeWithMetadata<CssMetadata> for SelectorList<'a> {
53 fn self_metadata(&self) -> CssMetadata {
54 CssMetadata::default().with_size(self.0.len().min(u16::MAX as usize) as u16)
55 }
56
57 fn metadata(&self) -> CssMetadata {
58 self.self_metadata()
59 }
60}
61
62#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
64#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
65#[derive(csskit_derives::NodeWithMetadata)]
66pub struct CompoundSelector<'a>(pub Vec<'a, SelectorComponent<'a>>);
67
68impl<'a> CompoundSelectorTrait<'a> for CompoundSelector<'a> {
69 type SelectorComponent = SelectorComponent<'a>;
70}
71
72impl<'a> Parse<'a> for CompoundSelector<'a> {
73 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
74 where
75 I: Iterator<Item = Cursor> + Clone,
76 {
77 Ok(Self(Self::parse_compound_selector(p)?))
78 }
79}
80
81pub type ComplexSelector<'a> = SelectorList<'a>;
82pub type ForgivingSelector<'a> = SelectorList<'a>;
83pub type RelativeSelector<'a> = SelectorList<'a>;
84
85#[derive(Peek, Parse, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
87#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
88#[derive(csskit_derives::NodeWithMetadata)]
89pub struct Id(T![Hash]);
90
91#[derive(Peek, Parse, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
93#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
94#[derive(csskit_derives::NodeWithMetadata)]
95pub struct Wildcard(T![*]);
96
97#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
102#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
103#[derive(csskit_derives::NodeWithMetadata)]
104pub enum SelectorComponent<'a> {
105 Id(Id),
106 Class(Class),
107 Tag(Tag),
108 Wildcard(Wildcard),
109 Combinator(Combinator),
110 Attribute(Attribute),
111 PseudoClass(PseudoClass),
112 PseudoElement(PseudoElement),
113 FunctionalPseudoElement(FunctionalPseudoElement<'a>),
114 LegacyPseudoElement(LegacyPseudoElement),
115 FunctionalPseudoClass(FunctionalPseudoClass<'a>),
116 Namespace(Namespace),
117}
118
119impl<'a> Parse<'a> for SelectorComponent<'a> {
120 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
121 where
122 I: Iterator<Item = Cursor> + Clone,
123 {
124 Self::parse_selector_component(p)
125 }
126}
127
128impl<'a> SelectorComponentTrait<'a> for SelectorComponent<'a> {
129 type Wildcard = Wildcard;
130 type Id = Id;
131 type Type = Tag;
132 type PseudoClass = PseudoClass;
133 type PseudoElement = PseudoElement;
134 type LegacyPseudoElement = LegacyPseudoElement;
135 type Class = Class;
136 type NsType = Namespace;
137 type Combinator = Combinator;
138 type Attribute = Attribute;
139 type FunctionalPseudoClass = FunctionalPseudoClass<'a>;
140 type FunctionalPseudoElement = FunctionalPseudoElement<'a>;
141
142 fn build_wildcard(node: Wildcard) -> Self {
143 Self::Wildcard(node)
144 }
145
146 fn build_id(node: Id) -> Self {
147 Self::Id(node)
148 }
149
150 fn build_class(node: Class) -> Self {
151 Self::Class(node)
152 }
153
154 fn build_type(node: Tag) -> Self {
155 Self::Tag(node)
156 }
157
158 fn build_pseudo_class(node: PseudoClass) -> Self {
159 Self::PseudoClass(node)
160 }
161
162 fn build_pseudo_element(node: PseudoElement) -> Self {
163 Self::PseudoElement(node)
164 }
165
166 fn build_legacy_pseudo_element(node: LegacyPseudoElement) -> Self {
167 Self::LegacyPseudoElement(node)
168 }
169
170 fn build_ns_type(node: Namespace) -> Self {
171 Self::Namespace(node)
172 }
173
174 fn build_combinator(node: Combinator) -> Self {
175 Self::Combinator(node)
176 }
177
178 fn build_attribute(node: Attribute) -> Self {
179 Self::Attribute(node)
180 }
181
182 fn build_functional_pseudo_class(node: FunctionalPseudoClass<'a>) -> Self {
183 Self::FunctionalPseudoClass(node)
184 }
185
186 fn build_functional_pseudo_element(node: FunctionalPseudoElement<'a>) -> Self {
187 Self::FunctionalPseudoElement(node)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::CssAtomSet;
195 use css_parse::assert_parse;
196
197 #[test]
198 fn size_test() {
199 assert_eq!(std::mem::size_of::<SelectorList>(), 32);
200 assert_eq!(std::mem::size_of::<ComplexSelector>(), 32);
201 assert_eq!(std::mem::size_of::<ForgivingSelector>(), 32);
202 assert_eq!(std::mem::size_of::<RelativeSelector>(), 32);
203 assert_eq!(std::mem::size_of::<SelectorComponent>(), 128);
204 assert_eq!(std::mem::size_of::<LegacyPseudoElement>(), 28);
205 assert_eq!(std::mem::size_of::<Combinator>(), 28);
206 }
207
208 #[test]
209 fn test_writes() {
210 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":root");
211 assert_parse!(CssAtomSet::ATOMS, SelectorList, "body,body");
212 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".body .body");
213 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*");
214 assert_parse!(CssAtomSet::ATOMS, SelectorList, "[attr|='foo']");
215 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*|x");
216 assert_parse!(CssAtomSet::ATOMS, SelectorList, "* x");
217 assert_parse!(CssAtomSet::ATOMS, SelectorList, "a b");
218 assert_parse!(CssAtomSet::ATOMS, SelectorList, " a b");
219 assert_parse!(CssAtomSet::ATOMS, SelectorList, "body [attr|='foo']");
220 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*|x :focus-within");
221 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"]");
222 assert_parse!(CssAtomSet::ATOMS, SelectorList, "a > b");
223 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"] > *");
224 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"] > * + *");
225 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":after");
226 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::after");
227 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":before");
228 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::before");
229 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::before:focus:target:right:playing:popover-open:blank");
230 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":dir(ltr)");
231 assert_parse!(CssAtomSet::ATOMS, SelectorList, "tr:nth-child(n-1):state(foo)");
232 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":lang(en-gb,en-us)");
234 assert_parse!(CssAtomSet::ATOMS, SelectorList, "& .foo");
235 assert_parse!(CssAtomSet::ATOMS, SelectorList, "&:hover");
236 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo &:hover");
237 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo & & &");
238 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".class&");
239 assert_parse!(CssAtomSet::ATOMS, SelectorList, "&&");
240 assert_parse!(CssAtomSet::ATOMS, SelectorList, "& + .foo,&.bar");
241 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":state(foo)&");
242 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":heading(1)");
243 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":heading(1,2,3)");
244 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::-moz-focus-inner");
246 assert_parse!(
247 CssAtomSet::ATOMS,
248 SelectorList,
249 "::-moz-list-bullet::-webkit-scrollbar::-ms-clear:-ms-input-placeholder::-o-scrollbar:-o-prefocus"
250 );
251 assert_parse!(CssAtomSet::ATOMS, SelectorList, "button:-moz-focusring");
252 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::view-transition-group(*)");
253 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::view-transition-new(thing.foo.bar.baz)");
254 }
255
256 #[test]
257 #[cfg(feature = "visitable")]
258 fn test_visits() {
259 use crate::assert_visits;
260 assert_visits!(".foo", CompoundSelector, Class);
261 assert_visits!("#bar", CompoundSelector, Id);
262 assert_visits!(".foo", SelectorList, CompoundSelector, Class);
263 assert_visits!(".foo, #bar", SelectorList, CompoundSelector, Class, CompoundSelector, Id);
264 assert_visits!(".foo#bar", CompoundSelector, Class, Id);
265 assert_visits!(".foo.bar", CompoundSelector, Class, Class);
266 assert_visits!(".foo", CompoundSelector, Class);
267 assert_visits!(".foo#bar", CompoundSelector, Class, Id);
268 assert_visits!(".foo", CompoundSelector, Class);
269 assert_visits!("*.foo#bar", CompoundSelector, Wildcard, Class, Id);
270 assert_visits!(".foo .bar", CompoundSelector, Class, Combinator, Class);
271 assert_visits!(".foo ", CompoundSelector, Class);
272 assert_visits!("a > b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
273 assert_visits!("a>b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
274 assert_visits!("a + b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
275 assert_visits!("a ~ b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
276 assert_visits!(".foo > .bar + .baz", CompoundSelector, Class, Combinator, Class, Combinator, Class);
277 }
278
279 #[test]
280 #[should_panic]
281 #[cfg(feature = "visitable")]
282 fn test_assert_visits_fails() {
283 use crate::assert_visits;
284 assert_visits!(".foo", CompoundSelector, visit_id<Id>);
285 }
286}