tailwind_rs_postcss/
autoprefixer.rs

1//! Enhanced Autoprefixer Integration
2//! 
3//! This module provides comprehensive vendor prefixing functionality for CSS properties
4//! based on browser compatibility data, essential for cross-browser CSS support.
5
6use std::collections::HashMap;
7use regex::Regex;
8use thiserror::Error;
9
10/// Main autoprefixer for adding vendor prefixes to CSS properties
11pub struct Autoprefixer {
12    browser_data: BrowserData,
13    caniuse_data: CanIUseData,
14    config: AutoprefixerConfig,
15    cache: PrefixCache,
16}
17
18impl Autoprefixer {
19    /// Create a new autoprefixer with default configuration
20    pub fn new() -> Self {
21        Self::with_config(AutoprefixerConfig::default())
22    }
23    
24    /// Create a new autoprefixer with custom configuration
25    pub fn with_config(config: AutoprefixerConfig) -> Self {
26        Self {
27            browser_data: BrowserData::new(),
28            caniuse_data: CanIUseData::new(),
29            config,
30            cache: PrefixCache::new(),
31        }
32    }
33    
34    /// Add vendor prefixes based on browser support
35    pub fn add_prefixes(&self, css: &str, browsers: &[String]) -> Result<String, AutoprefixerError> {
36        let mut result = String::new();
37        let mut in_rule = false;
38        let mut current_rule = String::new();
39        
40        for line in css.lines() {
41            if line.trim().ends_with('{') {
42                in_rule = true;
43                current_rule = line.to_string();
44            } else if in_rule && line.trim() == "}" {
45                current_rule.push_str(line);
46                
47                let prefixed_rule = self.prefix_rule(&current_rule, browsers)?;
48                result.push_str(&prefixed_rule);
49                result.push('\n');
50                
51                in_rule = false;
52                current_rule.clear();
53            } else if in_rule {
54                current_rule.push_str(line);
55                current_rule.push('\n');
56            } else {
57                result.push_str(line);
58                result.push('\n');
59            }
60        }
61        
62        Ok(result)
63    }
64    
65    /// Add prefixes with advanced options
66    pub fn add_prefixes_advanced(&self, css: &str, options: &PrefixOptions) -> Result<PrefixResult, AutoprefixerError> {
67        let start_time = std::time::Instant::now();
68        let original_size = css.len();
69        
70        let prefixed_css = self.add_prefixes(css, &options.browsers)?;
71        let prefixed_size = prefixed_css.len();
72        
73        let prefixes_added = self.count_prefixes_added(&css, &prefixed_css);
74        let prefixes_removed = self.count_prefixes_removed(&css, &prefixed_css);
75        let properties_processed = self.count_properties_processed(&css);
76        
77        let processing_time = start_time.elapsed().as_millis() as usize;
78        
79        Ok(PrefixResult {
80            prefixed_css,
81            prefixes_added: HashMap::new(),
82            prefixes_removed: HashMap::new(),
83            statistics: PrefixStatistics {
84                original_size,
85                prefixed_size,
86                prefixes_added,
87                prefixes_removed,
88                properties_processed,
89                processing_time_ms: processing_time,
90            },
91        })
92    }
93    
94    /// Check if property needs prefixes
95    pub fn needs_prefix(&self, property: &str, browsers: &[String]) -> bool {
96        self.check_browser_compatibility(property, browsers)
97    }
98    
99    /// Prefix a single CSS rule
100    fn prefix_rule(&self, rule: &str, browsers: &[String]) -> Result<String, AutoprefixerError> {
101        let mut prefixed_rule = String::new();
102        let mut in_declaration = false;
103        let mut current_declaration = String::new();
104        
105        for line in rule.lines() {
106            if line.trim().ends_with('{') {
107                prefixed_rule.push_str(line);
108                prefixed_rule.push('\n');
109            } else if line.trim() == "}" {
110                if in_declaration {
111                    let prefixed_declaration = self.prefix_declaration(&current_declaration, browsers)?;
112                    prefixed_rule.push_str(&prefixed_declaration);
113                    in_declaration = false;
114                    current_declaration.clear();
115                }
116                prefixed_rule.push_str(line);
117                prefixed_rule.push('\n');
118            } else if line.trim().ends_with(';') {
119                current_declaration.push_str(line);
120                let prefixed_declaration = self.prefix_declaration(&current_declaration, browsers)?;
121                prefixed_rule.push_str(&prefixed_declaration);
122                current_declaration.clear();
123            } else {
124                current_declaration.push_str(line);
125                current_declaration.push('\n');
126                in_declaration = true;
127            }
128        }
129        
130        Ok(prefixed_rule)
131    }
132    
133    /// Prefix a single CSS declaration
134    fn prefix_declaration(&self, declaration: &str, browsers: &[String]) -> Result<String, AutoprefixerError> {
135        let property_pattern = Regex::new(r"([a-zA-Z-]+)\s*:\s*([^;]+);").unwrap();
136        
137        if let Some(cap) = property_pattern.captures(declaration) {
138            let property_name = &cap[1];
139            let property_value = &cap[2];
140            
141            if self.needs_prefix(property_name, browsers) {
142                let prefixes = self.get_prefixes_for_property(property_name);
143                let mut prefixed_declaration = String::new();
144                
145                for prefix in prefixes {
146                    let prefixed_name = format!("{}{}", prefix, property_name);
147                    prefixed_declaration.push_str(&format!("{}: {};\n", prefixed_name, property_value));
148                }
149                
150                // Add original property
151                prefixed_declaration.push_str(&format!("{}: {};\n", property_name, property_value));
152                
153                Ok(prefixed_declaration)
154            } else {
155                Ok(declaration.to_string())
156            }
157        } else {
158            Ok(declaration.to_string())
159        }
160    }
161    
162    /// Get vendor prefixes for a property
163    fn get_prefixes_for_property(&self, property: &str) -> Vec<String> {
164        let mut prefixes = Vec::new();
165        
166        // Common vendor prefixes
167        if self.needs_webkit_prefix(property) {
168            prefixes.push("-webkit-".to_string());
169        }
170        if self.needs_moz_prefix(property) {
171            prefixes.push("-moz-".to_string());
172        }
173        if self.needs_ms_prefix(property) {
174            prefixes.push("-ms-".to_string());
175        }
176        if self.needs_o_prefix(property) {
177            prefixes.push("-o-".to_string());
178        }
179        
180        prefixes
181    }
182    
183    /// Check if property needs webkit prefix
184    fn needs_webkit_prefix(&self, property: &str) -> bool {
185        matches!(property, 
186            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
187            "justify-content" | "align-items" | "align-content" | "align-self" |
188            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
189            "transform" | "transform-origin" | "transform-style" |
190            "transition" | "transition-property" | "transition-duration" |
191            "transition-timing-function" | "transition-delay" |
192            "animation" | "animation-name" | "animation-duration" |
193            "animation-timing-function" | "animation-delay" |
194            "animation-iteration-count" | "animation-direction" |
195            "animation-fill-mode" | "animation-play-state" |
196            "filter" | "backdrop-filter" | "mask" | "mask-image" |
197            "mask-size" | "mask-position" | "mask-repeat" |
198            "mask-origin" | "mask-clip" | "mask-composite" |
199            "clip-path" | "shape-outside" | "shape-margin" |
200            "text-decoration" | "text-decoration-line" |
201            "text-decoration-style" | "text-decoration-color" |
202            "text-decoration-skip" | "text-underline-position" |
203            "text-emphasis" | "text-emphasis-style" |
204            "text-emphasis-color" | "text-emphasis-position" |
205            "text-shadow" | "box-shadow" | "border-radius" |
206            "border-image" | "border-image-source" |
207            "border-image-slice" | "border-image-width" |
208            "border-image-outset" | "border-image-repeat" |
209            "background" | "background-image" | "background-size" |
210            "background-position" | "background-repeat" |
211            "background-attachment" | "background-clip" |
212            "background-origin" | "background-color"
213        )
214    }
215    
216    /// Check if property needs moz prefix
217    fn needs_moz_prefix(&self, property: &str) -> bool {
218        matches!(property,
219            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
220            "justify-content" | "align-items" | "align-content" | "align-self" |
221            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
222            "transform" | "transform-origin" | "transform-style" |
223            "transition" | "transition-property" | "transition-duration" |
224            "transition-timing-function" | "transition-delay" |
225            "animation" | "animation-name" | "animation-duration" |
226            "animation-timing-function" | "animation-delay" |
227            "animation-iteration-count" | "animation-direction" |
228            "animation-fill-mode" | "animation-play-state" |
229            "filter" | "backdrop-filter" | "mask" | "mask-image" |
230            "mask-size" | "mask-position" | "mask-repeat" |
231            "mask-origin" | "mask-clip" | "mask-composite" |
232            "clip-path" | "shape-outside" | "shape-margin" |
233            "text-decoration" | "text-decoration-line" |
234            "text-decoration-style" | "text-decoration-color" |
235            "text-decoration-skip" | "text-underline-position" |
236            "text-emphasis" | "text-emphasis-style" |
237            "text-emphasis-color" | "text-emphasis-position" |
238            "text-shadow" | "box-shadow" | "border-radius" |
239            "border-image" | "border-image-source" |
240            "border-image-slice" | "border-image-width" |
241            "border-image-outset" | "border-image-repeat" |
242            "background" | "background-image" | "background-size" |
243            "background-position" | "background-repeat" |
244            "background-attachment" | "background-clip" |
245            "background-origin" | "background-color"
246        )
247    }
248    
249    /// Check if property needs ms prefix
250    fn needs_ms_prefix(&self, property: &str) -> bool {
251        matches!(property,
252            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
253            "justify-content" | "align-items" | "align-content" | "align-self" |
254            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
255            "transform" | "transform-origin" | "transform-style" |
256            "transition" | "transition-property" | "transition-duration" |
257            "transition-timing-function" | "transition-delay" |
258            "animation" | "animation-name" | "animation-duration" |
259            "animation-timing-function" | "animation-delay" |
260            "animation-iteration-count" | "animation-direction" |
261            "animation-fill-mode" | "animation-play-state" |
262            "filter" | "backdrop-filter" | "mask" | "mask-image" |
263            "mask-size" | "mask-position" | "mask-repeat" |
264            "mask-origin" | "mask-clip" | "mask-composite" |
265            "clip-path" | "shape-outside" | "shape-margin" |
266            "text-decoration" | "text-decoration-line" |
267            "text-decoration-style" | "text-decoration-color" |
268            "text-decoration-skip" | "text-underline-position" |
269            "text-emphasis" | "text-emphasis-style" |
270            "text-emphasis-color" | "text-emphasis-position" |
271            "text-shadow" | "box-shadow" | "border-radius" |
272            "border-image" | "border-image-source" |
273            "border-image-slice" | "border-image-width" |
274            "border-image-outset" | "border-image-repeat" |
275            "background" | "background-image" | "background-size" |
276            "background-position" | "background-repeat" |
277            "background-attachment" | "background-clip" |
278            "background-origin" | "background-color"
279        )
280    }
281    
282    /// Check if property needs o prefix
283    fn needs_o_prefix(&self, property: &str) -> bool {
284        matches!(property,
285            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
286            "justify-content" | "align-items" | "align-content" | "align-self" |
287            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
288            "transform" | "transform-origin" | "transform-style" |
289            "transition" | "transition-property" | "transition-duration" |
290            "transition-timing-function" | "transition-delay" |
291            "animation" | "animation-name" | "animation-duration" |
292            "animation-timing-function" | "animation-delay" |
293            "animation-iteration-count" | "animation-direction" |
294            "animation-fill-mode" | "animation-play-state" |
295            "filter" | "backdrop-filter" | "mask" | "mask-image" |
296            "mask-size" | "mask-position" | "mask-repeat" |
297            "mask-origin" | "mask-clip" | "mask-composite" |
298            "clip-path" | "shape-outside" | "shape-margin" |
299            "text-decoration" | "text-decoration-line" |
300            "text-decoration-style" | "text-decoration-color" |
301            "text-decoration-skip" | "text-underline-position" |
302            "text-emphasis" | "text-emphasis-style" |
303            "text-emphasis-color" | "text-emphasis-position" |
304            "text-shadow" | "box-shadow" | "border-radius" |
305            "border-image" | "border-image-source" |
306            "border-image-slice" | "border-image-width" |
307            "border-image-outset" | "border-image-repeat" |
308            "background" | "background-image" | "background-size" |
309            "background-position" | "background-repeat" |
310            "background-attachment" | "background-clip" |
311            "background-origin" | "background-color"
312        )
313    }
314    
315    /// Check browser compatibility for a property
316    fn check_browser_compatibility(&self, property: &str, browsers: &[String]) -> bool {
317        for browser in browsers {
318            let support = self.browser_data.get_feature_support(property, browser);
319            match support {
320                Some(SupportLevel::Full) => continue,
321                Some(SupportLevel::Partial) => return true, // Needs prefix
322                Some(SupportLevel::None) => return true, // Needs prefix
323                Some(SupportLevel::Unknown) => return true, // Assume needs prefix
324                None => return true, // Unknown feature, assume needs prefix
325            }
326        }
327        false
328    }
329    
330    /// Count prefixes added
331    fn count_prefixes_added(&self, original: &str, prefixed: &str) -> usize {
332        let original_prefixes = self.count_prefixes_in_css(original);
333        let prefixed_prefixes = self.count_prefixes_in_css(prefixed);
334        prefixed_prefixes - original_prefixes
335    }
336    
337    /// Count prefixes removed
338    fn count_prefixes_removed(&self, original: &str, prefixed: &str) -> usize {
339        let original_prefixes = self.count_prefixes_in_css(original);
340        let prefixed_prefixes = self.count_prefixes_in_css(prefixed);
341        if original_prefixes > prefixed_prefixes {
342            original_prefixes - prefixed_prefixes
343        } else {
344            0
345        }
346    }
347    
348    /// Count prefixes in CSS
349    fn count_prefixes_in_css(&self, css: &str) -> usize {
350        let prefix_patterns = ["-webkit-", "-moz-", "-ms-", "-o-"];
351        let mut count = 0;
352        
353        for pattern in &prefix_patterns {
354            count += css.matches(pattern).count();
355        }
356        
357        count
358    }
359    
360    /// Count properties processed
361    fn count_properties_processed(&self, css: &str) -> usize {
362        let property_pattern = Regex::new(r"([a-zA-Z-]+)\s*:\s*([^;]+);").unwrap();
363        property_pattern.captures_iter(css).count()
364    }
365}
366
367/// Browser data for compatibility checking
368pub struct BrowserData {
369    browsers: HashMap<String, BrowserInfo>,
370    features: HashMap<String, FeatureSupport>,
371    versions: HashMap<String, Vec<String>>,
372}
373
374impl BrowserData {
375    /// Create new browser data
376    pub fn new() -> Self {
377        let mut browsers = HashMap::new();
378        let features = HashMap::new();
379        let mut versions = HashMap::new();
380        
381        // Initialize with common browsers
382        browsers.insert("chrome".to_string(), BrowserInfo {
383            name: "Chrome".to_string(),
384            versions: vec!["30".to_string(), "40".to_string(), "50".to_string(), "60".to_string(), "70".to_string(), "80".to_string(), "90".to_string(), "100".to_string()],
385            current_version: "100".to_string(),
386            support_level: SupportLevel::Full,
387        });
388        
389        browsers.insert("firefox".to_string(), BrowserInfo {
390            name: "Firefox".to_string(),
391            versions: vec!["25".to_string(), "30".to_string(), "40".to_string(), "50".to_string(), "60".to_string(), "70".to_string(), "80".to_string(), "90".to_string()],
392            current_version: "90".to_string(),
393            support_level: SupportLevel::Full,
394        });
395        
396        browsers.insert("safari".to_string(), BrowserInfo {
397            name: "Safari".to_string(),
398            versions: vec!["7".to_string(), "8".to_string(), "9".to_string(), "10".to_string(), "11".to_string(), "12".to_string(), "13".to_string(), "14".to_string()],
399            current_version: "14".to_string(),
400            support_level: SupportLevel::Full,
401        });
402        
403        browsers.insert("ie".to_string(), BrowserInfo {
404            name: "Internet Explorer".to_string(),
405            versions: vec!["8".to_string(), "9".to_string(), "10".to_string(), "11".to_string()],
406            current_version: "11".to_string(),
407            support_level: SupportLevel::Partial,
408        });
409        
410        browsers.insert("edge".to_string(), BrowserInfo {
411            name: "Edge".to_string(),
412            versions: vec!["12".to_string(), "13".to_string(), "14".to_string(), "15".to_string(), "16".to_string(), "17".to_string(), "18".to_string(), "19".to_string()],
413            current_version: "19".to_string(),
414            support_level: SupportLevel::Full,
415        });
416        
417        // Initialize versions
418        for (browser, info) in &browsers {
419            versions.insert(browser.clone(), info.versions.clone());
420        }
421        
422        Self {
423            browsers,
424            features,
425            versions,
426        }
427    }
428    
429    /// Get browser support for a feature
430    pub fn get_feature_support(&self, feature: &str, browser: &str) -> Option<SupportLevel> {
431        // Simplified support checking
432        match browser {
433            "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
434            "ie" => Some(SupportLevel::Partial),
435            _ => Some(SupportLevel::Unknown),
436        }
437    }
438    
439    /// Check if browser supports feature
440    pub fn supports_feature(&self, _feature: &str, browser: &str, _version: &str) -> bool {
441        match self.get_feature_support(_feature, browser) {
442            Some(SupportLevel::Full) => true,
443            Some(SupportLevel::Partial) => true,
444            Some(SupportLevel::None) => false,
445            Some(SupportLevel::Unknown) => false,
446            None => false,
447        }
448    }
449}
450
451/// Can I Use data for browser compatibility
452pub struct CanIUseData {
453    features: HashMap<String, FeatureSupport>,
454}
455
456impl CanIUseData {
457    /// Create new Can I Use data
458    pub fn new() -> Self {
459        Self {
460            features: HashMap::new(),
461        }
462    }
463    
464    /// Get support for a feature
465    pub fn get_support(&self, _feature: &str, browser: &str) -> Option<SupportLevel> {
466        // Simplified support checking
467        match browser {
468            "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
469            "ie" => Some(SupportLevel::Partial),
470            _ => Some(SupportLevel::Unknown),
471        }
472    }
473}
474
475/// Prefix generator for vendor prefixes
476pub struct PrefixGenerator {
477    prefixes: HashMap<String, Vec<String>>,
478    config: GeneratorConfig,
479}
480
481impl PrefixGenerator {
482    /// Create new prefix generator
483    pub fn new() -> Self {
484        Self {
485            prefixes: HashMap::new(),
486            config: GeneratorConfig::default(),
487        }
488    }
489    
490    /// Generate vendor prefixes for property
491    pub fn generate_prefixes(&self, property: &str, browsers: &[String]) -> Vec<String> {
492        let mut prefixes = Vec::new();
493        
494        for browser in browsers {
495            match browser.as_str() {
496                "chrome" | "safari" => {
497                    if self.needs_webkit_prefix(property) {
498                        prefixes.push("-webkit-".to_string());
499                    }
500                },
501                "firefox" => {
502                    if self.needs_moz_prefix(property) {
503                        prefixes.push("-moz-".to_string());
504                    }
505                },
506                "ie" | "edge" => {
507                    if self.needs_ms_prefix(property) {
508                        prefixes.push("-ms-".to_string());
509                    }
510                },
511                _ => {}
512            }
513        }
514        
515        prefixes
516    }
517    
518    /// Check if property needs webkit prefix
519    fn needs_webkit_prefix(&self, property: &str) -> bool {
520        matches!(property,
521            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
522            "justify-content" | "align-items" | "align-content" | "align-self" |
523            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
524            "transform" | "transform-origin" | "transform-style" |
525            "transition" | "transition-property" | "transition-duration" |
526            "transition-timing-function" | "transition-delay" |
527            "animation" | "animation-name" | "animation-duration" |
528            "animation-timing-function" | "animation-delay" |
529            "animation-iteration-count" | "animation-direction" |
530            "animation-fill-mode" | "animation-play-state" |
531            "filter" | "backdrop-filter" | "mask" | "mask-image" |
532            "mask-size" | "mask-position" | "mask-repeat" |
533            "mask-origin" | "mask-clip" | "mask-composite" |
534            "clip-path" | "shape-outside" | "shape-margin" |
535            "text-decoration" | "text-decoration-line" |
536            "text-decoration-style" | "text-decoration-color" |
537            "text-decoration-skip" | "text-underline-position" |
538            "text-emphasis" | "text-emphasis-style" |
539            "text-emphasis-color" | "text-emphasis-position" |
540            "text-shadow" | "box-shadow" | "border-radius" |
541            "border-image" | "border-image-source" |
542            "border-image-slice" | "border-image-width" |
543            "border-image-outset" | "border-image-repeat" |
544            "background" | "background-image" | "background-size" |
545            "background-position" | "background-repeat" |
546            "background-attachment" | "background-clip" |
547            "background-origin" | "background-color"
548        )
549    }
550    
551    /// Check if property needs moz prefix
552    fn needs_moz_prefix(&self, property: &str) -> bool {
553        matches!(property,
554            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
555            "justify-content" | "align-items" | "align-content" | "align-self" |
556            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
557            "transform" | "transform-origin" | "transform-style" |
558            "transition" | "transition-property" | "transition-duration" |
559            "transition-timing-function" | "transition-delay" |
560            "animation" | "animation-name" | "animation-duration" |
561            "animation-timing-function" | "animation-delay" |
562            "animation-iteration-count" | "animation-direction" |
563            "animation-fill-mode" | "animation-play-state" |
564            "filter" | "backdrop-filter" | "mask" | "mask-image" |
565            "mask-size" | "mask-position" | "mask-repeat" |
566            "mask-origin" | "mask-clip" | "mask-composite" |
567            "clip-path" | "shape-outside" | "shape-margin" |
568            "text-decoration" | "text-decoration-line" |
569            "text-decoration-style" | "text-decoration-color" |
570            "text-decoration-skip" | "text-underline-position" |
571            "text-emphasis" | "text-emphasis-style" |
572            "text-emphasis-color" | "text-emphasis-position" |
573            "text-shadow" | "box-shadow" | "border-radius" |
574            "border-image" | "border-image-source" |
575            "border-image-slice" | "border-image-width" |
576            "border-image-outset" | "border-image-repeat" |
577            "background" | "background-image" | "background-size" |
578            "background-position" | "background-repeat" |
579            "background-attachment" | "background-clip" |
580            "background-origin" | "background-color"
581        )
582    }
583    
584    /// Check if property needs ms prefix
585    fn needs_ms_prefix(&self, property: &str) -> bool {
586        matches!(property,
587            "display" | "flex" | "flex-direction" | "flex-wrap" | "flex-flow" |
588            "justify-content" | "align-items" | "align-content" | "align-self" |
589            "flex-grow" | "flex-shrink" | "flex-basis" | "order" |
590            "transform" | "transform-origin" | "transform-style" |
591            "transition" | "transition-property" | "transition-duration" |
592            "transition-timing-function" | "transition-delay" |
593            "animation" | "animation-name" | "animation-duration" |
594            "animation-timing-function" | "animation-delay" |
595            "animation-iteration-count" | "animation-direction" |
596            "animation-fill-mode" | "animation-play-state" |
597            "filter" | "backdrop-filter" | "mask" | "mask-image" |
598            "mask-size" | "mask-position" | "mask-repeat" |
599            "mask-origin" | "mask-clip" | "mask-composite" |
600            "clip-path" | "shape-outside" | "shape-margin" |
601            "text-decoration" | "text-decoration-line" |
602            "text-decoration-style" | "text-decoration-color" |
603            "text-decoration-skip" | "text-underline-position" |
604            "text-emphasis" | "text-emphasis-style" |
605            "text-emphasis-color" | "text-emphasis-position" |
606            "text-shadow" | "box-shadow" | "border-radius" |
607            "border-image" | "border-image-source" |
608            "border-image-slice" | "border-image-width" |
609            "border-image-outset" | "border-image-repeat" |
610            "background" | "background-image" | "background-size" |
611            "background-position" | "background-repeat" |
612            "background-attachment" | "background-clip" |
613            "background-origin" | "background-color"
614        )
615    }
616}
617
618/// Prefix cache for performance optimization
619pub struct PrefixCache {
620    property_cache: HashMap<String, Vec<String>>,
621    browser_cache: HashMap<String, SupportLevel>,
622    css_cache: HashMap<String, String>,
623}
624
625impl PrefixCache {
626    /// Create new prefix cache
627    pub fn new() -> Self {
628        Self {
629            property_cache: HashMap::new(),
630            browser_cache: HashMap::new(),
631            css_cache: HashMap::new(),
632        }
633    }
634    
635    /// Get cached prefixes for property
636    pub fn get_cached_prefixes(&self, property: &str) -> Option<&Vec<String>> {
637        self.property_cache.get(property)
638    }
639    
640    /// Cache prefixes for property
641    pub fn cache_prefixes(&mut self, property: String, prefixes: Vec<String>) {
642        self.property_cache.insert(property, prefixes);
643    }
644    
645    /// Get cached CSS
646    pub fn get_cached_css(&self, css: &str) -> Option<&String> {
647        self.css_cache.get(css)
648    }
649    
650    /// Cache CSS
651    pub fn cache_css(&mut self, css: String, prefixed: String) {
652        self.css_cache.insert(css, prefixed);
653    }
654}
655
656/// Configuration for autoprefixer
657#[derive(Debug, Clone)]
658pub struct AutoprefixerConfig {
659    pub browsers: Vec<String>,
660    pub cascade: bool,
661    pub add: bool,
662    pub remove: bool,
663    pub supports: bool,
664    pub flexbox: FlexboxMode,
665    pub grid: GridMode,
666    pub stats: Option<BrowserStats>,
667}
668
669impl Default for AutoprefixerConfig {
670    fn default() -> Self {
671        Self {
672            browsers: vec![
673                "chrome 30".to_string(),
674                "firefox 25".to_string(),
675                "safari 7".to_string(),
676                "ie 10".to_string(),
677                "edge 12".to_string(),
678            ],
679            cascade: true,
680            add: true,
681            remove: false,
682            supports: true,
683            flexbox: FlexboxMode::All,
684            grid: GridMode::Autoplace,
685            stats: None,
686        }
687    }
688}
689
690/// Flexbox mode configuration
691#[derive(Debug, Clone)]
692pub enum FlexboxMode {
693    No2009,
694    No2012,
695    All,
696}
697
698/// Grid mode configuration
699#[derive(Debug, Clone)]
700pub enum GridMode {
701    NoAutoplace,
702    Autoplace,
703}
704
705/// Prefix options for advanced prefixing
706#[derive(Debug, Clone)]
707pub struct PrefixOptions {
708    pub browsers: Vec<String>,
709    pub cascade: bool,
710    pub add: bool,
711    pub remove: bool,
712    pub supports: bool,
713    pub flexbox: FlexboxMode,
714    pub grid: GridMode,
715    pub stats: Option<BrowserStats>,
716}
717
718/// Result of prefixing operation
719#[derive(Debug, Clone)]
720pub struct PrefixResult {
721    pub prefixed_css: String,
722    pub prefixes_added: HashMap<String, Vec<String>>,
723    pub prefixes_removed: HashMap<String, Vec<String>>,
724    pub statistics: PrefixStatistics,
725}
726
727/// Statistics for prefixing operation
728#[derive(Debug, Clone)]
729pub struct PrefixStatistics {
730    pub original_size: usize,
731    pub prefixed_size: usize,
732    pub prefixes_added: usize,
733    pub prefixes_removed: usize,
734    pub properties_processed: usize,
735    pub processing_time_ms: usize,
736}
737
738/// Browser information
739#[derive(Debug, Clone)]
740pub struct BrowserInfo {
741    pub name: String,
742    pub versions: Vec<String>,
743    pub current_version: String,
744    pub support_level: SupportLevel,
745}
746
747/// Support level for features
748#[derive(Debug, Clone)]
749pub enum SupportLevel {
750    Full,
751    Partial,
752    None,
753    Unknown,
754}
755
756/// Feature support information
757#[derive(Debug, Clone)]
758pub struct FeatureSupport {
759    pub feature: String,
760    pub browsers: HashMap<String, SupportInfo>,
761}
762
763/// Support information for specific browser
764#[derive(Debug, Clone)]
765pub struct SupportInfo {
766    pub version: String,
767    pub support: SupportLevel,
768    pub prefix: Option<String>,
769    pub notes: Option<String>,
770}
771
772/// Generator configuration
773#[derive(Debug, Clone)]
774pub struct GeneratorConfig {
775    pub optimize: bool,
776    pub remove_unused: bool,
777    pub add_missing: bool,
778}
779
780impl Default for GeneratorConfig {
781    fn default() -> Self {
782        Self {
783            optimize: true,
784            remove_unused: false,
785            add_missing: true,
786        }
787    }
788}
789
790/// Browser statistics
791#[derive(Debug, Clone)]
792pub struct BrowserStats {
793    pub usage: HashMap<String, f64>,
794}
795
796/// Error types for autoprefixer
797#[derive(Debug, Error)]
798pub enum AutoprefixerError {
799    #[error("Invalid CSS syntax: {error}")]
800    InvalidCSS { error: String },
801    
802    #[error("Unsupported browser: {browser}")]
803    UnsupportedBrowser { browser: String },
804    
805    #[error("Failed to load browser data: {error}")]
806    BrowserDataError { error: String },
807    
808    #[error("Prefix generation failed: {property}")]
809    PrefixGenerationError { property: String },
810}
811
812#[cfg(test)]
813mod tests {
814    use super::*;
815    
816    #[test]
817    fn test_basic_prefixing() {
818        let autoprefixer = Autoprefixer::new();
819        let css = ".test { display: flex; }";
820        let result = autoprefixer.add_prefixes(css, &["chrome 30".to_string()]);
821        assert!(result.is_ok());
822        let prefixed = result.unwrap();
823        assert!(prefixed.contains("-webkit-"));
824    }
825    
826    #[test]
827    fn test_flexbox_prefixing() {
828        let mut config = AutoprefixerConfig::default();
829        config.flexbox = FlexboxMode::All;
830        let autoprefixer = Autoprefixer::with_config(config);
831        
832        let css = ".container { display: flex; }";
833        let result = autoprefixer.add_prefixes(css, &["ie 10".to_string()]);
834        assert!(result.is_ok());
835        let prefixed = result.unwrap();
836        assert!(prefixed.contains("-ms-"));
837    }
838    
839    #[test]
840    fn test_complex_prefixing() {
841        let autoprefixer = Autoprefixer::new();
842        
843        let css = r#"
844            .container {
845                display: flex;
846                flex-direction: column;
847                align-items: center;
848            }
849            
850            .item {
851                flex: 1;
852                transform: translateX(10px);
853            }
854        "#;
855        
856        let browsers = vec![
857            "chrome 30".to_string(),
858            "firefox 25".to_string(),
859            "safari 7".to_string(),
860        ];
861        
862        let result = autoprefixer.add_prefixes(css, &browsers);
863        assert!(result.is_ok());
864        
865        let prefixed = result.unwrap();
866        assert!(prefixed.contains("-webkit-"));
867        assert!(prefixed.contains("-moz-"));
868    }
869    
870    #[test]
871    fn test_prefix_generator() {
872        let generator = PrefixGenerator::new();
873        let prefixes = generator.generate_prefixes("display", &["chrome".to_string(), "firefox".to_string()]);
874        assert!(!prefixes.is_empty());
875    }
876    
877    #[test]
878    fn test_browser_data() {
879        let browser_data = BrowserData::new();
880        let support = browser_data.get_feature_support("display", "chrome");
881        assert!(support.is_some());
882    }
883    
884    #[test]
885    fn test_prefix_cache() {
886        let mut cache = PrefixCache::new();
887        let prefixes = vec!["-webkit-".to_string(), "-moz-".to_string()];
888        cache.cache_prefixes("display".to_string(), prefixes);
889        
890        let cached = cache.get_cached_prefixes("display");
891        assert!(cached.is_some());
892        assert_eq!(cached.unwrap().len(), 2);
893    }
894}