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		let original_len = time.to_span().len() as usize;
27		let seconds = time.as_seconds();
28
29		if seconds == 0.0 && original_len > 1 {
30			self.transformer.replace_parsed::<Time>(time.to_span(), "0");
31			return;
32		}
33
34		if let Time::Ms(dim) = time {
35			let sc = self.transformer.to_source_cursor((*dim).into());
36			let value = if seconds.fract() == 0.0 { format!("{}", seconds as i64) } else { format!("{seconds}") };
37			let seconds_len = value.len() - value.starts_with("0.") as usize - value.starts_with("-0.") as usize + 1;
38			let ms_len = if sc.may_compact() {
39				format!("{}", self.transformer.to_source_cursor((*dim).into()).compact()).len()
40			} else {
41				sc.token().len() as usize
42			};
43			if seconds_len < ms_len {
44				self.transformer.replace_parsed::<Time>(time.to_span(), &format!("{value}s"));
45			}
46		}
47	}
48}
49
50#[cfg(test)]
51mod tests {
52	use crate::test_helpers::{assert_no_transform, assert_transform};
53	use css_ast::{CssAtomSet, StyleSheet};
54
55	#[test]
56	fn converts_milliseconds_to_seconds() {
57		assert_transform!(
58			CssMinifierFeature::ReduceTimeUnits,
59			CssAtomSet,
60			StyleSheet,
61			"div { transition-duration: 500ms; }",
62			"div { transition-duration: 0.5s; }"
63		);
64	}
65
66	#[test]
67	fn keeps_shorter_millisecond_values() {
68		assert_no_transform!(
69			CssMinifierFeature::ReduceTimeUnits,
70			CssAtomSet,
71			StyleSheet,
72			"div { transition-duration: 50ms; }"
73		);
74	}
75
76	#[test]
77	fn normalizes_zero_units() {
78		assert_transform!(
79			CssMinifierFeature::ReduceTimeUnits,
80			CssAtomSet,
81			StyleSheet,
82			"div { transition-delay: 0ms; animation-duration: 0s; }",
83			"div { transition-delay: 0; animation-duration: 0; }"
84		);
85	}
86
87	#[test]
88	fn keeps_second_values_when_not_shorter() {
89		assert_no_transform!(
90			CssMinifierFeature::ReduceTimeUnits,
91			CssAtomSet,
92			StyleSheet,
93			"div { transition-duration: 2s; }"
94		);
95	}
96
97	#[test]
98	fn converts_whole_seconds() {
99		assert_transform!(
100			CssMinifierFeature::ReduceTimeUnits,
101			CssAtomSet,
102			StyleSheet,
103			"div { animation-duration: 1000ms; }",
104			"div { animation-duration: 1s; }"
105		);
106	}
107
108	#[test]
109	fn converts_only_when_compact_is_larger() {
110		assert_no_transform!(
111			CssMinifierFeature::ReduceTimeUnits,
112			CssAtomSet,
113			StyleSheet,
114			"div { animation-duration: 00050ms; }"
115		);
116	}
117}