stylex_ast/ast/
convertors.rs1use anyhow::anyhow;
2use stylex_macros::stylex_panic;
3use stylex_utils::swc::get_default_expr_ctx;
4use swc_core::{
5 atoms::{Atom, Wtf8Atom},
6 ecma::{
7 ast::{
8 BigInt, Bool, CallExpr, Expr, Ident, KeyValueProp, Lit, Prop, PropName, Str, Tpl, TplElement,
9 },
10 parser::Context,
11 utils::{ExprExt, quote_ident, quote_str},
12 },
13};
14
15use stylex_constants::constants::messages::INVALID_UTF8;
16
17use super::factories::{
18 create_big_int_lit, create_boolean_lit, create_ident, create_null_lit, create_number_lit,
19 create_string_lit,
20};
21
22pub fn convert_lit_to_number(lit_num: &Lit) -> Result<f64, anyhow::Error> {
34 let result = match &lit_num {
35 Lit::Bool(Bool { value, .. }) => {
36 if value == &true {
37 1.0
38 } else {
39 0.0
40 }
41 },
42 Lit::Num(num) => num.value,
43 Lit::Str(strng) => {
44 let Result::Ok(num) = convert_atom_to_string(&strng.value).parse::<f64>() else {
45 return Err(anyhow!(
46 "Value in not a number: {}",
47 convert_atom_to_string(&strng.value)
48 ));
49 };
50
51 num
52 },
53 _ => {
54 return Err(anyhow!(
55 "Value in not a number: {:?}",
56 Expr::from(lit_num.clone()).get_type(get_default_expr_ctx())
57 ));
58 },
59 };
60
61 Result::Ok(result)
62}
63
64pub fn convert_tpl_to_string_lit(tpl: &Tpl) -> Option<Lit> {
76 if tpl.exprs.is_empty() && tpl.quasis.len() == 1 {
78 let quasi = &tpl.quasis[0];
79
80 let value = match quasi.cooked.as_ref() {
82 Some(cooked) => match cooked.as_str() {
83 Some(s) => s,
84 None => stylex_panic!("Failed to extract a string value from the expression."),
85 },
86 None => stylex_panic!("Failed to extract cooked value from template literal element."),
87 };
88
89 return Some(create_string_lit(value));
90 }
91
92 None
93}
94
95#[inline]
105pub fn convert_simple_tpl_to_str_expr(expr: Expr) -> Expr {
106 match expr {
107 Expr::Tpl(ref tpl) => {
108 if let Some(str_lit) = convert_tpl_to_string_lit(tpl) {
109 return Expr::Lit(str_lit);
110 }
111 expr
112 },
113 _ => expr,
114 }
115}
116
117#[inline]
132pub fn convert_concat_to_tpl_expr(expr: Expr) -> Expr {
133 match expr {
134 Expr::Call(ref call_expr) => {
135 if let Some(tpl_expr) = concat_call_to_template_literal(call_expr) {
136 return tpl_expr;
137 }
138 expr
139 },
140 _ => expr,
141 }
142}
143
144fn concat_call_to_template_literal(call_expr: &CallExpr) -> Option<Expr> {
153 use swc_core::common::DUMMY_SP;
154
155 let member_expr = call_expr.callee.as_expr()?.as_member()?;
157 let prop_ident = member_expr.prop.as_ident()?;
158
159 if prop_ident.sym.as_ref() != "concat" {
160 return None;
161 }
162
163 let base_string = extract_str_lit_ref(member_expr.obj.as_lit()?).map(|s| s.to_string())?;
165
166 let mut exprs = Vec::new();
167 let mut quasis = Vec::new();
168
169 quasis.push(TplElement {
171 span: DUMMY_SP,
172 tail: false,
173 cooked: Some(base_string.clone().into()),
174 raw: base_string.into(),
175 });
176
177 for (i, arg) in call_expr.args.iter().enumerate() {
179 if arg.spread.is_some() {
181 continue;
182 }
183
184 exprs.push(arg.expr.clone());
185
186 let is_last = i == call_expr.args.len() - 1;
187 quasis.push(TplElement {
188 span: DUMMY_SP,
189 tail: is_last,
190 cooked: Some("".into()),
191 raw: "".into(),
192 });
193 }
194
195 let template_literal = Tpl {
196 span: DUMMY_SP,
197 exprs,
198 quasis,
199 };
200
201 Some(Expr::Tpl(template_literal))
202}
203
204#[inline]
205pub fn create_number_expr(value: f64) -> Expr {
206 Expr::from(create_number_lit(value))
207}
208
209#[inline]
210pub fn create_big_int_expr(value: BigInt) -> Expr {
211 Expr::from(create_big_int_lit(value))
212}
213
214#[inline]
215pub fn create_string_expr(value: &str) -> Expr {
216 Expr::Lit(create_string_lit(value))
217}
218
219#[inline]
220pub fn create_bool_expr(value: bool) -> Expr {
221 Expr::Lit(create_boolean_lit(value))
222}
223
224#[inline]
225pub fn create_ident_expr(value: &str) -> Expr {
226 Expr::Ident(create_ident(value))
227}
228
229#[inline]
230pub fn create_null_expr() -> Expr {
231 Expr::Lit(create_null_lit())
232}
233
234fn should_wrap_prop_name_key_with_quotes(key: &str) -> bool {
235 Ident::verify_symbol(key).is_err() && {
236 let ctx = Context::default();
237
238 !ctx.is_reserved_word(&key.into())
239 }
240}
241#[inline]
242pub fn convert_string_to_prop_name(value: &str) -> Option<PropName> {
243 if should_wrap_prop_name_key_with_quotes(value) {
244 Some(PropName::Str(quote_str!(value)))
245 } else {
246 Some(PropName::Ident(quote_ident!(value)))
247 }
248}
249
250pub fn expand_shorthand_prop(prop: &mut Box<Prop>) {
251 if let Some(ident) = prop.as_shorthand() {
252 **prop = Prop::from(KeyValueProp {
253 key: match convert_string_to_prop_name(ident.sym.as_ref()) {
254 Some(k) => k,
255 None => stylex_panic!("Failed to convert string to a valid property name."),
256 },
257 value: Box::new(Expr::Ident(ident.clone())),
258 });
259 }
260}
261
262#[inline]
265pub fn convert_atom_to_string(atom: &Wtf8Atom) -> String {
266 match atom.as_str() {
267 Some(s) => s.to_string(),
268 None => stylex_panic!("{}", INVALID_UTF8),
269 }
270}
271
272pub fn convert_wtf8_to_atom(atom: &Wtf8Atom) -> Atom {
273 match atom.as_atom() {
274 Some(a) => a.clone(),
275 None => stylex_panic!("{}", INVALID_UTF8),
276 }
277}
278
279#[inline]
281pub fn convert_str_lit_to_string(str_lit: &Str) -> String {
282 match str_lit.value.as_str() {
283 Some(s) => s.to_string(),
284 None => stylex_panic!("{}", INVALID_UTF8),
285 }
286}
287
288pub fn convert_str_lit_to_atom(str_lit: &Str) -> Atom {
290 match str_lit.value.as_atom() {
291 Some(a) => a.clone(),
292 None => stylex_panic!("{}", INVALID_UTF8),
293 }
294}
295
296#[inline]
298pub fn extract_tpl_cooked_value(elem: &TplElement) -> String {
299 match elem.cooked.as_ref() {
300 Some(cooked) => match cooked.as_str() {
301 Some(s) => s.to_string(),
302 None => stylex_panic!("{}", INVALID_UTF8),
303 },
304 None => stylex_panic!(
305 "Template literal element has no cooked value (contains an invalid escape sequence)."
306 ),
307 }
308}
309
310#[inline]
313pub fn convert_atom_to_str_ref(atom: &swc_core::atoms::Wtf8Atom) -> &str {
314 match atom.as_str() {
315 Some(s) => s,
316 None => stylex_panic!("Failed to convert SWC Atom to string (invalid WTF-8 encoding)."),
317 }
318}
319
320#[inline]
321pub fn convert_lit_to_string(value: &Lit) -> Option<String> {
322 match value {
323 Lit::Str(strng) => Some(convert_str_lit_to_string(strng)),
324 Lit::Num(num) => Some(format!("{}", num.value)),
325 Lit::BigInt(big_int) => Some(format!("{}", big_int.value)),
326 _ => None,
327 }
328}
329
330#[inline]
332pub fn extract_str_lit_ref(lit: &Lit) -> Option<&str> {
333 match lit {
334 Lit::Str(s) => Some(convert_atom_to_str_ref(&s.value)),
335 _ => None,
336 }
337}