css_ast/selector/
mod.rs

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