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 _ => 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
745pub(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 if decimal_places == 1 {
772 let diff = (value - rounded).abs();
773 if diff < 1e-10 { rounded } else { value }
776 } else {
777 rounded
779 }
780}
781
782pub(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}