swc_ecma_transforms_typescript/
ts_enum.rs1use rustc_hash::FxHashMap;
2use swc_atoms::{atom, Atom, Wtf8Atom};
3use swc_common::{SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{
6 number::{JsNumber, ToJsString},
7 ExprFactory,
8};
9use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
10
11#[inline]
12fn atom_from_wtf8_atom(value: &Wtf8Atom) -> Atom {
13 value
14 .as_str()
15 .map(Atom::from)
16 .unwrap_or_else(|| Atom::from(value.to_string_lossy()))
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct TsEnumRecordKey {
21 pub enum_id: Id,
22 pub member_name: Atom,
23}
24
25pub(crate) type TsEnumRecord = FxHashMap<TsEnumRecordKey, TsEnumRecordValue>;
26
27#[derive(Debug, Clone)]
28pub(crate) enum TsEnumRecordValue {
29 String(Atom),
30 Number(JsNumber),
31 Opaque(Box<Expr>),
32 Void,
33}
34
35impl TsEnumRecordValue {
36 pub fn inc(&self) -> Self {
37 match self {
38 Self::Number(num) => Self::Number((**num + 1.0).into()),
39 _ => Self::Void,
40 }
41 }
42
43 pub fn is_const(&self) -> bool {
44 matches!(
45 self,
46 TsEnumRecordValue::String(..) | TsEnumRecordValue::Number(..)
47 )
48 }
49
50 pub fn is_string(&self) -> bool {
51 matches!(self, TsEnumRecordValue::String(..))
52 }
53
54 pub fn has_value(&self) -> bool {
55 !matches!(self, TsEnumRecordValue::Void)
56 }
57}
58
59impl From<TsEnumRecordValue> for Expr {
60 fn from(value: TsEnumRecordValue) -> Self {
61 match value {
62 TsEnumRecordValue::String(string) => Lit::Str(string.into()).into(),
63 TsEnumRecordValue::Number(num) if num.is_nan() => Ident {
64 span: DUMMY_SP,
65 sym: atom!("NaN"),
66 ..Default::default()
67 }
68 .into(),
69 TsEnumRecordValue::Number(num) if num.is_infinite() => {
70 let value: Expr = Ident {
71 span: DUMMY_SP,
72 sym: atom!("Infinity"),
73 ..Default::default()
74 }
75 .into();
76
77 if num.is_sign_negative() {
78 UnaryExpr {
79 span: DUMMY_SP,
80 op: op!(unary, "-"),
81 arg: value.into(),
82 }
83 .into()
84 } else {
85 value
86 }
87 }
88 TsEnumRecordValue::Number(num) => Lit::Num(Number {
89 span: DUMMY_SP,
90 value: *num,
91 raw: None,
92 })
93 .into(),
94 TsEnumRecordValue::Void => *Expr::undefined(DUMMY_SP),
95 TsEnumRecordValue::Opaque(expr) => *expr,
96 }
97 }
98}
99
100impl From<f64> for TsEnumRecordValue {
101 fn from(value: f64) -> Self {
102 Self::Number(value.into())
103 }
104}
105
106pub(crate) struct EnumValueComputer<'a> {
107 pub enum_id: &'a Id,
108 pub unresolved_ctxt: SyntaxContext,
109 pub record: &'a TsEnumRecord,
110}
111
112impl EnumValueComputer<'_> {
114 pub fn compute(&mut self, expr: Box<Expr>) -> TsEnumRecordValue {
115 let mut expr = self.compute_rec(expr);
116 if let TsEnumRecordValue::Opaque(expr) = &mut expr {
117 expr.visit_mut_with(self);
118 }
119 expr
120 }
121
122 fn compute_rec(&self, expr: Box<Expr>) -> TsEnumRecordValue {
123 match *expr {
124 Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)),
125 Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()),
126 Expr::Ident(Ident { ctxt, sym, .. })
127 if &*sym == "NaN" && ctxt == self.unresolved_ctxt =>
128 {
129 TsEnumRecordValue::Number(f64::NAN.into())
130 }
131 Expr::Ident(Ident { ctxt, sym, .. })
132 if &*sym == "Infinity" && ctxt == self.unresolved_ctxt =>
133 {
134 TsEnumRecordValue::Number(f64::INFINITY.into())
135 }
136 Expr::Ident(ref ident) => self
137 .record
138 .get(&TsEnumRecordKey {
139 enum_id: self.enum_id.clone(),
140 member_name: ident.sym.clone(),
141 })
142 .cloned()
143 .map(|value| match value {
144 TsEnumRecordValue::String(..) | TsEnumRecordValue::Number(..) => value,
145 _ => TsEnumRecordValue::Opaque(
146 self.enum_id
147 .clone()
148 .make_member(ident.clone().into())
149 .into(),
150 ),
151 })
152 .unwrap_or_else(|| TsEnumRecordValue::Opaque(expr)),
153 Expr::Paren(e) => self.compute_rec(e.expr),
154 Expr::Unary(e) => self.compute_unary(e),
155 Expr::Bin(e) => self.compute_bin(e),
156 Expr::Member(e) => self.compute_member(e),
157 Expr::Tpl(e) => self.compute_tpl(e),
158 _ => TsEnumRecordValue::Opaque(expr),
159 }
160 }
161
162 fn compute_unary(&self, expr: UnaryExpr) -> TsEnumRecordValue {
163 if !matches!(expr.op, op!(unary, "+") | op!(unary, "-") | op!("~")) {
164 return TsEnumRecordValue::Opaque(expr.into());
165 }
166
167 let inner = self.compute_rec(expr.arg);
168
169 let TsEnumRecordValue::Number(num) = inner else {
170 return TsEnumRecordValue::Opaque(
171 UnaryExpr {
172 span: expr.span,
173 op: expr.op,
174 arg: Box::new(inner.into()),
175 }
176 .into(),
177 );
178 };
179
180 match expr.op {
181 op!(unary, "+") => TsEnumRecordValue::Number(num),
182 op!(unary, "-") => TsEnumRecordValue::Number(-num),
183 op!("~") => TsEnumRecordValue::Number(!num),
184 _ => unreachable!(),
185 }
186 }
187
188 fn compute_bin(&self, expr: BinExpr) -> TsEnumRecordValue {
189 let origin_expr = expr.clone();
190 if !matches!(
191 expr.op,
192 op!(bin, "+")
193 | op!(bin, "-")
194 | op!("*")
195 | op!("/")
196 | op!("%")
197 | op!("**")
198 | op!("<<")
199 | op!(">>")
200 | op!(">>>")
201 | op!("|")
202 | op!("&")
203 | op!("^"),
204 ) {
205 return TsEnumRecordValue::Opaque(origin_expr.into());
206 }
207
208 let left = self.compute_rec(expr.left);
209 let right = self.compute_rec(expr.right);
210
211 match (left, right, expr.op) {
212 (TsEnumRecordValue::Number(left), TsEnumRecordValue::Number(right), op) => {
213 let value = match op {
214 op!(bin, "+") => left + right,
215 op!(bin, "-") => left - right,
216 op!("*") => left * right,
217 op!("/") => left / right,
218 op!("%") => left % right,
219 op!("**") => left.pow(right),
220 op!("<<") => left << right,
221 op!(">>") => left >> right,
222 op!(">>>") => left.unsigned_shr(right),
223 op!("|") => left | right,
224 op!("&") => left & right,
225 op!("^") => left ^ right,
226 _ => unreachable!(),
227 };
228
229 TsEnumRecordValue::Number(value)
230 }
231 (TsEnumRecordValue::String(left), TsEnumRecordValue::String(right), op!(bin, "+")) => {
232 TsEnumRecordValue::String(format!("{left}{right}").into())
233 }
234 (TsEnumRecordValue::Number(left), TsEnumRecordValue::String(right), op!(bin, "+")) => {
235 let left = left.to_js_string();
236
237 TsEnumRecordValue::String(format!("{left}{right}").into())
238 }
239 (TsEnumRecordValue::String(left), TsEnumRecordValue::Number(right), op!(bin, "+")) => {
240 let right = right.to_js_string();
241
242 TsEnumRecordValue::String(format!("{left}{right}").into())
243 }
244 (left, right, _) => {
245 let mut origin_expr = origin_expr;
246
247 if left.is_const() {
248 origin_expr.left = Box::new(left.into());
249 }
250
251 if right.is_const() {
252 origin_expr.right = Box::new(right.into());
253 }
254
255 TsEnumRecordValue::Opaque(origin_expr.into())
256 }
257 }
258 }
259
260 fn compute_member(&self, expr: MemberExpr) -> TsEnumRecordValue {
261 if matches!(expr.prop, MemberProp::PrivateName(..)) {
262 return TsEnumRecordValue::Opaque(expr.into());
263 }
264
265 let opaque_expr = TsEnumRecordValue::Opaque(expr.clone().into());
266
267 let member_name = match expr.prop {
268 MemberProp::Ident(ident) => ident.sym,
269 MemberProp::Computed(ComputedPropName { expr, .. }) => {
270 let Expr::Lit(Lit::Str(s)) = *expr else {
271 return opaque_expr;
272 };
273
274 atom_from_wtf8_atom(&s.value)
275 }
276 _ => return opaque_expr,
277 };
278
279 let Expr::Ident(ident) = *expr.obj else {
280 return opaque_expr;
281 };
282
283 self.record
284 .get(&TsEnumRecordKey {
285 enum_id: ident.to_id(),
286 member_name,
287 })
288 .cloned()
289 .filter(TsEnumRecordValue::has_value)
290 .unwrap_or(opaque_expr)
291 }
292
293 fn compute_tpl(&self, expr: Tpl) -> TsEnumRecordValue {
294 let opaque_expr = TsEnumRecordValue::Opaque(expr.clone().into());
295
296 let Tpl { exprs, quasis, .. } = expr;
297
298 let mut quasis_iter = quasis.into_iter();
299
300 let Some(mut string) = quasis_iter.next().map(|q| q.raw.to_string()) else {
301 return opaque_expr;
302 };
303
304 for (q, expr) in quasis_iter.zip(exprs) {
305 let expr = self.compute_rec(expr);
306
307 let expr = match expr {
308 TsEnumRecordValue::String(s) => s.to_string(),
309 TsEnumRecordValue::Number(n) => n.to_js_string(),
310 _ => return opaque_expr,
311 };
312
313 string.push_str(&expr);
314 string.push_str(&q.raw);
315 }
316
317 TsEnumRecordValue::String(string.into())
318 }
319}
320
321impl VisitMut for EnumValueComputer<'_> {
322 noop_visit_mut_type!();
323
324 fn visit_mut_expr(&mut self, expr: &mut Expr) {
325 expr.visit_mut_children_with(self);
326
327 let Expr::Ident(ident) = expr else { return };
328
329 if self.record.contains_key(&TsEnumRecordKey {
330 enum_id: self.enum_id.clone(),
331 member_name: ident.sym.clone(),
332 }) {
333 *expr = self
334 .enum_id
335 .clone()
336 .make_member(ident.clone().into())
337 .into();
338 }
339 }
340}