Skip to main content

csskit_transform/
reduce_time_units.rs

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