css_ast/selector/
mod.rs

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/// Represents a list of [CompoundSelectors][CompoundSelector], such as `body, dialog:modal`.
40///
41/// ```md
42/// <selector-list>
43///  │├─╭─ <compound-selector> ─╮─ "," ─╭─╮─┤│
44///     │                       ╰───────╯ │
45///     ╰─────────────────────────────────╯
46/// ```
47#[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// This encapsulates all `simple-selector` subtypes (e.g. `wq-name`,
98// `id-selector`) into one enum, as it makes parsing and visiting much more
99// practical.
100#[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, " /**/ .foo");
233		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		// Non Standard
245		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}