stylex_transform/transform/
mod.rs1use 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 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 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}