Skip to main content

stylex_transform/transform/
mod.rs

1use rustc_hash::FxHashSet;
2use swc_core::{
3  atoms::Atom,
4  common::{Mark, comments::Comments},
5  ecma::{
6    ast::{CallExpr, Callee, Expr, Id, MemberProp, Pass, VarDeclarator},
7    transforms::{base::resolver, typescript::strip},
8    utils::drop_span,
9    visit::fold_pass,
10  },
11};
12
13use crate::shared::{structures::state_manager::StateManager, utils::common::increase_ident_count};
14use stylex_enums::core::TransformationCycle;
15use stylex_structures::{
16  named_import_source::{ImportSources, RuntimeInjection},
17  plugin_pass::PluginPass,
18  stylex_options::{CheckModuleResolution, StyleXOptions, StyleXOptionsParams},
19};
20
21mod fold;
22pub(crate) mod styleq;
23pub(crate) mod stylex;
24
25pub struct StyleXTransform<C>
26where
27  C: Comments,
28{
29  pub comments: C,
30  props_declaration: Option<Id>,
31  pub state: StateManager,
32}
33
34impl<C> StyleXTransform<C>
35where
36  C: Comments,
37{
38  pub fn new(comments: C, plugin_pass: PluginPass, config: &mut StyleXOptionsParams) -> Self {
39    let stylex_imports = fill_stylex_imports(&Some(config));
40
41    let mut state = StateManager::new(config.clone().into());
42
43    state.options.import_sources = stylex_imports.into_iter().collect();
44
45    state._state = plugin_pass;
46
47    StyleXTransform {
48      comments,
49      props_declaration: None,
50      state,
51    }
52  }
53
54  pub fn new_test_force_runtime_injection(
55    comments: C,
56    plugin_pass: PluginPass,
57    config: Option<&mut StyleXOptionsParams>,
58  ) -> Self {
59    let stylex_imports = fill_stylex_imports(&config);
60
61    let mut state = match config {
62      Some(config) => {
63        config.runtime_injection = Some(RuntimeInjection::Boolean(true));
64        config.treeshake_compensation = Some(true);
65
66        StateManager::new(config.clone().into())
67      },
68      None => {
69        let config = StyleXOptions {
70          runtime_injection: RuntimeInjection::Boolean(true),
71          treeshake_compensation: true,
72          unstable_module_resolution: CheckModuleResolution::Haste(
73            StyleXOptions::get_haste_module_resolution(None),
74          ),
75          ..Default::default()
76        };
77
78        StateManager::new(config)
79      },
80    };
81
82    state.options.import_sources = stylex_imports.into_iter().collect();
83
84    state._state = plugin_pass;
85
86    StyleXTransform {
87      comments,
88      props_declaration: None,
89      state,
90    }
91  }
92
93  pub fn new_test_force_runtime_injection_with_pass(
94    comments: C,
95    plugin_pass: PluginPass,
96    config: Option<&mut StyleXOptionsParams>,
97  ) -> impl Pass + use<C> {
98    let unresolved_mark = Mark::new();
99    let top_level_mark = Mark::new();
100
101    (
102      resolve_factory(unresolved_mark, top_level_mark),
103      // typescript_factory(unresolved_mark, top_level_mark),
104      fold_pass(Self::new_test_force_runtime_injection(
105        comments,
106        plugin_pass,
107        config,
108      )),
109    )
110  }
111
112  pub fn new_test(
113    comments: C,
114    plugin_pass: PluginPass,
115    config: Option<&mut StyleXOptionsParams>,
116  ) -> Self {
117    let stylex_imports = fill_stylex_imports(&config);
118
119    let mut state = match config {
120      Some(config) => StateManager::new(config.clone().into()),
121      None => {
122        let config = StyleXOptions {
123          runtime_injection: RuntimeInjection::Boolean(false),
124          treeshake_compensation: true,
125          class_name_prefix: "x".to_string(),
126          unstable_module_resolution: CheckModuleResolution::Haste(
127            StyleXOptions::get_haste_module_resolution(None),
128          ),
129          ..Default::default()
130        };
131
132        StateManager::new(config)
133      },
134    };
135
136    state.options.import_sources = stylex_imports.into_iter().collect();
137
138    state._state = plugin_pass;
139
140    StyleXTransform {
141      comments,
142      props_declaration: None,
143      state,
144    }
145  }
146  pub fn new_test_with_pass(
147    comments: C,
148    plugin_pass: PluginPass,
149    config: Option<&mut StyleXOptionsParams>,
150  ) -> impl Pass + use<C> {
151    let unresolved_mark = Mark::new();
152    let top_level_mark = Mark::new();
153
154    (
155      resolve_factory(unresolved_mark, top_level_mark),
156      // typescript_factory(unresolved_mark, top_level_mark),
157      fold_pass(Self::new_test(comments, plugin_pass, config)),
158    )
159  }
160
161  fn is_stylex_import(&self, ident_sym: &str) -> bool {
162    let state = &self.state;
163    let stylex_imports = state.stylex_import_stringified();
164
165    let ident_sym = Atom::from(ident_sym);
166
167    let ident_str = ident_sym.as_str();
168
169    if stylex_imports.iter().any(|s| s == ident_str) {
170      return true;
171    }
172
173    match state.cycle {
174      TransformationCycle::TransformEnter => {
175        state.stylex_create_import.contains(&ident_sym)
176          || state.stylex_define_vars_import.contains(&ident_sym)
177          || state.stylex_define_consts_import.contains(&ident_sym)
178          || state.stylex_define_marker_import.contains(&ident_sym)
179          || state.stylex_create_theme_import.contains(&ident_sym)
180          || state.stylex_position_try_import.contains(&ident_sym)
181          || state.stylex_keyframes_import.contains(&ident_sym)
182          || state.stylex_first_that_works_import.contains(&ident_sym)
183          || state.stylex_types_import.contains(&ident_sym)
184          || state.stylex_default_marker_import.contains(&ident_sym)
185          || state.stylex_when_import.contains(&ident_sym)
186      },
187      TransformationCycle::TransformExit => {
188        state.stylex_attrs_import.contains(&ident_sym)
189          || state.stylex_props_import.contains(&ident_sym)
190      },
191      _ => false,
192    }
193  }
194
195  pub(crate) fn process_declaration(&mut self, call_expr: &mut CallExpr) -> Option<(Id, String)> {
196    if let Callee::Expr(callee) = &mut call_expr.callee {
197      match callee.as_ref() {
198        Expr::Ident(ident) => {
199          if self.is_stylex_import(ident.sym.as_ref()) {
200            increase_ident_count(&mut self.state, ident);
201            return Some((ident.to_id(), ident.sym.to_string()));
202          }
203        },
204        Expr::Member(member) => {
205          if let (Expr::Ident(obj_ident), MemberProp::Ident(prop_ident)) =
206            (member.obj.as_ref(), &member.prop)
207            && self.is_stylex_import(obj_ident.sym.as_ref())
208          {
209            return Some((obj_ident.to_id(), prop_ident.sym.to_string()));
210          }
211        },
212        _ => {},
213      }
214    }
215
216    None
217  }
218
219  pub(crate) fn transform_call_expression(&mut self, expr: &mut Expr) -> Option<Expr> {
220    if let Expr::Call(ex) = expr {
221      let declaration = self.process_declaration(ex);
222
223      if declaration.is_some() {
224        return self.transform_stylex_fns(ex);
225      }
226    }
227
228    None
229  }
230
231  pub(crate) fn get_call_var_name(
232    &mut self,
233    call: &CallExpr,
234  ) -> (Option<String>, Option<VarDeclarator>) {
235    let mut var_name: Option<String> = None;
236
237    let parent_var_decl = self
238      .state
239      .declarations
240      .iter()
241      .find(|decl| match &decl.init {
242        Some(init) => {
243          if let Expr::Call(init_call) = init.as_ref() {
244            init_call == &drop_span(call.clone())
245          } else {
246            false
247          }
248        },
249        _ => false,
250      })
251      .cloned();
252
253    if let Some(ref parent_var_decl) = parent_var_decl
254      && let Some(ident) = parent_var_decl.name.as_ident()
255    {
256      var_name = Some(ident.sym.to_string());
257    }
258
259    (var_name, parent_var_decl)
260  }
261}
262
263fn fill_stylex_imports(config: &Option<&mut StyleXOptionsParams>) -> FxHashSet<ImportSources> {
264  let mut stylex_imports = FxHashSet::default();
265
266  stylex_imports.insert(ImportSources::Regular("stylex".to_string()));
267  stylex_imports.insert(ImportSources::Regular("@stylexjs/stylex".to_string()));
268
269  if let Some(stylex_imports_extends) = match config {
270    Some(config) => config.import_sources.clone(),
271    None => None,
272  } {
273    stylex_imports.extend(stylex_imports_extends)
274  }
275
276  stylex_imports
277}
278
279#[warn(clippy::extra_unused_type_parameters)]
280fn resolve_factory(unresolved_mark: Mark, top_level_mark: Mark) -> impl Pass {
281  resolver(unresolved_mark, top_level_mark, true)
282}
283
284#[warn(clippy::extra_unused_type_parameters)]
285fn _typescript_factory(unresolved_mark: Mark, top_level_mark: Mark) -> impl Pass {
286  strip(unresolved_mark, top_level_mark)
287}