css_ast/selector/
mod.rs

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