csskit_transform/
reduce_time_units.rs1use crate::prelude::*;
2use css_ast::{CssTypes, Time, VisitNode, Visitable};
3use css_parse::format_in;
4
5pub struct ReduceTimeUnits<'a, 'ctx, N: Visitable + NodeWithMetadata<CssMetadata>> {
6 pub transformer: &'ctx Transformer<'a, CssMetadata, N, CssMinifierFeature>,
7}
8
9impl<'a, 'ctx, N> Transform<'a, 'ctx, CssMetadata, N, CssMinifierFeature> for ReduceTimeUnits<'a, 'ctx, N>
10where
11 N: Visitable + NodeWithMetadata<CssMetadata>,
12{
13 fn skips_subtree(metadata: &CssMetadata) -> bool {
14 !metadata.has_value_kinds(CssTypes::Time)
15 }
16
17 fn new(transformer: &'ctx Transformer<'a, CssMetadata, N, CssMinifierFeature>) -> Self {
18 Self { transformer }
19 }
20}
21
22#[visitor]
23impl<'a, 'ctx, N> Visit for ReduceTimeUnits<'a, 'ctx, N>
24where
25 N: Visitable + NodeWithMetadata<CssMetadata>,
26{
27 fn consider_node(&self, node: VisitNode) -> VisitFlow {
28 if node.node_id.is_some() && Self::skips_subtree(&node.subtree_metadata()) {
29 return VisitFlow::SKIP_CHILDREN;
30 }
31 VisitFlow::DESCEND
32 }
33
34 fn visit_time(&mut self, time: &Time) {
35 if let Time::Ms(dim) = time {
36 let arena = self.transformer.alloc();
37 let seconds = time.as_seconds();
38 let sc = self.transformer.to_source_cursor((*dim).into());
39 let value = if seconds.fract() == 0.0 {
40 format_in!(in arena, "{}", seconds as i64)
41 } else {
42 format_in!(in arena, "{seconds}")
43 };
44 let seconds_len = value.len() - value.starts_with("0.") as usize - value.starts_with("-0.") as usize + 1;
45 let ms_len = if sc.may_compact() {
46 format_in!(in arena, "{}", self.transformer.to_source_cursor((*dim).into()).compact()).len()
47 } else {
48 sc.token().len() as usize
49 };
50 if seconds_len < ms_len {
51 self.transformer.replace_parsed::<Time>(time.to_span(), format_in!(in arena, "{value}s").into_str());
52 }
53 }
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use crate::test_helpers::{assert_no_transform, assert_transform};
60 use css_ast::{CssAtomSet, StyleSheet};
61
62 #[test]
63 fn converts_milliseconds_to_seconds() {
64 assert_transform!(
65 CssMinifierFeature::ReduceTimeUnits,
66 CssAtomSet,
67 StyleSheet,
68 "div { transition-duration: 500ms; }",
69 "div { transition-duration: 0.5s; }"
70 );
71 }
72
73 #[test]
74 fn keeps_shorter_millisecond_values() {
75 assert_no_transform!(
76 CssMinifierFeature::ReduceTimeUnits,
77 CssAtomSet,
78 StyleSheet,
79 "div { transition-duration: 50ms; }"
80 );
81 }
82
83 #[test]
84 fn keeps_second_values_when_not_shorter() {
85 assert_no_transform!(
86 CssMinifierFeature::ReduceTimeUnits,
87 CssAtomSet,
88 StyleSheet,
89 "div { transition-duration: 2s; }"
90 );
91 }
92
93 #[test]
94 fn converts_whole_seconds() {
95 assert_transform!(
96 CssMinifierFeature::ReduceTimeUnits,
97 CssAtomSet,
98 StyleSheet,
99 "div { animation-duration: 1000ms; }",
100 "div { animation-duration: 1s; }"
101 );
102 }
103
104 #[test]
105 fn converts_only_when_compact_is_larger() {
106 assert_no_transform!(
107 CssMinifierFeature::ReduceTimeUnits,
108 CssAtomSet,
109 StyleSheet,
110 "div { animation-duration: 00050ms; }"
111 );
112 }
113}