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
76static 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 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 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
928pub(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}