stylex_transform/shared/utils/core/
stylex_merge.rs1use rustc_hash::FxHashMap;
2use stylex_macros::{stylex_panic, stylex_unreachable};
3use swc_core::ecma::{
4 ast::{
5 BinExpr, BinaryOp, CallExpr, CondExpr, Expr, ExprOrSpread, Ident, JSXAttrOrSpread,
6 JSXAttrValue, Lit, MemberExpr, Prop, PropName, PropOrSpread,
7 },
8 utils::{ExprExt, drop_span},
9 visit::FoldWith,
10};
11
12use crate::shared::enums::data_structures::fn_result::FnResult;
13use crate::shared::structures::functions::{FunctionConfigType, FunctionMap};
14use crate::shared::structures::member_transform::MemberTransform;
15use crate::shared::structures::state_manager::StateManager;
16use crate::shared::structures::types::{FunctionMapIdentifiers, FunctionMapMemberExpression};
17use crate::shared::transformers::stylex_default_maker;
18use crate::shared::utils::ast::convertors::{convert_key_value_to_str, convert_lit_to_string};
19use crate::shared::utils::common::{reduce_ident_count, reduce_member_expression_count};
20use crate::shared::utils::core::make_string_expression::make_string_expression;
21use crate::shared::utils::core::parse_nullable_style::{
22 ResolvedArg, StyleObject, parse_nullable_style,
23};
24use stylex_ast::ast::factories::{create_jsx_attr, create_jsx_attr_or_spread};
25use stylex_constants::constants::messages::{EXPECTED_COMPILED_STYLES, MEMBER_OBJ_NOT_IDENT};
26use stylex_enums::style_vars_to_keep::NonNullProps;
27use stylex_utils::swc::get_default_expr_ctx;
28
29pub(crate) fn stylex_merge(
30 call: &mut CallExpr,
31 transform: fn(&[ResolvedArg]) -> Option<FnResult>,
32 state: &mut StateManager,
33) -> Option<Expr> {
34 let mut bail_out = false;
35 let mut conditional = 0;
36 let mut current_index = -1;
37 let mut bail_out_index = None;
38 let mut resolved_args = vec![];
39
40 let mut identifiers: FunctionMapIdentifiers = FxHashMap::default();
41 let mut member_expressions: FunctionMapMemberExpression = FxHashMap::default();
42
43 for name in &state.stylex_default_marker_import {
44 let values = match stylex_default_maker::stylex_default_marker(&state.options).as_values() {
45 Some(v) => v.clone(),
46 None => stylex_panic!("{}", EXPECTED_COMPILED_STYLES),
47 };
48 identifiers.insert(name.clone(), Box::new(FunctionConfigType::IndexMap(values)));
49 }
50
51 for name in &state.stylex_import {
52 member_expressions.entry(name.clone()).or_default();
53
54 let member_expression = match member_expressions.get_mut(name) {
55 Some(m) => m,
56 None => stylex_panic!("Could not resolve the member expression for the import."),
57 };
58
59 let values = match stylex_default_maker::stylex_default_marker(&state.options).as_values() {
60 Some(v) => v.clone(),
61 None => stylex_panic!("{}", EXPECTED_COMPILED_STYLES),
62 };
63 member_expression.insert(
64 "defaultMarker".into(),
65 Box::new(FunctionConfigType::IndexMap(values)),
66 );
67 }
68
69 state.apply_stylex_env(&mut identifiers, &mut member_expressions);
70
71 let evaluate_path_fn_config = FunctionMap {
72 identifiers,
73 member_expressions,
74 disable_imports: true,
75 };
76
77 let args_path = call
78 .args
79 .iter()
80 .flat_map(|arg| match arg.expr.as_ref() {
81 Expr::Array(arr) => arr.elems.clone(),
82 _ => vec![Some(arg.clone())],
83 })
84 .flatten()
85 .collect::<Vec<ExprOrSpread>>();
86
87 for arg_path in args_path.iter() {
88 current_index += 1;
89
90 let arg = arg_path.expr.as_ref();
91
92 let resolved = if arg.is_object() || arg.is_ident() || arg.is_member() {
93 let resolved = parse_nullable_style(arg, state, &evaluate_path_fn_config, false);
94
95 if let StyleObject::Other = resolved {
96 bail_out_index = Some(current_index);
97 bail_out = true;
98 }
99
100 resolved
101 } else {
102 StyleObject::Unreachable
103 };
104
105 match &arg {
106 Expr::Object(_) => {
107 resolved_args.push(ResolvedArg::style_object(resolved));
108 },
109 Expr::Ident(ident) => {
110 resolved_args.push(ResolvedArg::style_object_with_ident(
111 resolved,
112 vec![ident.clone()],
113 ));
114 },
115 Expr::Member(member) => {
116 match resolved {
117 StyleObject::Other => {
118 },
120 StyleObject::Style(_) | StyleObject::Nullable => {
121 let ident = match member.obj.as_ident() {
122 Some(i) => i.clone(),
123 None => stylex_panic!("{}", MEMBER_OBJ_NOT_IDENT),
124 };
125
126 resolved_args.push(ResolvedArg::style_object_full(
127 resolved,
128 vec![ident],
129 vec![member.clone()],
130 ));
131 },
132 StyleObject::Unreachable => {
133 stylex_unreachable!("StyleObject::Unreachable");
134 },
135 }
136 },
137 Expr::Cond(CondExpr {
138 test,
139 cons: consequent,
140 alt: alternate,
141 ..
142 }) => {
143 let primary = parse_nullable_style(consequent, state, &evaluate_path_fn_config, true);
144 let fallback = parse_nullable_style(alternate, state, &evaluate_path_fn_config, true);
145
146 if primary.eq(&StyleObject::Other) || fallback.eq(&StyleObject::Other) {
147 bail_out_index = Some(current_index);
148 bail_out = true;
149 } else {
150 let idents = get_conditional_expr_idents(alternate.as_ref())?;
151 let members = get_conditional_expr_members(alternate.as_ref())?;
152
153 resolved_args.push(ResolvedArg::conditional(
154 *test.clone(),
155 Some(primary),
156 Some(fallback),
157 idents,
158 members,
159 ));
160
161 conditional += 1;
162 }
163 },
164 Expr::Bin(BinExpr {
165 left: left_path,
166 op,
167 right: right_path,
168 ..
169 }) => {
170 if !op.eq(&BinaryOp::LogicalAnd) {
171 bail_out_index = Some(current_index);
172 bail_out = true;
173 break;
174 }
175
176 let left_resolved = parse_nullable_style(left_path, state, &evaluate_path_fn_config, true);
177 let right_resolved =
178 parse_nullable_style(right_path, state, &evaluate_path_fn_config, true);
179
180 if !left_resolved.eq(&StyleObject::Other) || right_resolved.eq(&StyleObject::Other) {
181 bail_out_index = Some(current_index);
182 bail_out = true;
183 } else {
184 let ident = match right_path.as_ref() {
185 Expr::Ident(ident) => ident,
186 Expr::Member(member) => match member.obj.as_ident() {
187 Some(i) => i,
188 None => stylex_panic!("{}", MEMBER_OBJ_NOT_IDENT),
189 },
190 _ => stylex_panic!(
191 "Illegal argument: {:?}",
192 right_path.get_type(get_default_expr_ctx())
193 ),
194 };
195
196 let member = match right_path.as_ref() {
197 Expr::Member(member) => member,
198 _ => stylex_panic!(
199 "Illegal argument: {:?}",
200 right_path.get_type(get_default_expr_ctx())
201 ),
202 };
203
204 resolved_args.push(ResolvedArg::conditional(
205 *left_path.clone(),
206 Some(right_resolved),
207 None,
208 vec![ident.clone()],
209 vec![member.clone()],
210 ));
211
212 conditional += 1;
213 }
214 },
215 _ => {
216 bail_out_index = Some(current_index);
217 bail_out = true;
218 },
219 }
220
221 if conditional > 4 {
222 bail_out = true;
223 }
224
225 if bail_out {
226 break;
227 }
228 }
229
230 if !state.enable_inlined_conditional_merge() && conditional > 0 {
231 bail_out = true;
232 }
233
234 if bail_out {
235 let mut non_null_props: NonNullProps = NonNullProps::Vec(vec![]);
236 let mut index = -1;
237
238 for arg_path in call.args.iter_mut() {
239 index += 1;
240
241 let mut member_transform = MemberTransform {
242 index,
243 bail_out_index,
244 non_null_props: non_null_props.clone(),
245 state: state.clone(),
246 parents: vec![],
247 functions: evaluate_path_fn_config.clone(),
248 };
249
250 let transformed_expr = arg_path.expr.clone().fold_with(&mut member_transform);
251
252 arg_path.expr = transformed_expr;
253
254 index = member_transform.index;
255 bail_out_index = member_transform.bail_out_index;
256 non_null_props = member_transform.non_null_props;
257
258 *state = member_transform.state;
259 }
260
261 for arg in args_path.iter() {
262 if let Expr::Member(member_expression) = arg.expr.as_ref() {
263 reduce_member_expression_count(state, member_expression)
264 }
265 }
266 } else {
267 let string_expression = make_string_expression(&resolved_args, transform);
268
269 for arg in &resolved_args {
270 match arg {
271 ResolvedArg::StyleObject(_, idents, member_expr) => {
272 for ident in idents {
273 reduce_ident_count(state, ident);
274 }
275
276 for member_expr in member_expr {
277 reduce_member_expression_count(state, member_expr);
278 }
279 },
280 ResolvedArg::ConditionalStyle(_, _, _, idents, member_expr) => {
281 for ident in idents {
282 reduce_ident_count(state, ident);
283 }
284 for member_expr in member_expr {
285 reduce_member_expression_count(state, member_expr);
286 }
287 },
288 }
289 }
290
291 if let Some(Expr::Object(string_expression)) = string_expression.as_ref() {
292 let attr_expr = drop_span(Expr::Call(call.clone()));
293
294 if state.jsx_spread_attr_exprs_map.contains_key(&attr_expr)
295 && !string_expression.props.is_empty()
296 && string_expression.props.iter().all(|prop| {
297 matches!(prop, PropOrSpread::Prop(prop)
298 if matches!(prop.as_ref(), Prop::KeyValue(kv)
299 if !matches!(kv.key, PropName::Computed(_))))
300 })
301 {
302 let jsx_attr_expressions: Vec<JSXAttrOrSpread> = string_expression
305 .props
306 .iter()
307 .filter_map(|prop| {
308 if let PropOrSpread::Prop(prop) = prop {
309 if let Prop::KeyValue(key_value) = prop.as_ref() {
310 let attr_name = convert_key_value_to_str(key_value);
312 let attr_value = key_value.value.as_lit().map(|lit| {
313 let s = match convert_lit_to_string(&lit.clone()) {
314 Some(s) => s,
315 None => stylex_panic!("Expected a string class name in compiled styles."),
316 };
317 JSXAttrValue::Str(s.into())
318 });
319
320 attr_value.map(|attr_value| {
321 create_jsx_attr_or_spread(create_jsx_attr(attr_name.as_str(), attr_value.clone()))
322 })
323 } else {
324 None
325 }
326 } else {
327 None
328 }
329 })
330 .collect();
331
332 if !jsx_attr_expressions.is_empty() {
334 state
335 .jsx_spread_attr_exprs_map
336 .insert(attr_expr, jsx_attr_expressions);
337
338 return None; }
340 }
341 }
342
343 return string_expression;
344 }
345
346 None
347}
348
349fn get_conditional_expr_idents(alternate: &Expr) -> Option<Vec<Ident>> {
350 match alternate {
351 Expr::Ident(ident) => {
352 if ident.sym == "undefined" {
353 return None;
354 }
355
356 Some(vec![ident.clone()])
357 },
358 Expr::Member(member) => Some(vec![match member.obj.as_ident() {
359 Some(i) => i.clone(),
360 None => stylex_panic!("{}", MEMBER_OBJ_NOT_IDENT),
361 }]),
362 Expr::Lit(Lit::Null(_) | Lit::Bool(_)) => None,
363 Expr::Array(array) => {
364 let mut idents = Vec::new();
365
366 for elem in array.elems.iter().flatten() {
367 match get_conditional_expr_idents(&elem.expr) {
368 Some(mut elem_idents) => {
369 idents.append(&mut elem_idents);
370 },
371 None => {
372 return None;
373 },
374 }
375 }
376
377 Some(idents)
378 },
379 Expr::Cond(cond_expr) => {
380 let mut idents = Vec::new();
381
382 match get_conditional_expr_idents(&cond_expr.alt) {
383 Some(mut alt_idents) => {
384 idents.append(&mut alt_idents);
385 },
386 None => {
387 return None;
388 },
389 }
390
391 Some(idents)
392 },
393 _ => {
394 stylex_panic!(
395 "Illegal argument: {:?}",
396 alternate.get_type(get_default_expr_ctx())
397 )
398 },
399 }
400}
401
402fn get_conditional_expr_members(alternate: &Expr) -> Option<Vec<MemberExpr>> {
403 match alternate {
404 Expr::Member(member) => Some(vec![member.clone()]),
405 Expr::Array(array) => {
406 let mut members = Vec::new();
407
408 for elem in array.elems.iter().flatten() {
409 if let Some(mut elem_members) = get_conditional_expr_members(&elem.expr) {
410 members.append(&mut elem_members);
411 }
412 }
413
414 Some(members)
415 },
416 Expr::Cond(cond_expr) => get_conditional_expr_members(&cond_expr.alt),
417 _ => {
418 stylex_panic!(
419 "Illegal argument in get_conditional_expr_member: {:?}",
420 alternate.get_type(get_default_expr_ctx())
421 )
422 },
423 }
424}