1use crate::{
3 config::Config,
4 error::{ParseError, ParseErrorKind},
5 selector::{
6 Selector,
7 parser::{ARBITRARY_END, ARBITRARY_START, ESCAPE, GROUP_END, GROUP_START, parse},
8 },
9};
10
11use std::{cmp::Ordering, iter, str::CharIndices};
12
13pub mod buffer;
14pub mod color;
15pub mod shadow;
16pub mod spacing;
17pub mod value_matchers;
18
19#[cfg(test)]
20pub(crate) mod testing;
21
22pub trait Pattern {
33 fn is_matching(&self, val: char) -> bool;
35}
36
37impl Pattern for char {
38 fn is_matching(&self, val: char) -> bool {
39 val == *self
40 }
41}
42
43impl Pattern for &[char] {
44 fn is_matching(&self, val: char) -> bool {
45 #[allow(clippy::manual_contains)]
46 self.iter().any(|ch| val == *ch)
47 }
48}
49
50impl<F: Fn(char) -> bool> Pattern for F {
51 fn is_matching(&self, val: char) -> bool {
52 self(val)
53 }
54}
55
56#[derive(Debug)]
61pub struct SplitIgnoreArbitrary<'a, P: Pattern> {
62 val: &'a str,
63 iter: CharIndices<'a>,
64 searched_pattern: P,
65 ignore_parenthesis: bool,
66 is_next_escaped: bool,
67 last_slice_returned: bool,
68 parenthesis_level: usize,
69 bracket_level: usize,
70 last_index: usize,
71 seek_index: usize,
72}
73
74impl<'a, P: Pattern> Iterator for SplitIgnoreArbitrary<'a, P> {
75 type Item = (usize, &'a str);
76
77 fn next(&mut self) -> Option<Self::Item> {
78 loop {
79 if self.is_next_escaped {
80 let _ = self.iter.next()?;
81 self.is_next_escaped = false;
82 continue;
83 }
84
85 let ch = self.iter.next();
86
87 if let Some(ch) = ch {
88 match ch.1 {
89 ESCAPE => self.is_next_escaped = true,
90 GROUP_START if self.ignore_parenthesis && self.bracket_level == 0 => {
91 self.parenthesis_level += 1;
92 }
93 GROUP_END if self.ignore_parenthesis && self.bracket_level == 0 => {
94 if self.parenthesis_level > 0 {
95 self.parenthesis_level -= 1;
96 self.seek_index = ch.0 + 1;
97 }
98 }
99 ARBITRARY_START => self.bracket_level += 1,
100 ARBITRARY_END => {
101 if self.bracket_level > 0 {
102 self.bracket_level -= 1;
103 self.seek_index = ch.0 + 1;
104 }
105 }
106 _ => {
107 if self.searched_pattern.is_matching(ch.1)
108 && self.bracket_level == 0
109 && !(self.ignore_parenthesis && self.parenthesis_level > 0)
110 {
111 let last_index = self.last_index;
112 self.last_index = ch.0 + ch.1.len_utf8();
113 self.seek_index = self.last_index;
114 return Some((last_index, &self.val[last_index..ch.0]));
115 }
116 }
117 }
118 } else if !self.last_slice_returned {
119 let last_index = self.last_index;
121 self.last_index = self.val.len();
122 self.last_slice_returned = true;
123 return Some((last_index, &self.val[last_index..self.val.len()]));
124 } else {
125 return None;
128 }
129 }
130 }
131}
132
133pub fn split_ignore_arbitrary<P: Pattern>(
146 val: &str,
147 searched_pattern: P,
148 ignore_parenthesis: bool,
149) -> impl Iterator<Item = (usize, &str)> {
150 SplitIgnoreArbitrary {
151 val,
152 iter: val.char_indices(),
153 searched_pattern,
154 ignore_parenthesis,
155 is_next_escaped: false,
156 last_slice_returned: false,
157 parenthesis_level: 0,
158 bracket_level: 0,
159 last_index: 0,
160 seek_index: 0,
161 }
162}
163
164fn sort_selectors_recursive<'a>(
165 val: impl Iterator<Item = &'a str>,
166 separator: &str,
167 config: &Config,
168) -> String {
169 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
170 enum FoundSelector<'a> {
171 UnknownSelector(&'a str),
172 KnownSelector(Selector<'a>),
173 Group(String),
174 }
175
176 fn split_map_closure(s: (usize, &str)) -> &str {
177 s.1
178 }
179
180 fn dedup_key<'a>(s: &'a FoundSelector<'a>) -> &'a str {
181 match s {
182 FoundSelector::KnownSelector(s) => s.full,
183 FoundSelector::UnknownSelector(s) => s,
184 FoundSelector::Group(g) => g,
185 }
186 }
187
188 let trie = crate::selector::trie::build_trie(&config.custom_plugins);
189 let config_derived_variants = config.get_derived_variants();
190 let mut selectors = val
191 .filter_map(|v| {
192 let selectors = parse(
193 v.trim(),
194 None,
195 None,
196 config,
197 &config_derived_variants,
198 &trie,
199 );
200
201 if selectors.len() > 1 {
202 let start = split_ignore_arbitrary(v.trim(), '(', false)
204 .nth(1)
205 .map(|(n, _s)| n)
206 .unwrap_or_default();
207
208 Some(FoundSelector::Group(format!(
209 "{}{})",
210 &v[..start],
211 sort_selectors_recursive(
212 split_ignore_arbitrary(v[start..v.len() - 1].trim(), ',', true)
213 .map(split_map_closure),
214 ",",
215 config,
216 )
217 )))
218 } else {
219 match selectors.into_iter().next()? {
220 Ok(selector) => Some(FoundSelector::KnownSelector(selector)),
221 Err(ParseError {
222 kind:
223 ParseErrorKind::TooShort(selector)
224 | ParseErrorKind::VariantsWithoutModifier(selector)
225 | ParseErrorKind::UnknownPlugin(selector)
226 | ParseErrorKind::UnknownVariant(_, selector),
227 ..
228 }) => Some(FoundSelector::UnknownSelector(selector)),
229 }
230 }
231 })
232 .collect::<Vec<FoundSelector>>();
233
234 selectors.sort_unstable_by(|a, b| match (a, b) {
236 (FoundSelector::KnownSelector(_), FoundSelector::UnknownSelector(_))
237 | (FoundSelector::Group(_), _) => Ordering::Greater,
238 (FoundSelector::UnknownSelector(_), FoundSelector::KnownSelector(_))
239 | (_, FoundSelector::Group(_)) => Ordering::Less,
240 (FoundSelector::KnownSelector(a), FoundSelector::KnownSelector(b)) => a.cmp(b),
241 (FoundSelector::UnknownSelector(a), FoundSelector::UnknownSelector(b)) => a.cmp(b),
242 });
243
244 selectors.dedup_by(|a, b| dedup_key(&*a) == dedup_key(&*b));
246
247 selectors
248 .iter()
249 .map(|s| match s {
250 FoundSelector::KnownSelector(s) => s.full,
251 FoundSelector::UnknownSelector(s) => s,
252 FoundSelector::Group(g) => g,
253 })
254 .collect::<Vec<&str>>()
255 .join(separator)
256}
257
258pub fn sort_selectors(val: &str, config: &Config) -> String {
271 sort_selectors_recursive(val.split_whitespace(), " ", config)
272}
273
274pub fn check_selectors<'a>(val: &'a str, config: &Config) -> Vec<ParseError<'a>> {
295 let trie = crate::selector::trie::build_trie(&config.custom_plugins);
296 let config_derived_variants = config.get_derived_variants();
297 val.char_indices()
298 .chain(iter::once((val.len(), ' ')))
299 .filter(|(_, ch)| ch.is_whitespace())
300 .scan(0, |last_i, (i, _)| {
301 let old_i = *last_i;
302 *last_i = i + 1;
303 Some((old_i..i, &val[old_i..i]))
304 })
305 .filter(|(_, v)| !v.is_empty())
306 .flat_map(|(span, v)| {
307 parse(
308 v.trim(),
309 Some(span),
310 None,
311 config,
312 &config_derived_variants,
313 &trie,
314 )
315 })
316 .filter_map(Result::err)
317 .collect::<Vec<ParseError>>()
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::error::ParseErrorKind;
324
325 #[test]
326 fn sort_selectors_with_variant_groups() {
327 assert_eq!(
328 sort_selectors(
329 "hover:(text-white,bg-sky-800) focus-within:bg-red-100 text-blue-500 md:flex [()())):]:checked:([))]:text-white,[))]:bg-red-500) hover:(focus:(focus-within:bg-red-500,checked:text-black),active:bg-red-500)",
330 &Config::default()
331 ),
332 "text-blue-500 focus-within:bg-red-100 md:flex hover:(bg-sky-800,text-white) [()())):]:checked:([))]:bg-red-500,[))]:text-white) hover:(active:bg-red-500,focus:(checked:text-black,focus-within:bg-red-500))"
333 .to_string()
334 );
335 }
336
337 #[test]
338 fn sort_selectors_deduplicate() {
339 assert_eq!(sort_selectors("text-blue-100 text-blue-100 md:flex lg:block content-['hover:(md:text-white)'] md:flex focus:(hover:md:flex,lg:flex)", &Config::default()), "text-blue-100 content-['hover:(md:text-white)'] lg:block md:flex focus:(hover:md:flex,lg:flex)".to_string());
340 }
341
342 #[test]
343 fn check_selectors_ignore_newlines_and_spaces() {
344 assert_eq!(
345 check_selectors(
346 "text-blue-100 text-blue-100 md:flex lg:block
347content-['hover:(md:text-white)'] md:blue-flex
348
349focus:(hover:md:flex,lg:flex)
350 lg:bg-red-500",
351 &Config::default()
352 ),
353 vec![ParseError {
354 span: 84..96,
355 kind: ParseErrorKind::UnknownPlugin("md:blue-flex")
356 }]
357 );
358 }
359}