Skip to main content

stylex_transform/shared/utils/core/
stylex_merge.rs

1use 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            //  Already processed in the conditional block above; bail_out flag set if needed.
119          },
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        // Check if this is used as a JSX spread attribute and optimize
303        // Convert each property to JSX attributes for direct use
304        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                // Create JSX attribute directly
311                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        // Store the JSX attributes to replace the spread element
333        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; // Early return to skip normal object creation
339        }
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}