csskit_transform/
css_minifier.rs

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