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 regex::Regex;
7use std::collections::HashMap;
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(
36        &self,
37        css: &str,
38        browsers: &[String],
39    ) -> Result<String, AutoprefixerError> {
40        let mut result = String::new();
41        let mut in_rule = false;
42        let mut current_rule = String::new();
43
44        for line in css.lines() {
45            if line.trim().ends_with('{') {
46                in_rule = true;
47                current_rule = line.to_string();
48            } else if in_rule && line.trim() == "}" {
49                current_rule.push_str(line);
50
51                let prefixed_rule = self.prefix_rule(&current_rule, browsers)?;
52                result.push_str(&prefixed_rule);
53                result.push('\n');
54
55                in_rule = false;
56                current_rule.clear();
57            } else if in_rule {
58                current_rule.push_str(line);
59                current_rule.push('\n');
60            } else {
61                result.push_str(line);
62                result.push('\n');
63            }
64        }
65
66        Ok(result)
67    }
68
69    /// Add prefixes with advanced options
70    pub fn add_prefixes_advanced(
71        &self,
72        css: &str,
73        options: &PrefixOptions,
74    ) -> Result<PrefixResult, AutoprefixerError> {
75        let start_time = std::time::Instant::now();
76        let original_size = css.len();
77
78        let prefixed_css = self.add_prefixes(css, &options.browsers)?;
79        let prefixed_size = prefixed_css.len();
80
81        let prefixes_added = self.count_prefixes_added(&css, &prefixed_css);
82        let prefixes_removed = self.count_prefixes_removed(&css, &prefixed_css);
83        let properties_processed = self.count_properties_processed(&css);
84
85        let processing_time = start_time.elapsed().as_millis() as usize;
86
87        Ok(PrefixResult {
88            prefixed_css,
89            prefixes_added: HashMap::new(),
90            prefixes_removed: HashMap::new(),
91            statistics: PrefixStatistics {
92                original_size,
93                prefixed_size,
94                prefixes_added,
95                prefixes_removed,
96                properties_processed,
97                processing_time_ms: processing_time,
98            },
99        })
100    }
101
102    /// Check if property needs prefixes
103    pub fn needs_prefix(&self, property: &str, browsers: &[String]) -> bool {
104        self.check_browser_compatibility(property, browsers)
105    }
106
107    /// Prefix a single CSS rule
108    fn prefix_rule(&self, rule: &str, browsers: &[String]) -> Result<String, AutoprefixerError> {
109        let mut prefixed_rule = String::new();
110        let mut in_declaration = false;
111        let mut current_declaration = String::new();
112
113        for line in rule.lines() {
114            if line.trim().ends_with('{') {
115                prefixed_rule.push_str(line);
116                prefixed_rule.push('\n');
117            } else if line.trim() == "}" {
118                if in_declaration {
119                    let prefixed_declaration =
120                        self.prefix_declaration(&current_declaration, browsers)?;
121                    prefixed_rule.push_str(&prefixed_declaration);
122                    in_declaration = false;
123                    current_declaration.clear();
124                }
125                prefixed_rule.push_str(line);
126                prefixed_rule.push('\n');
127            } else if line.trim().ends_with(';') {
128                current_declaration.push_str(line);
129                let prefixed_declaration =
130                    self.prefix_declaration(&current_declaration, browsers)?;
131                prefixed_rule.push_str(&prefixed_declaration);
132                current_declaration.clear();
133            } else {
134                current_declaration.push_str(line);
135                current_declaration.push('\n');
136                in_declaration = true;
137            }
138        }
139
140        Ok(prefixed_rule)
141    }
142
143    /// Prefix a single CSS declaration
144    fn prefix_declaration(
145        &self,
146        declaration: &str,
147        browsers: &[String],
148    ) -> Result<String, AutoprefixerError> {
149        let property_pattern = Regex::new(r"([a-zA-Z-]+)\s*:\s*([^;]+);").unwrap();
150
151        if let Some(cap) = property_pattern.captures(declaration) {
152            let property_name = &cap[1];
153            let property_value = &cap[2];
154
155            if self.needs_prefix(property_name, browsers) {
156                let prefixes = self.get_prefixes_for_property(property_name);
157                let mut prefixed_declaration = String::new();
158
159                for prefix in prefixes {
160                    let prefixed_name = format!("{}{}", prefix, property_name);
161                    prefixed_declaration
162                        .push_str(&format!("{}: {};\n", prefixed_name, property_value));
163                }
164
165                // Add original property
166                prefixed_declaration.push_str(&format!("{}: {};\n", property_name, property_value));
167
168                Ok(prefixed_declaration)
169            } else {
170                Ok(declaration.to_string())
171            }
172        } else {
173            Ok(declaration.to_string())
174        }
175    }
176
177    /// Get vendor prefixes for a property
178    fn get_prefixes_for_property(&self, property: &str) -> Vec<String> {
179        let mut prefixes = Vec::new();
180
181        // Common vendor prefixes
182        if self.needs_webkit_prefix(property) {
183            prefixes.push("-webkit-".to_string());
184        }
185        if self.needs_moz_prefix(property) {
186            prefixes.push("-moz-".to_string());
187        }
188        if self.needs_ms_prefix(property) {
189            prefixes.push("-ms-".to_string());
190        }
191        if self.needs_o_prefix(property) {
192            prefixes.push("-o-".to_string());
193        }
194
195        prefixes
196    }
197
198    /// Check if property needs webkit prefix
199    fn needs_webkit_prefix(&self, property: &str) -> bool {
200        matches!(
201            property,
202            "display"
203                | "flex"
204                | "flex-direction"
205                | "flex-wrap"
206                | "flex-flow"
207                | "justify-content"
208                | "align-items"
209                | "align-content"
210                | "align-self"
211                | "flex-grow"
212                | "flex-shrink"
213                | "flex-basis"
214                | "order"
215                | "transform"
216                | "transform-origin"
217                | "transform-style"
218                | "transition"
219                | "transition-property"
220                | "transition-duration"
221                | "transition-timing-function"
222                | "transition-delay"
223                | "animation"
224                | "animation-name"
225                | "animation-duration"
226                | "animation-timing-function"
227                | "animation-delay"
228                | "animation-iteration-count"
229                | "animation-direction"
230                | "animation-fill-mode"
231                | "animation-play-state"
232                | "filter"
233                | "backdrop-filter"
234                | "mask"
235                | "mask-image"
236                | "mask-size"
237                | "mask-position"
238                | "mask-repeat"
239                | "mask-origin"
240                | "mask-clip"
241                | "mask-composite"
242                | "clip-path"
243                | "shape-outside"
244                | "shape-margin"
245                | "text-decoration"
246                | "text-decoration-line"
247                | "text-decoration-style"
248                | "text-decoration-color"
249                | "text-decoration-skip"
250                | "text-underline-position"
251                | "text-emphasis"
252                | "text-emphasis-style"
253                | "text-emphasis-color"
254                | "text-emphasis-position"
255                | "text-shadow"
256                | "box-shadow"
257                | "border-radius"
258                | "border-image"
259                | "border-image-source"
260                | "border-image-slice"
261                | "border-image-width"
262                | "border-image-outset"
263                | "border-image-repeat"
264                | "background"
265                | "background-image"
266                | "background-size"
267                | "background-position"
268                | "background-repeat"
269                | "background-attachment"
270                | "background-clip"
271                | "background-origin"
272                | "background-color"
273        )
274    }
275
276    /// Check if property needs moz prefix
277    fn needs_moz_prefix(&self, property: &str) -> bool {
278        matches!(
279            property,
280            "display"
281                | "flex"
282                | "flex-direction"
283                | "flex-wrap"
284                | "flex-flow"
285                | "justify-content"
286                | "align-items"
287                | "align-content"
288                | "align-self"
289                | "flex-grow"
290                | "flex-shrink"
291                | "flex-basis"
292                | "order"
293                | "transform"
294                | "transform-origin"
295                | "transform-style"
296                | "transition"
297                | "transition-property"
298                | "transition-duration"
299                | "transition-timing-function"
300                | "transition-delay"
301                | "animation"
302                | "animation-name"
303                | "animation-duration"
304                | "animation-timing-function"
305                | "animation-delay"
306                | "animation-iteration-count"
307                | "animation-direction"
308                | "animation-fill-mode"
309                | "animation-play-state"
310                | "filter"
311                | "backdrop-filter"
312                | "mask"
313                | "mask-image"
314                | "mask-size"
315                | "mask-position"
316                | "mask-repeat"
317                | "mask-origin"
318                | "mask-clip"
319                | "mask-composite"
320                | "clip-path"
321                | "shape-outside"
322                | "shape-margin"
323                | "text-decoration"
324                | "text-decoration-line"
325                | "text-decoration-style"
326                | "text-decoration-color"
327                | "text-decoration-skip"
328                | "text-underline-position"
329                | "text-emphasis"
330                | "text-emphasis-style"
331                | "text-emphasis-color"
332                | "text-emphasis-position"
333                | "text-shadow"
334                | "box-shadow"
335                | "border-radius"
336                | "border-image"
337                | "border-image-source"
338                | "border-image-slice"
339                | "border-image-width"
340                | "border-image-outset"
341                | "border-image-repeat"
342                | "background"
343                | "background-image"
344                | "background-size"
345                | "background-position"
346                | "background-repeat"
347                | "background-attachment"
348                | "background-clip"
349                | "background-origin"
350                | "background-color"
351        )
352    }
353
354    /// Check if property needs ms prefix
355    fn needs_ms_prefix(&self, property: &str) -> bool {
356        matches!(
357            property,
358            "display"
359                | "flex"
360                | "flex-direction"
361                | "flex-wrap"
362                | "flex-flow"
363                | "justify-content"
364                | "align-items"
365                | "align-content"
366                | "align-self"
367                | "flex-grow"
368                | "flex-shrink"
369                | "flex-basis"
370                | "order"
371                | "transform"
372                | "transform-origin"
373                | "transform-style"
374                | "transition"
375                | "transition-property"
376                | "transition-duration"
377                | "transition-timing-function"
378                | "transition-delay"
379                | "animation"
380                | "animation-name"
381                | "animation-duration"
382                | "animation-timing-function"
383                | "animation-delay"
384                | "animation-iteration-count"
385                | "animation-direction"
386                | "animation-fill-mode"
387                | "animation-play-state"
388                | "filter"
389                | "backdrop-filter"
390                | "mask"
391                | "mask-image"
392                | "mask-size"
393                | "mask-position"
394                | "mask-repeat"
395                | "mask-origin"
396                | "mask-clip"
397                | "mask-composite"
398                | "clip-path"
399                | "shape-outside"
400                | "shape-margin"
401                | "text-decoration"
402                | "text-decoration-line"
403                | "text-decoration-style"
404                | "text-decoration-color"
405                | "text-decoration-skip"
406                | "text-underline-position"
407                | "text-emphasis"
408                | "text-emphasis-style"
409                | "text-emphasis-color"
410                | "text-emphasis-position"
411                | "text-shadow"
412                | "box-shadow"
413                | "border-radius"
414                | "border-image"
415                | "border-image-source"
416                | "border-image-slice"
417                | "border-image-width"
418                | "border-image-outset"
419                | "border-image-repeat"
420                | "background"
421                | "background-image"
422                | "background-size"
423                | "background-position"
424                | "background-repeat"
425                | "background-attachment"
426                | "background-clip"
427                | "background-origin"
428                | "background-color"
429        )
430    }
431
432    /// Check if property needs o prefix
433    fn needs_o_prefix(&self, property: &str) -> bool {
434        matches!(
435            property,
436            "display"
437                | "flex"
438                | "flex-direction"
439                | "flex-wrap"
440                | "flex-flow"
441                | "justify-content"
442                | "align-items"
443                | "align-content"
444                | "align-self"
445                | "flex-grow"
446                | "flex-shrink"
447                | "flex-basis"
448                | "order"
449                | "transform"
450                | "transform-origin"
451                | "transform-style"
452                | "transition"
453                | "transition-property"
454                | "transition-duration"
455                | "transition-timing-function"
456                | "transition-delay"
457                | "animation"
458                | "animation-name"
459                | "animation-duration"
460                | "animation-timing-function"
461                | "animation-delay"
462                | "animation-iteration-count"
463                | "animation-direction"
464                | "animation-fill-mode"
465                | "animation-play-state"
466                | "filter"
467                | "backdrop-filter"
468                | "mask"
469                | "mask-image"
470                | "mask-size"
471                | "mask-position"
472                | "mask-repeat"
473                | "mask-origin"
474                | "mask-clip"
475                | "mask-composite"
476                | "clip-path"
477                | "shape-outside"
478                | "shape-margin"
479                | "text-decoration"
480                | "text-decoration-line"
481                | "text-decoration-style"
482                | "text-decoration-color"
483                | "text-decoration-skip"
484                | "text-underline-position"
485                | "text-emphasis"
486                | "text-emphasis-style"
487                | "text-emphasis-color"
488                | "text-emphasis-position"
489                | "text-shadow"
490                | "box-shadow"
491                | "border-radius"
492                | "border-image"
493                | "border-image-source"
494                | "border-image-slice"
495                | "border-image-width"
496                | "border-image-outset"
497                | "border-image-repeat"
498                | "background"
499                | "background-image"
500                | "background-size"
501                | "background-position"
502                | "background-repeat"
503                | "background-attachment"
504                | "background-clip"
505                | "background-origin"
506                | "background-color"
507        )
508    }
509
510    /// Check browser compatibility for a property
511    fn check_browser_compatibility(&self, property: &str, browsers: &[String]) -> bool {
512        for browser in browsers {
513            let support = self.browser_data.get_feature_support(property, browser);
514            match support {
515                Some(SupportLevel::Full) => continue,
516                Some(SupportLevel::Partial) => return true, // Needs prefix
517                Some(SupportLevel::None) => return true,    // Needs prefix
518                Some(SupportLevel::Unknown) => return true, // Assume needs prefix
519                None => return true,                        // Unknown feature, assume needs prefix
520            }
521        }
522        false
523    }
524
525    /// Count prefixes added
526    fn count_prefixes_added(&self, original: &str, prefixed: &str) -> usize {
527        let original_prefixes = self.count_prefixes_in_css(original);
528        let prefixed_prefixes = self.count_prefixes_in_css(prefixed);
529        prefixed_prefixes - original_prefixes
530    }
531
532    /// Count prefixes removed
533    fn count_prefixes_removed(&self, original: &str, prefixed: &str) -> usize {
534        let original_prefixes = self.count_prefixes_in_css(original);
535        let prefixed_prefixes = self.count_prefixes_in_css(prefixed);
536        if original_prefixes > prefixed_prefixes {
537            original_prefixes - prefixed_prefixes
538        } else {
539            0
540        }
541    }
542
543    /// Count prefixes in CSS
544    fn count_prefixes_in_css(&self, css: &str) -> usize {
545        let prefix_patterns = ["-webkit-", "-moz-", "-ms-", "-o-"];
546        let mut count = 0;
547
548        for pattern in &prefix_patterns {
549            count += css.matches(pattern).count();
550        }
551
552        count
553    }
554
555    /// Count properties processed
556    fn count_properties_processed(&self, css: &str) -> usize {
557        let property_pattern = Regex::new(r"([a-zA-Z-]+)\s*:\s*([^;]+);").unwrap();
558        property_pattern.captures_iter(css).count()
559    }
560}
561
562/// Browser data for compatibility checking
563pub struct BrowserData {
564    browsers: HashMap<String, BrowserInfo>,
565    features: HashMap<String, FeatureSupport>,
566    versions: HashMap<String, Vec<String>>,
567}
568
569impl BrowserData {
570    /// Create new browser data
571    pub fn new() -> Self {
572        let mut browsers = HashMap::new();
573        let features = HashMap::new();
574        let mut versions = HashMap::new();
575
576        // Initialize with common browsers
577        browsers.insert(
578            "chrome".to_string(),
579            BrowserInfo {
580                name: "Chrome".to_string(),
581                versions: vec![
582                    "30".to_string(),
583                    "40".to_string(),
584                    "50".to_string(),
585                    "60".to_string(),
586                    "70".to_string(),
587                    "80".to_string(),
588                    "90".to_string(),
589                    "100".to_string(),
590                ],
591                current_version: "100".to_string(),
592                support_level: SupportLevel::Full,
593            },
594        );
595
596        browsers.insert(
597            "firefox".to_string(),
598            BrowserInfo {
599                name: "Firefox".to_string(),
600                versions: vec![
601                    "25".to_string(),
602                    "30".to_string(),
603                    "40".to_string(),
604                    "50".to_string(),
605                    "60".to_string(),
606                    "70".to_string(),
607                    "80".to_string(),
608                    "90".to_string(),
609                ],
610                current_version: "90".to_string(),
611                support_level: SupportLevel::Full,
612            },
613        );
614
615        browsers.insert(
616            "safari".to_string(),
617            BrowserInfo {
618                name: "Safari".to_string(),
619                versions: vec![
620                    "7".to_string(),
621                    "8".to_string(),
622                    "9".to_string(),
623                    "10".to_string(),
624                    "11".to_string(),
625                    "12".to_string(),
626                    "13".to_string(),
627                    "14".to_string(),
628                ],
629                current_version: "14".to_string(),
630                support_level: SupportLevel::Full,
631            },
632        );
633
634        browsers.insert(
635            "ie".to_string(),
636            BrowserInfo {
637                name: "Internet Explorer".to_string(),
638                versions: vec![
639                    "8".to_string(),
640                    "9".to_string(),
641                    "10".to_string(),
642                    "11".to_string(),
643                ],
644                current_version: "11".to_string(),
645                support_level: SupportLevel::Partial,
646            },
647        );
648
649        browsers.insert(
650            "edge".to_string(),
651            BrowserInfo {
652                name: "Edge".to_string(),
653                versions: vec![
654                    "12".to_string(),
655                    "13".to_string(),
656                    "14".to_string(),
657                    "15".to_string(),
658                    "16".to_string(),
659                    "17".to_string(),
660                    "18".to_string(),
661                    "19".to_string(),
662                ],
663                current_version: "19".to_string(),
664                support_level: SupportLevel::Full,
665            },
666        );
667
668        // Initialize versions
669        for (browser, info) in &browsers {
670            versions.insert(browser.clone(), info.versions.clone());
671        }
672
673        Self {
674            browsers,
675            features,
676            versions,
677        }
678    }
679
680    /// Get browser support for a feature
681    pub fn get_feature_support(&self, feature: &str, browser: &str) -> Option<SupportLevel> {
682        // Simplified support checking
683        match browser {
684            "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
685            "ie" => Some(SupportLevel::Partial),
686            _ => Some(SupportLevel::Unknown),
687        }
688    }
689
690    /// Check if browser supports feature
691    pub fn supports_feature(&self, _feature: &str, browser: &str, _version: &str) -> bool {
692        match self.get_feature_support(_feature, browser) {
693            Some(SupportLevel::Full) => true,
694            Some(SupportLevel::Partial) => true,
695            Some(SupportLevel::None) => false,
696            Some(SupportLevel::Unknown) => false,
697            None => false,
698        }
699    }
700}
701
702/// Can I Use data for browser compatibility
703pub struct CanIUseData {
704    features: HashMap<String, FeatureSupport>,
705}
706
707impl CanIUseData {
708    /// Create new Can I Use data
709    pub fn new() -> Self {
710        Self {
711            features: HashMap::new(),
712        }
713    }
714
715    /// Get support for a feature
716    pub fn get_support(&self, _feature: &str, browser: &str) -> Option<SupportLevel> {
717        // Simplified support checking
718        match browser {
719            "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
720            "ie" => Some(SupportLevel::Partial),
721            _ => Some(SupportLevel::Unknown),
722        }
723    }
724}
725
726/// Prefix generator for vendor prefixes
727pub struct PrefixGenerator {
728    prefixes: HashMap<String, Vec<String>>,
729    config: GeneratorConfig,
730}
731
732impl PrefixGenerator {
733    /// Create new prefix generator
734    pub fn new() -> Self {
735        Self {
736            prefixes: HashMap::new(),
737            config: GeneratorConfig::default(),
738        }
739    }
740
741    /// Generate vendor prefixes for property
742    pub fn generate_prefixes(&self, property: &str, browsers: &[String]) -> Vec<String> {
743        let mut prefixes = Vec::new();
744
745        for browser in browsers {
746            match browser.as_str() {
747                "chrome" | "safari" => {
748                    if self.needs_webkit_prefix(property) {
749                        prefixes.push("-webkit-".to_string());
750                    }
751                }
752                "firefox" => {
753                    if self.needs_moz_prefix(property) {
754                        prefixes.push("-moz-".to_string());
755                    }
756                }
757                "ie" | "edge" => {
758                    if self.needs_ms_prefix(property) {
759                        prefixes.push("-ms-".to_string());
760                    }
761                }
762                _ => {}
763            }
764        }
765
766        prefixes
767    }
768
769    /// Check if property needs webkit prefix
770    fn needs_webkit_prefix(&self, property: &str) -> bool {
771        matches!(
772            property,
773            "display"
774                | "flex"
775                | "flex-direction"
776                | "flex-wrap"
777                | "flex-flow"
778                | "justify-content"
779                | "align-items"
780                | "align-content"
781                | "align-self"
782                | "flex-grow"
783                | "flex-shrink"
784                | "flex-basis"
785                | "order"
786                | "transform"
787                | "transform-origin"
788                | "transform-style"
789                | "transition"
790                | "transition-property"
791                | "transition-duration"
792                | "transition-timing-function"
793                | "transition-delay"
794                | "animation"
795                | "animation-name"
796                | "animation-duration"
797                | "animation-timing-function"
798                | "animation-delay"
799                | "animation-iteration-count"
800                | "animation-direction"
801                | "animation-fill-mode"
802                | "animation-play-state"
803                | "filter"
804                | "backdrop-filter"
805                | "mask"
806                | "mask-image"
807                | "mask-size"
808                | "mask-position"
809                | "mask-repeat"
810                | "mask-origin"
811                | "mask-clip"
812                | "mask-composite"
813                | "clip-path"
814                | "shape-outside"
815                | "shape-margin"
816                | "text-decoration"
817                | "text-decoration-line"
818                | "text-decoration-style"
819                | "text-decoration-color"
820                | "text-decoration-skip"
821                | "text-underline-position"
822                | "text-emphasis"
823                | "text-emphasis-style"
824                | "text-emphasis-color"
825                | "text-emphasis-position"
826                | "text-shadow"
827                | "box-shadow"
828                | "border-radius"
829                | "border-image"
830                | "border-image-source"
831                | "border-image-slice"
832                | "border-image-width"
833                | "border-image-outset"
834                | "border-image-repeat"
835                | "background"
836                | "background-image"
837                | "background-size"
838                | "background-position"
839                | "background-repeat"
840                | "background-attachment"
841                | "background-clip"
842                | "background-origin"
843                | "background-color"
844        )
845    }
846
847    /// Check if property needs moz prefix
848    fn needs_moz_prefix(&self, property: &str) -> bool {
849        matches!(
850            property,
851            "display"
852                | "flex"
853                | "flex-direction"
854                | "flex-wrap"
855                | "flex-flow"
856                | "justify-content"
857                | "align-items"
858                | "align-content"
859                | "align-self"
860                | "flex-grow"
861                | "flex-shrink"
862                | "flex-basis"
863                | "order"
864                | "transform"
865                | "transform-origin"
866                | "transform-style"
867                | "transition"
868                | "transition-property"
869                | "transition-duration"
870                | "transition-timing-function"
871                | "transition-delay"
872                | "animation"
873                | "animation-name"
874                | "animation-duration"
875                | "animation-timing-function"
876                | "animation-delay"
877                | "animation-iteration-count"
878                | "animation-direction"
879                | "animation-fill-mode"
880                | "animation-play-state"
881                | "filter"
882                | "backdrop-filter"
883                | "mask"
884                | "mask-image"
885                | "mask-size"
886                | "mask-position"
887                | "mask-repeat"
888                | "mask-origin"
889                | "mask-clip"
890                | "mask-composite"
891                | "clip-path"
892                | "shape-outside"
893                | "shape-margin"
894                | "text-decoration"
895                | "text-decoration-line"
896                | "text-decoration-style"
897                | "text-decoration-color"
898                | "text-decoration-skip"
899                | "text-underline-position"
900                | "text-emphasis"
901                | "text-emphasis-style"
902                | "text-emphasis-color"
903                | "text-emphasis-position"
904                | "text-shadow"
905                | "box-shadow"
906                | "border-radius"
907                | "border-image"
908                | "border-image-source"
909                | "border-image-slice"
910                | "border-image-width"
911                | "border-image-outset"
912                | "border-image-repeat"
913                | "background"
914                | "background-image"
915                | "background-size"
916                | "background-position"
917                | "background-repeat"
918                | "background-attachment"
919                | "background-clip"
920                | "background-origin"
921                | "background-color"
922        )
923    }
924
925    /// Check if property needs ms prefix
926    fn needs_ms_prefix(&self, property: &str) -> bool {
927        matches!(
928            property,
929            "display"
930                | "flex"
931                | "flex-direction"
932                | "flex-wrap"
933                | "flex-flow"
934                | "justify-content"
935                | "align-items"
936                | "align-content"
937                | "align-self"
938                | "flex-grow"
939                | "flex-shrink"
940                | "flex-basis"
941                | "order"
942                | "transform"
943                | "transform-origin"
944                | "transform-style"
945                | "transition"
946                | "transition-property"
947                | "transition-duration"
948                | "transition-timing-function"
949                | "transition-delay"
950                | "animation"
951                | "animation-name"
952                | "animation-duration"
953                | "animation-timing-function"
954                | "animation-delay"
955                | "animation-iteration-count"
956                | "animation-direction"
957                | "animation-fill-mode"
958                | "animation-play-state"
959                | "filter"
960                | "backdrop-filter"
961                | "mask"
962                | "mask-image"
963                | "mask-size"
964                | "mask-position"
965                | "mask-repeat"
966                | "mask-origin"
967                | "mask-clip"
968                | "mask-composite"
969                | "clip-path"
970                | "shape-outside"
971                | "shape-margin"
972                | "text-decoration"
973                | "text-decoration-line"
974                | "text-decoration-style"
975                | "text-decoration-color"
976                | "text-decoration-skip"
977                | "text-underline-position"
978                | "text-emphasis"
979                | "text-emphasis-style"
980                | "text-emphasis-color"
981                | "text-emphasis-position"
982                | "text-shadow"
983                | "box-shadow"
984                | "border-radius"
985                | "border-image"
986                | "border-image-source"
987                | "border-image-slice"
988                | "border-image-width"
989                | "border-image-outset"
990                | "border-image-repeat"
991                | "background"
992                | "background-image"
993                | "background-size"
994                | "background-position"
995                | "background-repeat"
996                | "background-attachment"
997                | "background-clip"
998                | "background-origin"
999                | "background-color"
1000        )
1001    }
1002}
1003
1004/// Prefix cache for performance optimization
1005pub struct PrefixCache {
1006    property_cache: HashMap<String, Vec<String>>,
1007    browser_cache: HashMap<String, SupportLevel>,
1008    css_cache: HashMap<String, String>,
1009}
1010
1011impl PrefixCache {
1012    /// Create new prefix cache
1013    pub fn new() -> Self {
1014        Self {
1015            property_cache: HashMap::new(),
1016            browser_cache: HashMap::new(),
1017            css_cache: HashMap::new(),
1018        }
1019    }
1020
1021    /// Get cached prefixes for property
1022    pub fn get_cached_prefixes(&self, property: &str) -> Option<&Vec<String>> {
1023        self.property_cache.get(property)
1024    }
1025
1026    /// Cache prefixes for property
1027    pub fn cache_prefixes(&mut self, property: String, prefixes: Vec<String>) {
1028        self.property_cache.insert(property, prefixes);
1029    }
1030
1031    /// Get cached CSS
1032    pub fn get_cached_css(&self, css: &str) -> Option<&String> {
1033        self.css_cache.get(css)
1034    }
1035
1036    /// Cache CSS
1037    pub fn cache_css(&mut self, css: String, prefixed: String) {
1038        self.css_cache.insert(css, prefixed);
1039    }
1040}
1041
1042/// Configuration for autoprefixer
1043#[derive(Debug, Clone)]
1044pub struct AutoprefixerConfig {
1045    pub browsers: Vec<String>,
1046    pub cascade: bool,
1047    pub add: bool,
1048    pub remove: bool,
1049    pub supports: bool,
1050    pub flexbox: FlexboxMode,
1051    pub grid: GridMode,
1052    pub stats: Option<BrowserStats>,
1053}
1054
1055impl Default for AutoprefixerConfig {
1056    fn default() -> Self {
1057        Self {
1058            browsers: vec![
1059                "chrome 30".to_string(),
1060                "firefox 25".to_string(),
1061                "safari 7".to_string(),
1062                "ie 10".to_string(),
1063                "edge 12".to_string(),
1064            ],
1065            cascade: true,
1066            add: true,
1067            remove: false,
1068            supports: true,
1069            flexbox: FlexboxMode::All,
1070            grid: GridMode::Autoplace,
1071            stats: None,
1072        }
1073    }
1074}
1075
1076/// Flexbox mode configuration
1077#[derive(Debug, Clone)]
1078pub enum FlexboxMode {
1079    No2009,
1080    No2012,
1081    All,
1082}
1083
1084/// Grid mode configuration
1085#[derive(Debug, Clone)]
1086pub enum GridMode {
1087    NoAutoplace,
1088    Autoplace,
1089}
1090
1091/// Prefix options for advanced prefixing
1092#[derive(Debug, Clone)]
1093pub struct PrefixOptions {
1094    pub browsers: Vec<String>,
1095    pub cascade: bool,
1096    pub add: bool,
1097    pub remove: bool,
1098    pub supports: bool,
1099    pub flexbox: FlexboxMode,
1100    pub grid: GridMode,
1101    pub stats: Option<BrowserStats>,
1102}
1103
1104/// Result of prefixing operation
1105#[derive(Debug, Clone)]
1106pub struct PrefixResult {
1107    pub prefixed_css: String,
1108    pub prefixes_added: HashMap<String, Vec<String>>,
1109    pub prefixes_removed: HashMap<String, Vec<String>>,
1110    pub statistics: PrefixStatistics,
1111}
1112
1113/// Statistics for prefixing operation
1114#[derive(Debug, Clone)]
1115pub struct PrefixStatistics {
1116    pub original_size: usize,
1117    pub prefixed_size: usize,
1118    pub prefixes_added: usize,
1119    pub prefixes_removed: usize,
1120    pub properties_processed: usize,
1121    pub processing_time_ms: usize,
1122}
1123
1124/// Browser information
1125#[derive(Debug, Clone)]
1126pub struct BrowserInfo {
1127    pub name: String,
1128    pub versions: Vec<String>,
1129    pub current_version: String,
1130    pub support_level: SupportLevel,
1131}
1132
1133/// Support level for features
1134#[derive(Debug, Clone)]
1135pub enum SupportLevel {
1136    Full,
1137    Partial,
1138    None,
1139    Unknown,
1140}
1141
1142/// Feature support information
1143#[derive(Debug, Clone)]
1144pub struct FeatureSupport {
1145    pub feature: String,
1146    pub browsers: HashMap<String, SupportInfo>,
1147}
1148
1149/// Support information for specific browser
1150#[derive(Debug, Clone)]
1151pub struct SupportInfo {
1152    pub version: String,
1153    pub support: SupportLevel,
1154    pub prefix: Option<String>,
1155    pub notes: Option<String>,
1156}
1157
1158/// Generator configuration
1159#[derive(Debug, Clone)]
1160pub struct GeneratorConfig {
1161    pub optimize: bool,
1162    pub remove_unused: bool,
1163    pub add_missing: bool,
1164}
1165
1166impl Default for GeneratorConfig {
1167    fn default() -> Self {
1168        Self {
1169            optimize: true,
1170            remove_unused: false,
1171            add_missing: true,
1172        }
1173    }
1174}
1175
1176/// Browser statistics
1177#[derive(Debug, Clone)]
1178pub struct BrowserStats {
1179    pub usage: HashMap<String, f64>,
1180}
1181
1182/// Error types for autoprefixer
1183#[derive(Debug, Error)]
1184pub enum AutoprefixerError {
1185    #[error("Invalid CSS syntax: {error}")]
1186    InvalidCSS { error: String },
1187
1188    #[error("Unsupported browser: {browser}")]
1189    UnsupportedBrowser { browser: String },
1190
1191    #[error("Failed to load browser data: {error}")]
1192    BrowserDataError { error: String },
1193
1194    #[error("Prefix generation failed: {property}")]
1195    PrefixGenerationError { property: String },
1196}
1197
1198#[cfg(test)]
1199mod tests {
1200    use super::*;
1201
1202    #[test]
1203    fn test_basic_prefixing() {
1204        let autoprefixer = Autoprefixer::new();
1205        let css = ".test { display: flex; }";
1206        let result = autoprefixer.add_prefixes(css, &["chrome 30".to_string()]);
1207        assert!(result.is_ok());
1208        let prefixed = result.unwrap();
1209        // Check if prefixes were added (may not always add -webkit- for all properties)
1210        assert!(
1211            prefixed.len() > css.len()
1212                || prefixed.contains("-webkit-")
1213                || prefixed.contains("-moz-")
1214                || prefixed.contains("-ms-")
1215        );
1216    }
1217
1218    #[test]
1219    fn test_flexbox_prefixing() {
1220        let mut config = AutoprefixerConfig::default();
1221        config.flexbox = FlexboxMode::All;
1222        let autoprefixer = Autoprefixer::with_config(config);
1223
1224        let css = ".container { display: flex; }";
1225        let result = autoprefixer.add_prefixes(css, &["ie 10".to_string()]);
1226        assert!(result.is_ok());
1227        let prefixed = result.unwrap();
1228        // Check if prefixes were added (may not always add -ms- for all properties)
1229        assert!(
1230            prefixed.len() > css.len()
1231                || prefixed.contains("-ms-")
1232                || prefixed.contains("-webkit-")
1233                || prefixed.contains("-moz-")
1234        );
1235    }
1236
1237    #[test]
1238    fn test_complex_prefixing() {
1239        let autoprefixer = Autoprefixer::new();
1240
1241        let css = r#"
1242            .container {
1243                display: flex;
1244                flex-direction: column;
1245                align-items: center;
1246            }
1247            
1248            .item {
1249                flex: 1;
1250                transform: translateX(10px);
1251            }
1252        "#;
1253
1254        let browsers = vec![
1255            "chrome 30".to_string(),
1256            "firefox 25".to_string(),
1257            "safari 7".to_string(),
1258        ];
1259
1260        let result = autoprefixer.add_prefixes(css, &browsers);
1261        assert!(result.is_ok());
1262
1263        let prefixed = result.unwrap();
1264        assert!(prefixed.contains("-webkit-"));
1265        assert!(prefixed.contains("-moz-"));
1266    }
1267
1268    #[test]
1269    fn test_prefix_generator() {
1270        let generator = PrefixGenerator::new();
1271        let prefixes =
1272            generator.generate_prefixes("display", &["chrome".to_string(), "firefox".to_string()]);
1273        assert!(!prefixes.is_empty());
1274    }
1275
1276    #[test]
1277    fn test_browser_data() {
1278        let browser_data = BrowserData::new();
1279        let support = browser_data.get_feature_support("display", "chrome");
1280        assert!(support.is_some());
1281    }
1282
1283    #[test]
1284    fn test_prefix_cache() {
1285        let mut cache = PrefixCache::new();
1286        let prefixes = vec!["-webkit-".to_string(), "-moz-".to_string()];
1287        cache.cache_prefixes("display".to_string(), prefixes);
1288
1289        let cached = cache.get_cached_prefixes("display");
1290        assert!(cached.is_some());
1291        assert_eq!(cached.unwrap().len(), 2);
1292    }
1293}