tailwind_rs_postcss/css_optimizer/
selector_optimizer.rs1use super::types::*;
4use regex::Regex;
5use std::collections::HashMap;
6
7pub struct SelectorOptimizer {
9 selector_rules: HashMap<String, SelectorRule>,
10}
11
12impl SelectorOptimizer {
13 pub fn new() -> Self {
15 Self {
16 selector_rules: Self::build_selector_rules(),
17 }
18 }
19
20 pub fn optimize_selectors(
22 &self,
23 css: &str,
24 ) -> Result<SelectorOptimizationResult, OptimizationError> {
25 let start_time = std::time::Instant::now();
26 let mut selectors_optimized = 0;
27 let mut optimized_css = css.to_string();
28
29 let redundant_selectors = self.find_redundant_selectors(css)?;
31
32 for selector in redundant_selectors {
33 optimized_css = self.remove_redundant_selector(&optimized_css, &selector)?;
34 selectors_optimized += 1;
35 }
36
37 Ok(SelectorOptimizationResult {
38 optimized_css,
39 selectors_optimized,
40 optimization_time: start_time.elapsed(),
41 })
42 }
43
44 fn find_redundant_selectors(&self, css: &str) -> Result<Vec<String>, OptimizationError> {
46 let mut redundant = Vec::new();
47 let rule_pattern = Regex::new(r"([^{]+)\s*\{([^}]+)\}").unwrap();
48 let mut selector_map: HashMap<String, Vec<String>> = HashMap::new();
49
50 for cap in rule_pattern.captures_iter(css) {
51 let selector = cap[1].trim().to_string();
52 let properties = cap[2].trim().to_string();
53
54 selector_map
55 .entry(properties)
56 .or_insert_with(Vec::new)
57 .push(selector);
58 }
59
60 for (_, selectors) in selector_map {
62 if selectors.len() > 1 {
63 let mut sorted_selectors = selectors;
65 sorted_selectors.sort_by(|a, b| self.compare_specificity(a, b));
66
67 for selector in sorted_selectors.iter().skip(1) {
68 redundant.push(selector.clone());
69 }
70 }
71 }
72
73 Ok(redundant)
74 }
75
76 fn remove_redundant_selector(
78 &self,
79 css: &str,
80 selector: &str,
81 ) -> Result<String, OptimizationError> {
82 let rule_pattern =
83 Regex::new(&format!(r"{}[^{{]*\{{[^}}]*\}}", regex::escape(selector))).unwrap();
84 Ok(rule_pattern.replace_all(css, "").to_string())
85 }
86
87 fn compare_specificity(&self, a: &str, b: &str) -> std::cmp::Ordering {
89 let specificity_a = self.calculate_specificity(a);
90 let specificity_b = self.calculate_specificity(b);
91 specificity_b.cmp(&specificity_a) }
93
94 fn calculate_specificity(&self, selector: &str) -> usize {
96 let mut specificity = 0;
97
98 specificity += selector.matches('#').count() * 100;
100
101 specificity += selector.matches('.').count() * 10;
103 specificity += selector.matches('[').count() * 10;
104
105 specificity += selector.split_whitespace().count();
107
108 specificity
109 }
110
111 fn build_selector_rules() -> HashMap<String, SelectorRule> {
113 let mut rules = HashMap::new();
114
115 rules.insert(
116 "universal".to_string(),
117 SelectorRule {
118 name: "universal".to_string(),
119 pattern: "*".to_string(),
120 optimization: SelectorOptimization::Remove,
121 },
122 );
123
124 rules
125 }
126}