Skip to main content

csskit_transform/
remove_inert_nodes.rs

1use crate::prelude::*;
2use css_ast::{VisitNode, Visitable};
3
4/// Removes nodes which have no effect on rendering, such as rules with an empty block, or rules holding nothing but
5/// such rules.
6pub struct RemoveInertNodes<'a, 'ctx, N: Visitable + NodeWithMetadata<CssMetadata>> {
7	pub transformer: &'ctx Transformer<'a, CssMetadata, N, CssMinifierFeature>,
8}
9
10impl<'a, 'ctx, N> Transform<'a, 'ctx, CssMetadata, N, CssMinifierFeature> for RemoveInertNodes<'a, 'ctx, N>
11where
12	N: Visitable + NodeWithMetadata<CssMetadata>,
13{
14	fn skips_subtree(metadata: &CssMetadata) -> bool {
15		!metadata.is_inert()
16	}
17
18	fn new(transformer: &'ctx Transformer<'a, CssMetadata, N, CssMinifierFeature>) -> Self {
19		Self { transformer }
20	}
21}
22
23#[visitor]
24impl<'a, 'ctx, N> Visit for RemoveInertNodes<'a, 'ctx, N>
25where
26	N: Visitable + NodeWithMetadata<CssMetadata>,
27{
28	fn consider_node(&self, node: VisitNode) -> VisitFlow {
29		if node.node_id.is_some() && Self::skips_subtree(&node.subtree_metadata()) {
30			return VisitFlow::SKIP_CHILDREN;
31		}
32		VisitFlow::DESCEND
33	}
34
35	fn enter_node(&mut self, node: VisitNode) -> VisitFlow {
36		if node.self_metadata().is_inert() {
37			self.transformer.delete(node.span);
38			return VisitFlow::SKIP_CHILDREN;
39		}
40		VisitFlow::DESCEND
41	}
42}
43
44#[cfg(test)]
45mod tests {
46	use crate::test_helpers::{assert_no_transform, assert_transform};
47	use css_ast::{CssAtomSet, StyleSheet};
48
49	#[test]
50	fn removes_empty_style_rule() {
51		assert_transform!(
52			CssMinifierFeature::RemoveInertNodes,
53			CssAtomSet,
54			StyleSheet,
55			"a {}\nb { color: red }",
56			"b { color: red }"
57		);
58	}
59
60	#[test]
61	fn removes_empty_media_rule() {
62		assert_transform!(
63			CssMinifierFeature::RemoveInertNodes,
64			CssAtomSet,
65			StyleSheet,
66			"@media screen {}\nb { color: red }",
67			"b { color: red }"
68		);
69	}
70
71	#[test]
72	fn removes_empty_nested_rule() {
73		assert_transform!(
74			CssMinifierFeature::RemoveInertNodes,
75			CssAtomSet,
76			StyleSheet,
77			"a { color: red; & b {} }",
78			"a { color: red; }"
79		);
80	}
81
82	#[test]
83	fn removes_rule_containing_only_inert_rules() {
84		assert_transform!(
85			CssMinifierFeature::RemoveInertNodes,
86			CssAtomSet,
87			StyleSheet,
88			"nav { a {} }\nb { color: red }",
89			"b { color: red }"
90		);
91	}
92
93	#[test]
94	fn removes_keyframes_with_only_empty_keyframes() {
95		assert_transform!(
96			CssMinifierFeature::RemoveInertNodes,
97			CssAtomSet,
98			StyleSheet,
99			"@keyframes fade { 0% {} 100% {} }\nb { color: red }",
100			"b { color: red }"
101		);
102	}
103
104	#[test]
105	fn removes_empty_font_face_rule() {
106		assert_transform!(
107			CssMinifierFeature::RemoveInertNodes,
108			CssAtomSet,
109			StyleSheet,
110			"@font-face {}\nb { color: red }",
111			"b { color: red }"
112		);
113	}
114
115	#[test]
116	fn keeps_rule_with_declarations() {
117		assert_no_transform!(CssMinifierFeature::RemoveInertNodes, CssAtomSet, StyleSheet, "a { color: red }");
118	}
119
120	#[test]
121	fn keeps_rule_with_unknown_declaration() {
122		assert_no_transform!(CssMinifierFeature::RemoveInertNodes, CssAtomSet, StyleSheet, "a { color: fnord }");
123	}
124
125	#[test]
126	fn keeps_rule_containing_a_statement_at_rule() {
127		assert_no_transform!(
128			CssMinifierFeature::RemoveInertNodes,
129			CssAtomSet,
130			StyleSheet,
131			"@media screen { @layer a; }"
132		);
133	}
134
135	#[test]
136	fn keeps_empty_layer_rule() {
137		assert_no_transform!(CssMinifierFeature::RemoveInertNodes, CssAtomSet, StyleSheet, "@layer a {}");
138	}
139
140	#[test]
141	fn keeps_empty_unknown_at_rule() {
142		assert_no_transform!(CssMinifierFeature::RemoveInertNodes, CssAtomSet, StyleSheet, "@fnord {}");
143	}
144
145	#[test]
146	fn keeps_rule_containing_an_unknown_at_rule() {
147		assert_no_transform!(CssMinifierFeature::RemoveInertNodes, CssAtomSet, StyleSheet, "a { @fnord {} }");
148	}
149}