Skip to main content

css_parse/traits/
selectors.rs

1use super::prelude::*;
2use crate::{Kind, KindSet};
3
4pub trait CompoundSelector<'a>: Sized + Parse<'a> {
5	/// SelectorComponent represents a Selector, or Combinator.
6	/// <https://drafts.csswg.org/selectors-4/#typedef-combinator>
7	/// <https://drafts.csswg.org/selectors-4/#typedef-type-selector>
8	/// <https://drafts.csswg.org/selectors-4/#typedef-subclass-selector>
9	/// <https://drafts.csswg.org/selectors-4/#typedef-pseudo-element-selector>
10	type SelectorComponent: Parse<'a> + SelectorComponent<'a>;
11
12	/// Parse the next selector component, or return Ok(None) if at a terminator.
13	/// This allows implementors to process components incrementally without building the full Vec first.
14	fn parse_compound_selector_part<I>(p: &mut Parser<'a, I>) -> Result<Option<Self::SelectorComponent>>
15	where
16		I: Iterator<Item = Cursor> + Clone,
17	{
18		// If a stop token has been reached (skipping whitespace), return None
19		let skip = p.set_skip(KindSet::TRIVIA);
20		let next = p.peek_n(1);
21		p.set_skip(skip);
22		if next == Kind::Eof || next == KindSet::LEFT_CURLY_RIGHT_PAREN_COMMA_OR_SEMICOLON {
23			return Ok(None);
24		}
25		p.parse::<Self::SelectorComponent>().map(Some)
26	}
27
28	fn parse_compound_selector<I>(p: &mut Parser<'a, I>) -> Result<Vec<'a, Self::SelectorComponent>>
29	where
30		I: Iterator<Item = Cursor> + Clone,
31	{
32		let mut components = Vec::new_in(p.alloc());
33		// Trim leading whitespace
34		p.consume_trivia();
35		while let Some(component) = Self::parse_compound_selector_part(p)? {
36			components.push(component);
37		}
38		Ok(components)
39	}
40}
41
42pub trait SelectorComponent<'a>: Sized {
43	type Wildcard: Peek<'a> + Parse<'a>;
44	type Id: Peek<'a> + Parse<'a>;
45	type Type: Peek<'a> + Parse<'a>;
46	type PseudoClass: Parse<'a>;
47	type PseudoElement: Parse<'a>;
48	type LegacyPseudoElement: Peek<'a> + Parse<'a>;
49	type Class: Parse<'a>;
50	type NsType: Parse<'a>;
51	type Combinator: Parse<'a>;
52	type Attribute: Parse<'a>;
53	type FunctionalPseudoClass: Parse<'a>;
54	type FunctionalPseudoElement: Parse<'a>;
55
56	fn build_wildcard(node: Self::Wildcard) -> Self;
57	fn build_id(node: Self::Id) -> Self;
58	fn build_class(node: Self::Class) -> Self;
59	fn build_type(node: Self::Type) -> Self;
60	fn build_pseudo_class(node: Self::PseudoClass) -> Self;
61	fn build_pseudo_element(node: Self::PseudoElement) -> Self;
62	fn build_legacy_pseudo_element(node: Self::LegacyPseudoElement) -> Self;
63	fn build_ns_type(node: Self::NsType) -> Self;
64	fn build_combinator(node: Self::Combinator) -> Self;
65	fn build_attribute(node: Self::Attribute) -> Self;
66	fn build_functional_pseudo_class(node: Self::FunctionalPseudoClass) -> Self;
67	fn build_functional_pseudo_element(node: Self::FunctionalPseudoElement) -> Self;
68
69	fn parse_selector_component<I>(p: &mut Parser<'a, I>) -> Result<Self>
70	where
71		I: Iterator<Item = Cursor> + Clone,
72	{
73		let skip = p.set_skip(KindSet::COMMENTS);
74		let c = p.peek_n(1);
75		let t = c.token();
76		match t.kind() {
77			Kind::Ident => match p.peek_n(2) {
78				t if t == '|' => {
79					p.set_skip(skip);
80					p.parse::<Self::NsType>().map(Self::build_ns_type)
81				}
82				_ => {
83					p.set_skip(skip);
84					if Self::Type::peek(p, c) {
85						Ok(Self::build_type(p.parse::<Self::Type>()?))
86					} else {
87						Err(Diagnostic::new(c, Diagnostic::unexpected_tag))?
88					}
89				}
90			},
91			Kind::Hash if t.hash_is_id_like() => {
92				p.set_skip(skip);
93				if Self::Id::peek(p, c) {
94					Ok(Self::build_id(p.parse::<Self::Id>()?))
95				} else {
96					Err(Diagnostic::new(c, Diagnostic::unexpected_id))?
97				}
98			}
99			Kind::LeftSquare => {
100				p.set_skip(skip);
101				p.parse::<Self::Attribute>().map(Self::build_attribute)
102			}
103			Kind::Delim => match t.char().unwrap() {
104				'.' => {
105					let c = p.peek_n(2);
106					p.set_skip(skip);
107					match c.token().kind() {
108						Kind::Ident => p.parse::<Self::Class>().map(Self::build_class),
109						_ => Err(Diagnostic::new(c, Diagnostic::expected_ident))?,
110					}
111				}
112				'*' => {
113					let t = p.peek_n(2);
114					p.set_skip(skip);
115					if t == '|' {
116						p.parse::<Self::NsType>().map(Self::build_ns_type)
117					} else {
118						Ok(Self::build_wildcard(p.parse::<Self::Wildcard>()?))
119					}
120				}
121				_ => {
122					p.set_skip(skip);
123					let value = p.parse::<Self::Combinator>().map(Self::build_combinator);
124					p.set_skip(KindSet::WHITESPACE);
125					p.consume_trivia_as_leading();
126					p.set_skip(skip);
127					value
128				}
129			},
130			Kind::Colon => {
131				let c2 = p.peek_n(2);
132				match c2.token().kind() {
133					Kind::Colon => {
134						let c3 = p.peek_n(3);
135						p.set_skip(skip);
136						match c3.token().kind() {
137							Kind::Ident => p.parse::<Self::PseudoElement>().map(Self::build_pseudo_element),
138							Kind::Function => {
139								p.parse::<Self::FunctionalPseudoElement>().map(Self::build_functional_pseudo_element)
140							}
141							_ => Err(Diagnostic::new(c3, Diagnostic::unexpected))?,
142						}
143					}
144					Kind::Ident => {
145						p.set_skip(skip);
146						if Self::LegacyPseudoElement::peek(p, c) {
147							p.parse::<Self::LegacyPseudoElement>().map(Self::build_legacy_pseudo_element)
148						} else {
149							p.parse::<Self::PseudoClass>().map(Self::build_pseudo_class)
150						}
151					}
152					Kind::Function => {
153						p.set_skip(skip);
154						p.parse::<Self::FunctionalPseudoClass>().map(Self::build_functional_pseudo_class)
155					}
156					_ => Err(Diagnostic::new(c2, Diagnostic::unexpected))?,
157				}
158			}
159			_ => {
160				// If this is whitespace, check if there's an explicit combinator ahead.
161				// Combinators cannot be adjacent, so whitespace before an explicit
162				// combinator (>, +, ~, ||, &) should be consumed as trivia, not parsed
163				// as a Descendant combinator.
164				if t.kind() == Kind::Whitespace {
165					p.set_skip(KindSet::TRIVIA);
166					let next = p.peek_n(1);
167					let next_is_explicit_combinator = match next.token().kind() {
168						Kind::Delim => matches!(next.token().char(), Some('>' | '+' | '~' | '|' | '&')),
169						_ => false,
170					};
171					if next_is_explicit_combinator {
172						p.consume_trivia_as_leading();
173						p.set_skip(skip);
174						return Self::parse_selector_component(p);
175					}
176					p.set_skip(skip);
177				}
178				let value = p.parse::<Self::Combinator>().map(Self::build_combinator);
179				// Given descendant combinators cannot appear in sequence with other combinators, we can safely eat trivia here
180				// in order to remove unecessary conjoined descendant combinators
181				p.set_skip(KindSet::WHITESPACE);
182				p.consume_trivia_as_leading();
183				p.set_skip(skip);
184				value
185			}
186		}
187	}
188}
189
190/// An `<an+b>` value, such as the `2n+1` of `:nth-child(2n+1)`.
191///
192/// <https://drafts.csswg.org/css-syntax-3/#anb-microsyntax>
193pub trait Nth<'a>: Sized {
194	/// The set of [Kinds][Kind] that can start an `<an+b>` value.
195	const NTH_KINDSET: KindSet = KindSet::new(&[Kind::Number, Kind::Ident, Kind::Dimension, Kind::Delim]);
196
197	/// Returns true if the cursor is the `odd` keyword.
198	fn peek_odd<I>(p: &Parser<'a, I>, c: Cursor) -> bool
199	where
200		I: Iterator<Item = Cursor> + Clone;
201
202	/// Returns true if the cursor is the `even` keyword.
203	fn peek_even<I>(p: &Parser<'a, I>, c: Cursor) -> bool
204	where
205		I: Iterator<Item = Cursor> + Clone;
206
207	fn build_odd(node: T![Ident]) -> Self;
208	fn build_even(node: T![Ident]) -> Self;
209	fn build_integer(node: T![Number]) -> Self;
210	fn build_anb(a: i32, b: i32, cursors: [Cursor; 4]) -> Self;
211
212	/// Returns true if the 1-based `index` is one of the indices selected by `an+b`.
213	fn anb_matches(a: i32, b: i32, index: i32) -> bool {
214		if a == 0 {
215			index == b
216		} else {
217			let diff = index - b;
218			diff % a == 0 && diff / a >= 0
219		}
220	}
221
222	fn parse_nth<I>(p: &mut Parser<'a, I>) -> Result<Self>
223	where
224		I: Iterator<Item = Cursor> + Clone,
225	{
226		if p.peek::<T![Number]>() && p.peek_n(1).token().is_int() {
227			return p.parse::<T![Number]>().map(Self::build_integer);
228		} else if p.peek::<T![Ident]>() {
229			let peek_cursor = p.peek_n(1);
230			if Self::peek_odd(p, peek_cursor) {
231				return p.parse::<T![Ident]>().map(Self::build_odd);
232			} else if Self::peek_even(p, peek_cursor) {
233				return p.parse::<T![Ident]>().map(Self::build_even);
234			}
235		}
236
237		let mut c = p.next();
238
239		let mut b_sign = 0;
240		let mut cursors = [c, Cursor::EMPTY, Cursor::EMPTY, Cursor::EMPTY];
241
242		if c == '+' {
243			let skip = p.set_skip(KindSet::NONE);
244			c = p.next();
245			p.set_skip(skip);
246			debug_assert!(cursors[1] == Cursor::EMPTY);
247			cursors[1] = c;
248		}
249		if !matches!(c.token().kind(), Kind::Number | Kind::Dimension | Kind::Ident) {
250			Err(Diagnostic::new(c, Diagnostic::unexpected))?
251		}
252		if c.token().is_float() {
253			Err(Diagnostic::new(c, Diagnostic::expected_int))?
254		}
255
256		let source_cursor = p.to_source_cursor(c);
257		let anb = source_cursor.parse(p.alloc());
258		let mut chars = anb.chars();
259		let mut char = chars.next();
260		let a = if c.token().is_int() {
261			c.token().value() as i32
262		} else if char == Some('-') {
263			char = chars.next();
264			-1
265		} else {
266			1
267		};
268		if !matches!(char, Some('n') | Some('N')) {
269			Err(Diagnostic::new(c, Diagnostic::unexpected))?
270		}
271		let rest = chars.as_str();
272		if rest == "-" {
273			// A trailing `-` on the unit, as in `n-`, `-n-` or `5n-`, makes b negative and leaves its
274			// digits to arrive as a separate unsigned token.
275			b_sign = -1;
276		} else if let Ok(b) = rest.parse::<i32>() {
277			return Ok(Self::build_anb(a, b, cursors));
278		} else if !rest.is_empty() {
279			Err(Diagnostic::new(c, Diagnostic::unexpected))?
280		}
281
282		if b_sign == 0 {
283			if p.peek::<T![+]>() {
284				b_sign = 1;
285				c = p.parse::<T![+]>()?.into();
286				debug_assert!(cursors[2] == Cursor::EMPTY);
287				cursors[2] = c;
288			} else if p.peek::<T![-]>() {
289				b_sign = -1;
290				c = p.parse::<T![-]>()?.into();
291				debug_assert!(cursors[2] == Cursor::EMPTY);
292				cursors[2] = c;
293			}
294		}
295
296		let b = if p.peek::<T![Number]>() {
297			c = p.parse::<T![Number]>()?.into();
298			debug_assert!(cursors[3] == Cursor::EMPTY);
299			if c.token().is_float() {
300				Err(Diagnostic::new(c, Diagnostic::expected_int))?
301			}
302			if c.token().has_sign() && b_sign != 0 {
303				Err(Diagnostic::new(c, Diagnostic::expected_unsigned))?
304			}
305			// If the number has a sign (like +1 or -1), mark it as required for minification
306			if c.token().has_sign() {
307				c = c.map_token(|t| t.with_sign_required());
308			}
309			cursors[3] = c;
310			if b_sign == 0 {
311				b_sign = 1;
312			}
313			let i = c.token().value();
314			(i.abs() as i32) * b_sign
315		} else {
316			0
317		};
318		Ok(Self::build_anb(a, b, cursors))
319	}
320}