csskit_transform/
css_minifier.rs

1use crate::{ReduceColors, ReduceLengths, ReduceTimeUnits, ReduceUrls, transformer};
2use bitmask_enum::bitmask;
3use css_ast::{CssMetadata, Visitable};
4
5transformer!(
6	/// Runtime feature flags for the CSS minifier, enabling individual transforms.
7	pub enum CssMinifierFeature[CssMetadata, Visitable] {
8		/// Enables the [ReduceColors] transformer.
9		ReduceColors,
10		/// Enables the [ReduceLengths] transformer.
11		ReduceLengths,
12		/// Enables the [ReduceTimeUnits] transformer.
13		ReduceTimeUnits,
14		/// Enables the [ReduceUrls] transformer.
15		ReduceUrls,
16	}
17);
18
19impl Default for CssMinifierFeature {
20	fn default() -> Self {
21		Self::none()
22	}
23}
24
25#[cfg(test)]
26mod tests {
27	use super::*;
28	use crate::Transformer;
29	use bumpalo::Bump;
30	use css_ast::{CssAtomSet, StyleSheet};
31	use css_lexer::Lexer;
32	use css_parse::{CursorCompactWriteSink, CursorOverlaySink, Parser, ToCursors};
33
34	fn minify(source_text: &str, features: CssMinifierFeature) -> (String, bool) {
35		let bump = Bump::default();
36		let mut transformer = Transformer::new_in(&bump, features, &CssAtomSet::ATOMS, source_text);
37		let lexer = Lexer::new(&CssAtomSet::ATOMS, source_text);
38		let mut parser = Parser::new(&bump, source_text, lexer);
39		let mut result = parser.parse_entirely::<StyleSheet>();
40		let mut output = String::new();
41		if let Some(ref mut node) = result.output {
42			transformer.transform(node);
43			let overlays = transformer.overlays();
44			let changed = transformer.has_changed();
45			{
46				let mut overlay_stream = CursorOverlaySink::new(
47					source_text,
48					&*overlays,
49					CursorCompactWriteSink::new(source_text, &mut output),
50				);
51				result.output.to_cursors(&mut overlay_stream);
52			}
53			(output, changed)
54		} else {
55			panic!("Could not transform output");
56		}
57	}
58
59	#[test]
60	fn test_reduce_lengths_feature() {
61		let input = "body { width: 0px; }";
62		let (output, changed) = minify(input, CssMinifierFeature::ReduceLengths);
63		assert!(changed);
64		assert!(output.contains("width:0"), "Should apply length reduction, got: {}", output);
65		assert!(!output.contains("0px"), "Should not contain 0px, got: {}", output);
66	}
67
68	#[test]
69	fn test_no_features() {
70		let input = "body { width: 0px; }";
71		let (output, changed) = minify(input, CssMinifierFeature::none());
72		assert!(!changed, "Should not make changes with no features enabled");
73		assert!(output.contains("width:0px"));
74	}
75
76	#[test]
77	fn test_changed_flag_accuracy() {
78		let input = "body { width: 10px; }";
79		let (_, changed) = minify(input, CssMinifierFeature::all_bits());
80		assert!(!changed, "Should report no changes when no optimizations apply");
81	}
82}