swc_ecma_compat_es2015/
computed_props.rs

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
11/// `@babel/plugin-transform-computed-properties`
12///
13/// # Example
14/// ## In
15///
16/// ```js
17/// var obj = {
18///   ["x" + foo]: "heh",
19///   ["y" + bar]: "noo",
20///   foo: "foo",
21///   bar: "bar"
22/// };
23/// ```
24///
25/// ## Out
26///
27/// ```js
28/// var _obj;
29///
30/// var obj = (
31///   _obj = {},
32///   _define_property(_obj, "x" + foo, "heh"),
33///   _define_property(_obj, "y" + bar, "noo"),
34///   _define_property(_obj, "foo", "foo"),
35///   _define_property(_obj, "bar", "bar"),
36///   _obj
37/// );
38/// ```
39///
40/// TODO(kdy1): cache reference like (_f = f, mutatorMap[_f].get = function(){})
41///     instead of (mutatorMap[f].get = function(){}
42pub 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            // Optimization
86            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                            // getter/setter property name
156                            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                            // mutator[f]
196                            let mutator_elem = mutator_map
197                                .clone()
198                                .computed_member(prop_name_to_expr(key, false).0);
199
200                            // mutator[f] = mutator[f] || {}
201                            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                            // mutator[f].get = function(){}
223                            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                            // unimplemented!("getter /setter property")
243                        }
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            // Last value
332            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            // Add variable declaration
394            // e.g. var ref
395            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}