csskit_transform/
reduce_urls.rs1use crate::prelude::*;
2use css_ast::{CssTypes, Url, UrlOrString, VisitNode, Visitable};
3use css_parse::format_in;
4
5pub struct ReduceUrls<'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 ReduceUrls<'a, 'ctx, N>
10where
11 N: Visitable + NodeWithMetadata<CssMetadata>,
12{
13 fn skips_subtree(metadata: &CssMetadata) -> bool {
14 !metadata.has_value_kinds(CssTypes::Url)
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 ReduceUrls<'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_url_or_string(&mut self, url_or_string: &UrlOrString) {
35 let UrlOrString::Url(url) = url_or_string else {
36 return;
37 };
38 let arena = self.transformer.alloc();
39 match url {
40 Url::UrlFunction(_, string, _) | Url::SrcFunction(_, string, _) => {
41 let sc = self.transformer.to_source_cursor((*string).into());
42 let token = sc.token();
43 let start = token.leading_len() as usize;
44 let end = sc.source().len() - token.trailing_len() as usize;
45 self.transformer.replace_parsed::<UrlOrString>(
46 url_or_string.to_span(),
47 format_in!(in arena, "\"{}\"", &sc.source()[start..end]).into_str(),
48 );
49 }
50 Url::Url(url_token) => {
51 let sc = self.transformer.to_source_cursor((*url_token).into());
52 let token = sc.token();
53 let leading_len = token.leading_len() as usize;
54 let trailing_len = token.trailing_len() as usize;
55 let url_content = &sc.source()[leading_len..(sc.source().len() - trailing_len)];
56 let url_content = url_content.trim();
57 self.transformer.replace_parsed::<UrlOrString>(
58 url_or_string.to_span(),
59 format_in!(in arena, "\"{url_content}\"").into_str(),
60 );
61 }
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use crate::test_helpers::{assert_no_transform, assert_transform};
69 use css_ast::{CssAtomSet, StyleSheet};
70
71 #[test]
72 fn reduces_url_function_to_string_in_import() {
73 assert_transform!(
74 CssMinifierFeature::ReduceUrls,
75 CssAtomSet,
76 StyleSheet,
77 "@import url(\"foo.css\");",
78 "@import \"foo.css\";"
79 );
80 }
81
82 #[test]
83 fn reduces_url_function_single_quotes_to_string_in_import() {
84 assert_transform!(
85 CssMinifierFeature::ReduceUrls,
86 CssAtomSet,
87 StyleSheet,
88 "@import url('foo.css');",
89 "@import \"foo.css\";"
90 );
91 }
92
93 #[test]
94 fn reduces_bare_url_to_string_in_import() {
95 assert_transform!(
96 CssMinifierFeature::ReduceUrls,
97 CssAtomSet,
98 StyleSheet,
99 "@import url(foo.css);",
100 "@import \"foo.css\";"
101 );
102 }
103
104 #[test]
105 fn reduces_url_with_media_query() {
106 assert_transform!(
107 CssMinifierFeature::ReduceUrls,
108 CssAtomSet,
109 StyleSheet,
110 "@import url(\"foo.css\") screen;",
111 "@import \"foo.css\" screen;"
112 );
113 }
114
115 #[test]
116 fn reduces_url_with_layer() {
117 assert_transform!(
118 CssMinifierFeature::ReduceUrls,
119 CssAtomSet,
120 StyleSheet,
121 "@import url(\"foo.css\") layer;",
122 "@import \"foo.css\" layer;"
123 );
124 }
125
126 #[test]
127 fn reduces_url_with_supports() {
128 assert_transform!(
129 CssMinifierFeature::ReduceUrls,
130 CssAtomSet,
131 StyleSheet,
132 "@import url(\"foo.css\") supports(not (display: flex));",
133 "@import \"foo.css\" supports(not (display: flex));"
134 );
135 }
136
137 #[test]
138 fn reduces_url_with_layer_and_supports() {
139 assert_transform!(
140 CssMinifierFeature::ReduceUrls,
141 CssAtomSet,
142 StyleSheet,
143 "@import url(\"foo.css\") layer(base) supports(not (display: flex));",
144 "@import \"foo.css\" layer(base) supports(not (display: flex));"
145 );
146 }
147
148 #[test]
149 fn does_not_transform_bare_string() {
150 assert_no_transform!(CssMinifierFeature::ReduceUrls, CssAtomSet, StyleSheet, "@import \"foo.css\";");
151 }
152
153 #[test]
154 fn reduces_namespace_url_to_string() {
155 assert_transform!(
156 CssMinifierFeature::ReduceUrls,
157 CssAtomSet,
158 StyleSheet,
159 "@namespace url(\"http://www.w3.org/1999/xhtml\");",
160 "@namespace \"http://www.w3.org/1999/xhtml\";"
161 );
162 }
163
164 #[test]
165 fn reduces_namespace_url_with_prefix() {
166 assert_transform!(
167 CssMinifierFeature::ReduceUrls,
168 CssAtomSet,
169 StyleSheet,
170 "@namespace html url(\"http://www.w3.org/1999/xhtml\");",
171 "@namespace html \"http://www.w3.org/1999/xhtml\";"
172 );
173 }
174}