Skip to main content

stylex_compiler_rs/utils/
fn_parser.rs

1use std::rc::Rc;
2
3use indexmap::IndexMap;
4use log::debug;
5use napi::{JsNumber, JsObject, JsString, JsValue, Unknown, ValueType};
6use stylex_ast::ast::{
7  convertors::{create_bool_expr, create_null_expr, create_number_expr, create_string_expr},
8  factories::{create_array_expression, create_key_value_prop, create_object_expression},
9};
10use stylex_structures::stylex_env::{EnvEntry, JSFunction};
11use stylex_utils::swc::get_default_expr_ctx;
12use swc_core::ecma::{
13  ast::{Expr, ExprOrSpread, Lit, PropName, PropOrSpread},
14  utils::ExprExt,
15};
16
17thread_local! {
18  static NAPI_ENV_RAW: std::cell::Cell<Option<napi::sys::napi_env>> =
19    const { std::cell::Cell::new(None) };
20}
21
22/// Sets the NAPI env for the duration of a closure, then clears it.
23pub(crate) fn with_napi_env<F, R>(env: &napi::Env, f: F) -> R
24where
25  F: FnOnce() -> R,
26{
27  NAPI_ENV_RAW.set(Some(env.raw()));
28  let result = f();
29  NAPI_ENV_RAW.set(None);
30  result
31}
32
33/// Parses a JS object into an `IndexMap<String, EnvEntry>`.
34pub(crate) fn parse_env_object(
35  env: &napi::Env,
36  obj: &JsObject,
37) -> napi::Result<IndexMap<String, EnvEntry>> {
38  let names = obj.get_property_names()?;
39  let len = names.get_array_length()?;
40  let mut map = IndexMap::new();
41
42  for i in 0..len {
43    let key: JsString = names.get_element(i)?;
44    let key_str = key.into_utf8()?.as_str()?.to_string();
45    let value: Unknown = obj.get_named_property(&key_str)?;
46    let env_entry = parse_env_value(env, value)?;
47    map.insert(key_str, env_entry);
48  }
49
50  Ok(map)
51}
52
53fn parse_env_value(env: &napi::Env, value: Unknown) -> napi::Result<EnvEntry> {
54  match value.get_type()? {
55    ValueType::String => {
56      let s: JsString = unsafe { value.cast()? };
57      Ok(EnvEntry::Expr(create_string_expr(s.into_utf8()?.as_str()?)))
58    },
59    ValueType::Number => {
60      let n: JsNumber = unsafe { value.cast()? };
61      Ok(EnvEntry::Expr(create_number_expr(n.get_double()?)))
62    },
63    ValueType::Boolean => {
64      let raw_env = env.raw();
65      let mut b = false;
66      let status = unsafe { napi::sys::napi_get_value_bool(raw_env, value.raw(), &mut b) };
67      if status != napi::sys::Status::napi_ok {
68        return Err(napi::Error::from_reason("Failed to get boolean value"));
69      }
70      Ok(EnvEntry::Expr(create_bool_expr(b)))
71    },
72    ValueType::Null | ValueType::Undefined => Ok(EnvEntry::Expr(create_null_expr())),
73    ValueType::Function => parse_env_function(env, value.raw()),
74    ValueType::Object => Ok(EnvEntry::Expr(napi_value_to_expr(env.raw(), value.raw()))),
75    _ => Ok(EnvEntry::Expr(create_null_expr())),
76  }
77}
78
79fn parse_env_function(env: &napi::Env, js_fn_raw: napi::sys::napi_value) -> napi::Result<EnvEntry> {
80  let raw_env = env.raw();
81
82  let mut ref_ptr: napi::sys::napi_ref = std::ptr::null_mut();
83  let status = unsafe { napi::sys::napi_create_reference(raw_env, js_fn_raw, 1, &mut ref_ptr) };
84
85  if status != napi::sys::Status::napi_ok {
86    return Err(napi::Error::from_reason(
87      "Failed to create reference for env function",
88    ));
89  }
90
91  let ref_ptr = Rc::new(EnvFnRef {
92    ref_ptr,
93    env: raw_env,
94  });
95
96  Ok(EnvEntry::Function(JSFunction::new(
97    move |args: Vec<Expr>| {
98      let raw_env = NAPI_ENV_RAW
99        .get()
100        .expect("NAPI env not available during env function call");
101
102      let mut js_fn_raw: napi::sys::napi_value = std::ptr::null_mut();
103      unsafe {
104        napi::sys::napi_get_reference_value(raw_env, ref_ptr.ref_ptr, &mut js_fn_raw);
105      }
106
107      let js_args: Vec<napi::sys::napi_value> = args
108        .iter()
109        .map(|arg| expr_to_napi_value(raw_env, arg))
110        .collect();
111
112      let mut result: napi::sys::napi_value = std::ptr::null_mut();
113      let mut undefined: napi::sys::napi_value = std::ptr::null_mut();
114      unsafe {
115        napi::sys::napi_get_undefined(raw_env, &mut undefined);
116        napi::sys::napi_call_function(
117          raw_env,
118          undefined,
119          js_fn_raw,
120          js_args.len(),
121          js_args.as_ptr(),
122          &mut result,
123        );
124      }
125
126      napi_value_to_expr(raw_env, result)
127    },
128  )))
129}
130
131fn expr_to_napi_value(raw_env: napi::sys::napi_env, expr: &Expr) -> napi::sys::napi_value {
132  let mut result: napi::sys::napi_value = std::ptr::null_mut();
133  match expr {
134    Expr::Lit(Lit::Str(s)) => {
135      let val = s.value.as_str().unwrap_or("");
136      unsafe {
137        napi::sys::napi_create_string_utf8(
138          raw_env,
139          val.as_ptr() as *const _,
140          val.len() as isize,
141          &mut result,
142        );
143      }
144    },
145    Expr::Lit(Lit::Num(n)) => unsafe {
146      napi::sys::napi_create_double(raw_env, n.value, &mut result);
147    },
148    Expr::Lit(Lit::Bool(b)) => unsafe {
149      napi::sys::napi_get_boolean(raw_env, b.value, &mut result);
150    },
151    Expr::Object(obj) => unsafe {
152      napi::sys::napi_create_object(raw_env, &mut result);
153      for prop in &obj.props {
154        if let PropOrSpread::Prop(prop) = prop
155          && let Some(kv) = prop.as_key_value()
156        {
157          let key = match &kv.key {
158            PropName::Ident(id) => id.sym.to_string(),
159            PropName::Str(s) => s.value.as_str().unwrap_or("").to_string(),
160            PropName::Num(n) => n.value.to_string(),
161            _ => continue,
162          };
163          let prop_val = expr_to_napi_value(raw_env, &kv.value);
164          let mut key_val: napi::sys::napi_value = std::ptr::null_mut();
165          napi::sys::napi_create_string_utf8(
166            raw_env,
167            key.as_ptr() as *const _,
168            key.len() as isize,
169            &mut key_val,
170          );
171          napi::sys::napi_set_property(raw_env, result, key_val, prop_val);
172        }
173      }
174    },
175    Expr::Array(arr) => unsafe {
176      napi::sys::napi_create_array_with_length(raw_env, arr.elems.len(), &mut result);
177      for (i, elem) in arr.elems.iter().enumerate() {
178        let elem_val = match elem {
179          Some(e) => expr_to_napi_value(raw_env, &e.expr),
180          None => {
181            let mut undef: napi::sys::napi_value = std::ptr::null_mut();
182            napi::sys::napi_get_undefined(raw_env, &mut undef);
183            undef
184          },
185        };
186        napi::sys::napi_set_element(raw_env, result, i as u32, elem_val);
187      }
188    },
189    _ => {
190      debug!("Unsupported napi value type: {:#?}.", expr);
191
192      panic!(
193        "Unsupported napi value type: {:?}. If its not enough, please run in debug mode to see more details",
194        expr.get_type(get_default_expr_ctx())
195      );
196    },
197  }
198  result
199}
200
201/// Parses a JS string or function into a `JSFunction` for use as `debugFilePath`.
202pub(crate) fn parse_debug_file_path(
203  env: &napi::Env,
204  unknown_val: Unknown,
205) -> napi::Result<JSFunction> {
206  let raw_val = unknown_val.raw();
207  let raw_env = env.raw();
208
209  let mut val_type: napi::sys::napi_valuetype = napi::sys::ValueType::napi_undefined;
210  unsafe { napi::sys::napi_typeof(raw_env, raw_val, &mut val_type) };
211
212  match val_type {
213    napi::sys::ValueType::napi_string => {
214      let mut len = 0;
215      unsafe {
216        napi::sys::napi_get_value_string_utf8(raw_env, raw_val, std::ptr::null_mut(), 0, &mut len);
217      }
218      let mut buf = vec![0u8; len + 1];
219      let mut written = 0;
220      unsafe {
221        napi::sys::napi_get_value_string_utf8(
222          raw_env,
223          raw_val,
224          buf.as_mut_ptr() as *mut _,
225          len + 1,
226          &mut written,
227        );
228      }
229      buf.truncate(written);
230      let s = String::from_utf8(buf).unwrap_or_default();
231      Ok(JSFunction::new(move |_args| create_string_expr(&s)))
232    },
233    napi::sys::ValueType::napi_function => {
234      parse_env_function(env, raw_val).and_then(|entry| match entry {
235        EnvEntry::Function(f) => Ok(f),
236        _ => Err(napi::Error::from_reason(
237          "Expected function from parse_env_function",
238        )),
239      })
240    },
241    _ => Err(napi::Error::from_reason(
242      "debugFilePath must be a string or function",
243    )),
244  }
245}
246
247fn read_napi_string(raw_env: napi::sys::napi_env, value: napi::sys::napi_value) -> String {
248  let mut len = 0;
249  unsafe {
250    napi::sys::napi_get_value_string_utf8(raw_env, value, std::ptr::null_mut(), 0, &mut len);
251  }
252  let mut buf = vec![0u8; len + 1];
253  let mut written = 0;
254  unsafe {
255    napi::sys::napi_get_value_string_utf8(
256      raw_env,
257      value,
258      buf.as_mut_ptr() as *mut _,
259      len + 1,
260      &mut written,
261    );
262  }
263  buf.truncate(written);
264  String::from_utf8(buf).unwrap_or_default()
265}
266
267fn napi_value_to_expr(raw_env: napi::sys::napi_env, value: napi::sys::napi_value) -> Expr {
268  let mut val_type: napi::sys::napi_valuetype = napi::sys::ValueType::napi_undefined;
269  unsafe {
270    napi::sys::napi_typeof(raw_env, value, &mut val_type);
271  }
272
273  match val_type {
274    napi::sys::ValueType::napi_string => create_string_expr(&read_napi_string(raw_env, value)),
275    napi::sys::ValueType::napi_number => {
276      let mut n: f64 = 0.0;
277      unsafe {
278        napi::sys::napi_get_value_double(raw_env, value, &mut n);
279      }
280      create_number_expr(n)
281    },
282    napi::sys::ValueType::napi_boolean => {
283      let mut b = false;
284      unsafe {
285        napi::sys::napi_get_value_bool(raw_env, value, &mut b);
286      }
287      create_bool_expr(b)
288    },
289    napi::sys::ValueType::napi_object => {
290      let mut is_array = false;
291      unsafe {
292        napi::sys::napi_is_array(raw_env, value, &mut is_array);
293      }
294
295      if is_array {
296        let mut length: u32 = 0;
297        unsafe {
298          napi::sys::napi_get_array_length(raw_env, value, &mut length);
299        }
300
301        let elems: Vec<Option<ExprOrSpread>> = (0..length)
302          .map(|i| {
303            let mut elem_val: napi::sys::napi_value = std::ptr::null_mut();
304            unsafe {
305              napi::sys::napi_get_element(raw_env, value, i, &mut elem_val);
306            }
307            Some(ExprOrSpread {
308              spread: None,
309              expr: Box::new(napi_value_to_expr(raw_env, elem_val)),
310            })
311          })
312          .collect();
313
314        create_array_expression(elems)
315      } else {
316        let mut props = Vec::new();
317
318        let mut property_names: napi::sys::napi_value = std::ptr::null_mut();
319        unsafe {
320          napi::sys::napi_get_property_names(raw_env, value, &mut property_names);
321        }
322
323        let mut length: u32 = 0;
324        unsafe {
325          napi::sys::napi_get_array_length(raw_env, property_names, &mut length);
326        }
327
328        for i in 0..length {
329          let mut key_val: napi::sys::napi_value = std::ptr::null_mut();
330          unsafe {
331            napi::sys::napi_get_element(raw_env, property_names, i, &mut key_val);
332          }
333          let key = read_napi_string(raw_env, key_val);
334
335          let mut prop_val: napi::sys::napi_value = std::ptr::null_mut();
336          unsafe {
337            napi::sys::napi_get_property(raw_env, value, key_val, &mut prop_val);
338          }
339
340          props.push(create_key_value_prop(
341            &key,
342            napi_value_to_expr(raw_env, prop_val),
343          ));
344        }
345
346        create_object_expression(props)
347      }
348    },
349    _ => {
350      debug!("Unsupported napi value type: {:#?}.", val_type);
351
352      panic!(
353        "Unsupported napi value type: {:?}. If its not enough, please run in debug mode to see more details",
354        val_type
355      );
356    },
357  }
358}
359
360/// Wrapper for a raw napi_ref with its associated environment.
361struct EnvFnRef {
362  ref_ptr: napi::sys::napi_ref,
363  env: napi::sys::napi_env,
364}
365
366impl Drop for EnvFnRef {
367  fn drop(&mut self) {
368    let status = unsafe { napi::sys::napi_delete_reference(self.env, self.ref_ptr) };
369    if status != napi::sys::Status::napi_ok {
370      log::warn!(
371        "Failed to delete napi_ref during cleanup (status: {:?})",
372        status
373      );
374    }
375  }
376}