Skip to main content

stylex_transform/shared/utils/
common.rs

1use radix_fmt::radix;
2use rustc_hash::{FxHashMap, FxHashSet};
3use std::{
4  any::type_name,
5  collections::hash_map::Entry,
6  hash::{DefaultHasher, Hash, Hasher},
7  ops::Deref,
8  path::PathBuf,
9};
10use stylex_macros::{stylex_panic, stylex_unimplemented};
11use stylex_types::traits::StyleOptions;
12use swc_core::{
13  atoms::Atom,
14  common::{DUMMY_SP, EqIgnoreSpan, FileName},
15  ecma::{
16    ast::{
17      BinaryOp, Decl, Expr, Ident, ImportDecl, ImportSpecifier, KeyValueProp, MemberExpr, Module,
18      ModuleDecl, ModuleExportName, ModuleItem, ObjectLit, ObjectPatProp, Pat, Prop, PropName,
19      PropOrSpread, Stmt, VarDeclarator,
20    },
21    utils::drop_span,
22  },
23};
24
25use stylex_enums::top_level_expression::TopLevelExpressionKind;
26use stylex_structures::top_level_expression::TopLevelExpression;
27
28use crate::shared::structures::base_css_type::BaseCSSType;
29use crate::shared::structures::functions::{FunctionConfigType, FunctionMap, FunctionType};
30use crate::shared::structures::state_manager::StateManager;
31use crate::shared::utils::ast::convertors::{convert_str_lit_to_atom, convert_wtf8_to_atom};
32use stylex_constants::constants::messages::{
33  ILLEGAL_PROP_VALUE, INVALID_UTF8, SPREAD_NOT_SUPPORTED, VAR_DECL_NAME_NOT_IDENT,
34};
35use stylex_enums::misc::VarDeclAction;
36use stylex_regex::regex::{DASHIFY_REGEX, JSON_REGEX};
37
38use super::ast::convertors::expand_shorthand_prop;
39use stylex_ast::ast::factories::create_var_declarator;
40
41pub(crate) fn extract_filename_from_path(path: &FileName) -> String {
42  match path {
43    FileName::Real(path_buf) => {
44      let stem = match path_buf.file_stem() {
45        Some(s) => s,
46        None => stylex_panic!("File path has no file stem component."),
47      };
48      match stem.to_str() {
49        Some(s) => s.to_string(),
50        None => stylex_panic!("{}", INVALID_UTF8),
51      }
52    },
53    _ => "".to_string(),
54  }
55}
56
57pub(crate) fn extract_path(path: &FileName) -> &str {
58  match path {
59    FileName::Real(path_buf) => match path_buf.to_str() {
60      Some(s) => s,
61      None => stylex_panic!("{}", INVALID_UTF8),
62    },
63    _ => "",
64  }
65}
66
67pub(crate) fn extract_filename_with_ext_from_path(path: &FileName) -> Option<&str> {
68  match path {
69    FileName::Real(path_buf) => {
70      let name = match path_buf.file_name() {
71        Some(n) => n,
72        None => stylex_panic!("File path has no file name component."),
73      };
74      Some(match name.to_str() {
75        Some(s) => s,
76        None => stylex_panic!("{}", INVALID_UTF8),
77      })
78    },
79    _ => None,
80  }
81}
82
83pub fn create_hash(value: &str) -> String {
84  radix(murmur2::murmur2(value.as_bytes(), 1), 36).to_string()
85}
86
87pub(crate) fn wrap_key_in_quotes(key: &str, should_wrap_in_quotes: bool) -> String {
88  if should_wrap_in_quotes {
89    format!("\"{}\"", key)
90  } else {
91    key.to_string()
92  }
93}
94
95pub fn reduce_ident_count<'a>(state: &'a mut StateManager, ident: &'a Ident) {
96  if let Entry::Occupied(mut entry) = state.var_decl_count_map.entry(ident.sym.clone()) {
97    *entry.get_mut() -= 1;
98  }
99}
100
101pub fn increase_member_ident(state: &mut StateManager, member_obj: &MemberExpr) {
102  if let Some(obj_ident) = member_obj.obj.as_ident() {
103    increase_member_ident_count(state, &obj_ident.sym);
104  }
105}
106
107pub fn reduce_member_expression_count(state: &mut StateManager, member_expression: &MemberExpr) {
108  if let Some(obj_ident) = member_expression.obj.as_ident() {
109    reduce_member_ident_count(state, &obj_ident.sym);
110  }
111}
112
113pub fn reduce_member_ident_count(state: &mut StateManager, ident_atom: &Atom) {
114  if let Entry::Occupied(mut entry) = state
115    .member_object_ident_count_map
116    .entry(ident_atom.clone())
117  {
118    *entry.get_mut() -= 1;
119  }
120}
121pub fn increase_ident_count(state: &mut StateManager, ident: &Ident) {
122  increase_ident_count_by_count(state, ident, 1);
123}
124
125pub fn increase_member_ident_count(state: &mut StateManager, ident_atom: &Atom) {
126  increase_member_ident_count_by_count(state, ident_atom, 1);
127}
128pub fn increase_ident_count_by_count(state: &mut StateManager, ident: &Ident, count: i16) {
129  let ident_id = &ident.sym;
130
131  *state
132    .var_decl_count_map
133    .entry(ident_id.clone())
134    .or_insert(0) += count;
135}
136
137pub fn increase_member_ident_count_by_count(
138  state: &mut StateManager,
139  ident_atom: &Atom,
140  count: i16,
141) {
142  *state
143    .member_object_ident_count_map
144    .entry(ident_atom.clone())
145    .or_insert(0) += count;
146}
147
148pub fn get_var_decl_by_ident<'a>(
149  ident: &'a Ident,
150  traversal_state: &'a mut StateManager,
151  functions: &'a FunctionMap,
152  action: VarDeclAction,
153) -> Option<VarDeclarator> {
154  match action {
155    VarDeclAction::Increase => increase_ident_count(traversal_state, ident),
156    VarDeclAction::Reduce => reduce_ident_count(traversal_state, ident),
157    VarDeclAction::None => {},
158  };
159
160  if let Some(var_decl) = get_var_decl_from(traversal_state, ident) {
161    return Some(var_decl.clone());
162  }
163
164  if let Some(func) = functions.identifiers.get(&ident.sym) {
165    match func.as_ref() {
166      FunctionConfigType::Regular(func) => match &func.fn_ptr {
167        FunctionType::Mapper(func) => {
168          let result = func();
169
170          let var_decl = create_var_declarator(ident.clone(), result);
171
172          return Some(var_decl);
173        },
174        _ => stylex_panic!("Function type not supported: {:?}", func),
175      },
176      FunctionConfigType::Map(_) => {
177        stylex_unimplemented!("Map values are not supported in this context.")
178      },
179      FunctionConfigType::IndexMap(_) => {
180        stylex_unimplemented!("IndexMap values are not supported in this context.")
181      },
182      FunctionConfigType::EnvObject(_) => return None,
183    }
184  }
185
186  None
187}
188
189pub fn get_import_by_ident<'a>(
190  ident: &'a Ident,
191  state: &'a StateManager,
192) -> Option<&'a ImportDecl> {
193  get_import_from(state, ident)
194}
195
196pub(crate) fn get_var_decl_from<'a>(
197  state: &'a StateManager,
198  ident: &'a Ident,
199) -> Option<&'a VarDeclarator> {
200  state
201    .declarations
202    .iter()
203    .find(|var_declarator| matches_ident_with_var_decl_name(ident, var_declarator))
204}
205
206fn matches_ident_with_var_decl_name(ident: &Ident, var_declarator: &&VarDeclarator) -> bool {
207  var_declarator
208    .name
209    .clone()
210    .ident()
211    .is_some_and(|var_decl_ident| &var_decl_ident.id == ident)
212}
213
214pub(crate) fn get_import_from<'a>(
215  state: &'a StateManager,
216  ident: &'a Ident,
217) -> Option<&'a ImportDecl> {
218  state.top_imports.iter().find(|import| {
219    import.specifiers.iter().any(|specifier| match specifier {
220      ImportSpecifier::Named(named_import) => {
221        named_import.local.sym == ident.sym || {
222          match &named_import.imported {
223            Some(imported) => match imported {
224              ModuleExportName::Ident(export_ident) => export_ident.eq_ignore_span(ident),
225              ModuleExportName::Str(strng) => convert_str_lit_to_atom(strng) == ident.sym,
226            },
227            _ => false,
228          }
229        }
230      },
231      ImportSpecifier::Default(default_import) => default_import.local.eq_ignore_span(ident),
232      ImportSpecifier::Namespace(namespace_import) => namespace_import.local.eq_ignore_span(ident),
233    })
234  })
235}
236
237pub(crate) fn _get_var_decl_by_ident_or_member<'a>(
238  state: &'a StateManager,
239  ident: &'a Ident,
240) -> Option<VarDeclarator> {
241  state
242    .declarations
243    .iter()
244    .find(|var_declarator| {
245      matches_ident_with_var_decl_name(ident, var_declarator)
246        || matches!(
247          var_declarator.init.as_ref()
248            .and_then(|init| init.as_call())
249            .and_then(|call| call.callee.as_expr())
250            .and_then(|callee| callee.as_member())
251            .and_then(|member| member.prop.as_ident()),
252          Some(member_ident) if member_ident.sym == ident.sym
253        )
254    })
255    .cloned()
256}
257
258pub fn get_expr_from_var_decl(var_decl: &VarDeclarator) -> &Expr {
259  match &var_decl.init {
260    Some(var_decl_init) => var_decl_init,
261    None => stylex_panic!("Variable declaration must be initialized with an expression."),
262  }
263}
264
265pub fn evaluate_bin_expr(op: BinaryOp, left: f64, right: f64) -> f64 {
266  match &op {
267    BinaryOp::Add => left + right,
268    BinaryOp::Sub => left - right,
269    BinaryOp::Mul => left * right,
270    BinaryOp::Div => left / right,
271    _ => stylex_panic!("Operator '{}' is not supported", op),
272  }
273}
274
275#[allow(dead_code)]
276pub(crate) fn type_of<T>(_: T) -> &'static str {
277  type_name::<T>()
278}
279
280fn prop_name_eq(a: &PropName, b: &PropName) -> bool {
281  match (a, b) {
282    (PropName::Ident(a), PropName::Ident(b)) => a.sym == b.sym,
283    (PropName::Str(a), PropName::Str(b)) => a.value == b.value,
284    (PropName::Num(a), PropName::Num(b)) => (a.value - b.value).abs() < f64::EPSILON,
285
286    (PropName::BigInt(a), PropName::BigInt(b)) => a.value == b.value,
287    // Add more cases as needed
288    _ => false,
289  }
290}
291
292pub(crate) fn remove_duplicates(props: Vec<PropOrSpread>) -> Vec<PropOrSpread> {
293  let mut set = FxHashSet::default();
294  let mut result = vec![];
295
296  for prop in props.into_iter().rev() {
297    let key = match &prop {
298      PropOrSpread::Prop(prop) => match prop.as_ref() {
299        Prop::Shorthand(ident) => ident.sym.clone(),
300        Prop::KeyValue(key_val) => match &key_val.key {
301          PropName::Ident(ident) => ident.sym.clone(),
302          PropName::Str(strng) => convert_wtf8_to_atom(&strng.value),
303          _ => continue,
304        },
305        _ => continue,
306      },
307      _ => continue,
308    };
309
310    if set.insert(key) {
311      result.push(prop);
312    }
313  }
314
315  result.reverse();
316
317  result
318}
319
320pub(crate) fn deep_merge_props(
321  old_props: Vec<PropOrSpread>,
322  mut new_props: Vec<PropOrSpread>,
323) -> Vec<PropOrSpread> {
324  for prop in old_props {
325    match prop {
326      PropOrSpread::Prop(prop) => match *prop {
327        Prop::KeyValue(mut kv) => {
328          if new_props.iter().any(|p| match p {
329            PropOrSpread::Prop(p) => match p.as_ref() {
330              Prop::KeyValue(existing_kv) => prop_name_eq(&kv.key, &existing_kv.key),
331              _ => false,
332            },
333            _ => false,
334          }) {
335            if let Expr::Object(ref mut obj) = *kv.value {
336              new_props.push(PropOrSpread::Prop(Box::new(Prop::from(KeyValueProp {
337                key: kv.key.clone(),
338                value: Box::new(Expr::Object(ObjectLit {
339                  span: DUMMY_SP,
340                  props: obj.props.clone(),
341                })),
342              }))));
343            }
344          } else {
345            new_props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(kv))));
346          }
347        },
348        _ => new_props.push(PropOrSpread::Prop(Box::new(*prop))),
349      },
350      _ => new_props.push(prop),
351    }
352  }
353
354  remove_duplicates(new_props.into_iter().rev().collect())
355}
356
357pub(crate) fn get_hash_map_difference<K, V>(
358  orig_map: &FxHashMap<K, V>,
359  compare_map: &FxHashMap<K, V>,
360) -> FxHashMap<K, V>
361where
362  K: Eq + Hash + Clone,
363  V: PartialEq + Clone,
364{
365  let mut diff = FxHashMap::default();
366
367  for (key, value) in orig_map {
368    if let Some(map2_value) = compare_map.get(key) {
369      if value != map2_value {
370        diff.insert(key.clone(), value.clone());
371      }
372    } else {
373      diff.insert(key.clone(), value.clone());
374    }
375  }
376
377  for (key, value) in compare_map {
378    if !orig_map.contains_key(key) {
379      diff.insert(key.clone(), value.clone());
380    }
381  }
382
383  diff
384}
385
386pub(crate) fn get_hash_map_value_difference(
387  orig_map: &FxHashMap<Atom, i16>,
388  map2: &FxHashMap<Atom, i16>,
389) -> FxHashMap<Atom, i16> {
390  let mut diff = FxHashMap::default();
391
392  for (key, value) in orig_map {
393    if let Some(map2_value) = map2.get(key) {
394      if value != map2_value {
395        diff.insert(key.clone(), value - map2_value);
396      }
397    } else {
398      diff.insert(key.clone(), *value);
399    }
400  }
401
402  diff
403}
404
405pub(crate) fn sum_hash_map_values(
406  orig_map: &FxHashMap<Atom, i16>,
407  compare_map: &FxHashMap<Atom, i16>,
408) -> FxHashMap<Atom, i16> {
409  let mut sum_map = FxHashMap::default();
410
411  for (key, value) in orig_map {
412    sum_map.insert(key.clone(), *value);
413  }
414
415  for (key, value) in compare_map {
416    sum_map
417      .entry(key.clone())
418      .and_modify(|e| *e += value)
419      .or_insert(*value);
420  }
421
422  sum_map
423}
424
425pub(crate) fn dashify(s: &str) -> String {
426  DASHIFY_REGEX.replace_all(s, "-$1").to_lowercase()
427}
428
429pub(crate) fn get_css_value(key_value: KeyValueProp) -> (Box<Expr>, Option<BaseCSSType>) {
430  let Some(obj) = key_value.value.as_object() else {
431    return (key_value.value, None);
432  };
433
434  for prop in obj.props.clone().into_iter() {
435    match prop {
436      PropOrSpread::Spread(_) => stylex_unimplemented!("{}", SPREAD_NOT_SUPPORTED),
437      PropOrSpread::Prop(mut prop) => {
438        expand_shorthand_prop(&mut prop);
439
440        match prop.deref() {
441          Prop::KeyValue(key_value) => {
442            if let Some(ident) = key_value.key.as_ident()
443              && ident.sym == "syntax"
444            {
445              let value = obj.props.iter().find(|prop| {
446                match prop {
447                  PropOrSpread::Spread(_) => stylex_unimplemented!("{}", SPREAD_NOT_SUPPORTED),
448                  PropOrSpread::Prop(prop) => {
449                    let mut prop = prop.clone();
450                    expand_shorthand_prop(&mut prop);
451
452                    match prop.as_ref() {
453                      Prop::KeyValue(key_value) => {
454                        if let Some(ident) = key_value.key.as_ident() {
455                          return ident.sym == "value";
456                        }
457                      },
458                      _ => stylex_unimplemented!("Unsupported prop type in CSS value"),
459                    }
460                  },
461                }
462
463                false
464              });
465
466              if let Some(value) = value {
467                let result_key_value = match value.as_prop().and_then(|prop| prop.as_key_value()) {
468                  Some(kv) => kv,
469                  None => stylex_panic!("Expected key-value property"),
470                };
471
472                return (result_key_value.value.clone(), Some(obj.clone().into()));
473              }
474            }
475          },
476          _ => stylex_unimplemented!("Unsupported prop type in CSS value"),
477        }
478      },
479    }
480  }
481
482  (key_value.value, None)
483}
484
485pub(crate) fn get_key_values_from_object(object: &ObjectLit) -> Vec<KeyValueProp> {
486  let mut key_values = vec![];
487
488  for prop in object.props.iter() {
489    match prop {
490      PropOrSpread::Spread(_) => stylex_unimplemented!("{}", SPREAD_NOT_SUPPORTED),
491      PropOrSpread::Prop(prop) => {
492        let mut prop = prop.clone();
493
494        expand_shorthand_prop(&mut prop);
495
496        match prop.as_ref() {
497          Prop::KeyValue(key_value) => {
498            key_values.push(key_value.clone());
499          },
500          _ => stylex_panic!("{}", ILLEGAL_PROP_VALUE),
501        }
502      },
503    }
504  }
505  key_values
506}
507
508pub fn fill_top_level_expressions(module: &Module, state: &mut StateManager) {
509  module.clone().body.iter().for_each(|item| match &item {
510    ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
511      if let Decl::Var(decl_var) = &export_decl.decl {
512        for decl in &decl_var.decls {
513          if let Some(decl_init) = decl.init.as_ref() {
514            let ident_sym = match decl.name.as_ident() {
515              Some(i) => i.sym.clone(),
516              None => stylex_panic!("{}", VAR_DECL_NAME_NOT_IDENT),
517            };
518            state.top_level_expressions.push(TopLevelExpression(
519              TopLevelExpressionKind::NamedExport,
520              *drop_span(decl_init.clone()),
521              Some(ident_sym),
522            ));
523            fill_state_declarations(state, decl);
524          }
525        }
526      }
527    },
528    ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_decl)) => {
529      match export_decl.expr.as_paren() {
530        Some(paren) => {
531          state.top_level_expressions.push(TopLevelExpression(
532            TopLevelExpressionKind::DefaultExport,
533            *drop_span(paren.expr.clone()),
534            None,
535          ));
536        },
537        _ => {
538          state.top_level_expressions.push(TopLevelExpression(
539            TopLevelExpressionKind::DefaultExport,
540            *drop_span(export_decl.expr.clone()),
541            None,
542          ));
543        },
544      }
545    },
546    ModuleItem::Stmt(Stmt::Decl(Decl::Var(var))) => {
547      for decl in &var.decls {
548        if let Some(decl_init) = decl.init.as_ref()
549          && decl.name.as_ident().is_some()
550        {
551          let stmt_ident_sym = match decl.name.as_ident() {
552            Some(i) => i.sym.clone(),
553            None => stylex_panic!("{}", VAR_DECL_NAME_NOT_IDENT),
554          };
555          state.top_level_expressions.push(TopLevelExpression(
556            TopLevelExpressionKind::Stmt,
557            *drop_span(decl_init.clone()),
558            Some(stmt_ident_sym),
559          ));
560
561          fill_state_declarations(state, decl);
562        }
563      }
564    },
565    _ => {},
566  });
567}
568
569pub fn fill_state_declarations(state: &mut StateManager, decl: &VarDeclarator) {
570  let normalized_decl = drop_span(decl.clone());
571
572  if !state.declarations.contains(&normalized_decl) {
573    state.declarations.push(normalized_decl.clone());
574  }
575}
576
577fn _get_variable_names(name: &Pat) -> Vec<String> {
578  match name {
579    Pat::Ident(ident) => vec![ident.id.sym.to_string()],
580    Pat::Object(pat_object) => {
581      let mut names = vec![];
582
583      for prop in pat_object.props.iter() {
584        match prop {
585          ObjectPatProp::KeyValue(key_value_pat_prop) => {
586            names.append(&mut _get_variable_names(&key_value_pat_prop.value));
587          },
588          ObjectPatProp::Assign(assign_pat_prop) => {
589            names.append(&mut _get_variable_names(&Pat::Ident(
590              assign_pat_prop.key.clone(),
591            )));
592          },
593          ObjectPatProp::Rest(rest_pat) => {
594            names.append(&mut _get_variable_names(&rest_pat.arg));
595          },
596        }
597      }
598
599      names
600    },
601    Pat::Array(pat_array) => {
602      let mut names = vec![];
603
604      for elem in pat_array.elems.iter().flatten() {
605        names.append(&mut _get_variable_names(elem));
606      }
607
608      names
609    },
610    Pat::Rest(rest_pat) => _get_variable_names(&rest_pat.arg),
611    Pat::Invalid(_) => vec![],
612    Pat::Expr(_) => vec![],
613    Pat::Assign(assign) => {
614      let mut names = vec![];
615
616      names.append(&mut _get_variable_names(&assign.left));
617
618      names
619    },
620  }
621}
622
623pub(crate) fn gen_file_based_identifier(
624  file_name: &str,
625  export_name: &str,
626  key: Option<&str>,
627) -> String {
628  let key = key.map_or(String::new(), |k| format!(".{}", k));
629
630  format!("{}//{}{}", file_name, export_name, key)
631}
632
633#[allow(unused_imports)]
634pub(crate) use stylex_utils::hash::hash_f64;
635
636pub(crate) fn round_f64(value: f64, decimal_places: u32) -> f64 {
637  let multiplier = 10f64.powi(decimal_places as i32);
638  (value * multiplier).round() / multiplier
639}
640
641pub(crate) fn _resolve_node_package_path(package_name: &str) -> Result<PathBuf, String> {
642  match node_resolve::Resolver::default()
643    .with_basedir(PathBuf::from("./cwd"))
644    .preserve_symlinks(true)
645    .with_extensions([".ts", ".tsx", ".js", ".jsx", ".json"])
646    .with_main_fields(vec![String::from("main"), String::from("module")])
647    .resolve(package_name)
648  {
649    Ok(path) => Ok(path),
650    Err(error) => Err(format!(
651      "Error resolving package {}: {:?}",
652      package_name, error
653    )),
654  }
655}
656
657pub(crate) fn normalize_expr(expr: &mut Expr) -> &mut Expr {
658  match expr {
659    Expr::Paren(paren) => normalize_expr(paren.expr.as_mut()),
660    _ => {
661      *expr = drop_span(expr.clone());
662      expr
663    },
664  }
665}
666
667pub(crate) fn sort_numbers_factory() -> impl FnMut(&f64, &f64) -> std::cmp::Ordering {
668  |a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
669}
670
671pub(crate) fn char_code_at(s: &str, index: usize) -> Option<u32> {
672  s.chars().nth(index).map(|c| c as u32)
673}
674
675pub fn stable_hash<T: Hash>(t: &T) -> u64 {
676  let mut hasher = DefaultHasher::new();
677  t.hash(&mut hasher);
678  hasher.finish()
679}
680
681pub(crate) fn find_and_swap_remove<T, F>(vec: &mut Vec<T>, predicate: F) -> Option<T>
682where
683  F: Fn(&T) -> bool,
684{
685  vec
686    .iter()
687    .position(predicate)
688    .map(|index| vec.swap_remove(index))
689}
690
691pub(crate) fn create_short_hash(value: &str) -> String {
692  let hash = murmur2::murmur2(value.as_bytes(), 1) % (62u32.pow(5));
693  base62::encode(hash)
694}
695
696pub(crate) fn _md5_hash<T: serde::Serialize>(value: T, length: usize) -> String {
697  let serialized_value = serialize_value_to_json_string(value);
698
699  let digest = md5::compute(serialized_value.as_bytes());
700  let hex = format!("{:x}", digest);
701
702  if length >= hex.len() {
703    hex
704  } else {
705    hex[..length].to_string()
706  }
707}
708
709pub(crate) fn remove_quotes(s: &str) -> String {
710  s.trim_matches('"').to_string()
711}
712
713pub(crate) fn serialize_value_to_json_string<T: serde::Serialize>(value: T) -> String {
714  match serde_json::to_string(&value) {
715    Ok(json_str) => {
716      if json_str.starts_with('"') && json_str.ends_with('"') && json_str.len() > 2 {
717        match serde_json::from_str::<String>(&json_str) {
718          Ok(inner_string) => {
719            if inner_string.trim_start().starts_with('{') && !inner_string.contains("\":") {
720              return js_object_to_json(&inner_string);
721            }
722
723            if inner_string.parse::<f64>().is_ok() {
724              return inner_string;
725            }
726
727            remove_quotes(&inner_string)
728          },
729          _ => remove_quotes(&json_str),
730        }
731      } else {
732        json_str
733      }
734    },
735    Err(err) => {
736      stylex_panic!("Failed to serialize value. Error: {}", err)
737    },
738  }
739}
740
741fn js_object_to_json(js_str: &str) -> String {
742  JSON_REGEX.replace_all(js_str, r#"$1"$2":"#).to_string()
743}
744
745/// Universal rounding function that rounds a floating-point number to the specified
746/// number of decimal places and returns f64.
747///
748/// For 1 decimal place (priorities): Only rounds when within floating-point error
749/// tolerance to preserve legitimate decimals like 0.25.
750///
751/// For other decimal places: Always rounds to the specified precision.
752///
753/// # Arguments
754/// * `value` - The floating-point number to round
755/// * `decimal_places` - Number of decimal places to round to (default: 1)
756///
757/// # Examples
758/// ```ignore
759/// round_to_decimal_places(0.6000000000000001, 1) // → 0.6
760/// round_to_decimal_places(0.25, 1)               // → 0.25 (preserved)
761///
762/// round_to_decimal_places(33.333333333333336, 4) // → 33.3333
763/// round_to_decimal_places(10.0, 4)               // → 10.0
764/// ```
765pub(crate) fn round_to_decimal_places(value: f64, decimal_places: u32) -> f64 {
766  let multiplier = 10_f64.powi(decimal_places as i32);
767  let rounded = (value * multiplier).round() / multiplier;
768
769  // For single decimal place (priorities), use smart rounding that preserves
770  // legitimate decimals like 0.25 while fixing precision errors
771  if decimal_places == 1 {
772    let diff = (value - rounded).abs();
773    // If difference is within floating-point error tolerance, use rounded value
774    // Otherwise, keep the original to preserve values like 0.25
775    if diff < 1e-10 { rounded } else { value }
776  } else {
777    // For other decimal places, always round
778    rounded
779  }
780}
781
782/// Utility function to get the `StateManager` from the `StyleOptions` trait.
783/// This is a helper function to get the `StateManager` from the `StyleOptions` trait.
784pub(crate) fn downcast_style_options_to_state_manager(
785  state: &mut dyn StyleOptions,
786) -> &mut StateManager {
787  state
788    .as_any_mut()
789    .downcast_mut::<StateManager>()
790    .expect("StyleOptions must be StateManager")
791}