Skip to main content

csskit_transform/
reduce_time_units.rs

1use 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}