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 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 pub(crate) style_map: FxHashMap<String, Rc<StylesObjectMap>>,
116 pub(crate) style_vars: FxHashMap<String, VarDeclarator>,
117
118 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 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 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 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 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 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 Some((
381 package_json.name,
382 package_json_path.to_string_lossy().into_owned(),
383 ))
384 } else {
385 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 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 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 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 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_treeshake_compensation(&self) -> bool {
753 self.options.treeshake_compensation
754 }
755
756 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}