swc_ecma_compat_es2015/
object_super.rs

1use std::iter;
2
3use swc_common::{util::take::Take, Span, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::helper;
6use swc_ecma_utils::{
7    alias_ident_for, is_rest_arguments, prepend_stmt, private_ident, quote_ident, ExprFactory,
8};
9use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
10use swc_trace_macro::swc_trace;
11
12struct ObjectSuper {
13    extra_vars: Vec<Ident>,
14}
15
16pub fn object_super() -> impl Pass {
17    visit_mut_pass(ObjectSuper {
18        extra_vars: Vec::new(),
19    })
20}
21
22#[swc_trace]
23impl VisitMut for ObjectSuper {
24    noop_visit_mut_type!(fail);
25
26    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
27        n.visit_mut_children_with(self);
28        if !self.extra_vars.is_empty() {
29            prepend_stmt(
30                n,
31                VarDecl {
32                    span: DUMMY_SP,
33                    kind: VarDeclKind::Var,
34                    declare: false,
35                    decls: self
36                        .extra_vars
37                        .take()
38                        .into_iter()
39                        .map(|v| VarDeclarator {
40                            span: DUMMY_SP,
41                            name: v.into(),
42                            init: None,
43                            definite: false,
44                        })
45                        .collect(),
46                    ..Default::default()
47                }
48                .into(),
49            );
50        }
51    }
52
53    fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
54        stmts.visit_mut_children_with(self);
55        if !self.extra_vars.is_empty() {
56            prepend_stmt(
57                stmts,
58                VarDecl {
59                    span: DUMMY_SP,
60                    kind: VarDeclKind::Var,
61                    decls: self
62                        .extra_vars
63                        .drain(..)
64                        .map(|v| VarDeclarator {
65                            span: DUMMY_SP,
66                            name: v.into(),
67                            init: None,
68                            definite: false,
69                        })
70                        .collect(),
71                    ..Default::default()
72                }
73                .into(),
74            );
75        }
76    }
77
78    fn visit_mut_expr(&mut self, expr: &mut Expr) {
79        expr.visit_mut_children_with(self);
80        if let Expr::Object(ObjectLit { span: _, props }) = expr {
81            let mut replacer = SuperReplacer {
82                obj: None,
83                vars: Vec::new(),
84            };
85            for prop_or_spread in props.iter_mut() {
86                if let PropOrSpread::Prop(ref mut prop) = prop_or_spread {
87                    if let Prop::Method(MethodProp { key: _, function }) = &mut **prop {
88                        function.visit_mut_with(&mut replacer);
89                        if !replacer.vars.is_empty() {
90                            if let Some(BlockStmt { span: _, stmts, .. }) = &mut function.body {
91                                prepend_stmt(
92                                    stmts,
93                                    VarDecl {
94                                        span: DUMMY_SP,
95                                        kind: VarDeclKind::Var,
96                                        declare: false,
97                                        decls: replacer
98                                            .vars
99                                            .drain(..)
100                                            .map(|v| VarDeclarator {
101                                                span: DUMMY_SP,
102                                                name: v.into(),
103                                                init: None,
104                                                definite: false,
105                                            })
106                                            .collect(),
107                                        ..Default::default()
108                                    }
109                                    .into(),
110                                );
111                            }
112                        }
113                    }
114                }
115            }
116            if let Some(obj) = replacer.obj {
117                *expr = AssignExpr {
118                    span: DUMMY_SP,
119                    op: op!("="),
120                    left: obj.clone().into(),
121                    right: Box::new(expr.take()),
122                }
123                .into();
124                self.extra_vars.push(obj);
125            }
126        }
127    }
128}
129
130struct SuperReplacer {
131    obj: Option<Ident>,
132    vars: Vec<Ident>,
133}
134
135#[swc_trace]
136impl VisitMut for SuperReplacer {
137    noop_visit_mut_type!(fail);
138
139    fn visit_mut_object_lit(&mut self, obj: &mut ObjectLit) {
140        for prop_or_spread in obj.props.iter_mut() {
141            if let PropOrSpread::Prop(prop) = prop_or_spread {
142                match &mut **prop {
143                    Prop::Method(MethodProp { key, .. })
144                    | Prop::Getter(GetterProp { key, .. })
145                    | Prop::Setter(SetterProp { key, .. }) => key.visit_mut_with(self),
146                    Prop::KeyValue(KeyValueProp { key, value }) => {
147                        key.visit_mut_with(self);
148                        if !(value.is_fn_expr() || value.is_class()) {
149                            value.visit_mut_with(self)
150                        }
151                    }
152                    Prop::Shorthand(_) | Prop::Assign(_) => (),
153                    #[cfg(swc_ast_unknown)]
154                    _ => panic!("unable to access unknown nodes"),
155                }
156            }
157        }
158    }
159
160    fn visit_mut_expr(&mut self, expr: &mut Expr) {
161        self.visit_mut_super_member_call(expr);
162        self.visit_mut_super_member_set(expr);
163        self.visit_mut_super_member_get(expr);
164
165        expr.visit_mut_children_with(self)
166    }
167}
168
169#[swc_trace]
170impl SuperReplacer {
171    fn get_obj_ref(&mut self) -> Ident {
172        if let Some(obj) = &self.obj {
173            obj.clone()
174        } else {
175            let ident = private_ident!("_obj");
176            self.obj = Some(ident.clone());
177            ident
178        }
179    }
180
181    fn get_proto(&mut self) -> ExprOrSpread {
182        CallExpr {
183            span: DUMMY_SP,
184            callee: helper!(get_prototype_of),
185            args: vec![self.get_obj_ref().as_arg()],
186
187            ..Default::default()
188        }
189        .as_arg()
190    }
191
192    // .a -> "a"
193    fn normalize_computed_expr(&mut self, prop: &mut SuperProp) -> Box<Expr> {
194        match prop.take() {
195            SuperProp::Ident(IdentName {
196                sym: value, span, ..
197            }) => Lit::Str(Str {
198                raw: None,
199                value: value.into(),
200                span,
201            })
202            .into(),
203
204            SuperProp::Computed(ComputedPropName { expr, .. }) => expr,
205            #[cfg(swc_ast_unknown)]
206            _ => panic!("unable to access unknown nodes"),
207        }
208    }
209
210    /// # In
211    /// ```js
212    /// super.foo(a)
213    /// ```
214    /// # out
215    /// ```js
216    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this).call(this, a)
217    /// ```
218    fn visit_mut_super_member_call(&mut self, n: &mut Expr) {
219        if let Expr::Call(CallExpr {
220            callee: Callee::Expr(callee_expr),
221            args,
222            ..
223        }) = n
224        {
225            if let Expr::SuperProp(SuperPropExpr {
226                obj: Super { span: super_token },
227                prop,
228                ..
229            }) = &mut **callee_expr
230            {
231                let prop = self.normalize_computed_expr(prop);
232                let callee =
233                    SuperReplacer::super_to_get_call(self.get_proto(), *super_token, prop.as_arg());
234                let this = ThisExpr { span: DUMMY_SP }.as_arg();
235                if args.len() == 1 && is_rest_arguments(&args[0]) {
236                    *n = CallExpr {
237                        span: DUMMY_SP,
238                        callee: MemberExpr {
239                            span: DUMMY_SP,
240                            obj: Box::new(callee),
241                            prop: quote_ident!("apply").into(),
242                        }
243                        .as_callee(),
244                        args: iter::once(this)
245                            .chain(iter::once({
246                                let mut arg = args.pop().unwrap();
247                                arg.spread = None;
248                                arg
249                            }))
250                            .collect(),
251                        ..Default::default()
252                    }
253                    .into();
254                    return;
255                }
256
257                *n = CallExpr {
258                    span: DUMMY_SP,
259                    callee: MemberExpr {
260                        span: DUMMY_SP,
261                        obj: Box::new(callee),
262                        prop: MemberProp::Ident(quote_ident!("call")),
263                    }
264                    .as_callee(),
265                    args: iter::once(this).chain(args.take()).collect(),
266                    ..Default::default()
267                }
268                .into();
269            }
270        }
271    }
272
273    /// # In
274    /// ```js
275    /// super.foo = bar
276    /// # out
277    /// ```js
278    /// _set(_get_prototype_of(_obj), "foo", bar, this, true)
279    /// ```
280    fn visit_mut_super_member_set(&mut self, n: &mut Expr) {
281        match n {
282            Expr::Update(UpdateExpr {
283                arg, op, prefix, ..
284            }) => {
285                if let Expr::SuperProp(SuperPropExpr {
286                    obj: Super { span: super_token },
287                    prop,
288                    ..
289                }) = &mut **arg
290                {
291                    let op = match op {
292                        op!("++") => op!("+="),
293                        op!("--") => op!("-="),
294                        #[cfg(swc_ast_unknown)]
295                        _ => panic!("unable to access unknown nodes"),
296                    };
297                    *n = self.super_to_set_call(*super_token, true, prop, op, 1.0.into(), *prefix);
298                }
299            }
300
301            Expr::Assign(AssignExpr {
302                span,
303                left,
304                op,
305                right,
306            }) => {
307                if let AssignTarget::Simple(SimpleAssignTarget::SuperProp(SuperPropExpr {
308                    obj: Super { span: super_token },
309                    prop,
310                    ..
311                })) = left
312                {
313                    *n =
314                        self.super_to_set_call(*super_token, false, prop, *op, right.take(), false);
315                    return;
316                }
317                left.visit_mut_children_with(self);
318                *n = AssignExpr {
319                    span: *span,
320                    left: left.take(),
321                    op: *op,
322                    right: right.take(),
323                }
324                .into();
325            }
326            _ => {}
327        }
328    }
329
330    /// # In
331    /// ```js
332    /// super.foo
333    /// ```
334    /// # out
335    /// ```js
336    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this)
337    /// ```
338    fn visit_mut_super_member_get(&mut self, n: &mut Expr) {
339        if let Expr::SuperProp(SuperPropExpr {
340            obj: Super {
341                span: super_token, ..
342            },
343            prop,
344            ..
345        }) = n
346        {
347            let prop = self.normalize_computed_expr(prop);
348            *n = SuperReplacer::super_to_get_call(self.get_proto(), *super_token, prop.as_arg());
349        }
350    }
351
352    fn super_to_get_call(proto: ExprOrSpread, super_token: Span, prop: ExprOrSpread) -> Expr {
353        CallExpr {
354            span: super_token,
355            callee: helper!(get),
356            args: vec![proto, prop, ThisExpr { span: super_token }.as_arg()],
357            ..Default::default()
358        }
359        .into()
360    }
361
362    fn to_bin_expr(left: Box<Expr>, op: AssignOp, rhs: Box<Expr>) -> BinExpr {
363        BinExpr {
364            span: DUMMY_SP,
365            left,
366            op: op.to_update().unwrap(),
367            right: rhs,
368        }
369    }
370
371    fn call_set_helper(
372        &mut self,
373        super_token: Span,
374        prop: ExprOrSpread,
375        rhs: ExprOrSpread,
376    ) -> Expr {
377        CallExpr {
378            span: super_token,
379            callee: helper!(set),
380            args: vec![
381                self.get_proto(),
382                prop,
383                rhs,
384                ThisExpr { span: super_token }.as_arg(),
385                // strict
386                true.as_arg(),
387            ],
388            ..Default::default()
389        }
390        .into()
391    }
392
393    fn super_to_set_call(
394        &mut self,
395        super_token: Span,
396        is_update: bool,
397        prop: &mut SuperProp,
398        op: AssignOp,
399        rhs: Box<Expr>,
400        prefix: bool,
401    ) -> Expr {
402        let computed = match prop {
403            SuperProp::Ident(_) => false,
404            SuperProp::Computed(_) => true,
405            #[cfg(swc_ast_unknown)]
406            _ => panic!("unable to access unknown nodes"),
407        };
408        let mut prop = self.normalize_computed_expr(prop);
409        match op {
410            op!("=") => self.call_set_helper(super_token, prop.as_arg(), rhs.as_arg()),
411            _ => {
412                let left = Box::new(SuperReplacer::super_to_get_call(
413                    self.get_proto(),
414                    super_token,
415                    if computed {
416                        let ref_ident = alias_ident_for(&rhs, "_ref").into_private();
417                        self.vars.push(ref_ident.clone());
418                        *prop = AssignExpr {
419                            span: DUMMY_SP,
420                            left: ref_ident.clone().into(),
421                            op: op!("="),
422                            right: prop.take(),
423                        }
424                        .into();
425                        ref_ident.as_arg()
426                    } else {
427                        prop.clone().as_arg()
428                    },
429                ));
430                if is_update {
431                    if prefix {
432                        self.call_set_helper(
433                            super_token,
434                            prop.as_arg(),
435                            SuperReplacer::to_bin_expr(
436                                UnaryExpr {
437                                    span: DUMMY_SP,
438                                    op: op!(unary, "+"),
439                                    arg: left,
440                                }
441                                .into(),
442                                op,
443                                rhs,
444                            )
445                            .as_arg(),
446                        )
447                    } else {
448                        let update_ident = alias_ident_for(&rhs, "_super").into_private();
449                        self.vars.push(update_ident.clone());
450                        SeqExpr {
451                            span: DUMMY_SP,
452                            exprs: vec![
453                                Box::new(
454                                    self.call_set_helper(
455                                        super_token,
456                                        prop.as_arg(),
457                                        SuperReplacer::to_bin_expr(
458                                            Box::new(
459                                                AssignExpr {
460                                                    span: DUMMY_SP,
461                                                    left: update_ident.clone().into(),
462                                                    op: op!("="),
463                                                    right: Box::new(Expr::Unary(UnaryExpr {
464                                                        span: DUMMY_SP,
465                                                        op: op!(unary, "+"),
466                                                        arg: left,
467                                                    })),
468                                                }
469                                                .into(),
470                                            ),
471                                            op,
472                                            rhs,
473                                        )
474                                        .as_arg(),
475                                    ),
476                                ),
477                                Box::new(Expr::Ident(update_ident)),
478                            ],
479                        }
480                        .into()
481                    }
482                } else {
483                    self.call_set_helper(
484                        super_token,
485                        prop.as_arg(),
486                        SuperReplacer::to_bin_expr(left, op, rhs).as_arg(),
487                    )
488                }
489            }
490        }
491    }
492}
493#[cfg(test)]
494mod tests {
495    use swc_common::Mark;
496    use swc_ecma_parser::{EsSyntax, Syntax};
497    use swc_ecma_transforms_base::resolver;
498    use swc_ecma_transforms_testing::test;
499
500    use super::*;
501    use crate::{function_name, shorthand};
502    test!(
503        ::swc_ecma_parser::Syntax::default(),
504        |_| {
505            let unresolved_mark = Mark::new();
506            let top_level_mark = Mark::new();
507            (
508                resolver(unresolved_mark, top_level_mark, false),
509                object_super(),
510                shorthand(),
511                function_name(),
512            )
513        },
514        get,
515        "let obj = {
516            a(){
517                let c = super.x;
518            }
519        }"
520    );
521    test!(
522        ::swc_ecma_parser::Syntax::default(),
523        |_| {
524            (
525                resolver(Mark::new(), Mark::new(), false),
526                object_super(),
527                shorthand(),
528                function_name(),
529            )
530        },
531        call,
532        "let obj = {
533            a(){
534                super.y(1,2,3);
535            }
536        }"
537    );
538    test!(
539        ::swc_ecma_parser::Syntax::default(),
540        |_| {
541            (
542                resolver(Mark::new(), Mark::new(), false),
543                object_super(),
544                shorthand(),
545                function_name(),
546            )
547        },
548        set,
549        "let obj = {
550            a(){
551                super.x = 1;
552            }
553        }"
554    );
555    test!(
556        ::swc_ecma_parser::Syntax::default(),
557        |_| {
558            (
559                resolver(Mark::new(), Mark::new(), false),
560                object_super(),
561                shorthand(),
562                function_name(),
563            )
564        },
565        nest,
566        "let obj = {
567            b(){
568                super.bar()
569                let o = {
570                    d(){
571                        super.d()
572                    }
573                }
574            },
575        }"
576    );
577    test!(
578        Syntax::Es(EsSyntax {
579            allow_super_outside_method: true,
580            ..Default::default()
581        }),
582        |_| {
583            (
584                resolver(Mark::new(), Mark::new(), false),
585                object_super(),
586                shorthand(),
587                function_name(),
588            )
589        },
590        do_not_transform,
591        "let outer = {
592            b(){
593                let inner = {
594                    d:function d(){
595                        super.d() // should not transform
596                    }
597                }
598            },
599        }"
600    );
601}