tailwind_rs_postcss/css_optimizer/
selector_optimizer.rs

1//! Selector optimizer for simplification and optimization
2
3use super::types::*;
4use regex::Regex;
5use std::collections::HashMap;
6
7/// Selector optimizer for simplification and optimization
8pub struct SelectorOptimizer {
9    selector_rules: HashMap<String, SelectorRule>,
10}
11
12impl SelectorOptimizer {
13    /// Create new selector optimizer
14    pub fn new() -> Self {
15        Self {
16            selector_rules: Self::build_selector_rules(),
17        }
18    }
19
20    /// Optimize CSS selectors
21    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        // Find and optimize redundant selectors
30        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    /// Find redundant selectors
45    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        // Find selectors with identical properties
61        for (_, selectors) in selector_map {
62            if selectors.len() > 1 {
63                // Keep the most specific selector, mark others as redundant
64                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    /// Remove redundant selector from CSS
77    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    /// Compare selector specificity
88    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) // Higher specificity first
92    }
93
94    /// Calculate selector specificity
95    fn calculate_specificity(&self, selector: &str) -> usize {
96        let mut specificity = 0;
97
98        // Count IDs
99        specificity += selector.matches('#').count() * 100;
100
101        // Count classes and attributes
102        specificity += selector.matches('.').count() * 10;
103        specificity += selector.matches('[').count() * 10;
104
105        // Count elements
106        specificity += selector.split_whitespace().count();
107
108        specificity
109    }
110
111    /// Build selector rules for optimization
112    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}