Skip to main content

stylex_transform/shared/utils/ast/
convertors.rs

1// Re-export convertor functions from stylex-ast (canonical source)
2#[allow(unused_imports)]
3pub use stylex_ast::ast::convertors::{
4  convert_atom_to_str_ref, convert_atom_to_string, convert_concat_to_tpl_expr,
5  convert_lit_to_number, convert_lit_to_string, convert_simple_tpl_to_str_expr,
6  convert_str_lit_to_atom, convert_str_lit_to_string, convert_string_to_prop_name,
7  convert_tpl_to_string_lit, convert_wtf8_to_atom, create_big_int_expr, create_bool_expr,
8  create_ident_expr, create_null_expr, create_number_expr, create_string_expr,
9  expand_shorthand_prop, extract_str_lit_ref, extract_tpl_cooked_value,
10};
11
12use anyhow::anyhow;
13// Import error handling macros from shared utilities
14use stylex_macros::{
15  as_expr_or_err, as_expr_or_opt_err, as_expr_or_panic, convert_expr_to_str_or_err, stylex_panic,
16  stylex_unimplemented, unwrap_or_panic,
17};
18use swc_core::ecma::ast::{
19  BinExpr, BinaryOp, Expr, Ident, KeyValueProp, Lit, PropName, Tpl, UnaryExpr, UnaryOp,
20};
21use swc_core::ecma::utils::ExprExt;
22
23use crate::shared::enums::data_structures::evaluate_result_value::EvaluateResultValue;
24use crate::shared::structures::functions::FunctionMap;
25use crate::shared::structures::state::EvaluationState;
26use crate::shared::structures::state_manager::StateManager;
27use crate::shared::utils::common::{
28  evaluate_bin_expr, get_expr_from_var_decl, get_var_decl_by_ident, wrap_key_in_quotes,
29};
30use crate::shared::utils::js::evaluate::{deopt, evaluate_cached};
31use stylex_constants::constants::messages::{
32  ILLEGAL_PROP_VALUE, VAR_DECL_INIT_REQUIRED, non_static_value,
33};
34use stylex_enums::misc::{BinaryExprType, VarDeclAction};
35use stylex_utils::swc::get_default_expr_ctx;
36
37pub fn expr_to_num(
38  expr_num: &Expr,
39  state: &mut EvaluationState,
40  traversal_state: &mut StateManager,
41  fns: &FunctionMap,
42) -> Result<f64, anyhow::Error> {
43  let result = match &expr_num {
44    Expr::Ident(ident) => ident_to_number(ident, state, traversal_state, &FunctionMap::default()),
45    Expr::Lit(lit) => return convert_lit_to_number(lit),
46    Expr::Unary(unary) => convert_unary_to_num(unary, state, traversal_state, fns),
47    Expr::Bin(lit) => {
48      let mut state = Box::new(EvaluationState::new());
49
50      match binary_expr_to_num(lit, &mut state, traversal_state, fns)
51        .unwrap_or_else(|error| stylex_panic!("{}", error))
52      {
53        BinaryExprType::Number(number) => number,
54        _ => stylex_panic!(
55          "Binary expression is not a number: {:?}",
56          expr_num.get_type(get_default_expr_ctx())
57        ),
58      }
59    },
60    _ => stylex_panic!(
61      "Expression in not a number: {:?}",
62      expr_num.get_type(get_default_expr_ctx())
63    ),
64  };
65
66  Result::Ok(result)
67}
68
69fn ident_to_string(ident: &Ident, state: &mut StateManager, functions: &FunctionMap) -> String {
70  let var_decl = get_var_decl_by_ident(ident, state, functions, VarDeclAction::Reduce);
71
72  match &var_decl {
73    Some(var_decl) => {
74      let var_decl_expr = get_expr_from_var_decl(var_decl);
75
76      match &var_decl_expr {
77        Expr::Lit(lit) => match convert_lit_to_string(lit) {
78          Some(s) => s,
79          None => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
80        },
81        Expr::Ident(ident) => ident_to_string(ident, state, functions),
82        _ => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
83      }
84    },
85    None => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
86  }
87}
88
89#[inline]
90pub fn convert_ident_to_expr(
91  ident: &Ident,
92  state: &mut StateManager,
93  functions: &FunctionMap,
94) -> Expr {
95  match get_var_decl_by_ident(ident, state, functions, VarDeclAction::Reduce) {
96    Some(var_decl) => get_expr_from_var_decl(&var_decl).clone(),
97    _ => {
98      stylex_panic!("{}", ILLEGAL_PROP_VALUE)
99    },
100  }
101}
102
103pub fn convert_expr_to_str(
104  expr_string: &Expr,
105  state: &mut StateManager,
106  functions: &FunctionMap,
107) -> Option<String> {
108  match &expr_string {
109    Expr::Ident(ident) => Some(ident_to_string(ident, state, functions)),
110    Expr::Lit(lit) => convert_lit_to_string(lit),
111    _ => stylex_panic!(
112      "Expression in not a string, got {:?}",
113      expr_string.get_type(get_default_expr_ctx())
114    ),
115  }
116}
117
118pub fn convert_unary_to_num(
119  unary_expr: &UnaryExpr,
120  state: &mut EvaluationState,
121  traversal_state: &mut StateManager,
122  fns: &FunctionMap,
123) -> f64 {
124  let arg = unary_expr.arg.as_ref();
125  let op = unary_expr.op;
126
127  match &op {
128    UnaryOp::Minus => match expr_to_num(arg, state, traversal_state, fns) {
129      Ok(result) => -result,
130      Err(error) => stylex_panic!("{}", error),
131    },
132    UnaryOp::Plus => match expr_to_num(arg, state, traversal_state, fns) {
133      Ok(result) => result,
134      Err(error) => stylex_panic!("{}", error),
135    },
136    _ => stylex_panic!(
137      "Union operation '{:?}' is invalid",
138      Expr::from(unary_expr.clone()).get_type(get_default_expr_ctx())
139    ),
140  }
141}
142
143pub fn binary_expr_to_num(
144  binary_expr: &BinExpr,
145  state: &mut EvaluationState,
146  traversal_state: &mut StateManager,
147  fns: &FunctionMap,
148) -> Result<BinaryExprType, anyhow::Error> {
149  let op = binary_expr.op;
150  let Some(left) = evaluate_cached(&binary_expr.left, state, traversal_state, fns) else {
151    if !state.confident {
152      return Result::Err(anyhow::anyhow!("Left expression is not a number"));
153    }
154
155    stylex_panic!("Left expression is not a number")
156  };
157
158  let left_expr = as_expr_or_err!(left, "Left argument not expression");
159  let left_num = expr_to_num(left_expr, state, traversal_state, fns)?;
160
161  let Some(right) = evaluate_cached(&binary_expr.right, state, traversal_state, fns) else {
162    if !state.confident {
163      if op == BinaryOp::LogicalOr && left_num != 0.0 {
164        state.confident = true;
165
166        return Result::Ok(BinaryExprType::Number(left_num));
167      }
168
169      return Result::Err(anyhow::anyhow!("Right expression is not a number"));
170    }
171
172    stylex_panic!("Right expression is not a number")
173  };
174
175  let right_expr = as_expr_or_err!(right, "Right argument not expression");
176  let right_num = expr_to_num(right_expr, state, traversal_state, fns)?;
177
178  let result = match &op {
179    BinaryOp::Add => {
180      if let Some(value) =
181        evaluate_left_and_right_expression(state, traversal_state, fns, &left, &right)
182      {
183        return value;
184      }
185
186      left_num + right_num
187    },
188    BinaryOp::Sub => left_num - right_num,
189    BinaryOp::Mul => left_num * right_num,
190    BinaryOp::Div => left_num / right_num,
191    BinaryOp::Mod => left_num % right_num,
192    BinaryOp::Exp => left_num.powf(right_num),
193    BinaryOp::RShift => ((left_num as i32) >> right_num as i32) as f64,
194    BinaryOp::LShift => ((left_num as i32) << right_num as i32) as f64,
195    BinaryOp::BitAnd => ((left_num as i32) & right_num as i32) as f64,
196    BinaryOp::BitOr => ((left_num as i32) | right_num as i32) as f64,
197    BinaryOp::BitXor => ((left_num as i32) ^ right_num as i32) as f64,
198    BinaryOp::In => {
199      if right_num == 0.0 {
200        1.0
201      } else {
202        0.0
203      }
204    },
205    BinaryOp::InstanceOf => {
206      if right_num == 0.0 {
207        1.0
208      } else {
209        0.0
210      }
211    },
212    BinaryOp::EqEq => {
213      if left_num == right_num {
214        1.0
215      } else {
216        0.0
217      }
218    },
219    BinaryOp::NotEq => {
220      if left_num != right_num {
221        1.0
222      } else {
223        0.0
224      }
225    },
226    BinaryOp::EqEqEq => {
227      if left_num == right_num {
228        1.0
229      } else {
230        0.0
231      }
232    },
233    BinaryOp::NotEqEq => {
234      if left_num != right_num {
235        1.0
236      } else {
237        0.0
238      }
239    },
240    BinaryOp::Lt => {
241      if left_num < right_num {
242        1.0
243      } else {
244        0.0
245      }
246    },
247    BinaryOp::LtEq => {
248      if left_num <= right_num {
249        1.0
250      } else {
251        0.0
252      }
253    },
254    BinaryOp::Gt => {
255      if left_num > right_num {
256        1.0
257      } else {
258        0.0
259      }
260    },
261    BinaryOp::GtEq => {
262      if left_num >= right_num {
263        1.0
264      } else {
265        0.0
266      }
267    },
268    // #region Logical
269    BinaryOp::LogicalOr => {
270      if let Some(value) =
271        evaluate_left_and_right_expression(state, traversal_state, fns, &left, &right)
272      {
273        return value;
274      }
275
276      if left_num != 0.0 { left_num } else { right_num }
277    },
278    BinaryOp::LogicalAnd => {
279      if let Some(value) =
280        evaluate_left_and_right_expression(state, traversal_state, fns, &left, &right)
281      {
282        return value;
283      }
284
285      if left_num != 0.0 { right_num } else { left_num }
286    },
287    BinaryOp::NullishCoalescing => {
288      if let Some(value) =
289        evaluate_left_and_right_expression(state, traversal_state, fns, &left, &right)
290      {
291        return value;
292      }
293
294      if left_num == 0.0 { right_num } else { left_num }
295    },
296    // #endregion Logical
297    BinaryOp::ZeroFillRShift => ((left_num as i32) >> right_num as i32) as f64,
298  };
299
300  Result::Ok(BinaryExprType::Number(result))
301}
302
303pub fn binary_expr_to_string(
304  binary_expr: &BinExpr,
305  state: &mut EvaluationState,
306  traversal_state: &mut StateManager,
307  fns: &FunctionMap,
308) -> Result<BinaryExprType, anyhow::Error> {
309  let op = binary_expr.op;
310  let Some(left) = evaluate_cached(&binary_expr.left, state, traversal_state, fns) else {
311    if !state.confident {
312      return Result::Err(anyhow::anyhow!("Left expression is not a string"));
313    }
314
315    stylex_panic!("Left expression is not a string")
316  };
317
318  let left_expr = as_expr_or_err!(left, "Left argument not expression");
319  let left_str = convert_expr_to_str_or_err!(
320    left_expr,
321    traversal_state,
322    fns,
323    "Left expression is not a string"
324  );
325
326  let Some(right) = evaluate_cached(&binary_expr.right, state, traversal_state, fns) else {
327    if !state.confident {
328      if op == BinaryOp::LogicalOr {
329        state.confident = true;
330
331        return Result::Ok(BinaryExprType::String(left_str));
332      }
333
334      return Result::Err(anyhow::anyhow!("Right expression is not a string"));
335    }
336
337    stylex_panic!("Right expression is not a string")
338  };
339
340  let right_expr = as_expr_or_err!(right, "Right argument not expression");
341  let right_str = convert_expr_to_str_or_err!(
342    right_expr,
343    traversal_state,
344    fns,
345    "Right expression is not a string"
346  );
347
348  let result = match &op {
349    BinaryOp::Add => {
350      format!("{}{}", left_str, right_str)
351    },
352    _ => stylex_panic!(
353      "For string expressions, only addition is supported, got {:?}",
354      op
355    ),
356  };
357
358  Result::Ok(BinaryExprType::String(result))
359}
360
361fn evaluate_left_and_right_expression(
362  state: &mut EvaluationState,
363  traversal_state: &mut StateManager,
364  fns: &FunctionMap,
365  left: &EvaluateResultValue,
366  right: &EvaluateResultValue,
367) -> Option<Result<BinaryExprType, anyhow::Error>> {
368  let left_expr = as_expr_or_opt_err!(left, "Left argument not expression");
369  let right_expr = as_expr_or_opt_err!(right, "Right argument not expression");
370
371  let mut state_for_left = EvaluationState {
372    confident: true,
373    deopt_path: None,
374    ..state.clone()
375  };
376  let left_result = expr_to_num(left_expr, &mut state_for_left, traversal_state, fns);
377  let left_confident = state.confident;
378
379  let mut state_for_right = EvaluationState {
380    confident: true,
381    deopt_path: None,
382    ..state.clone()
383  };
384  let right_result = expr_to_num(right_expr, &mut state_for_right, traversal_state, fns);
385  let right_confident = state.confident;
386
387  if left_result.is_err() || right_result.is_err() {
388    let left_str = match left_expr {
389      Expr::Lit(Lit::Str(_)) => match left_expr.as_lit() {
390        Some(lit) => convert_lit_to_string(lit).unwrap_or_else(|| {
391          stylex_panic!(
392            "Left is not a string: {:?}",
393            left_expr.get_type(get_default_expr_ctx())
394          )
395        }),
396        None => stylex_panic!(
397          "Left is not a string: {:?}",
398          left_expr.get_type(get_default_expr_ctx())
399        ),
400      },
401      _ => String::default(),
402    };
403
404    let right_str = match right_expr {
405      Expr::Lit(Lit::Str(_)) => match right_expr.as_lit() {
406        Some(lit) => convert_lit_to_string(lit).unwrap_or_else(|| {
407          stylex_panic!(
408            "Right is not a string: {:?}",
409            left_expr.get_type(get_default_expr_ctx())
410          )
411        }),
412        None => stylex_panic!(
413          "Right is not a string: {:?}",
414          left_expr.get_type(get_default_expr_ctx())
415        ),
416      },
417      _ => String::default(),
418    };
419
420    if !left_str.is_empty() && !right_str.is_empty() {
421      return Some(Result::Ok(BinaryExprType::String(format!(
422        "{}{}",
423        left_str, right_str
424      ))));
425    }
426  }
427
428  if !left_confident {
429    let deopt_reason = state_for_left
430      .deopt_reason
431      .as_deref()
432      .unwrap_or("unknown error")
433      .to_string();
434
435    deopt(left_expr, state, &deopt_reason);
436
437    return Some(Result::Ok(BinaryExprType::Null));
438  }
439
440  if !right_confident {
441    let deopt_reason = state_for_right
442      .deopt_reason
443      .as_deref()
444      .unwrap_or("unknown error")
445      .to_string();
446
447    deopt(right_expr, state, &deopt_reason);
448
449    return Some(Result::Ok(BinaryExprType::Null));
450  }
451
452  None
453}
454
455pub fn ident_to_number(
456  ident: &Ident,
457  state: &mut EvaluationState,
458  traversal_state: &mut StateManager,
459  fns: &FunctionMap,
460) -> f64 {
461  let var_decl = get_var_decl_by_ident(ident, traversal_state, fns, VarDeclAction::Reduce);
462
463  match &var_decl {
464    Some(var_decl) => {
465      let var_decl_expr = get_expr_from_var_decl(var_decl);
466
467      match &var_decl_expr {
468        Expr::Bin(bin_expr) => {
469          match binary_expr_to_num(bin_expr, state, traversal_state, fns)
470            .unwrap_or_else(|error| stylex_panic!("{}", error))
471          {
472            BinaryExprType::Number(number) => number,
473            _ => stylex_panic!(
474              "Binary expression is not a number: {:?}",
475              var_decl_expr.get_type(get_default_expr_ctx())
476            ),
477          }
478        },
479        Expr::Unary(unary_expr) => convert_unary_to_num(unary_expr, state, traversal_state, fns),
480        Expr::Lit(lit) => {
481          convert_lit_to_number(lit).unwrap_or_else(|error| stylex_panic!("{}", error))
482        },
483        _ => stylex_panic!(
484          "Varable {:?} is not a number",
485          var_decl_expr.get_type(get_default_expr_ctx())
486        ),
487      }
488    },
489    None => {
490      stylex_panic!("Variable {} is not declared", ident.sym)
491    },
492  }
493}
494
495pub fn handle_tpl_to_expression(
496  tpl: &Tpl,
497  state: &mut StateManager,
498  functions: &FunctionMap,
499) -> Expr {
500  // Clone the template, so we can work on it
501  let mut tpl = tpl.clone();
502
503  // Loop through each expression in the template
504  for expr in tpl.exprs.iter_mut() {
505    // Check if the expression is an identifier
506    if let Expr::Ident(ident) = expr.as_ref() {
507      // Find the variable declaration for this identifier in the AST
508      let var_decl = get_var_decl_by_ident(ident, state, functions, VarDeclAction::Reduce);
509
510      // If a variable declaration was found
511      if let Some(var_decl) = &var_decl {
512        // Swap the placeholder expression in the template with the variable declaration's initializer
513        *expr = match var_decl.init.clone() {
514          Some(init) => init,
515          None => stylex_panic!("{}", VAR_DECL_INIT_REQUIRED),
516        };
517      }
518    };
519  }
520
521  Expr::Tpl(tpl)
522}
523pub fn expr_tpl_to_string(
524  tpl: &Tpl,
525  state: &mut EvaluationState,
526  traversal_state: &mut StateManager,
527  fns: &FunctionMap,
528) -> String {
529  let mut tpl_str: String = String::new();
530
531  for (i, quasi) in tpl.quasis.iter().enumerate() {
532    tpl_str.push_str(quasi.raw.as_ref());
533
534    if i < tpl.exprs.len() {
535      match &tpl.exprs[i].as_ref() {
536        Expr::Ident(ident) => {
537          let ident = get_var_decl_by_ident(ident, traversal_state, fns, VarDeclAction::Reduce);
538
539          match ident {
540            Some(var_decl) => {
541              let var_decl_expr = get_expr_from_var_decl(&var_decl);
542
543              let value = match &var_decl_expr {
544                Expr::Lit(lit) => match convert_lit_to_string(lit) {
545                  Some(s) => s,
546                  None => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
547                },
548                _ => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
549              };
550
551              tpl_str.push_str(value.as_str());
552            },
553            None => stylex_panic!("{}", non_static_value("expr_tpl_to_string")),
554          }
555        },
556        Expr::Bin(bin) => tpl_str.push_str(
557          transform_bin_expr_to_number(bin, state, traversal_state, fns)
558            .to_string()
559            .as_str(),
560        ),
561        Expr::Lit(lit) => tpl_str.push_str(&match convert_lit_to_string(lit) {
562          Some(s) => s,
563          None => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
564        }),
565        _ => stylex_unimplemented!(
566          "TPL expression: {:?}",
567          tpl.exprs[i].get_type(get_default_expr_ctx())
568        ),
569      }
570    }
571  }
572
573  tpl_str
574}
575
576pub fn transform_bin_expr_to_number(
577  bin: &BinExpr,
578  state: &mut EvaluationState,
579  traversal_state: &mut StateManager,
580  fns: &FunctionMap,
581) -> f64 {
582  let op = bin.op;
583  let Some(left) = evaluate_cached(&bin.left, state, traversal_state, fns) else {
584    stylex_panic!(
585      "Left expression is not a number: {:?}",
586      bin.left.get_type(get_default_expr_ctx())
587    )
588  };
589
590  let Some(right) = evaluate_cached(&bin.right, state, traversal_state, fns) else {
591    stylex_panic!(
592      "Left expression is not a number: {:?}",
593      bin.right.get_type(get_default_expr_ctx())
594    )
595  };
596
597  let left_expr = as_expr_or_panic!(left, "Left argument not expression");
598  let right_expr = as_expr_or_panic!(right, "Right argument not expression");
599
600  let left = unwrap_or_panic!(expr_to_num(left_expr, state, traversal_state, fns));
601  let right = unwrap_or_panic!(expr_to_num(right_expr, state, traversal_state, fns));
602
603  evaluate_bin_expr(op, left, right)
604}
605
606#[inline]
607pub fn convert_expr_to_bool(
608  expr: &Expr,
609  state: &mut StateManager,
610  functions: &FunctionMap,
611) -> bool {
612  match expr {
613    Expr::Lit(lit) => match lit {
614      Lit::Bool(b) => b.value,
615      Lit::Num(n) => n.value != 0.0,
616      Lit::Str(s) => !s.value.is_empty(),
617      Lit::Null(_) => false,
618      _ => stylex_unimplemented!(
619        "Conversion {:?} expression to boolean",
620        expr.get_type(get_default_expr_ctx())
621      ),
622    },
623    Expr::Ident(ident) => convert_expr_to_bool(
624      &convert_ident_to_expr(ident, state, functions),
625      state,
626      functions,
627    ),
628    Expr::Array(_) => true,
629    Expr::Object(_) => true,
630    Expr::Fn(_) | Expr::Class(_) => true,
631    Expr::Unary(unary) => match unary.op {
632      UnaryOp::Void => false,
633      UnaryOp::TypeOf => true,
634      UnaryOp::Bang => !convert_expr_to_bool(&unary.arg, state, functions),
635      UnaryOp::Minus => !convert_expr_to_bool(&unary.arg, state, functions),
636      UnaryOp::Plus => !convert_expr_to_bool(&unary.arg, state, functions),
637      UnaryOp::Tilde => !convert_expr_to_bool(&unary.arg, state, functions),
638      _ => stylex_unimplemented!(
639        "Conversion {:?} expression to boolean",
640        expr.get_type(get_default_expr_ctx())
641      ),
642    },
643    _ => {
644      stylex_unimplemented!(
645        "Conversion {:?} expression to boolean",
646        expr.get_type(get_default_expr_ctx())
647      )
648    },
649  }
650}
651
652#[inline]
653pub(crate) fn convert_key_value_to_str(key_value: &KeyValueProp) -> String {
654  let key = &key_value.key;
655  let mut should_wrap_in_quotes = false;
656
657  let key = match key {
658    PropName::Ident(ident) => ident.sym.to_string(),
659    PropName::Str(strng) => {
660      should_wrap_in_quotes = false;
661      convert_str_lit_to_string(strng)
662    },
663    PropName::Num(num) => {
664      should_wrap_in_quotes = false;
665      num.value.to_string()
666    },
667    PropName::BigInt(big_int) => {
668      should_wrap_in_quotes = false;
669      big_int.value.to_string()
670    },
671    PropName::Computed(computed) => match computed.expr.as_lit() {
672      Some(lit) => match convert_lit_to_string(lit) {
673        Some(s) => s,
674        None => stylex_panic!("Computed property key must be a string or number literal."),
675      },
676      None => stylex_unimplemented!("Computed key is not a literal"),
677    },
678  };
679
680  wrap_key_in_quotes(&key, should_wrap_in_quotes)
681}