1use serde::Deserialize;
2use swc_common::{Mark, Spanned, SyntaxContext, DUMMY_SP};
3use swc_ecma_ast::*;
4use swc_ecma_transforms_base::helper;
5use swc_ecma_utils::{quote_ident, ExprFactory, StmtLike};
6use swc_ecma_visit::{
7 noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
8};
9use swc_trace_macro::swc_trace;
10
11pub fn computed_properties(c: Config) -> impl Pass {
43 visit_mut_pass(ComputedProps {
44 c,
45 ..Default::default()
46 })
47}
48
49#[derive(Debug, Clone, Copy, Default, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct Config {
52 #[serde(default)]
53 pub loose: bool,
54}
55
56#[derive(Default)]
57struct ComputedProps {
58 vars: Vec<VarDeclarator>,
59 used_define_enum_props: bool,
60 c: Config,
61}
62
63#[swc_trace]
64impl VisitMut for ComputedProps {
65 noop_visit_mut_type!(fail);
66
67 fn visit_mut_expr(&mut self, expr: &mut Expr) {
68 expr.visit_mut_children_with(self);
69
70 if let Expr::Object(ObjectLit { props, span }) = expr {
71 if !is_complex(props) {
72 return;
73 }
74
75 let mark = Mark::fresh(Mark::root());
76 let obj_ident = quote_ident!(SyntaxContext::empty().apply_mark(mark), *span, "_obj");
77
78 let mut exprs: Vec<Box<Expr>> = Vec::with_capacity(props.len() + 2);
79 let mutator_map = quote_ident!(
80 SyntaxContext::empty().apply_mark(mark),
81 *span,
82 "_mutatorMap"
83 );
84
85 let obj_props = {
87 let idx = props.iter().position(is_complex).unwrap_or(0);
88
89 props.drain(0..idx).collect()
90 };
91
92 let props_cnt = props.len();
93
94 self.used_define_enum_props = props.iter().any(
95 |pp| matches!(*pp, PropOrSpread::Prop(ref p) if p.is_getter() || p.is_setter()),
96 );
97
98 exprs.push(
99 if !self.c.loose && props_cnt == 1 && !self.used_define_enum_props {
100 ObjectLit {
101 span: DUMMY_SP,
102 props: obj_props,
103 }
104 .into()
105 } else {
106 AssignExpr {
107 span: DUMMY_SP,
108 left: obj_ident.clone().into(),
109 op: op!("="),
110 right: Box::new(
111 ObjectLit {
112 span: DUMMY_SP,
113 props: obj_props,
114 }
115 .into(),
116 ),
117 }
118 .into()
119 },
120 );
121
122 let mut single_cnt_prop = None;
123
124 for prop in props.drain(..) {
125 let span = prop.span();
126
127 let ((key, is_compute), value) = match prop {
128 PropOrSpread::Prop(prop) => match *prop {
129 Prop::Shorthand(ident) => (
130 (
131 if self.c.loose {
132 ident.clone().into()
133 } else {
134 Lit::Str(Str {
135 span: ident.span,
136 raw: None,
137 value: ident.sym.clone(),
138 })
139 .into()
140 },
141 false,
142 ),
143 ident.into(),
144 ),
145 Prop::KeyValue(KeyValueProp { key, value }) => {
146 (prop_name_to_expr(key, self.c.loose), *value)
147 }
148 Prop::Assign(..) => {
149 unreachable!("assign property in object literal is invalid")
150 }
151 prop @ Prop::Getter(GetterProp { .. })
152 | prop @ Prop::Setter(SetterProp { .. }) => {
153 self.used_define_enum_props = true;
154
155 let gs_prop_name = match prop {
157 Prop::Getter(..) => Some("get"),
158 Prop::Setter(..) => Some("set"),
159 _ => None,
160 };
161 let (key, function) = match prop {
162 Prop::Getter(GetterProp {
163 span, body, key, ..
164 }) => (
165 key,
166 Box::new(Function {
167 span,
168 body,
169 is_async: false,
170 is_generator: false,
171 params: Vec::new(),
172 ..Default::default()
173 }),
174 ),
175 Prop::Setter(SetterProp {
176 span,
177 body,
178 param,
179 key,
180 ..
181 }) => (
182 key,
183 Box::new(Function {
184 span,
185 body,
186 is_async: false,
187 is_generator: false,
188 params: vec![(*param).into()],
189 ..Default::default()
190 }),
191 ),
192 _ => unreachable!(),
193 };
194
195 let mutator_elem = mutator_map
197 .clone()
198 .computed_member(prop_name_to_expr(key, false).0);
199
200 exprs.push(
202 AssignExpr {
203 span,
204 left: mutator_elem.clone().into(),
205 op: op!("="),
206 right: Box::new(
207 BinExpr {
208 span,
209 left: mutator_elem.clone().into(),
210 op: op!("||"),
211 right: Box::new(Expr::Object(ObjectLit {
212 span,
213 props: Vec::new(),
214 })),
215 }
216 .into(),
217 ),
218 }
219 .into(),
220 );
221
222 exprs.push(
224 AssignExpr {
225 span,
226 left: mutator_elem
227 .make_member(quote_ident!(gs_prop_name.unwrap()))
228 .into(),
229 op: op!("="),
230 right: Box::new(
231 FnExpr {
232 ident: None,
233 function,
234 }
235 .into(),
236 ),
237 }
238 .into(),
239 );
240
241 continue;
242 }
244 Prop::Method(MethodProp { key, function }) => (
245 prop_name_to_expr(key, self.c.loose),
246 FnExpr {
247 ident: None,
248 function,
249 }
250 .into(),
251 ),
252 },
253 PropOrSpread::Spread(..) => unimplemented!("computed spread property"),
254 };
255
256 if !self.c.loose && props_cnt == 1 {
257 single_cnt_prop = Some(
258 CallExpr {
259 span,
260 callee: helper!(define_property),
261 args: vec![exprs.pop().unwrap().as_arg(), key.as_arg(), value.as_arg()],
262 ..Default::default()
263 }
264 .into(),
265 );
266 break;
267 }
268 exprs.push(if self.c.loose {
269 let left = if is_compute {
270 obj_ident.clone().computed_member(key)
271 } else {
272 obj_ident.clone().make_member(key.ident().unwrap().into())
273 };
274 AssignExpr {
275 span,
276 op: op!("="),
277 left: left.into(),
278 right: value.into(),
279 }
280 .into()
281 } else {
282 CallExpr {
283 span,
284 callee: helper!(define_property),
285 args: vec![obj_ident.clone().as_arg(), key.as_arg(), value.as_arg()],
286 ..Default::default()
287 }
288 .into()
289 });
290 }
291
292 if let Some(single_expr) = single_cnt_prop {
293 *expr = single_expr;
294 return;
295 }
296
297 self.vars.push(VarDeclarator {
298 span: *span,
299 name: obj_ident.clone().into(),
300 init: None,
301 definite: false,
302 });
303 if self.used_define_enum_props {
304 self.vars.push(VarDeclarator {
305 span: DUMMY_SP,
306 name: mutator_map.clone().into(),
307 init: Some(
308 ObjectLit {
309 span: DUMMY_SP,
310 props: Vec::new(),
311 }
312 .into(),
313 ),
314 definite: false,
315 });
316 exprs.push(
317 CallExpr {
318 span: *span,
319 callee: helper!(define_enumerable_properties),
320 args: vec![obj_ident.clone().as_arg(), mutator_map.as_arg()],
321 ..Default::default()
322 }
323 .into(),
324 );
325 }
326
327 exprs.push(obj_ident.into());
329 *expr = SeqExpr {
330 span: DUMMY_SP,
331 exprs,
332 }
333 .into();
334 };
335 }
336
337 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
338 self.visit_mut_stmt_like(n);
339 }
340
341 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
342 self.visit_mut_stmt_like(n);
343 }
344}
345
346fn is_complex<T: VisitWith<ComplexVisitor>>(node: &T) -> bool {
347 let mut visitor = ComplexVisitor::default();
348 node.visit_children_with(&mut visitor);
349 visitor.found
350}
351
352#[derive(Default)]
353struct ComplexVisitor {
354 found: bool,
355}
356
357impl Visit for ComplexVisitor {
358 noop_visit_type!(fail);
359
360 fn visit_prop_name(&mut self, pn: &PropName) {
361 if let PropName::Computed(..) = *pn {
362 self.found = true
363 }
364 }
365}
366
367#[swc_trace]
368impl ComputedProps {
369 fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
370 where
371 T: StmtLike + VisitWith<ShouldWork> + VisitMutWith<Self>,
372 Vec<T>: VisitWith<ShouldWork>,
373 {
374 let mut stmts_updated = Vec::with_capacity(stmts.len());
375
376 for mut stmt in stmts.drain(..) {
377 if !contains_computed_expr(&stmt) {
378 stmts_updated.push(stmt);
379 continue;
380 }
381
382 let mut folder = Self {
383 c: self.c,
384 ..Default::default()
385 };
386
387 stmt.visit_mut_with(&mut folder);
388
389 if !folder.vars.is_empty() {
392 stmts_updated.push(T::from(
393 VarDecl {
394 kind: VarDeclKind::Var,
395 decls: folder.vars,
396 ..Default::default()
397 }
398 .into(),
399 ));
400 }
401
402 stmts_updated.push(stmt);
403 }
404
405 *stmts = stmts_updated;
406 }
407}
408
409fn prop_name_to_expr(p: PropName, loose: bool) -> (Expr, bool) {
410 match p {
411 PropName::Ident(i) => (
412 if loose {
413 i.into()
414 } else {
415 Lit::Str(Str {
416 raw: None,
417 value: i.sym,
418 span: i.span,
419 })
420 .into()
421 },
422 false,
423 ),
424 PropName::Str(s) => (Lit::Str(s).into(), true),
425 PropName::Num(n) => (Lit::Num(n).into(), true),
426 PropName::BigInt(b) => (Lit::BigInt(b).into(), true),
427 PropName::Computed(c) => (*c.expr, true),
428 }
429}
430
431fn contains_computed_expr<N>(node: &N) -> bool
432where
433 N: VisitWith<ShouldWork>,
434{
435 let mut v = ShouldWork { found: false };
436 node.visit_with(&mut v);
437 v.found
438}
439
440struct ShouldWork {
441 found: bool,
442}
443
444impl Visit for ShouldWork {
445 noop_visit_type!(fail);
446
447 fn visit_prop_name(&mut self, node: &PropName) {
448 if let PropName::Computed(_) = *node {
449 self.found = true
450 }
451 }
452}