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().into(),
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 #[cfg(swc_ast_unknown)]
253 _ => panic!("unable to access unknown nodes"),
254 },
255 PropOrSpread::Spread(..) => unimplemented!("computed spread property"),
256 #[cfg(swc_ast_unknown)]
257 _ => panic!("unable to access unknown nodes"),
258 };
259
260 if !self.c.loose && props_cnt == 1 {
261 single_cnt_prop = Some(
262 CallExpr {
263 span,
264 callee: helper!(define_property),
265 args: vec![exprs.pop().unwrap().as_arg(), key.as_arg(), value.as_arg()],
266 ..Default::default()
267 }
268 .into(),
269 );
270 break;
271 }
272 exprs.push(if self.c.loose {
273 let left = if is_compute {
274 obj_ident.clone().computed_member(key)
275 } else {
276 obj_ident.clone().make_member(key.ident().unwrap().into())
277 };
278 AssignExpr {
279 span,
280 op: op!("="),
281 left: left.into(),
282 right: value.into(),
283 }
284 .into()
285 } else {
286 CallExpr {
287 span,
288 callee: helper!(define_property),
289 args: vec![obj_ident.clone().as_arg(), key.as_arg(), value.as_arg()],
290 ..Default::default()
291 }
292 .into()
293 });
294 }
295
296 if let Some(single_expr) = single_cnt_prop {
297 *expr = single_expr;
298 return;
299 }
300
301 self.vars.push(VarDeclarator {
302 span: *span,
303 name: obj_ident.clone().into(),
304 init: None,
305 definite: false,
306 });
307 if self.used_define_enum_props {
308 self.vars.push(VarDeclarator {
309 span: DUMMY_SP,
310 name: mutator_map.clone().into(),
311 init: Some(
312 ObjectLit {
313 span: DUMMY_SP,
314 props: Vec::new(),
315 }
316 .into(),
317 ),
318 definite: false,
319 });
320 exprs.push(
321 CallExpr {
322 span: *span,
323 callee: helper!(define_enumerable_properties),
324 args: vec![obj_ident.clone().as_arg(), mutator_map.as_arg()],
325 ..Default::default()
326 }
327 .into(),
328 );
329 }
330
331 exprs.push(obj_ident.into());
333 *expr = SeqExpr {
334 span: DUMMY_SP,
335 exprs,
336 }
337 .into();
338 };
339 }
340
341 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
342 self.visit_mut_stmt_like(n);
343 }
344
345 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
346 self.visit_mut_stmt_like(n);
347 }
348}
349
350fn is_complex<T: VisitWith<ComplexVisitor>>(node: &T) -> bool {
351 let mut visitor = ComplexVisitor::default();
352 node.visit_children_with(&mut visitor);
353 visitor.found
354}
355
356#[derive(Default)]
357struct ComplexVisitor {
358 found: bool,
359}
360
361impl Visit for ComplexVisitor {
362 noop_visit_type!(fail);
363
364 fn visit_prop_name(&mut self, pn: &PropName) {
365 if let PropName::Computed(..) = *pn {
366 self.found = true
367 }
368 }
369}
370
371#[swc_trace]
372impl ComputedProps {
373 fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
374 where
375 T: StmtLike + VisitWith<ShouldWork> + VisitMutWith<Self>,
376 Vec<T>: VisitWith<ShouldWork>,
377 {
378 let mut stmts_updated = Vec::with_capacity(stmts.len());
379
380 for mut stmt in stmts.drain(..) {
381 if !contains_computed_expr(&stmt) {
382 stmts_updated.push(stmt);
383 continue;
384 }
385
386 let mut folder = Self {
387 c: self.c,
388 ..Default::default()
389 };
390
391 stmt.visit_mut_with(&mut folder);
392
393 if !folder.vars.is_empty() {
396 stmts_updated.push(T::from(
397 VarDecl {
398 kind: VarDeclKind::Var,
399 decls: folder.vars,
400 ..Default::default()
401 }
402 .into(),
403 ));
404 }
405
406 stmts_updated.push(stmt);
407 }
408
409 *stmts = stmts_updated;
410 }
411}
412
413fn prop_name_to_expr(p: PropName, loose: bool) -> (Expr, bool) {
414 match p {
415 PropName::Ident(i) => (
416 if loose {
417 i.into()
418 } else {
419 Lit::Str(Str {
420 raw: None,
421 value: i.sym.into(),
422 span: i.span,
423 })
424 .into()
425 },
426 false,
427 ),
428 PropName::Str(s) => (Lit::Str(s).into(), true),
429 PropName::Num(n) => (Lit::Num(n).into(), true),
430 PropName::BigInt(b) => (Lit::BigInt(b).into(), true),
431 PropName::Computed(c) => (*c.expr, true),
432 #[cfg(swc_ast_unknown)]
433 _ => panic!("unable to access unknown nodes"),
434 }
435}
436
437fn contains_computed_expr<N>(node: &N) -> bool
438where
439 N: VisitWith<ShouldWork>,
440{
441 let mut v = ShouldWork { found: false };
442 node.visit_with(&mut v);
443 v.found
444}
445
446struct ShouldWork {
447 found: bool,
448}
449
450impl Visit for ShouldWork {
451 noop_visit_type!(fail);
452
453 fn visit_prop_name(&mut self, node: &PropName) {
454 if let PropName::Computed(_) = *node {
455 self.found = true
456 }
457 }
458}