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