Skip to main content

stylex_transform/transform/stylex/
transform_stylex_create_call.rs

1use once_cell::sync::Lazy;
2use std::{rc::Rc, sync::Arc};
3use stylex_macros::stylex_panic;
4use stylex_path_resolver::package_json::PackageJsonExtended;
5
6use indexmap::IndexMap;
7use rustc_hash::FxHashMap;
8use swc_core::{
9  common::DUMMY_SP,
10  ecma::{
11    ast::{BinExpr, Bool, Decl, Lit, ModuleItem, ParenExpr, Stmt, UnaryOp, VarDecl, VarDeclKind},
12    utils::drop_span,
13  },
14};
15use swc_core::{
16  common::SyntaxContext,
17  ecma::ast::{ArrowExpr, BinaryOp, BlockStmtOrExpr, CondExpr, Pat, Prop, PropName},
18};
19use swc_core::{
20  common::comments::Comments,
21  ecma::ast::{CallExpr, Expr, PropOrSpread},
22};
23
24use crate::shared::structures::functions::{FunctionConfig, FunctionMap, FunctionType};
25use crate::shared::structures::pre_rule::PreRuleValue;
26use crate::shared::structures::state::EvaluationState;
27use crate::shared::structures::types::FunctionMapIdentifiers;
28use crate::shared::structures::types::InjectableStylesMap;
29use crate::shared::structures::types::{FlatCompiledStyles, FunctionMapMemberExpression};
30use crate::shared::transformers::stylex_create::stylex_create_set;
31use crate::shared::transformers::stylex_default_maker;
32use crate::shared::transformers::stylex_first_that_works::stylex_first_that_works;
33use crate::shared::transformers::stylex_keyframes::get_keyframes_fn;
34use crate::shared::transformers::stylex_position_try::get_position_try_fn;
35use crate::shared::utils::ast::convertors::create_null_expr;
36use crate::shared::utils::ast::convertors::create_string_expr;
37use crate::shared::utils::ast::convertors::{
38  convert_atom_to_string, convert_expr_to_str, convert_key_value_to_str, convert_lit_to_string,
39};
40use crate::shared::utils::common::get_key_values_from_object;
41use crate::shared::utils::common::normalize_expr;
42use crate::shared::utils::core::add_source_map_data::add_source_map_data;
43use crate::shared::utils::core::dev_class_name::{convert_to_test_styles, inject_dev_class_names};
44use crate::shared::utils::core::evaluate_stylex_create_arg::evaluate_stylex_create_arg;
45use crate::shared::utils::core::flat_map_expanded_shorthands::flat_map_expanded_shorthands;
46use crate::shared::utils::core::js_to_expr::{
47  NestedStringObject, convert_object_to_ast, remove_objects_with_spreads,
48};
49use crate::shared::utils::log::build_code_frame_error::build_code_frame_error;
50use crate::shared::utils::log::build_code_frame_error::build_code_frame_error_and_panic;
51use crate::shared::utils::validators::{is_create_call, validate_stylex_create};
52use crate::shared::{
53  structures::functions::FunctionConfigType, utils::common::downcast_style_options_to_state_manager,
54};
55use crate::{shared::structures::functions::StylexExprFn, transform::StyleXTransform};
56use stylex_ast::ast::factories::create_key_value_prop;
57use stylex_ast::ast::factories::create_object_expression;
58use stylex_ast::ast::factories::create_string_var_declarator;
59use stylex_ast::ast::factories::{
60  create_array_expression, create_expr_or_spread, create_prop_from_name, create_var_declarator,
61};
62use stylex_constants::constants::common::COMPILED_KEY;
63use stylex_constants::constants::messages::{EXPECTED_COMPILED_STYLES, non_static_value};
64use stylex_css::utils::when as stylex_when;
65use stylex_enums::counter_mode::CounterMode;
66use stylex_enums::style_resolution::StyleResolution;
67use stylex_regex::regex::VAR_EXTRACTION_REGEX;
68use stylex_structures::dynamic_style::DynamicStyle;
69use stylex_structures::order_pair::OrderPair;
70use stylex_structures::stylex_state_options::StyleXStateOptions;
71use stylex_structures::top_level_expression::TopLevelExpression;
72use stylex_structures::uid_generator::UidGenerator;
73use stylex_types::enums::data_structures::injectable_style::InjectableStyleKind;
74use stylex_types::structures::injectable_style::InjectableStyle;
75
76/// Lazily-initialized Arc-wrapped map of stylex.when helper functions.
77///
78/// Thread-safety: Arc ensures safe sharing across threads; Lazy guarantees one-time initialization.
79/// Lifecycle: Initialized on first access, immutable thereafter.
80/// Contains pure, stateless transformation functions (ancestor, descendant, etc.)
81/// that convert expressions to CSS selectors for relational styling.
82static STYLEX_WHEN_MAP: Lazy<Arc<IndexMap<String, StylexExprFn>>> = Lazy::new(|| {
83  let mut map: IndexMap<String, StylexExprFn> = IndexMap::default();
84
85  map.insert(
86    "ancestor".to_string(),
87    |expr: Expr, state: &mut dyn stylex_types::traits::StyleOptions| {
88      let state = downcast_style_options_to_state_manager(state);
89      let expr_str = match convert_expr_to_str(&expr, state, &FunctionMap::default()) {
90        Some(s) => s,
91        None => stylex_panic!("stylex.when ancestor: expression is not a string"),
92      };
93      let result = match stylex_when::ancestor(&expr_str, Some(&state.options)) {
94        Ok(v) => v,
95        Err(e) => stylex_panic!("stylex.when ancestor error: {}", e),
96      };
97      create_string_expr(&result)
98    },
99  );
100
101  map.insert(
102    "descendant".to_string(),
103    |expr: Expr, state: &mut dyn stylex_types::traits::StyleOptions| {
104      let state = downcast_style_options_to_state_manager(state);
105      let expr_str = match convert_expr_to_str(&expr, state, &FunctionMap::default()) {
106        Some(s) => s,
107        None => stylex_panic!("stylex.when descendant: expression is not a string"),
108      };
109      let result = match stylex_when::descendant(&expr_str, Some(&state.options)) {
110        Ok(v) => v,
111        Err(e) => stylex_panic!("stylex.when descendant error: {}", e),
112      };
113      create_string_expr(&result)
114    },
115  );
116
117  map.insert(
118    "siblingBefore".to_string(),
119    |expr: Expr, state: &mut dyn stylex_types::traits::StyleOptions| {
120      let state = downcast_style_options_to_state_manager(state);
121      let expr_str = match convert_expr_to_str(&expr, state, &FunctionMap::default()) {
122        Some(s) => s,
123        None => stylex_panic!("stylex.when siblingBefore: expression is not a string"),
124      };
125      let result = match stylex_when::sibling_before(&expr_str, Some(&state.options)) {
126        Ok(v) => v,
127        Err(e) => stylex_panic!("stylex.when siblingBefore error: {}", e),
128      };
129      create_string_expr(&result)
130    },
131  );
132
133  map.insert(
134    "siblingAfter".to_string(),
135    |expr: Expr, state: &mut dyn stylex_types::traits::StyleOptions| {
136      let state = downcast_style_options_to_state_manager(state);
137      let expr_str = match convert_expr_to_str(&expr, state, &FunctionMap::default()) {
138        Some(s) => s,
139        None => stylex_panic!("stylex.when siblingAfter: expression is not a string"),
140      };
141      let result = match stylex_when::sibling_after(&expr_str, Some(&state.options)) {
142        Ok(v) => v,
143        Err(e) => stylex_panic!("stylex.when siblingAfter error: {}", e),
144      };
145      create_string_expr(&result)
146    },
147  );
148
149  map.insert(
150    "anySibling".to_string(),
151    |expr: Expr, state: &mut dyn stylex_types::traits::StyleOptions| {
152      let state = downcast_style_options_to_state_manager(state);
153      let expr_str = match convert_expr_to_str(&expr, state, &FunctionMap::default()) {
154        Some(s) => s,
155        None => stylex_panic!("stylex.when anySibling: expression is not a string"),
156      };
157      let result = match stylex_when::any_sibling(&expr_str, Some(&state.options)) {
158        Ok(v) => v,
159        Err(e) => stylex_panic!("stylex.when anySibling error: {}", e),
160      };
161      create_string_expr(&result)
162    },
163  );
164
165  Arc::new(map)
166});
167
168impl<C> StyleXTransform<C>
169where
170  C: Comments,
171{
172  pub(crate) fn transform_stylex_create(&mut self, call: &CallExpr) -> Option<Expr> {
173    self.state.in_stylex_create = true;
174    let mut package_json_seen: FxHashMap<String, PackageJsonExtended> = FxHashMap::default();
175
176    let is_create_call = is_create_call(call, &self.state);
177
178    let result = if is_create_call {
179      validate_stylex_create(call, &mut self.state);
180
181      let is_program_level = self
182        .state
183        .find_top_level_expr(
184          call,
185          |tpe: &TopLevelExpression| matches!(tpe.1, Expr::Array(_)),
186          None,
187        )
188        .is_some();
189
190      let mut first_arg = call.args.first()?.expr.clone();
191
192      let mut resolved_namespaces: IndexMap<String, Box<FlatCompiledStyles>> = IndexMap::new();
193      let mut identifiers: FunctionMapIdentifiers = FxHashMap::default();
194      let mut member_expressions: FunctionMapMemberExpression = FxHashMap::default();
195
196      let first_that_works_fn = FunctionConfig {
197        fn_ptr: FunctionType::ArrayArgs(stylex_first_that_works),
198        takes_path: false,
199      };
200
201      let keyframes_fn = get_keyframes_fn();
202      let position_try_fn = get_position_try_fn();
203
204      for name in &self.state.stylex_first_that_works_import {
205        identifiers.insert(
206          name.clone(),
207          Box::new(FunctionConfigType::Regular(first_that_works_fn.clone())),
208        );
209      }
210
211      for name in &self.state.stylex_keyframes_import {
212        identifiers.insert(
213          name.clone(),
214          Box::new(FunctionConfigType::Regular(keyframes_fn.clone())),
215        );
216      }
217
218      for name in &self.state.stylex_position_try_import {
219        identifiers.insert(
220          name.clone(),
221          Box::new(FunctionConfigType::Regular(position_try_fn.clone())),
222        );
223      }
224
225      for name in &self.state.stylex_default_marker_import {
226        identifiers.insert(
227          name.clone(),
228          Box::new(FunctionConfigType::IndexMap(
229            stylex_default_maker::stylex_default_marker(&self.state.options)
230              .as_values()
231              .unwrap_or_else(|| stylex_panic!("{}", EXPECTED_COMPILED_STYLES))
232              .clone(),
233          )),
234        );
235      }
236
237      for name in &self.state.stylex_when_import {
238        identifiers.insert(
239          name.clone(),
240          Box::new(FunctionConfigType::Regular(FunctionConfig {
241            fn_ptr: FunctionType::DefaultMarker(Arc::clone(Lazy::force(&STYLEX_WHEN_MAP))),
242            takes_path: false,
243          })),
244        );
245      }
246
247      for name in &self.state.stylex_import {
248        member_expressions.entry(name.clone()).or_default();
249
250        let member_expression = match member_expressions.get_mut(name) {
251          Some(me) => me,
252          None => stylex_panic!("Could not resolve the member expression for the StyleX import."),
253        };
254
255        member_expression.insert(
256          "firstThatWorks".into(),
257          Box::new(FunctionConfigType::Regular(first_that_works_fn.clone())),
258        );
259
260        member_expression.insert(
261          "keyframes".into(),
262          Box::new(FunctionConfigType::Regular(keyframes_fn.clone())),
263        );
264
265        member_expression.insert(
266          "positionTry".into(),
267          Box::new(FunctionConfigType::Regular(position_try_fn.clone())),
268        );
269
270        member_expression.insert(
271          "defaultMarker".into(),
272          Box::new(FunctionConfigType::IndexMap(
273            stylex_default_maker::stylex_default_marker(&self.state.options)
274              .as_values()
275              .unwrap_or_else(|| stylex_panic!("{}", EXPECTED_COMPILED_STYLES))
276              .clone(),
277          )),
278        );
279
280        identifiers
281          .entry(name.get_import_str().into())
282          .and_modify(|func_type| {
283            if let Some(map) = func_type.as_map_mut() {
284              map.insert(
285                "when".into(),
286                FunctionConfig {
287                  fn_ptr: FunctionType::DefaultMarker(Arc::clone(Lazy::force(&STYLEX_WHEN_MAP))),
288                  takes_path: false,
289                },
290              );
291            }
292          })
293          .or_insert_with(|| {
294            let mut map = FxHashMap::default();
295            map.insert(
296              "when".into(),
297              FunctionConfig {
298                fn_ptr: FunctionType::DefaultMarker(Arc::clone(Lazy::force(&STYLEX_WHEN_MAP))),
299                takes_path: false,
300              },
301            );
302            Box::new(FunctionConfigType::Map(map))
303          });
304      }
305
306      self
307        .state
308        .apply_stylex_env(&mut identifiers, &mut member_expressions);
309
310      let function_map: Box<FunctionMap> = Box::new(FunctionMap {
311        identifiers,
312        member_expressions,
313        disable_imports: false,
314      });
315
316      let evaluated_arg =
317        evaluate_stylex_create_arg(&mut first_arg, &mut self.state, &function_map);
318
319      assert!(
320        evaluated_arg.confident,
321        "{}",
322        build_code_frame_error(
323          &Expr::Call(call.clone()),
324          &evaluated_arg.deopt.unwrap_or_else(|| *first_arg.to_owned()),
325          evaluated_arg
326            .reason
327            .as_deref()
328            .unwrap_or(&non_static_value("create")),
329          &mut self.state,
330        )
331      );
332
333      let value = match evaluated_arg.value {
334        Some(v) => v,
335        None => stylex_panic!("{}", non_static_value("create")),
336      };
337
338      assert!(
339        evaluated_arg.confident,
340        "{}",
341        build_code_frame_error(
342          &Expr::Call(call.clone()),
343          &evaluated_arg.deopt.unwrap_or_else(|| *first_arg.to_owned()),
344          evaluated_arg
345            .reason
346            .as_deref()
347            .unwrap_or(&non_static_value("create")),
348          &mut self.state,
349        )
350      );
351
352      let mut injected_inherit_styles: InjectableStylesMap = IndexMap::default();
353
354      if let Some(fns) = &evaluated_arg.fns {
355        let dynamic_fns_names = fns
356          .values()
357          .flat_map(|(_, map)| {
358            map.keys().map(|k| {
359              let path = map.get(k).map(|p| p.path.clone()).unwrap_or_default();
360
361              (k.clone(), path)
362            })
363          })
364          .collect::<Vec<(String, Vec<String>)>>();
365
366        for (variable_name, paths) in dynamic_fns_names {
367          // Pseudo elements can only access css vars via inheritance
368          let is_pseudo_element = paths.iter().any(|path| path.starts_with(':'));
369
370          injected_inherit_styles.insert(
371            variable_name.clone(),
372            InjectableStyle::regular(
373              format!(
374                "@property {} {{ syntax: \"*\"; inherits: {};}}",
375                variable_name,
376                if is_pseudo_element { "true" } else { "false" },
377              ),
378              Some(0f64),
379            ),
380          );
381        }
382      }
383
384      let (mut compiled_styles, injected_styles_sans_keyframes, class_paths_per_namespace) =
385        stylex_create_set(
386          &value,
387          &mut EvaluationState::new(),
388          &mut self.state,
389          &function_map,
390        );
391
392      for (namespace, properties) in compiled_styles.iter() {
393        resolved_namespaces
394          .entry(namespace.clone())
395          .or_default()
396          .extend(properties.iter().map(|(k, v)| (k.clone(), v.clone())));
397      }
398
399      let mut injected_styles = self.state.other_injected_css_rules.clone();
400
401      injected_styles.extend(injected_styles_sans_keyframes);
402
403      injected_styles.extend(injected_inherit_styles);
404
405      let (var_name, parent_var_decl) = self.get_call_var_name(call);
406
407      if self.state.is_debug() && self.state.options.enable_debug_data_prop {
408        compiled_styles = add_source_map_data(
409          &compiled_styles,
410          call,
411          &mut self.state,
412          &mut package_json_seen,
413          &function_map,
414        );
415      }
416
417      if self.state.is_dev() && self.state.options.enable_dev_class_names {
418        compiled_styles = inject_dev_class_names(&compiled_styles, &var_name, &self.state);
419      }
420
421      if self.state.is_test() {
422        compiled_styles = convert_to_test_styles(&compiled_styles, &var_name, &self.state);
423      }
424
425      if is_program_level && let Some(var_name) = var_name.as_ref() {
426        let styles_to_remember = remove_objects_with_spreads(&compiled_styles);
427
428        self
429          .state
430          .style_map
431          .insert(var_name.clone(), Rc::new(styles_to_remember));
432
433        if let Some(parent_var_decl) = parent_var_decl {
434          self
435            .state
436            .style_vars
437            .insert(var_name.clone(), drop_span(parent_var_decl.clone()));
438        } else {
439          let call_expr = Expr::Call(call.clone());
440
441          build_code_frame_error_and_panic(
442            &Expr::Paren(ParenExpr {
443              span: DUMMY_SP,
444              expr: Box::new(call_expr.clone()),
445            }),
446            &call_expr,
447            "Function type",
448            &mut self.state,
449          )
450        }
451      }
452
453      let styles_ast =
454        convert_object_to_ast(&NestedStringObject::FlatCompiledStyles(compiled_styles));
455
456      let mut result_ast =
457        path_replace_hoisted(styles_ast.clone(), is_program_level, &mut self.state);
458
459      if let Some(fns) = evaluated_arg.fns
460        && let Some(object) = result_ast.as_object()
461      {
462        let key_values = get_key_values_from_object(object);
463
464        let props: Vec<PropOrSpread> = key_values
465            .iter()
466            .map(|key_value| {
467              let orig_key = convert_key_value_to_str(key_value);
468              let mut value = key_value.value.clone();
469
470              let key = match &key_value.key {
471                PropName::Ident(ident) => Some(ident.sym.to_string()),
472                PropName::Str(strng) => Some(convert_atom_to_string(&strng.value)),
473                _ => None,
474              };
475
476              let mut prop: Option<PropOrSpread> = None;
477
478              if let Some(key) = key
479                && let Some((params, inline_styles)) = fns.get(&key) {
480                  let mut orig_class_paths = IndexMap::new();
481
482                  if let Some(namespace) = class_paths_per_namespace.get(&key) {
483                    for (class_name, class_paths) in namespace.iter() {
484                      orig_class_paths.insert(class_name.clone(), class_paths.join("_"));
485                    }
486                  }
487
488                  let mut dynamic_styles: Vec<DynamicStyle> = inline_styles
489                    .iter()
490                    .map(|(var_name, v)| {
491                      let key = v
492                        .path
493                        .iter()
494                        .take(
495                          v.path
496                            .iter()
497                            .position(|p| !p.starts_with(':') && !p.starts_with('@'))
498                            .map_or(0, |index| index + 1),
499                        )
500                        .cloned()
501                        .collect::<Vec<String>>()
502                        .join("_");
503
504                      DynamicStyle {
505                        expression: v.original_expression.clone(),
506                        key,
507                        path: v.path.join("_"),
508                        var_name: var_name.clone(),
509                      }
510                    })
511                    .collect();
512
513                  if self.state.options.style_resolution == StyleResolution::LegacyExpandShorthands
514                  {
515                    dynamic_styles = legacy_expand_shorthands(dynamic_styles);
516                  }
517
518                  let mut nullish_var_expressions: FxHashMap<String, Expr> = FxHashMap::default();
519                  for dynamic_style in dynamic_styles.iter() {
520                    if has_explicit_nullish_fallback(&mut dynamic_style.expression.clone()) {
521                      nullish_var_expressions
522                        .insert(dynamic_style.var_name.clone(), dynamic_style.expression.clone());
523                    }
524                  }
525
526                  if let Some(value) = value.as_mut_object() {
527                    let mut css_tag_value:Box<Expr> = Box::new(Expr::Lit(Lit::Bool(Bool {
528                      span: DUMMY_SP,
529                      value: true,
530                    })));
531
532                    let mut static_props = vec![];
533                    let mut conditional_props = vec![];
534
535                    for prop in value.props.iter_mut() {
536                      if let PropOrSpread::Prop(prop) = prop {
537                        if let Some(obj_prop) = prop.as_mut_key_value() {
538                          let prop_key = match &obj_prop.key {
539                            PropName::Ident(ident) => Some(ident.sym.to_string()),
540                            PropName::Str(strng) => Some(convert_atom_to_string(&strng.value)),
541                            _ => None,
542                          };
543
544                          if let Some(prop_key) = prop_key {
545                            if prop_key == COMPILED_KEY {
546                              css_tag_value = obj_prop.value.clone();
547                              continue;
548                            }
549
550                            let class_list = obj_prop
551                              .value
552                              .as_lit()
553                              .and_then(convert_lit_to_string)
554                              .map(|s| {
555                                s.split_whitespace()
556                                  .map(str::to_owned)
557                                  .collect::<Vec<String>>()
558                              })
559                              .unwrap_or_default();
560
561                            if !class_list.is_empty() {
562                              let mut is_static = true;
563                              let mut expr_list = vec![];
564
565                              // Pre-calculate class strings with spaces to avoid repeated allocations
566                              let class_strings: Vec<String> = class_list
567                                .iter()
568                                .enumerate()
569                                .map(|(index, cls)| {
570                                  if index == class_list.len() - 1 {
571                                    cls.clone()
572                                  } else {
573                                    format!("{} ", cls)
574                                  }
575                                })
576                                .collect();
577
578                              for (index, cls) in class_list.iter().enumerate() {
579                                let expr = dynamic_styles
580                                  .iter()
581                                  .find(|dynamic_style| {
582                                    orig_class_paths.get(cls) == Some(&dynamic_style.path)
583                                  })
584                                  .map(|dynamic_style| dynamic_style.expression.clone());
585
586                                let expr = if expr.is_none() && !nullish_var_expressions.is_empty()
587                                {
588                                  injected_styles.get(cls).and_then(|style| {
589                                    let rule = match style.as_ref() {
590                                      InjectableStyleKind::Regular(s) => {
591                                        let ltr = s.ltr.as_str();
592                                        let rtl = s.rtl.as_deref().unwrap_or_default();
593
594                                        if ltr.is_empty() {
595                                          rtl
596                                        } else {
597                                          ltr
598                                        }
599                                      },
600                                      InjectableStyleKind::Const(s) => {
601                                        let ltr = s.ltr.as_str();
602                                        let rtl = s.rtl.as_deref().unwrap_or_default();
603
604                                        if ltr.is_empty() {
605                                          rtl
606                                        } else {
607                                          ltr
608                                        }
609                                      },
610                                    };
611                                    extract_expr_from_rule(rule, &nullish_var_expressions)
612                                  })
613                                } else {
614                                  expr
615                                };
616
617                                let cls_with_space = &class_strings[index];
618
619                                if let Some(expr) = expr.and_then(|mut e| {
620                                  if is_safe_to_skip_null_check(&mut e) {
621                                    None
622                                  } else {
623                                    Some(e)
624                                  }
625                                }) {
626                                  is_static = false;
627                                  expr_list.push(Expr::Cond(CondExpr {
628                                    span: DUMMY_SP,
629                                    test: Box::new(Expr::Bin(BinExpr {
630                                      span: DUMMY_SP,
631                                      op: BinaryOp::NotEq,
632                                      left: Box::new(expr.clone()),
633                                      right: Box::new(create_null_expr()),
634                                    })),
635                                    cons: Box::new(create_string_expr(cls_with_space)),
636                                    alt: Box::new(expr),
637                                  }));
638                                } else {
639                                  expr_list.push(create_string_expr(cls_with_space));
640                                }
641                              }
642
643                              let joined = if expr_list.is_empty() {
644                                create_string_expr("")
645                              } else {
646                                expr_list
647                                  .into_iter()
648                                  .reduce(|acc, curr| {
649                                    Expr::Bin(BinExpr {
650                                      span: DUMMY_SP,
651                                      op: BinaryOp::Add,
652                                      left: Box::new(acc),
653                                      right: Box::new(curr),
654                                    })
655                                  })
656                                  .unwrap_or_else(|| {
657                                    stylex_panic!(
658                                      "Expected at least one expression to reduce in class name concatenation."
659                                    )
660                                  })
661                              };
662
663                              if is_static {
664                                static_props.push(create_prop_from_name(
665                                  obj_prop.key.clone(),
666                                  joined,
667                                ));
668                              } else {
669                                conditional_props.push(create_prop_from_name(
670                                  obj_prop.key.clone(),
671                                  joined,
672                                ));
673                              }
674                            }
675                          } else {
676                            static_props.push(PropOrSpread::Prop(Box::new(Prop::from(
677                                obj_prop.to_owned(),
678                              ))));
679                              continue;
680                          }
681                        } else {
682                          let expr = Expr::from(call.clone());
683
684                          build_code_frame_error_and_panic(
685                            &expr,
686                            &expr,
687                            "Unsupported prop type encountered in stylex.create. Only object properties are allowed.",
688                            &mut self.state,
689                          );
690                        }
691                      } else {
692                        let expr = Expr::from(call.clone());
693
694                        build_code_frame_error_and_panic(
695                          &expr,
696                          &expr,
697                          "Unsupported prop type encountered in stylex.create. Only object properties are allowed.",
698                          &mut self.state,
699                        );
700                      }
701                    }
702
703                    let mut static_obj = None;
704                    let mut conditional_obj = None;
705
706                    if !static_props.is_empty(){
707                      static_props.push(create_key_value_prop(
708                        COMPILED_KEY,
709                        *css_tag_value.clone(),
710                      ));
711
712                      static_obj = Some(create_object_expression(static_props));
713                    }
714
715                    if !conditional_props.is_empty(){
716                      conditional_props.push(create_key_value_prop(
717                        COMPILED_KEY,
718                        *css_tag_value.clone(),
719                      ));
720
721                      conditional_obj = Some(create_object_expression(conditional_props.clone()));
722                    }
723
724                    let mut final_fn_value = create_object_expression(
725                      inline_styles
726                        .iter()
727                        .map(|(key, val)| {
728                          create_key_value_prop(
729                            key.as_str(),
730                            val.expression.clone(),
731                          )
732                        })
733                        .collect(),
734                    );
735
736                    if static_obj.is_some() || conditional_obj.is_some() {
737                      let mut array_elements = Vec::new();
738
739                      if let Some(static_obj) = static_obj {
740                        let hoist_ident = create_expr_or_spread(hoist_expression(
741                          static_obj,
742                          &mut self.state,
743                        ));
744
745                        let hoist_ident_expr = match hoist_ident.expr.as_ident() {
746                          Some(ident) => ident.clone(),
747                          None => stylex_panic!("Expected an identifier for the hoisted style variable."),
748                        };
749                        self.state.declarations.push(
750                          create_string_var_declarator(hoist_ident_expr, "hoisted variable"),
751                        );
752
753                        array_elements.push(Some(hoist_ident));
754                      }
755
756                      if let Some(conditional_obj) = conditional_obj {
757                        array_elements.push(Some(create_expr_or_spread(conditional_obj)));
758                      }
759
760                      array_elements.push(Some(create_expr_or_spread(final_fn_value)));
761
762                      final_fn_value = create_array_expression(array_elements);
763                    }
764
765                    value.props = conditional_props;
766
767                    let value = Expr::from(ArrowExpr {
768                      span: DUMMY_SP,
769                      params: params.iter().map(|arg| Pat::Ident(arg.clone())).collect(),
770                      body: Box::new(BlockStmtOrExpr::from(Box::new(final_fn_value))),
771                      is_async: false,
772                      is_generator: false,
773                      type_params: None,
774                      return_type: None,
775                      ctxt: SyntaxContext::empty(),
776                    });
777
778                    prop = Some(create_key_value_prop(orig_key.as_str(), value));
779                  }
780                }
781
782              prop.unwrap_or_else(|| {
783                create_key_value_prop(orig_key.as_str(), *value.clone())
784              })
785            })
786            .collect();
787
788        result_ast = path_replace_hoisted(
789          create_object_expression(props),
790          is_program_level,
791          &mut self.state,
792        );
793      };
794
795      self.state.register_styles(
796        call,
797        &injected_styles,
798        &result_ast,
799        (!result_ast.eq(&styles_ast)).then_some(&styles_ast),
800      );
801
802      Some(result_ast)
803    } else {
804      None
805    };
806
807    self.state.in_stylex_create = false;
808
809    result
810  }
811}
812
813fn legacy_expand_shorthands(dynamic_styles: Vec<DynamicStyle>) -> Vec<DynamicStyle> {
814  let expanded_keys_to_key_paths: Vec<DynamicStyle> = dynamic_styles
815    .iter()
816    .enumerate()
817    .flat_map(|(i, dynamic_style)| {
818      let obj_entry = (
819        dynamic_style.key.clone(),
820        PreRuleValue::String(format!("p{}", i)),
821      );
822
823      let options = StyleXStateOptions {
824        style_resolution: StyleResolution::LegacyExpandShorthands,
825        ..Default::default()
826      };
827
828      flat_map_expanded_shorthands(obj_entry, &options)
829    })
830    .filter_map(|OrderPair(key, value)| {
831      let value = value?;
832
833      let index = value[1..].parse::<usize>().ok()?;
834      let that_dyn_style = dynamic_styles.get(index)?;
835
836      Some(DynamicStyle {
837        key: key.clone(),
838        path: if that_dyn_style.path == that_dyn_style.key {
839          key.clone()
840        } else if that_dyn_style
841          .path
842          .contains(&(that_dyn_style.key.clone() + "_"))
843        {
844          that_dyn_style
845            .path
846            .replace(&(that_dyn_style.key.clone() + "_"), &(key.clone() + "_"))
847        } else {
848          that_dyn_style.path.replace(
849            &("_".to_string() + that_dyn_style.key.as_str()),
850            &("_".to_string() + key.as_str()),
851          )
852        },
853        ..that_dyn_style.clone()
854      })
855    })
856    .collect();
857
858  expanded_keys_to_key_paths
859}
860
861fn is_safe_to_skip_null_check(expr: &mut Expr) -> bool {
862  let expr = normalize_expr(expr);
863
864  match expr {
865    Expr::Tpl(_) => true,
866    Expr::Lit(lit) => matches!(lit, Lit::Str(_) | Lit::Num(_) | Lit::Bool(_)),
867    Expr::Bin(bin_expr) => match bin_expr.op {
868      BinaryOp::Add
869      | BinaryOp::Sub
870      | BinaryOp::Mul
871      | BinaryOp::Div
872      | BinaryOp::Mod
873      | BinaryOp::Exp => true,
874      BinaryOp::NullishCoalescing | BinaryOp::LogicalOr => {
875        is_safe_to_skip_null_check(&mut bin_expr.left)
876          || is_safe_to_skip_null_check(&mut bin_expr.right)
877      },
878      BinaryOp::LogicalAnd => {
879        is_safe_to_skip_null_check(&mut bin_expr.left)
880          && is_safe_to_skip_null_check(&mut bin_expr.right)
881      },
882      _ => false,
883    },
884    Expr::Unary(unary_expr) => matches!(unary_expr.op, UnaryOp::Minus | UnaryOp::Plus),
885    Expr::Cond(cond_expr) => {
886      is_safe_to_skip_null_check(&mut cond_expr.cons)
887        && is_safe_to_skip_null_check(&mut cond_expr.alt)
888    },
889    _ => false,
890  }
891}
892
893fn has_explicit_nullish_fallback(expr: &mut Expr) -> bool {
894  let expr = normalize_expr(expr);
895  match expr {
896    Expr::Lit(Lit::Null(_)) => true,
897    Expr::Ident(ident) if ident.sym == "undefined" => true,
898    Expr::Unary(unary) if matches!(unary.op, UnaryOp::Void) => true,
899    Expr::Cond(cond) => {
900      has_explicit_nullish_fallback(&mut cond.cons) || has_explicit_nullish_fallback(&mut cond.alt)
901    },
902    Expr::Bin(bin) => match bin.op {
903      BinaryOp::LogicalOr | BinaryOp::NullishCoalescing | BinaryOp::LogicalAnd => {
904        has_explicit_nullish_fallback(&mut bin.left)
905          || has_explicit_nullish_fallback(&mut bin.right)
906      },
907      _ => false,
908    },
909    _ => false,
910  }
911}
912
913fn extract_expr_from_rule(
914  rule: &str,
915  nullish_var_expressions: &FxHashMap<String, Expr>,
916) -> Option<Expr> {
917  for cap in VAR_EXTRACTION_REGEX.captures_iter(rule).flatten() {
918    if let Some(var_match) = cap.get(1) {
919      let var_name = var_match.as_str();
920      if let Some(expr) = nullish_var_expressions.get(var_name) {
921        return Some(expr.clone());
922      }
923    }
924  }
925  None
926}
927
928/// Hoists an expression to the program level by creating a const variable declaration.
929/// This is the Rust equivalent of the JavaScript `hoistExpression` function.
930///
931/// # Arguments
932/// * `ast_expression` - The expression to hoist
933/// * `state` - The state manager to add the hoisted declaration to
934///
935/// # Returns
936/// An identifier referencing the hoisted variable
937pub(crate) fn hoist_expression(
938  ast_expression: Expr,
939  state: &mut crate::shared::structures::state_manager::StateManager,
940) -> Expr {
941  let uid_generator = UidGenerator::new("temp", CounterMode::ThreadLocal);
942  let hoisted_ident = uid_generator.generate_ident();
943
944  let var_decl = VarDecl {
945    span: DUMMY_SP,
946    kind: VarDeclKind::Const,
947    declare: false,
948    decls: vec![create_var_declarator(hoisted_ident.clone(), ast_expression)],
949    ctxt: swc_core::common::SyntaxContext::empty(),
950  };
951
952  let module_item = ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(var_decl))));
953  state.hoisted_module_items.push(module_item);
954
955  Expr::Ident(hoisted_ident)
956}
957
958pub(crate) fn path_replace_hoisted(
959  ast_expression: Expr,
960  is_program_level: bool,
961  state: &mut crate::shared::structures::state_manager::StateManager,
962) -> Expr {
963  if is_program_level {
964    return ast_expression;
965  }
966
967  let uid_generator = UidGenerator::new("styles", CounterMode::ThreadLocal);
968  let name_ident = uid_generator.generate_ident();
969
970  let var_decl = VarDecl {
971    span: DUMMY_SP,
972    kind: VarDeclKind::Const,
973    declare: false,
974    decls: vec![create_var_declarator(name_ident.clone(), ast_expression)],
975    ctxt: swc_core::common::SyntaxContext::empty(),
976  };
977
978  let module_item = ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(var_decl))));
979  state.hoisted_module_items.push(module_item);
980
981  Expr::Ident(name_ident)
982}