stylex_transform/shared/utils/css/normalizers/
base.rs1use swc_core::{
2 common::DUMMY_SP,
3 css::{
4 ast::{
5 ComponentValue, Declaration, DeclarationName, Dimension, Function, Ident, Length,
6 ListOfComponentValues, Number, Stylesheet,
7 },
8 visit::{Fold, FoldWith},
9 },
10};
11
12use crate::shared::utils::common::dashify;
13use stylex_constants::constants::common::ROOT_FONT_SIZE;
14
15struct CssFolder {
16 enable_font_size_px_to_rem: bool,
17 parent_key: Option<String>,
18 is_function_arg: bool,
19 current_property: Option<String>,
20}
21
22impl CssFolder {
23 fn convert_font_size_to_rem_normalizer<'a>(
24 &'a mut self,
25 declaration: &'a mut Declaration,
26 ) -> &'a mut Declaration {
27 if let DeclarationName::Ident(ident) = &declaration.name
28 && (ident.value == "fontSize" || self.parent_key.as_deref() == Some("fontSize"))
29 {
30 self.parent_key = Some("fontSize".into());
31 declaration.value = declaration.value.clone().fold_children_with(self);
32 self.parent_key = None;
33 }
34
35 declaration
36 }
37}
38
39impl Fold for CssFolder {
40 fn fold_list_of_component_values(
41 &mut self,
42 list: ListOfComponentValues,
43 ) -> ListOfComponentValues {
44 list.fold_children_with(self)
45 }
46
47 fn fold_declaration(&mut self, mut declaration: Declaration) -> Declaration {
48 let declaration = kebab_case_normalizer(&mut declaration);
49
50 if self.enable_font_size_px_to_rem {
51 self.convert_font_size_to_rem_normalizer(declaration);
52 }
53
54 declaration.clone().fold_children_with(self)
55 }
56
57 fn fold_dimension(&mut self, mut dimension: Dimension) -> Dimension {
58 let dimension = timing_normalizer(&mut dimension);
59 let dimension = zero_dimension_normalizer(
60 dimension,
61 self.is_function_arg,
62 self.current_property.clone(),
63 );
64
65 dimension.clone().fold_children_with(self)
66 }
67
68 fn fold_length(&mut self, mut length: Length) -> Length {
69 if self.parent_key == Some("fontSize".into())
70 && length.unit.value.eq("px")
71 && length.value.value != 0.0
72 {
73 length = Length {
74 value: Number {
75 value: length.value.value / ROOT_FONT_SIZE as f64,
76 raw: None,
77 span: length.span,
78 },
79 unit: Ident {
80 value: "rem".into(),
81 raw: None,
82 span: length.span,
83 },
84 span: DUMMY_SP,
85 };
86 };
87
88 length
89 }
90
91 fn fold_function(&mut self, func: Function) -> Function {
92 self.is_function_arg = true;
93
94 let mut fnc = func;
95
96 if let Some(last) = fnc.value.last_mut() {
98 *last = last.clone().fold_children_with(self);
99 }
100
101 self.is_function_arg = false;
102
103 fnc
104 }
105}
106
107fn timing_normalizer(dimension: &mut Dimension) -> &mut Dimension {
108 match dimension {
109 Dimension::Time(time) => {
110 if !time.unit.eq("ms") || time.value.value < 10.0 {
111 return dimension;
112 }
113
114 time.value = Number {
115 value: time.value.value / 1000.0,
116 raw: None,
117 span: DUMMY_SP,
118 };
119
120 time.unit = Ident {
121 span: DUMMY_SP,
122 value: "s".into(),
123 raw: None,
124 };
125
126 dimension
127 },
128 _ => dimension,
129 }
130}
131
132fn kebab_case_normalizer(declaration: &mut Declaration) -> &mut Declaration {
133 match &declaration.name {
134 DeclarationName::Ident(ident) => {
135 if !ident.value.eq("transitionProperty") && !ident.value.eq("willChange") {
136 return declaration;
137 }
138 },
139 DeclarationName::DashedIdent(_) => return declaration,
140 }
141
142 declaration.value.iter_mut().for_each(|value| {
143 if let ComponentValue::Ident(ident) = value
144 && !ident.value.starts_with("--")
145 {
146 ident.value = dashify(ident.value.as_str()).into();
147 }
148 });
149
150 declaration
151}
152
153pub(crate) fn base_normalizer(
154 ast: Stylesheet,
155 enable_font_size_px_to_rem: bool,
156 current_property: Option<&str>,
157) -> Stylesheet {
158 let mut folder = CssFolder {
159 enable_font_size_px_to_rem,
160 parent_key: None,
161 is_function_arg: false,
162 current_property: current_property.map(|p| p.to_string()),
163 };
164 ast.fold_with(&mut folder)
165}
166
167fn zero_dimension_normalizer(
168 dimension: &mut Dimension,
169 is_function_arg: bool,
170 current_property: Option<String>,
171) -> &mut Dimension {
172 if let Some(prop) = current_property
174 && prop.starts_with("--")
175 {
176 return dimension;
177 }
178
179 if is_function_arg {
180 return dimension;
181 }
182
183 match dimension {
184 Dimension::Length(length) => {
185 if length.value.value != 0.0 {
186 return dimension;
187 }
188
189 length.value = get_zero_dimension_value();
190 length.unit = get_zero_dimension_unit(&length.unit);
191
192 dimension
193 },
194 Dimension::Angle(angle) => {
195 if angle.value.value != 0.0 {
196 return dimension;
197 }
198
199 angle.value = get_zero_dimension_value();
200
201 angle.unit = Ident {
202 span: DUMMY_SP,
203 value: "deg".into(),
204 raw: None,
205 };
206
207 dimension
208 },
209 Dimension::Time(time) => {
210 if time.value.value != 0.0 {
211 return dimension;
212 }
213
214 time.value = get_zero_dimension_value();
215
216 time.unit = Ident {
217 span: DUMMY_SP,
218 value: "s".into(),
219 raw: None,
220 };
221
222 dimension
223 },
224 Dimension::Frequency(frequency) => {
225 if frequency.value.value != 0.0 {
226 return dimension;
227 }
228
229 frequency.value = get_zero_dimension_value();
230 frequency.unit = get_zero_dimension_unit(&frequency.unit);
231
232 dimension
233 },
234 Dimension::Resolution(resolution) => {
235 if resolution.value.value != 0.0 {
236 return dimension;
237 }
238
239 resolution.value = get_zero_dimension_value();
240 resolution.unit = get_zero_dimension_unit(&resolution.unit);
241
242 dimension
243 },
244 Dimension::Flex(flex) => {
245 if flex.value.value != 0.0 {
246 return dimension;
247 }
248
249 flex.value = get_zero_dimension_value();
250 flex.unit = get_zero_dimension_unit(&flex.unit);
251
252 dimension
253 },
254 Dimension::UnknownDimension(unknown) => {
255 if unknown.value.value != 0.0 {
256 return dimension;
257 }
258
259 unknown.value = get_zero_dimension_value();
260 unknown.unit = get_zero_dimension_unit(&unknown.unit);
261
262 dimension
263 },
264 }
265}
266
267fn get_zero_dimension_value() -> Number {
268 Number {
269 value: 0.0,
270 raw: None,
271 span: DUMMY_SP,
272 }
273}
274
275fn get_zero_dimension_unit(unit: &Ident) -> Ident {
276 if unit.value.eq("fr") {
277 return Ident {
278 value: "fr".into(),
279 raw: None,
280 span: DUMMY_SP,
281 };
282 }
283
284 if unit.value.eq("%") {
285 return Ident {
286 value: "%".into(),
287 raw: None,
288 span: DUMMY_SP,
289 };
290 }
291
292 Ident {
293 value: "".into(),
294 raw: None,
295 span: DUMMY_SP,
296 }
297}