tailwind_rs_postcss/css_optimizer/
rule_merger.rs

1//! Rule merger for consolidating compatible CSS rules
2
3use super::types::*;
4use regex::Regex;
5use std::collections::HashMap;
6
7/// Rule merger for consolidating compatible CSS rules
8pub struct RuleMerger {
9    compatibility_matrix: HashMap<String, Vec<String>>,
10}
11
12impl RuleMerger {
13    /// Create new rule merger
14    pub fn new() -> Self {
15        Self {
16            compatibility_matrix: Self::build_compatibility_matrix(),
17        }
18    }
19
20    /// Merge compatible CSS rules
21    pub fn merge_rules(&self, css: &str) -> Result<RuleMergeResult, OptimizationError> {
22        let start_time = std::time::Instant::now();
23        let mut rules_merged = 0;
24        let mut optimized_css = css.to_string();
25
26        // Find and merge duplicate selectors
27        let duplicate_selectors = self.find_duplicate_selectors(css)?;
28
29        for (selector, rules) in duplicate_selectors {
30            if rules.len() > 1 {
31                let merged_rule = self.merge_rule_properties(&rules)?;
32                optimized_css =
33                    self.replace_duplicate_rules(&optimized_css, &selector, &merged_rule)?;
34                rules_merged += 1;
35            }
36        }
37
38        Ok(RuleMergeResult {
39            optimized_css,
40            rules_merged,
41            merge_time: start_time.elapsed(),
42        })
43    }
44
45    /// Find duplicate selectors in CSS
46    fn find_duplicate_selectors(
47        &self,
48        css: &str,
49    ) -> Result<HashMap<String, Vec<CSSRule>>, OptimizationError> {
50        let mut selector_map: HashMap<String, Vec<CSSRule>> = HashMap::new();
51        let rule_pattern = Regex::new(r"([^{]+)\s*\{([^}]+)\}").unwrap();
52
53        for cap in rule_pattern.captures_iter(css) {
54            let selector = cap[1].trim().to_string();
55            let properties = cap[2].trim();
56
57            let rule = CSSRule {
58                selector: selector.clone(),
59                properties: self.parse_properties(properties)?,
60            };
61
62            selector_map
63                .entry(selector)
64                .or_insert_with(Vec::new)
65                .push(rule);
66        }
67
68        Ok(selector_map)
69    }
70
71    /// Merge properties from multiple rules
72    fn merge_rule_properties(&self, rules: &[CSSRule]) -> Result<CSSRule, OptimizationError> {
73        let mut merged_properties = Vec::new();
74        let mut seen_properties: HashMap<String, String> = HashMap::new();
75
76        for rule in rules {
77            for property in &rule.properties {
78                seen_properties.insert(property.name.clone(), property.value.clone());
79            }
80        }
81
82        for (name, value) in seen_properties {
83            merged_properties.push(CSSProperty { name, value });
84        }
85
86        Ok(CSSRule {
87            selector: rules[0].selector.clone(),
88            properties: merged_properties,
89        })
90    }
91
92    /// Replace duplicate rules with merged rule
93    fn replace_duplicate_rules(
94        &self,
95        css: &str,
96        selector: &str,
97        merged_rule: &CSSRule,
98    ) -> Result<String, OptimizationError> {
99        let mut result = css.to_string();
100        let rule_pattern =
101            Regex::new(&format!(r"{}[^{{]*\{{[^}}]*\}}", regex::escape(selector))).unwrap();
102
103        // Remove all instances of the selector
104        result = rule_pattern.replace_all(&result, "").to_string();
105
106        // Add merged rule
107        let merged_css = format!(
108            "{} {{\n{}\n}}",
109            merged_rule.selector,
110            merged_rule
111                .properties
112                .iter()
113                .map(|p| format!("  {}: {};", p.name, p.value))
114                .collect::<Vec<_>>()
115                .join("\n")
116        );
117
118        result.push_str(&merged_css);
119        Ok(result)
120    }
121
122    /// Parse CSS properties from string
123    fn parse_properties(
124        &self,
125        properties_str: &str,
126    ) -> Result<Vec<CSSProperty>, OptimizationError> {
127        let mut properties = Vec::new();
128        let property_pattern = Regex::new(r"([^:]+):\s*([^;]+);").unwrap();
129
130        for cap in property_pattern.captures_iter(properties_str) {
131            properties.push(CSSProperty {
132                name: cap[1].trim().to_string(),
133                value: cap[2].trim().to_string(),
134            });
135        }
136
137        Ok(properties)
138    }
139
140    /// Build compatibility matrix for different selector types
141    fn build_compatibility_matrix() -> HashMap<String, Vec<String>> {
142        let mut matrix = HashMap::new();
143
144        // Class selectors can be merged with other classes
145        matrix.insert(
146            "class".to_string(),
147            vec!["class".to_string(), "element".to_string()],
148        );
149
150        // Element selectors can be merged with classes
151        matrix.insert(
152            "element".to_string(),
153            vec!["element".to_string(), "class".to_string()],
154        );
155
156        matrix
157    }
158}