1use crate::{
3 config::Config,
4 error::{ParseError, ParseErrorKind},
5 selector::{
6 parser::{parse, ARBITRARY_END, ARBITRARY_START, ESCAPE, GROUP_END, GROUP_START},
7 Selector,
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 fn format_negative(is_negative: &bool) -> &'static str {
24 if *is_negative {
25 "-"
26 } else {
27 ""
28 }
29}
30
31pub trait Pattern {
42 fn is_matching(&self, val: char) -> bool;
44}
45
46impl Pattern for char {
47 fn is_matching(&self, val: char) -> bool {
48 val == *self
49 }
50}
51
52impl Pattern for &[char] {
53 fn is_matching(&self, val: char) -> bool {
54 #[allow(clippy::manual_contains)]
55 self.iter().any(|ch| val == *ch)
56 }
57}
58
59impl<F: Fn(char) -> bool> Pattern for F {
60 fn is_matching(&self, val: char) -> bool {
61 self(val)
62 }
63}
64
65#[derive(Debug)]
70pub struct SplitIgnoreArbitrary<'a, P: Pattern> {
71 val: &'a str,
72 iter: CharIndices<'a>,
73 searched_pattern: P,
74 ignore_parenthesis: bool,
75 is_next_escaped: bool,
76 last_slice_returned: bool,
77 parenthesis_level: usize,
78 bracket_level: usize,
79 last_index: usize,
80 seek_index: usize,
81}
82
83impl<'a, P: Pattern> Iterator for SplitIgnoreArbitrary<'a, P> {
84 type Item = (usize, &'a str);
85
86 fn next(&mut self) -> Option<Self::Item> {
87 loop {
88 if self.is_next_escaped {
89 let _ = self.iter.next()?;
90 self.is_next_escaped = false;
91 continue;
92 }
93
94 let ch = self.iter.next();
95
96 if let Some(ch) = ch {
97 match ch.1 {
98 ESCAPE => self.is_next_escaped = true,
99 GROUP_START if self.ignore_parenthesis && self.bracket_level == 0 => {
100 self.parenthesis_level += 1;
101 }
102 GROUP_END if self.ignore_parenthesis && self.bracket_level == 0 => {
103 if self.parenthesis_level > 0 {
104 self.parenthesis_level -= 1;
105 self.seek_index = ch.0 + 1;
106 }
107 }
108 ARBITRARY_START => self.bracket_level += 1,
109 ARBITRARY_END => {
110 if self.bracket_level > 0 {
111 self.bracket_level -= 1;
112 self.seek_index = ch.0 + 1;
113 }
114 }
115 _ => {
116 if self.searched_pattern.is_matching(ch.1)
117 && self.bracket_level == 0
118 && !(self.ignore_parenthesis && self.parenthesis_level > 0)
119 {
120 let last_index = self.last_index;
121 self.last_index = ch.0 + ch.1.len_utf8();
122 self.seek_index = self.last_index;
123 return Some((last_index, &self.val[last_index..ch.0]));
124 }
125 }
126 }
127 } else if !self.last_slice_returned {
128 let last_index = self.last_index;
130 self.last_index = self.val.len();
131 self.last_slice_returned = true;
132 return Some((last_index, &self.val[last_index..self.val.len()]));
133 } else {
134 return None;
137 }
138 }
139 }
140}
141
142pub fn split_ignore_arbitrary<P: Pattern>(
155 val: &str,
156 searched_pattern: P,
157 ignore_parenthesis: bool,
158) -> impl Iterator<Item = (usize, &str)> {
159 SplitIgnoreArbitrary {
160 val,
161 iter: val.char_indices(),
162 searched_pattern,
163 ignore_parenthesis,
164 is_next_escaped: false,
165 last_slice_returned: false,
166 parenthesis_level: 0,
167 bracket_level: 0,
168 last_index: 0,
169 seek_index: 0,
170 }
171}
172
173fn sort_selectors_recursive<'a>(
174 val: impl Iterator<Item = &'a str>,
175 separator: &str,
176 config: &Config,
177) -> String {
178 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
179 enum FoundSelector<'a> {
180 UnknownSelector(&'a str),
181 KnownSelector(Selector<'a>),
182 Group(String),
183 }
184
185 fn split_map_closure(s: (usize, &str)) -> &str {
186 s.1
187 }
188
189 fn dedup_key<'a>(s: &'a FoundSelector<'a>) -> &'a str {
190 match s {
191 FoundSelector::KnownSelector(s) => s.full,
192 FoundSelector::UnknownSelector(s) => s,
193 FoundSelector::Group(g) => g,
194 }
195 }
196
197 let config_derived_variants = config.get_derived_variants();
198 let mut selectors = val
199 .filter_map(|v| {
200 let selectors = parse(v.trim(), None, None, config, &config_derived_variants);
201
202 if selectors.len() > 1 {
203 let start = split_ignore_arbitrary(v.trim(), '(', false)
205 .nth(1)
206 .map(|(n, _s)| n)
207 .unwrap_or_default();
208
209 Some(FoundSelector::Group(format!(
210 "{}{})",
211 &v[..start],
212 sort_selectors_recursive(
213 split_ignore_arbitrary(v[start..v.len() - 1].trim(), ',', true)
214 .map(split_map_closure),
215 ",",
216 config,
217 )
218 )))
219 } else {
220 match selectors.into_iter().next()? {
221 Ok(selector) => Some(FoundSelector::KnownSelector(selector)),
222 Err(ParseError {
223 kind:
224 ParseErrorKind::TooShort(selector)
225 | ParseErrorKind::VariantsWithoutModifier(selector)
226 | ParseErrorKind::UnknownPlugin(selector)
227 | ParseErrorKind::UnknownVariant(_, selector),
228 ..
229 }) => Some(FoundSelector::UnknownSelector(selector)),
230 }
231 }
232 })
233 .collect::<Vec<FoundSelector>>();
234
235 selectors.sort_unstable_by(|a, b| match (a, b) {
237 (FoundSelector::KnownSelector(_), FoundSelector::UnknownSelector(_))
238 | (FoundSelector::Group(_), _) => Ordering::Greater,
239 (FoundSelector::UnknownSelector(_), FoundSelector::KnownSelector(_))
240 | (_, FoundSelector::Group(_)) => Ordering::Less,
241 (FoundSelector::KnownSelector(a), FoundSelector::KnownSelector(b)) => a.cmp(b),
242 (FoundSelector::UnknownSelector(a), FoundSelector::UnknownSelector(b)) => a.cmp(b),
243 });
244
245 selectors.dedup_by(|a, b| dedup_key(&*a) == dedup_key(&*b));
247
248 selectors
249 .iter()
250 .map(|s| match s {
251 FoundSelector::KnownSelector(s) => s.full,
252 FoundSelector::UnknownSelector(s) => s,
253 FoundSelector::Group(g) => g,
254 })
255 .collect::<Vec<&str>>()
256 .join(separator)
257}
258
259pub fn sort_selectors(val: &str, config: &Config) -> String {
272 sort_selectors_recursive(val.split_whitespace(), " ", config)
273}
274
275pub fn check_selectors<'a>(val: &'a str, config: &Config) -> Vec<ParseError<'a>> {
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)| parse(v.trim(), Some(span), None, config, &config_derived_variants))
307 .filter_map(Result::err)
308 .collect::<Vec<ParseError>>()
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::error::ParseErrorKind;
315
316 #[test]
317 fn sort_selectors_with_variant_groups() {
318 assert_eq!(
319 sort_selectors(
320 "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)",
321 &Config::default()
322 ),
323 "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))"
324 .to_string()
325 );
326 }
327
328 #[test]
329 fn sort_selectors_deduplicate() {
330 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());
331 }
332
333 #[test]
334 fn check_selectors_ignore_newlines_and_spaces() {
335 assert_eq!(
336 check_selectors(
337 "text-blue-100 text-blue-100 md:flex lg:block
338content-['hover:(md:text-white)'] md:blue-flex
339
340focus:(hover:md:flex,lg:flex)
341 lg:bg-red-500",
342 &Config::default()
343 ),
344 vec![ParseError {
345 span: 84..96,
346 kind: ParseErrorKind::UnknownPlugin("md:blue-flex")
347 }]
348 );
349 }
350}