1use regex::Regex;
7use std::collections::HashMap;
8use thiserror::Error;
9
10pub struct Autoprefixer {
12 browser_data: BrowserData,
13 caniuse_data: CanIUseData,
14 config: AutoprefixerConfig,
15 cache: PrefixCache,
16}
17
18impl Autoprefixer {
19 pub fn new() -> Self {
21 Self::with_config(AutoprefixerConfig::default())
22 }
23
24 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 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(¤t_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 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 pub fn needs_prefix(&self, property: &str, browsers: &[String]) -> bool {
104 self.check_browser_compatibility(property, browsers)
105 }
106
107 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(¤t_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(¤t_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 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 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 fn get_prefixes_for_property(&self, property: &str) -> Vec<String> {
179 let mut prefixes = Vec::new();
180
181 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 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 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 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 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 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, Some(SupportLevel::None) => return true, Some(SupportLevel::Unknown) => return true, None => return true, }
521 }
522 false
523 }
524
525 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 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 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 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
562pub struct BrowserData {
564 browsers: HashMap<String, BrowserInfo>,
565 features: HashMap<String, FeatureSupport>,
566 versions: HashMap<String, Vec<String>>,
567}
568
569impl BrowserData {
570 pub fn new() -> Self {
572 let mut browsers = HashMap::new();
573 let features = HashMap::new();
574 let mut versions = HashMap::new();
575
576 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 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 pub fn get_feature_support(&self, feature: &str, browser: &str) -> Option<SupportLevel> {
682 match browser {
684 "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
685 "ie" => Some(SupportLevel::Partial),
686 _ => Some(SupportLevel::Unknown),
687 }
688 }
689
690 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
702pub struct CanIUseData {
704 features: HashMap<String, FeatureSupport>,
705}
706
707impl CanIUseData {
708 pub fn new() -> Self {
710 Self {
711 features: HashMap::new(),
712 }
713 }
714
715 pub fn get_support(&self, _feature: &str, browser: &str) -> Option<SupportLevel> {
717 match browser {
719 "chrome" | "firefox" | "safari" | "edge" => Some(SupportLevel::Full),
720 "ie" => Some(SupportLevel::Partial),
721 _ => Some(SupportLevel::Unknown),
722 }
723 }
724}
725
726pub struct PrefixGenerator {
728 prefixes: HashMap<String, Vec<String>>,
729 config: GeneratorConfig,
730}
731
732impl PrefixGenerator {
733 pub fn new() -> Self {
735 Self {
736 prefixes: HashMap::new(),
737 config: GeneratorConfig::default(),
738 }
739 }
740
741 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 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 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 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
1004pub 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 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 pub fn get_cached_prefixes(&self, property: &str) -> Option<&Vec<String>> {
1023 self.property_cache.get(property)
1024 }
1025
1026 pub fn cache_prefixes(&mut self, property: String, prefixes: Vec<String>) {
1028 self.property_cache.insert(property, prefixes);
1029 }
1030
1031 pub fn get_cached_css(&self, css: &str) -> Option<&String> {
1033 self.css_cache.get(css)
1034 }
1035
1036 pub fn cache_css(&mut self, css: String, prefixed: String) {
1038 self.css_cache.insert(css, prefixed);
1039 }
1040}
1041
1042#[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#[derive(Debug, Clone)]
1078pub enum FlexboxMode {
1079 No2009,
1080 No2012,
1081 All,
1082}
1083
1084#[derive(Debug, Clone)]
1086pub enum GridMode {
1087 NoAutoplace,
1088 Autoplace,
1089}
1090
1091#[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#[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#[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#[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#[derive(Debug, Clone)]
1135pub enum SupportLevel {
1136 Full,
1137 Partial,
1138 None,
1139 Unknown,
1140}
1141
1142#[derive(Debug, Clone)]
1144pub struct FeatureSupport {
1145 pub feature: String,
1146 pub browsers: HashMap<String, SupportInfo>,
1147}
1148
1149#[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#[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#[derive(Debug, Clone)]
1178pub struct BrowserStats {
1179 pub usage: HashMap<String, f64>,
1180}
1181
1182#[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 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 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}