1use crate::{
2 CssMetadata,
3 specificity::{Specificity, ToSpecificity},
4};
5use bumpalo::collections::Vec;
6use css_parse::{
7 CompoundSelector as CompoundSelectorTrait, Cursor, NodeMetadata, NodeWithMetadata, Parse, Parser,
8 Result as ParserResult, SelectorComponent as SelectorComponentTrait, T, syntax::CommaSeparated,
9};
10use csskit_derives::*;
11
12mod attribute;
13mod class;
14mod combinator;
15mod functional_pseudo_class;
16mod functional_pseudo_element;
17mod moz;
18mod ms;
19mod namespace;
20mod nth;
21mod o;
22mod pseudo_class;
23mod pseudo_element;
24mod tag;
25mod webkit;
26
27pub use attribute::*;
28pub use class::*;
29pub use combinator::*;
30pub use functional_pseudo_class::*;
31pub use functional_pseudo_element::*;
32pub use moz::*;
33pub use ms::*;
34pub use namespace::*;
35pub use nth::*;
36pub use o::*;
37pub use pseudo_class::*;
38pub use pseudo_element::*;
39pub use tag::*;
40pub use webkit::*;
41
42#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
52#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
53pub struct SelectorList<'a>(pub CommaSeparated<'a, CompoundSelector<'a>>);
54
55impl<'a> NodeWithMetadata<CssMetadata> for SelectorList<'a> {
56 fn self_metadata(&self) -> CssMetadata {
57 CssMetadata::default().with_size(self.0.len().min(u16::MAX as usize) as u16)
58 }
59
60 fn metadata(&self) -> CssMetadata {
61 self.self_metadata()
62 }
63}
64
65#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
67#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
68#[derive(csskit_derives::NodeWithMetadata)]
69pub struct CompoundSelector<'a>(pub Vec<'a, SelectorComponent<'a>>);
70
71impl<'a> CompoundSelectorTrait<'a> for CompoundSelector<'a> {
72 type SelectorComponent = SelectorComponent<'a>;
73}
74
75impl<'a> Parse<'a> for CompoundSelector<'a> {
76 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
77 where
78 I: Iterator<Item = Cursor> + Clone,
79 {
80 Ok(Self(Self::parse_compound_selector(p)?))
81 }
82}
83
84pub type ComplexSelector<'a> = SelectorList<'a>;
85pub type ForgivingSelector<'a> = SelectorList<'a>;
86pub type RelativeSelector<'a> = SelectorList<'a>;
87
88#[derive(
89 Peek, Parse, ToCursors, IntoCursor, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
90)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
92#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
93#[derive(csskit_derives::NodeWithMetadata)]
94pub struct Id(T![Hash]);
95
96#[derive(
97 Peek, Parse, ToCursors, IntoCursor, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
98)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
100#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
101#[derive(csskit_derives::NodeWithMetadata)]
102pub struct Wildcard(T![*]);
103
104#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
109#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
110#[derive(csskit_derives::NodeWithMetadata)]
111pub enum SelectorComponent<'a> {
112 Id(Id),
113 Class(Class),
114 Tag(Tag),
115 Wildcard(Wildcard),
116 Combinator(Combinator),
117 Attribute(Attribute),
118 PseudoClass(PseudoClass),
119 PseudoElement(PseudoElement),
120 FunctionalPseudoElement(FunctionalPseudoElement<'a>),
121 LegacyPseudoElement(LegacyPseudoElement),
122 FunctionalPseudoClass(FunctionalPseudoClass<'a>),
123 Namespace(Namespace),
124}
125
126impl<'a> Parse<'a> for SelectorComponent<'a> {
127 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
128 where
129 I: Iterator<Item = Cursor> + Clone,
130 {
131 Self::parse_selector_component(p)
132 }
133}
134
135impl<'a> ToSpecificity for SelectorComponent<'a> {
136 fn specificity(&self) -> Specificity {
137 match self {
138 Self::Id(_) => Specificity(1, 0, 0),
139 Self::Class(_) | Self::Attribute(_) | Self::PseudoClass(_) => Specificity(0, 1, 0),
140 Self::Tag(_) | Self::PseudoElement(_) | Self::LegacyPseudoElement(_) => Specificity(0, 0, 1),
141 Self::FunctionalPseudoElement(_) => Specificity(0, 0, 1),
142 Self::Combinator(_) | Self::Namespace(_) | Self::Wildcard(_) => Specificity(0, 0, 0),
143 Self::FunctionalPseudoClass(f) => f.specificity(),
144 }
145 }
146}
147
148impl<'a> ToSpecificity for CompoundSelector<'a> {
149 fn specificity(&self) -> Specificity {
150 self.0.iter().map(ToSpecificity::specificity).sum()
151 }
152}
153
154impl<'a> ToSpecificity for SelectorList<'a> {
155 fn specificity(&self) -> Specificity {
156 (&self.0).into_iter().map(|(s, _)| s.specificity()).max().unwrap_or_default()
157 }
158}
159
160impl<'a> SelectorComponentTrait<'a> for SelectorComponent<'a> {
161 type Wildcard = Wildcard;
162 type Id = Id;
163 type Type = Tag;
164 type PseudoClass = PseudoClass;
165 type PseudoElement = PseudoElement;
166 type LegacyPseudoElement = LegacyPseudoElement;
167 type Class = Class;
168 type NsType = Namespace;
169 type Combinator = Combinator;
170 type Attribute = Attribute;
171 type FunctionalPseudoClass = FunctionalPseudoClass<'a>;
172 type FunctionalPseudoElement = FunctionalPseudoElement<'a>;
173
174 fn build_wildcard(node: Wildcard) -> Self {
175 Self::Wildcard(node)
176 }
177
178 fn build_id(node: Id) -> Self {
179 Self::Id(node)
180 }
181
182 fn build_class(node: Class) -> Self {
183 Self::Class(node)
184 }
185
186 fn build_type(node: Tag) -> Self {
187 Self::Tag(node)
188 }
189
190 fn build_pseudo_class(node: PseudoClass) -> Self {
191 Self::PseudoClass(node)
192 }
193
194 fn build_pseudo_element(node: PseudoElement) -> Self {
195 Self::PseudoElement(node)
196 }
197
198 fn build_legacy_pseudo_element(node: LegacyPseudoElement) -> Self {
199 Self::LegacyPseudoElement(node)
200 }
201
202 fn build_ns_type(node: Namespace) -> Self {
203 Self::Namespace(node)
204 }
205
206 fn build_combinator(node: Combinator) -> Self {
207 Self::Combinator(node)
208 }
209
210 fn build_attribute(node: Attribute) -> Self {
211 Self::Attribute(node)
212 }
213
214 fn build_functional_pseudo_class(node: FunctionalPseudoClass<'a>) -> Self {
215 Self::FunctionalPseudoClass(node)
216 }
217
218 fn build_functional_pseudo_element(node: FunctionalPseudoElement<'a>) -> Self {
219 Self::FunctionalPseudoElement(node)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::{CssAtomSet, specificity::ToSpecificity};
227 use bumpalo::Bump;
228 use css_lexer::Lexer;
229 use css_parse::Parser;
230 use css_parse::assert_parse;
231
232 #[test]
233 fn size_test() {
234 assert_eq!(std::mem::size_of::<SelectorList>(), 32);
235 assert_eq!(std::mem::size_of::<ComplexSelector>(), 32);
236 assert_eq!(std::mem::size_of::<ForgivingSelector>(), 32);
237 assert_eq!(std::mem::size_of::<RelativeSelector>(), 32);
238 assert_eq!(std::mem::size_of::<SelectorComponent>(), 128);
239 assert_eq!(std::mem::size_of::<LegacyPseudoElement>(), 28);
240 assert_eq!(std::mem::size_of::<Combinator>(), 28);
241 }
242
243 #[test]
244 fn test_writes() {
245 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":root");
246 assert_parse!(CssAtomSet::ATOMS, SelectorList, "body,body");
247 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".body .body");
248 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*");
249 assert_parse!(CssAtomSet::ATOMS, SelectorList, "[attr|='foo']");
250 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*|x");
251 assert_parse!(CssAtomSet::ATOMS, SelectorList, "* x");
252 assert_parse!(CssAtomSet::ATOMS, SelectorList, "a b");
253 assert_parse!(CssAtomSet::ATOMS, SelectorList, " a b");
254 assert_parse!(CssAtomSet::ATOMS, SelectorList, "body [attr|='foo']");
255 assert_parse!(CssAtomSet::ATOMS, SelectorList, "*|x :focus-within");
256 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"]");
257 assert_parse!(CssAtomSet::ATOMS, SelectorList, "a > b");
258 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"] > *");
259 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo[attr*=\"foo\"] > * + *");
260 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":after");
261 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::after");
262 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":before");
263 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::before");
264 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::before:focus:target:right:playing:popover-open:blank");
265 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":dir(ltr)");
266 assert_parse!(CssAtomSet::ATOMS, SelectorList, "tr:nth-child(n-1):state(foo)");
267 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":lang(en-gb,en-us)");
269 assert_parse!(CssAtomSet::ATOMS, SelectorList, "& .foo");
270 assert_parse!(CssAtomSet::ATOMS, SelectorList, "&:hover");
271 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo &:hover");
272 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".foo & & &");
273 assert_parse!(CssAtomSet::ATOMS, SelectorList, ".class&");
274 assert_parse!(CssAtomSet::ATOMS, SelectorList, "&&");
275 assert_parse!(CssAtomSet::ATOMS, SelectorList, "& + .foo,&.bar");
276 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":state(foo)&");
277 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":heading(1)");
278 assert_parse!(CssAtomSet::ATOMS, SelectorList, ":heading(1,2,3)");
279 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::-moz-focus-inner");
281 assert_parse!(
282 CssAtomSet::ATOMS,
283 SelectorList,
284 "::-moz-list-bullet::-webkit-scrollbar::-ms-clear:-ms-input-placeholder::-o-scrollbar:-o-prefocus"
285 );
286 assert_parse!(CssAtomSet::ATOMS, SelectorList, "button:-moz-focusring");
287 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::view-transition-group(*)");
288 assert_parse!(CssAtomSet::ATOMS, SelectorList, "::view-transition-new(thing.foo.bar.baz)");
289 }
290
291 #[test]
292 #[cfg(feature = "visitable")]
293 fn test_visits() {
294 use crate::assert_visits;
295 assert_visits!(".foo", CompoundSelector, Class);
296 assert_visits!("#bar", CompoundSelector, Id);
297 assert_visits!(".foo", SelectorList, CompoundSelector, Class);
298 assert_visits!(".foo, #bar", SelectorList, CompoundSelector, Class, CompoundSelector, Id);
299 assert_visits!(".foo#bar", CompoundSelector, Class, Id);
300 assert_visits!(".foo.bar", CompoundSelector, Class, Class);
301 assert_visits!(".foo", CompoundSelector, Class);
302 assert_visits!(".foo#bar", CompoundSelector, Class, Id);
303 assert_visits!(".foo", CompoundSelector, Class);
304 assert_visits!("*.foo#bar", CompoundSelector, Wildcard, Class, Id);
305 assert_visits!(".foo .bar", CompoundSelector, Class, Combinator, Class);
306 assert_visits!(".foo ", CompoundSelector, Class);
307 assert_visits!("a > b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
308 assert_visits!("a>b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
309 assert_visits!("a + b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
310 assert_visits!("a ~ b", CompoundSelector, Tag, HtmlTag, Combinator, Tag, HtmlTag);
311 assert_visits!(".foo > .bar + .baz", CompoundSelector, Class, Combinator, Class, Combinator, Class);
312 }
313
314 #[test]
315 #[should_panic]
316 #[cfg(feature = "visitable")]
317 fn test_assert_visits_fails() {
318 use crate::assert_visits;
319 assert_visits!(".foo", CompoundSelector, visit_id<Id>);
320 }
321
322 macro_rules! assert_specificity {
323 ($sel:literal, $a:literal, $b:literal, $c:literal) => {{
324 let bump = Bump::default();
325 let lexer = Lexer::new(&CssAtomSet::ATOMS, $sel);
326 let mut parser = Parser::new(&bump, $sel, lexer);
327 let result = parser.parse_entirely::<SelectorList>().with_trivia();
328 assert!(result.errors.is_empty(), "parse failed for {:?}: {:?}", $sel, result.errors);
329 let s = result.output.unwrap().specificity();
330 assert_eq!(
331 s,
332 Specificity($a, $b, $c),
333 "selector {:?}: expected ({},{},{}) got ({},{},{})",
334 $sel,
335 $a,
336 $b,
337 $c,
338 s.0,
339 s.1,
340 s.2
341 );
342 }};
343 }
344
345 #[test]
346 fn test_specificity_arithmetic() {
347 assert_eq!(Specificity(0, 1, 0) + Specificity(0, 1, 0), Specificity(0, 2, 0));
348 assert_eq!(Specificity(1, 0, 0) + Specificity(0, 1, 0), Specificity(1, 1, 0));
349 assert_eq!(Specificity(255, 0, 0) + Specificity(1, 0, 0), Specificity(255, 0, 0));
350 }
351
352 #[test]
353 fn test_specificity() {
354 assert_specificity!("#foo", 1, 0, 0);
355 assert_specificity!(".foo", 0, 1, 0);
356 assert_specificity!(".a.b.c", 0, 3, 0);
357 assert_specificity!("div", 0, 0, 1);
358 assert_specificity!(":hover", 0, 1, 0);
359 assert_specificity!("::before", 0, 0, 1);
360 assert_specificity!(":before", 0, 0, 1);
361 assert_specificity!("[href]", 0, 1, 0);
362 assert_specificity!("*", 0, 0, 0);
363 assert_specificity!("a.foo", 0, 1, 1);
364 assert_specificity!("a.foo:hover", 0, 2, 1);
365 assert_specificity!("#a.b", 1, 1, 0);
366 assert_specificity!(":where(.a.b)", 0, 0, 0);
367 assert_specificity!(":is(.a, #b)", 1, 0, 0);
368 assert_specificity!(":not(.a, .b)", 0, 1, 0);
369 assert_specificity!("a:has(.b)", 0, 1, 1);
370 assert_specificity!(":nth-child(2)", 0, 1, 0);
371 assert_specificity!(":nth-of-type(2n+1)", 0, 1, 0);
372 assert_specificity!(".a, #b", 1, 0, 0);
373 }
374
375 #[test]
376 fn test_specificity_complex() {
377 assert_specificity!("nav ul li:nth-child(even) a:not([href^='#'])", 0, 2, 4);
378 assert_specificity!("button:only-of-type:enabled:active:hover", 0, 4, 1);
379 assert_specificity!("table tr:not(:first-child):hover td:nth-child(2n+1)", 0, 3, 3);
380 assert_specificity!("input[type='checkbox'][checked]:indeterminate + label", 0, 3, 2);
381 }
382}