Skip to main content

stylex_transform/shared/structures/
state_manager.rs

1use rustc_hash::{FxHashMap, FxHashSet};
2use std::hash::Hash;
3use std::path::Path;
4use std::{option::Option, rc::Rc};
5use stylex_macros::{stylex_panic, stylex_unimplemented};
6
7use indexmap::{IndexMap, IndexSet};
8use log::debug;
9use once_cell::sync::Lazy;
10use stylex_path_resolver::{
11  package_json::{PackageJsonExtended, find_closest_package_json_folder, get_package_json},
12  resolvers::{EXTENSIONS, resolve_file_path},
13  utils::relative_path,
14};
15use swc_core::{
16  atoms::Atom,
17  common::{DUMMY_SP, EqIgnoreSpan, FileName},
18  ecma::{
19    ast::{JSXAttrOrSpread, Module, NamedExport, Program},
20    utils::drop_span,
21  },
22};
23use swc_core::{
24  common::SyntaxContext,
25  ecma::ast::{
26    CallExpr, Callee, Decl, Expr, ExprStmt, Ident, ImportDecl, ImportDefaultSpecifier,
27    ImportNamedSpecifier, ImportPhase, ImportSpecifier, ModuleDecl, ModuleExportName, ModuleItem,
28    Pat, Stmt, Str, VarDecl, VarDeclKind, VarDeclarator,
29  },
30};
31
32use crate::shared::structures::types::InjectableStylesMap;
33use crate::shared::utils::ast::convertors::create_number_expr;
34use crate::shared::utils::common::stable_hash;
35use crate::shared::utils::common::{
36  extract_filename_from_path, extract_filename_with_ext_from_path, extract_path, round_f64,
37};
38use stylex_ast::ast::factories::create_binding_ident;
39use stylex_ast::ast::factories::{
40  create_expr_or_spread, create_key_value_prop, create_number_expr_or_spread,
41  create_object_expression, create_string_expr_or_spread, create_string_key_value_prop,
42};
43use stylex_constants::constants::common::{CONSTS_FILE_EXTENSION, DEFAULT_INJECT_PATH};
44use stylex_enums::core::TransformationCycle;
45use stylex_enums::counter_mode::CounterMode;
46use stylex_enums::import_path_resolution::{ImportPathResolution, ImportPathResolutionType};
47use stylex_enums::top_level_expression::TopLevelExpressionKind;
48use stylex_structures::style_vars_to_keep::StyleVarsToKeep;
49use stylex_structures::top_level_expression::TopLevelExpression;
50use stylex_types::enums::data_structures::injectable_style::InjectableStyleKind;
51
52use super::seen_value::SeenValue;
53use super::types::StylesObjectMap;
54use stylex_structures::named_import_source::{
55  ImportSources, NamedImportSource, RuntimeInjectionState,
56};
57use stylex_structures::plugin_pass::PluginPass;
58use stylex_structures::stylex_options::ModuleResolution;
59use stylex_structures::stylex_options::{CheckModuleResolution, StyleXOptions};
60use stylex_structures::stylex_state_options::StyleXStateOptions;
61use stylex_structures::uid_generator::UidGenerator;
62use stylex_types::structures::meta_data::MetaData;
63
64static TRANSFORMED_VARS_FILE_EXTENSION: Lazy<&'static str> = Lazy::new(|| ".transformed");
65
66type AtomHashMap = FxHashMap<Atom, i16>;
67type AtomHashSet = FxHashSet<Atom>;
68
69#[derive(Clone, Debug, PartialEq)]
70pub(crate) struct SeenValueWithVarDeclCount {
71  pub(crate) seen_value: SeenValue,
72  pub(crate) var_decl_count: Option<AtomHashMap>,
73}
74
75#[derive(Clone, Debug)]
76pub struct StateManager {
77  pub(crate) _state: PluginPass,
78
79  // Imports
80  pub(crate) import_paths: FxHashSet<String>,
81  pub(crate) stylex_import: FxHashSet<ImportSources>,
82  pub(crate) import_specifiers: Vec<String>,
83  pub(crate) stylex_props_import: AtomHashSet,
84  pub(crate) stylex_attrs_import: AtomHashSet,
85  pub(crate) stylex_create_import: AtomHashSet,
86  pub(crate) stylex_first_that_works_import: AtomHashSet,
87  pub(crate) stylex_keyframes_import: AtomHashSet,
88  pub(crate) stylex_define_vars_import: AtomHashSet,
89  pub(crate) stylex_define_marker_import: AtomHashSet,
90  pub(crate) stylex_define_consts_import: AtomHashSet,
91  pub(crate) stylex_create_theme_import: AtomHashSet,
92  pub(crate) stylex_position_try_import: AtomHashSet,
93  pub(crate) stylex_view_transition_class_import: AtomHashSet,
94  pub(crate) stylex_default_marker_import: AtomHashSet,
95  pub(crate) stylex_when_import: AtomHashSet,
96  pub(crate) stylex_types_import: AtomHashSet,
97  pub(crate) stylex_env_import: AtomHashSet,
98  pub(crate) inject_import_inserted: Option<(Ident, Ident)>,
99  pub(crate) export_id: Option<String>,
100
101  pub(crate) seen_module_source_code: Option<Box<(Program, Option<String>)>>,
102
103  pub(crate) class_name_declarations: Vec<Ident>,
104  pub(crate) function_name_declarations: Vec<Ident>,
105  pub(crate) declarations: Vec<VarDeclarator>,
106  pub(crate) top_level_expressions: Vec<TopLevelExpression>,
107  pub all_call_expressions: FxHashMap<u64, CallExpr>,
108  pub(crate) var_decl_count_map: AtomHashMap,
109  pub(crate) seen: FxHashMap<u64, Rc<SeenValueWithVarDeclCount>>,
110  pub(crate) css_property_seen: FxHashMap<String, String>,
111  pub(crate) span_cache: FxHashMap<u64, swc_core::common::Span>,
112  pub(crate) jsx_spread_attr_exprs_map: FxHashMap<Expr, Vec<JSXAttrOrSpread>>,
113
114  // `stylex.create` calls
115  pub(crate) style_map: FxHashMap<String, Rc<StylesObjectMap>>,
116  pub(crate) style_vars: FxHashMap<String, VarDeclarator>,
117
118  // results of `stylex.create` calls that should be kept
119  pub(crate) style_vars_to_keep: IndexSet<StyleVarsToKeep>,
120  pub(crate) member_object_ident_count_map: AtomHashMap,
121
122  pub(crate) in_stylex_create: bool,
123
124  pub(crate) options: StyleXStateOptions,
125  pub metadata: IndexMap<String, IndexSet<MetaData>>,
126  pub(crate) styles_to_inject: IndexMap<u64, Vec<ModuleItem>>,
127  pub(crate) prepend_include_module_items: Vec<ModuleItem>,
128  pub(crate) hoisted_module_items: Vec<ModuleItem>,
129  pub(crate) prepend_import_module_items: Vec<ModuleItem>,
130
131  pub(crate) other_injected_css_rules: InjectableStylesMap,
132  pub(crate) top_imports: Vec<ImportDecl>,
133  pub(crate) named_exports: FxHashSet<NamedExport>,
134
135  pub cycle: TransformationCycle,
136}
137
138impl Default for StateManager {
139  fn default() -> Self {
140    StateManager::new(StyleXOptions::default())
141  }
142}
143
144impl StateManager {
145  pub fn new(stylex_options: StyleXOptions) -> Self {
146    let options = StyleXStateOptions::from(stylex_options);
147
148    Self {
149      _state: PluginPass::default(),
150      import_paths: FxHashSet::default(),
151      stylex_import: FxHashSet::default(),
152      import_specifiers: vec![],
153      stylex_props_import: FxHashSet::default(),
154      stylex_attrs_import: FxHashSet::default(),
155      stylex_create_import: FxHashSet::default(),
156      stylex_first_that_works_import: FxHashSet::default(),
157      stylex_keyframes_import: FxHashSet::default(),
158      stylex_define_vars_import: FxHashSet::default(),
159      stylex_define_marker_import: FxHashSet::default(),
160      stylex_define_consts_import: FxHashSet::default(),
161      stylex_create_theme_import: FxHashSet::default(),
162      stylex_types_import: FxHashSet::default(),
163      stylex_position_try_import: FxHashSet::default(),
164      stylex_view_transition_class_import: FxHashSet::default(),
165      stylex_default_marker_import: FxHashSet::default(),
166      stylex_when_import: FxHashSet::default(),
167      stylex_env_import: FxHashSet::default(),
168      inject_import_inserted: None,
169      style_map: FxHashMap::default(),
170      style_vars: FxHashMap::default(),
171      style_vars_to_keep: IndexSet::default(),
172      member_object_ident_count_map: FxHashMap::default(),
173      export_id: None,
174
175      seen: FxHashMap::default(),
176      css_property_seen: FxHashMap::default(),
177      seen_module_source_code: None,
178      span_cache: FxHashMap::default(),
179
180      top_imports: vec![],
181      named_exports: FxHashSet::default(),
182
183      declarations: vec![],
184      class_name_declarations: vec![],
185      function_name_declarations: vec![],
186      top_level_expressions: vec![],
187      all_call_expressions: FxHashMap::default(),
188      var_decl_count_map: FxHashMap::default(),
189      jsx_spread_attr_exprs_map: FxHashMap::default(),
190
191      in_stylex_create: false,
192      options,
193
194      metadata: IndexMap::new(),
195      styles_to_inject: IndexMap::new(),
196      prepend_include_module_items: vec![],
197      prepend_import_module_items: vec![],
198      hoisted_module_items: vec![],
199
200      other_injected_css_rules: IndexMap::new(),
201
202      cycle: TransformationCycle::Initializing,
203    }
204  }
205
206  /// Applies the `env` configuration to the given identifiers and member_expressions maps.
207  /// This is the Rust equivalent of the JavaScript `applyStylexEnv` method.
208  pub(crate) fn apply_stylex_env(
209    &self,
210    identifiers: &mut super::types::FunctionMapIdentifiers,
211    member_expressions: &mut super::types::FunctionMapMemberExpression,
212  ) {
213    if self.options.env.is_empty() {
214      return;
215    }
216
217    let env = self.options.env.clone();
218
219    // For namespace imports (e.g., `import stylex from '@stylexjs/stylex'`),
220    // add `env` to member_expressions so `stylex.env.x` resolves.
221    for name in &self.stylex_import {
222      let member_expression = member_expressions.entry(name.clone()).or_default();
223      member_expression.insert(
224        "env".into(),
225        Box::new(super::functions::FunctionConfigType::EnvObject(env.clone())),
226      );
227    }
228
229    // For direct env imports (e.g., `import { env } from '@stylexjs/stylex'`),
230    // add the env object directly to identifiers.
231    for name in &self.stylex_env_import {
232      identifiers.insert(
233        name.clone(),
234        Box::new(super::functions::FunctionConfigType::EnvObject(env.clone())),
235      );
236    }
237  }
238
239  /// Gets the source code program if it exists and is not yet normalized
240  pub(crate) fn get_seen_module_source_code(&self) -> Option<(&Module, &Option<String>)> {
241    if let Some((program, source_code)) = self.seen_module_source_code.as_ref().map(|b| b.as_ref())
242      && let Program::Module(module) = program
243    {
244      return Some((module, source_code));
245    }
246
247    None
248  }
249
250  /// Sets the source code module (marks as not yet normalized)
251  pub(crate) fn set_seen_module_source_code(
252    &mut self,
253    module: &Module,
254    source_code: Option<String>,
255  ) {
256    self.seen_module_source_code = Some(Box::new((Program::Module(module.clone()), source_code)));
257  }
258
259  pub fn import_as(&self, import: &str) -> Option<String> {
260    for import_source in &self.options.import_sources {
261      match import_source {
262        ImportSources::Regular(_) => {},
263        ImportSources::Named(named) => {
264          if named.from.eq(import) {
265            return Some(named.r#as.to_string());
266          }
267        },
268      }
269    }
270
271    None
272  }
273
274  pub fn import_sources(&self) -> Vec<ImportSources> {
275    self.options.import_sources.to_vec()
276  }
277
278  pub fn import_sources_stringified(&self) -> Vec<String> {
279    self
280      .options
281      .import_sources
282      .iter()
283      .map(|import_source| match import_source {
284        ImportSources::Regular(regular) => regular.as_str(),
285        ImportSources::Named(named) => named.from.as_str(),
286      })
287      .map(ToString::to_string)
288      .collect()
289  }
290
291  pub fn stylex_import_stringified(&self) -> Vec<String> {
292    self
293      .stylex_import
294      .iter()
295      .map(|import_source| match &import_source {
296        ImportSources::Regular(regular) => regular.as_str(),
297        ImportSources::Named(named) => named.r#as.as_str(),
298      })
299      .map(ToString::to_string)
300      .collect()
301  }
302
303  pub(crate) fn is_test(&self) -> bool {
304    self.options.test
305  }
306
307  pub(crate) fn is_dev(&self) -> bool {
308    self.options.dev
309  }
310  pub(crate) fn is_debug(&self) -> bool {
311    self.options.debug
312  }
313
314  pub(crate) fn enable_inlined_conditional_merge(&self) -> bool {
315    self.options.enable_inlined_conditional_merge
316  }
317
318  pub(crate) fn get_short_filename(&self) -> String {
319    extract_filename_from_path(&self._state.filename)
320  }
321  pub(crate) fn get_filename(&self) -> &str {
322    extract_path(&self._state.filename)
323  }
324  pub(crate) fn get_filename_for_hashing(
325    &self,
326    package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
327  ) -> Option<String> {
328    let filename = self.get_filename();
329
330    let unstable_module_resolution = &self.options.unstable_module_resolution;
331
332    let theme_file_extension = match unstable_module_resolution {
333      CheckModuleResolution::CommonJS(ModuleResolution {
334        theme_file_extension,
335        ..
336      })
337      | CheckModuleResolution::Haste(ModuleResolution {
338        theme_file_extension,
339        ..
340      })
341      | CheckModuleResolution::CrossFileParsing(ModuleResolution {
342        theme_file_extension,
343        ..
344      }) => theme_file_extension.as_deref().unwrap_or(".stylex"),
345    };
346
347    if filename.is_empty() {
348      return None;
349    }
350
351    let consts_file_extension = format!("{}{}", theme_file_extension, CONSTS_FILE_EXTENSION);
352
353    let is_theme_file = matches_file_suffix(theme_file_extension, filename);
354    let is_consts_only_file = matches_file_suffix(&consts_file_extension, filename);
355
356    if !is_theme_file && !is_consts_only_file {
357      return None;
358    }
359
360    match unstable_module_resolution {
361      CheckModuleResolution::Haste(_) => {
362        let filename = FileName::Real(filename.into());
363        extract_filename_with_ext_from_path(&filename).map(|s| s.to_string())
364      },
365      CheckModuleResolution::CommonJS(_) | CheckModuleResolution::CrossFileParsing(_) => {
366        Some(self.get_canonical_file_path(filename, package_json_seen))
367      },
368    }
369  }
370  pub(crate) fn get_package_name_and_path(
371    filepath: &str,
372    package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
373  ) -> Option<(Option<String>, String)> {
374    let folder = Path::new(filepath).parent()?;
375    let package_json_path = find_closest_package_json_folder(Path::new(filepath));
376
377    if let Some(package_json_path) = package_json_path {
378      let (package_json, _) = get_package_json(&package_json_path, package_json_seen);
379      // Try to read and parse package.json
380      Some((
381        package_json.name,
382        package_json_path.to_string_lossy().into_owned(),
383      ))
384    } else {
385      // Recursively check parent directory if not at root
386      if folder.parent().is_some() && !folder.as_os_str().is_empty() {
387        StateManager::get_package_name_and_path(
388          folder.to_string_lossy().as_ref(),
389          package_json_seen,
390        )
391      } else {
392        None
393      }
394    }
395  }
396  pub(crate) fn get_canonical_file_path(
397    &self,
398    file_path: &str,
399    package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
400  ) -> String {
401    if let Some(pkg_info) = StateManager::get_package_name_and_path(file_path, package_json_seen) {
402      let (package_name, package_dir) = pkg_info;
403
404      let package_dir_path = Path::new(&package_dir);
405      let file_path = Path::new(file_path);
406      let relative_package_path = relative_path(file_path, package_dir_path);
407
408      if let Some(package_dir) = relative_package_path.to_str() {
409        // Normalize path separators to forward slashes for consistency across platforms
410        let normalized_path = package_dir.replace('\\', "/");
411        return format!(
412          "{}:{}",
413          package_name.unwrap_or_else(|| "_unknown_name_".to_string()),
414          normalized_path
415        );
416      }
417    }
418
419    if let Some(root_dir) = match &self.options.unstable_module_resolution {
420      CheckModuleResolution::CommonJS(module_resolution) => module_resolution.root_dir.as_deref(),
421      CheckModuleResolution::Haste(module_resolution) => module_resolution.root_dir.as_deref(),
422      CheckModuleResolution::CrossFileParsing(module_resolution) => {
423        module_resolution.root_dir.as_deref()
424      },
425    } {
426      let file_path = Path::new(file_path);
427      let root_dir = Path::new(root_dir);
428
429      if let Some(rel_path) = relative_path(file_path, root_dir).to_str() {
430        // Normalize path separators to forward slashes for consistency across platforms
431        let normalized_path = rel_path.replace('\\', "/");
432        return normalized_path;
433      }
434    };
435
436    let file_name = Path::new(file_path)
437      .file_name()
438      .unwrap_or_default()
439      .to_string_lossy();
440
441    format!("_unknown_path_:{}", file_name)
442  }
443
444  pub(crate) fn import_path_resolver(
445    &self,
446    import_path: &str,
447    package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
448  ) -> ImportPathResolution {
449    let source_file_path = self.get_filename();
450
451    if source_file_path.is_empty() {
452      return ImportPathResolution::False;
453    }
454
455    let theme_file_extension = (match &self.options.unstable_module_resolution {
456      CheckModuleResolution::CommonJS(module_resolution) => module_resolution,
457      CheckModuleResolution::Haste(module_resolution) => module_resolution,
458      CheckModuleResolution::CrossFileParsing(module_resolution) => module_resolution,
459    })
460    .theme_file_extension
461    .as_deref()
462    .unwrap_or(".stylex");
463
464    let consts_file_extension = format!("{}{}", theme_file_extension, CONSTS_FILE_EXTENSION);
465
466    let is_theme_file = matches_file_suffix(theme_file_extension, import_path);
467    let is_consts_only_file = matches_file_suffix(&consts_file_extension, import_path);
468
469    let is_valid_transformed_vars_file =
470      matches_file_suffix(&TRANSFORMED_VARS_FILE_EXTENSION, import_path);
471
472    if !is_theme_file && !is_valid_transformed_vars_file && !is_consts_only_file {
473      return ImportPathResolution::False;
474    }
475
476    match &self.options.unstable_module_resolution {
477      CheckModuleResolution::CommonJS(_) => {
478        let filename = self.get_filename();
479
480        let (_, root_dir) = StateManager::get_package_name_and_path(filename, package_json_seen)
481          .unwrap_or_else(|| stylex_panic!("Cannot get package name and path for: {}", filename));
482
483        let aliases = self.options.aliases.as_ref().cloned().unwrap_or_default();
484
485        let resolved_file_path = file_path_resolver(
486          import_path,
487          source_file_path,
488          &root_dir,
489          &aliases,
490          package_json_seen,
491        );
492
493        debug!("Resolved import path: {}", resolved_file_path);
494
495        let resolved_file_path =
496          self.get_canonical_file_path(&resolved_file_path, package_json_seen);
497
498        ImportPathResolution::Tuple(ImportPathResolutionType::ThemeNameRef, resolved_file_path)
499      },
500      CheckModuleResolution::Haste(_) => ImportPathResolution::Tuple(
501        ImportPathResolutionType::ThemeNameRef,
502        add_file_extension(import_path, source_file_path),
503      ),
504      _ => stylex_unimplemented!("This module resolution strategy is not yet supported."),
505    }
506  }
507
508  pub(crate) fn find_top_level_expr(
509    &self,
510    call: &CallExpr,
511    extended_predicate_fn: impl Fn(&TopLevelExpression) -> bool,
512    kind: Option<TopLevelExpressionKind>,
513  ) -> Option<&TopLevelExpression> {
514    self.top_level_expressions.iter().find(|tpe| {
515      kind.is_none_or(|kind| tpe.0 == kind)
516        && (matches!(tpe.1, Expr::Call(ref c) if c.eq_ignore_span(call))
517          || extended_predicate_fn(tpe))
518    })
519  }
520
521  pub(crate) fn find_call_declaration(&self, call: &CallExpr) -> Option<&VarDeclarator> {
522    self.declarations.iter().find(|decl| {
523      decl
524        .init
525        .as_ref()
526        .is_some_and(|expr| matches!(**expr, Expr::Call(ref c) if c.eq_ignore_span(call)))
527    })
528  }
529
530  pub(crate) fn register_styles(
531    &mut self,
532    call: &CallExpr,
533    style: &InjectableStylesMap,
534    ast: &Expr,
535    fallback_ast: Option<&Expr>,
536  ) {
537    // Early return if there are no styles to process
538    if style.is_empty() {
539      return;
540    }
541
542    let metadatas = MetaData::convert_from_injected_styles_map(style);
543    if metadatas.is_empty() {
544      return;
545    }
546
547    let needs_runtime_injection = style.values().any(|value| {
548      matches!(
549        value.as_ref(),
550        InjectableStyleKind::Regular(_) | InjectableStyleKind::Const(_)
551      )
552    });
553
554    let inject_var_ident = if needs_runtime_injection {
555      Some(self.setup_injection_imports())
556    } else {
557      None
558    };
559
560    for metadata in metadatas {
561      self.add_style(&metadata);
562
563      if let Some(ref inject_var_ident) = inject_var_ident {
564        self.add_style_to_inject(&metadata, inject_var_ident, ast, fallback_ast);
565      }
566    }
567
568    // Update all references to this call expression with the new AST
569    self.update_references(call, ast, fallback_ast);
570  }
571
572  fn setup_injection_imports(&mut self) -> Ident {
573    if !self.prepend_include_module_items.is_empty() {
574      return match self.inject_import_inserted.as_ref() {
575        Some(idents) => idents.1.clone(),
576        None => stylex_panic!(
577          "inject_import_inserted is None when prepend_include_module_items is non-empty"
578        ),
579      };
580    }
581    let mut uid_generator = UidGenerator::new("inject", CounterMode::Local);
582
583    let runtime_injection = self
584      .options
585      .runtime_injection
586      .as_ref()
587      .cloned()
588      .unwrap_or(RuntimeInjectionState::Boolean(true));
589
590    let (inject_module_ident, inject_var_ident) = match self.inject_import_inserted.take() {
591      Some(idents) => idents,
592      None => {
593        let module_ident = uid_generator.generate_ident();
594
595        let var_ident = match &runtime_injection {
596          RuntimeInjectionState::Regular(_) | RuntimeInjectionState::Boolean(_) => {
597            uid_generator.generate_ident()
598          },
599          RuntimeInjectionState::Named(NamedImportSource { r#as, .. }) => {
600            uid_generator = UidGenerator::new(r#as, CounterMode::Local);
601            uid_generator.generate_ident()
602          },
603        };
604
605        let idents = (module_ident, var_ident);
606        self.inject_import_inserted = Some(idents.clone());
607
608        idents
609      },
610    };
611
612    let module_items = match &runtime_injection {
613      RuntimeInjectionState::Boolean(_) => vec![
614        add_inject_default_import_expression(&inject_module_ident, None),
615        add_inject_var_decl_expression(&inject_var_ident, &inject_module_ident),
616      ],
617      RuntimeInjectionState::Regular(name) => vec![
618        add_inject_default_import_expression(&inject_module_ident, Some(name)),
619        add_inject_var_decl_expression(&inject_var_ident, &inject_module_ident),
620      ],
621      RuntimeInjectionState::Named(_) => vec![
622        add_inject_named_import_expression(&inject_module_ident, &inject_var_ident),
623        add_inject_var_decl_expression(&inject_var_ident, &inject_module_ident),
624      ],
625    };
626
627    self.prepend_include_module_items.extend(module_items);
628    inject_var_ident
629  }
630
631  fn update_references(&mut self, call: &CallExpr, ast: &Expr, _fallback_ast: Option<&Expr>) {
632    if let Some(item) = self.declarations.iter_mut().find(|decl| {
633      decl.init.as_ref().is_some_and(
634        |expr| matches!(**expr, Expr::Call(ref existing_call) if existing_call == call),
635      )
636    }) {
637      item.init = Some(Box::new(ast.clone()));
638    }
639
640    if let Some((_, item)) = self.style_vars.iter_mut().find(|(_, decl)| {
641      decl.init.as_ref().is_some_and(
642        |expr| matches!(**expr, Expr::Call(ref existing_call) if existing_call == call),
643      )
644    }) {
645      item.init = Some(Box::new(ast.clone()));
646    }
647
648    if let Some(top_level_expr) = self
649      .top_level_expressions
650      .iter_mut()
651      .find(|TopLevelExpression(_, expr, _)| matches!(expr, Expr::Call(c) if c == call))
652    {
653      top_level_expr.1 = ast.clone();
654    }
655
656    let call_hash = stable_hash(call);
657
658    self.all_call_expressions.remove(&call_hash);
659
660    if let Some(call_expr) = ast.as_call() {
661      let new_call_hash = stable_hash(call_expr);
662
663      self
664        .all_call_expressions
665        .insert(new_call_hash, call_expr.clone());
666    }
667  }
668
669  fn add_style(&mut self, metadata: &MetaData) {
670    let var_name = "stylex";
671    let value = self.metadata.entry(var_name.to_string()).or_default();
672
673    if !value.contains(metadata) {
674      value.insert(metadata.clone());
675    }
676  }
677
678  fn add_style_to_inject(
679    &mut self,
680    metadata: &MetaData,
681    inject_var_ident: &Ident,
682    ast: &Expr,
683    fallback_ast: Option<&Expr>,
684  ) {
685    let priority = metadata.get_priority();
686    let css_ltr = metadata.get_css();
687    let css_rtl = metadata.get_css_rtl();
688    let const_key = metadata.get_const_key();
689    let const_value = metadata.get_const_value();
690
691    let mut stylex_inject_args = vec![
692      create_string_key_value_prop("ltr", css_ltr),
693      create_key_value_prop("priority", create_number_expr(round_f64(*priority, 1))),
694    ];
695
696    if let Some(const_key) = const_key
697      && let Some(const_value) = const_value
698    {
699      let const_value_expr = match const_value.parse::<f64>() {
700        Ok(value) => create_number_expr_or_spread(value),
701        Err(_) => create_string_expr_or_spread(const_value),
702      };
703
704      stylex_inject_args.push(create_string_key_value_prop("constKey", const_key));
705      stylex_inject_args.push(create_key_value_prop("constVal", *const_value_expr.expr));
706    }
707
708    if let Some(rtl) = css_rtl {
709      stylex_inject_args.push(create_string_key_value_prop("rtl", rtl));
710    }
711
712    let stylex_inject_obj = create_object_expression(stylex_inject_args);
713
714    let stylex_call_expr = CallExpr {
715      span: DUMMY_SP,
716      type_args: None,
717      callee: Callee::Expr(Box::new(Expr::Ident(inject_var_ident.clone()))),
718      args: vec![create_expr_or_spread(stylex_inject_obj)],
719      ctxt: SyntaxContext::empty(),
720    };
721
722    let stylex_call = Expr::Call(stylex_call_expr);
723
724    let module = ModuleItem::Stmt(Stmt::Expr(ExprStmt {
725      span: DUMMY_SP,
726      expr: Box::new(stylex_call),
727    }));
728
729    let ast_hash = stable_hash(ast);
730
731    let styles_to_inject = self.styles_to_inject.entry(ast_hash).or_default();
732
733    if !styles_to_inject.contains(&drop_span(module.clone())) {
734      styles_to_inject.push(drop_span(module.clone()));
735    }
736
737    if let Some(fallback_ast) = fallback_ast {
738      let fallback_ast_hash = stable_hash(fallback_ast);
739
740      let fallback_styles_to_inject = self.styles_to_inject.entry(fallback_ast_hash).or_default();
741
742      if !fallback_styles_to_inject.contains(&drop_span(module.clone())) {
743        fallback_styles_to_inject.push(drop_span(module.clone()));
744      }
745    }
746  }
747
748  // pub(crate) fn _get_css_vars(&self) -> FxHashMap<String, String> {
749  //   self.options.defined_stylex_css_variables.clone()
750  // }
751
752  pub(crate) fn get_treeshake_compensation(&self) -> bool {
753    self.options.treeshake_compensation
754  }
755
756  // Now you can use these helper functions to simplify your function
757  pub fn combine(&mut self, other: &Self) {
758    self.import_paths = union_hash_set(&self.import_paths, &other.import_paths);
759    self.stylex_import = union_hash_set(&self.stylex_import, &other.stylex_import);
760    self.stylex_props_import =
761      union_hash_set(&self.stylex_props_import, &other.stylex_props_import);
762    self.stylex_create_import =
763      union_hash_set(&self.stylex_create_import, &other.stylex_create_import);
764    self.stylex_first_that_works_import = union_hash_set(
765      &self.stylex_first_that_works_import,
766      &other.stylex_first_that_works_import,
767    );
768    self.stylex_keyframes_import = union_hash_set(
769      &self.stylex_keyframes_import,
770      &other.stylex_keyframes_import,
771    );
772    self.stylex_define_vars_import = union_hash_set(
773      &self.stylex_define_vars_import,
774      &other.stylex_define_vars_import,
775    );
776    self.stylex_create_theme_import = union_hash_set(
777      &self.stylex_create_theme_import,
778      &other.stylex_create_theme_import,
779    );
780    self.stylex_types_import =
781      union_hash_set(&self.stylex_types_import, &other.stylex_types_import);
782    self.inject_import_inserted = self
783      .inject_import_inserted
784      .clone()
785      .or(other.inject_import_inserted.clone());
786    self.export_id = self.export_id.clone().or(other.export_id.clone());
787    self.declarations = chain_collect(self.declarations.clone(), other.declarations.clone());
788    self.top_level_expressions = chain_collect(
789      self.top_level_expressions.clone(),
790      other.top_level_expressions.clone(),
791    );
792
793    self.all_call_expressions = chain_collect_hash_map(
794      self.all_call_expressions.clone(),
795      other.all_call_expressions.clone(),
796    );
797    self.var_decl_count_map = chain_collect_hash_map(
798      self.var_decl_count_map.clone(),
799      other.var_decl_count_map.clone(),
800    );
801    self.style_map = chain_collect_hash_map(self.style_map.clone(), other.style_map.clone());
802    self.style_vars = chain_collect_hash_map(self.style_vars.clone(), other.style_vars.clone());
803    self.style_vars_to_keep =
804      union_index_set(&self.style_vars_to_keep.clone(), &other.style_vars_to_keep);
805    self.member_object_ident_count_map = chain_collect_hash_map(
806      self.member_object_ident_count_map.clone(),
807      other.member_object_ident_count_map.clone(),
808    );
809    self.stylex_env_import = union_hash_set(&self.stylex_env_import, &other.stylex_env_import);
810    self.in_stylex_create = self.in_stylex_create || other.in_stylex_create;
811
812    self.metadata = chain_collect_index_map(self.metadata.clone(), other.metadata.clone());
813    self.seen = chain_collect_hash_map(self.seen.clone(), other.seen.clone());
814    self.styles_to_inject = chain_collect_index_map(
815      self.styles_to_inject.clone(),
816      other.styles_to_inject.clone(),
817    );
818    self.prepend_include_module_items = chain_collect(
819      self.prepend_include_module_items.clone(),
820      other.prepend_include_module_items.clone(),
821    );
822    self.prepend_import_module_items = chain_collect(
823      self.prepend_import_module_items.clone(),
824      other.prepend_import_module_items.clone(),
825    );
826    self.other_injected_css_rules = chain_collect_index_map(
827      self.other_injected_css_rules.clone(),
828      other.other_injected_css_rules.clone(),
829    );
830    self.top_imports = chain_collect(self.top_imports.clone(), other.top_imports.clone());
831  }
832}
833
834fn add_inject_default_import_expression(ident: &Ident, inject_path: Option<&str>) -> ModuleItem {
835  ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
836    span: DUMMY_SP,
837    specifiers: vec![ImportSpecifier::Default(ImportDefaultSpecifier {
838      span: DUMMY_SP,
839      local: ident.clone(),
840    })],
841    src: Box::new(Str {
842      span: DUMMY_SP,
843      raw: None,
844      value: inject_path.unwrap_or(DEFAULT_INJECT_PATH).into(),
845    }),
846    type_only: false,
847    with: None,
848    phase: ImportPhase::Evaluation,
849  }))
850}
851
852pub(crate) fn add_import_expression(path: &str) -> ModuleItem {
853  ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
854    span: DUMMY_SP,
855    specifiers: vec![],
856    src: Box::new(Str {
857      span: DUMMY_SP,
858      raw: None,
859      value: path.into(),
860    }),
861    type_only: false,
862    with: None,
863    phase: ImportPhase::Evaluation,
864  }))
865}
866
867fn add_inject_named_import_expression(ident: &Ident, imported_ident: &Ident) -> ModuleItem {
868  ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
869    span: DUMMY_SP,
870    specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
871      span: DUMMY_SP,
872      local: ident.clone(),
873      imported: Some(ModuleExportName::Ident(imported_ident.clone())),
874      is_type_only: false,
875    })],
876    src: Box::new(Str {
877      span: DUMMY_SP,
878      raw: None,
879      value: DEFAULT_INJECT_PATH.into(),
880    }),
881    type_only: false,
882    with: None,
883    phase: ImportPhase::Evaluation,
884  }))
885}
886
887fn add_inject_var_decl_expression(decl_ident: &Ident, value_ident: &Ident) -> ModuleItem {
888  ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
889    declare: false,
890    decls: vec![VarDeclarator {
891      definite: true,
892      span: DUMMY_SP,
893      name: Pat::from(create_binding_ident(decl_ident.clone())),
894      init: Some(Box::new(Expr::from(value_ident.clone()))),
895    }],
896    kind: VarDeclKind::Var,
897    span: DUMMY_SP,
898    ctxt: SyntaxContext::empty(),
899  }))))
900}
901
902pub(crate) fn matches_file_suffix(allowed_suffix: &str, filename: &str) -> bool {
903  if filename.ends_with(allowed_suffix) {
904    return true;
905  }
906
907  EXTENSIONS.iter().any(|&suffix| {
908    let suffix = if allowed_suffix.is_empty() {
909      suffix
910    } else {
911      &format!("{}{}", allowed_suffix, suffix)[..]
912    };
913    filename.ends_with(suffix)
914  })
915}
916
917fn add_file_extension(imported_file_path: &str, source_file: &str) -> String {
918  if EXTENSIONS
919    .iter()
920    .any(|ext| imported_file_path.ends_with(ext))
921  {
922    return imported_file_path.to_string();
923  }
924
925  let file_extension = Path::new(source_file)
926    .extension()
927    .and_then(std::ffi::OsStr::to_str)
928    .unwrap_or_default();
929
930  if file_extension.is_empty() {
931    return imported_file_path.to_string();
932  }
933
934  format!("{}.{}", imported_file_path, file_extension)
935}
936
937fn chain_collect<T: Clone + Eq + PartialEq>(vec1: Vec<T>, vec2: Vec<T>) -> Vec<T> {
938  if vec1 == vec2 {
939    return vec1;
940  }
941
942  if vec1.len() < vec2.len() {
943    let commom_part = vec2.iter().take(vec1.len()).cloned().collect::<Vec<T>>();
944
945    if commom_part == vec1 {
946      return vec2;
947    }
948
949    let mut vec = vec1.clone();
950
951    vec.retain(|item| vec2.contains(item));
952
953    for item in vec2.iter() {
954      if !vec.contains(item) {
955        vec.push(item.clone());
956      }
957    }
958
959    return vec;
960  }
961
962  let mut vec = vec1.clone();
963
964  vec.retain(|item| !vec2.contains(item));
965
966  for item in vec2.iter() {
967    if !vec.contains(item) {
968      vec.push(item.clone());
969    }
970  }
971
972  vec
973}
974
975fn union_hash_set<T: Clone + Eq + Hash>(set1: &FxHashSet<T>, set2: &FxHashSet<T>) -> FxHashSet<T> {
976  set1.union(set2).cloned().collect()
977}
978
979fn chain_collect_hash_map<K: Eq + Hash, V: Clone + PartialEq>(
980  map1: FxHashMap<K, V>,
981  map2: FxHashMap<K, V>,
982) -> FxHashMap<K, V> {
983  if map1 == map2 {
984    return map1;
985  }
986  map1.into_iter().chain(map2).collect()
987}
988
989fn union_index_set<T: Clone + Eq + Hash>(set1: &IndexSet<T>, set2: &IndexSet<T>) -> IndexSet<T> {
990  if set1 == set2 {
991    return set1.clone();
992  }
993
994  set1.union(set2).cloned().collect()
995}
996
997fn chain_collect_index_map<K: Eq + Hash, V: Clone + PartialEq>(
998  map1: IndexMap<K, V>,
999  map2: IndexMap<K, V>,
1000) -> IndexMap<K, V> {
1001  if map1 == map2 {
1002    return map1;
1003  }
1004
1005  map1.into_iter().chain(map2).collect()
1006}
1007
1008fn file_path_resolver(
1009  relative_file_path: &str,
1010  source_file_path: &str,
1011  root_path: &str,
1012  aliases: &FxHashMap<String, Vec<String>>,
1013  package_json_seen: &mut FxHashMap<String, PackageJsonExtended>,
1014) -> String {
1015  let resolved_file_path = resolve_file_path(
1016    relative_file_path,
1017    source_file_path,
1018    root_path,
1019    aliases,
1020    package_json_seen,
1021  );
1022
1023  if let Ok(resolved_path) = resolved_file_path {
1024    let resolved_path_str = resolved_path.display().to_string();
1025
1026    return resolved_path_str;
1027  }
1028
1029  stylex_panic!("Cannot resolve file path: {}", relative_file_path)
1030}
1031
1032impl stylex_types::traits::StyleOptions for StateManager {
1033  fn options(&self) -> &StyleXStateOptions {
1034    &self.options
1035  }
1036
1037  fn css_property_seen(&self) -> &FxHashMap<String, String> {
1038    &self.css_property_seen
1039  }
1040
1041  fn css_property_seen_mut(&mut self) -> &mut FxHashMap<String, String> {
1042    &mut self.css_property_seen
1043  }
1044
1045  fn other_injected_css_rules(&self) -> &stylex_types::traits::InjectableStylesMap {
1046    &self.other_injected_css_rules
1047  }
1048
1049  fn other_injected_css_rules_mut(&mut self) -> &mut stylex_types::traits::InjectableStylesMap {
1050    &mut self.other_injected_css_rules
1051  }
1052
1053  fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
1054    self
1055  }
1056}