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(),
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                    },
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            // Last value
328            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            // Add variable declaration
390            // e.g. var ref
391            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}