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                }
154            }
155        }
156    }
157
158    fn visit_mut_expr(&mut self, expr: &mut Expr) {
159        self.visit_mut_super_member_call(expr);
160        self.visit_mut_super_member_set(expr);
161        self.visit_mut_super_member_get(expr);
162
163        expr.visit_mut_children_with(self)
164    }
165}
166
167#[swc_trace]
168impl SuperReplacer {
169    fn get_obj_ref(&mut self) -> Ident {
170        if let Some(obj) = &self.obj {
171            obj.clone()
172        } else {
173            let ident = private_ident!("_obj");
174            self.obj = Some(ident.clone());
175            ident
176        }
177    }
178
179    fn get_proto(&mut self) -> ExprOrSpread {
180        CallExpr {
181            span: DUMMY_SP,
182            callee: helper!(get_prototype_of),
183            args: vec![self.get_obj_ref().as_arg()],
184
185            ..Default::default()
186        }
187        .as_arg()
188    }
189
190    // .a -> "a"
191    fn normalize_computed_expr(&mut self, prop: &mut SuperProp) -> Box<Expr> {
192        match prop.take() {
193            SuperProp::Ident(IdentName {
194                sym: value, span, ..
195            }) => Lit::Str(Str {
196                raw: None,
197                value,
198                span,
199            })
200            .into(),
201
202            SuperProp::Computed(ComputedPropName { expr, .. }) => expr,
203        }
204    }
205
206    /// # In
207    /// ```js
208    /// super.foo(a)
209    /// ```
210    /// # out
211    /// ```js
212    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this).call(this, a)
213    /// ```
214    fn visit_mut_super_member_call(&mut self, n: &mut Expr) {
215        if let Expr::Call(CallExpr {
216            callee: Callee::Expr(callee_expr),
217            args,
218            ..
219        }) = n
220        {
221            if let Expr::SuperProp(SuperPropExpr {
222                obj: Super { span: super_token },
223                prop,
224                ..
225            }) = &mut **callee_expr
226            {
227                let prop = self.normalize_computed_expr(prop);
228                let callee =
229                    SuperReplacer::super_to_get_call(self.get_proto(), *super_token, prop.as_arg());
230                let this = ThisExpr { span: DUMMY_SP }.as_arg();
231                if args.len() == 1 && is_rest_arguments(&args[0]) {
232                    *n = CallExpr {
233                        span: DUMMY_SP,
234                        callee: MemberExpr {
235                            span: DUMMY_SP,
236                            obj: Box::new(callee),
237                            prop: quote_ident!("apply").into(),
238                        }
239                        .as_callee(),
240                        args: iter::once(this)
241                            .chain(iter::once({
242                                let mut arg = args.pop().unwrap();
243                                arg.spread = None;
244                                arg
245                            }))
246                            .collect(),
247                        ..Default::default()
248                    }
249                    .into();
250                    return;
251                }
252
253                *n = CallExpr {
254                    span: DUMMY_SP,
255                    callee: MemberExpr {
256                        span: DUMMY_SP,
257                        obj: Box::new(callee),
258                        prop: MemberProp::Ident(quote_ident!("call")),
259                    }
260                    .as_callee(),
261                    args: iter::once(this).chain(args.take()).collect(),
262                    ..Default::default()
263                }
264                .into();
265            }
266        }
267    }
268
269    /// # In
270    /// ```js
271    /// super.foo = bar
272    /// # out
273    /// ```js
274    /// _set(_get_prototype_of(_obj), "foo", bar, this, true)
275    /// ```
276    fn visit_mut_super_member_set(&mut self, n: &mut Expr) {
277        match n {
278            Expr::Update(UpdateExpr {
279                arg, op, prefix, ..
280            }) => {
281                if let Expr::SuperProp(SuperPropExpr {
282                    obj: Super { span: super_token },
283                    prop,
284                    ..
285                }) = &mut **arg
286                {
287                    let op = match op {
288                        op!("++") => op!("+="),
289                        op!("--") => op!("-="),
290                    };
291                    *n = self.super_to_set_call(*super_token, true, prop, op, 1.0.into(), *prefix);
292                }
293            }
294
295            Expr::Assign(AssignExpr {
296                span,
297                left,
298                op,
299                right,
300            }) => {
301                if let AssignTarget::Simple(SimpleAssignTarget::SuperProp(SuperPropExpr {
302                    obj: Super { span: super_token },
303                    prop,
304                    ..
305                })) = left
306                {
307                    *n =
308                        self.super_to_set_call(*super_token, false, prop, *op, right.take(), false);
309                    return;
310                }
311                left.visit_mut_children_with(self);
312                *n = AssignExpr {
313                    span: *span,
314                    left: left.take(),
315                    op: *op,
316                    right: right.take(),
317                }
318                .into();
319            }
320            _ => {}
321        }
322    }
323
324    /// # In
325    /// ```js
326    /// super.foo
327    /// ```
328    /// # out
329    /// ```js
330    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this)
331    /// ```
332    fn visit_mut_super_member_get(&mut self, n: &mut Expr) {
333        if let Expr::SuperProp(SuperPropExpr {
334            obj: Super {
335                span: super_token, ..
336            },
337            prop,
338            ..
339        }) = n
340        {
341            let prop = self.normalize_computed_expr(prop);
342            *n = SuperReplacer::super_to_get_call(self.get_proto(), *super_token, prop.as_arg());
343        }
344    }
345
346    fn super_to_get_call(proto: ExprOrSpread, super_token: Span, prop: ExprOrSpread) -> Expr {
347        CallExpr {
348            span: super_token,
349            callee: helper!(get),
350            args: vec![proto, prop, ThisExpr { span: super_token }.as_arg()],
351            ..Default::default()
352        }
353        .into()
354    }
355
356    fn to_bin_expr(left: Box<Expr>, op: AssignOp, rhs: Box<Expr>) -> BinExpr {
357        BinExpr {
358            span: DUMMY_SP,
359            left,
360            op: op.to_update().unwrap(),
361            right: rhs,
362        }
363    }
364
365    fn call_set_helper(
366        &mut self,
367        super_token: Span,
368        prop: ExprOrSpread,
369        rhs: ExprOrSpread,
370    ) -> Expr {
371        CallExpr {
372            span: super_token,
373            callee: helper!(set),
374            args: vec![
375                self.get_proto(),
376                prop,
377                rhs,
378                ThisExpr { span: super_token }.as_arg(),
379                // strict
380                true.as_arg(),
381            ],
382            ..Default::default()
383        }
384        .into()
385    }
386
387    fn super_to_set_call(
388        &mut self,
389        super_token: Span,
390        is_update: bool,
391        prop: &mut SuperProp,
392        op: AssignOp,
393        rhs: Box<Expr>,
394        prefix: bool,
395    ) -> Expr {
396        let computed = match prop {
397            SuperProp::Ident(_) => false,
398            SuperProp::Computed(_) => true,
399        };
400        let mut prop = self.normalize_computed_expr(prop);
401        match op {
402            op!("=") => self.call_set_helper(super_token, prop.as_arg(), rhs.as_arg()),
403            _ => {
404                let left = Box::new(SuperReplacer::super_to_get_call(
405                    self.get_proto(),
406                    super_token,
407                    if computed {
408                        let ref_ident = alias_ident_for(&rhs, "_ref").into_private();
409                        self.vars.push(ref_ident.clone());
410                        *prop = AssignExpr {
411                            span: DUMMY_SP,
412                            left: ref_ident.clone().into(),
413                            op: op!("="),
414                            right: prop.take(),
415                        }
416                        .into();
417                        ref_ident.as_arg()
418                    } else {
419                        prop.clone().as_arg()
420                    },
421                ));
422                if is_update {
423                    if prefix {
424                        self.call_set_helper(
425                            super_token,
426                            prop.as_arg(),
427                            SuperReplacer::to_bin_expr(
428                                UnaryExpr {
429                                    span: DUMMY_SP,
430                                    op: op!(unary, "+"),
431                                    arg: left,
432                                }
433                                .into(),
434                                op,
435                                rhs,
436                            )
437                            .as_arg(),
438                        )
439                    } else {
440                        let update_ident = alias_ident_for(&rhs, "_super").into_private();
441                        self.vars.push(update_ident.clone());
442                        SeqExpr {
443                            span: DUMMY_SP,
444                            exprs: vec![
445                                Box::new(
446                                    self.call_set_helper(
447                                        super_token,
448                                        prop.as_arg(),
449                                        SuperReplacer::to_bin_expr(
450                                            Box::new(
451                                                AssignExpr {
452                                                    span: DUMMY_SP,
453                                                    left: update_ident.clone().into(),
454                                                    op: op!("="),
455                                                    right: Box::new(Expr::Unary(UnaryExpr {
456                                                        span: DUMMY_SP,
457                                                        op: op!(unary, "+"),
458                                                        arg: left,
459                                                    })),
460                                                }
461                                                .into(),
462                                            ),
463                                            op,
464                                            rhs,
465                                        )
466                                        .as_arg(),
467                                    ),
468                                ),
469                                Box::new(Expr::Ident(update_ident)),
470                            ],
471                        }
472                        .into()
473                    }
474                } else {
475                    self.call_set_helper(
476                        super_token,
477                        prop.as_arg(),
478                        SuperReplacer::to_bin_expr(left, op, rhs).as_arg(),
479                    )
480                }
481            }
482        }
483    }
484}
485#[cfg(test)]
486mod tests {
487    use swc_common::Mark;
488    use swc_ecma_parser::{EsSyntax, Syntax};
489    use swc_ecma_transforms_base::resolver;
490    use swc_ecma_transforms_testing::test;
491
492    use super::*;
493    use crate::{function_name, shorthand};
494    test!(
495        ::swc_ecma_parser::Syntax::default(),
496        |_| {
497            let unresolved_mark = Mark::new();
498            let top_level_mark = Mark::new();
499            (
500                resolver(unresolved_mark, top_level_mark, false),
501                object_super(),
502                shorthand(),
503                function_name(),
504            )
505        },
506        get,
507        "let obj = {
508            a(){
509                let c = super.x;
510            }
511        }"
512    );
513    test!(
514        ::swc_ecma_parser::Syntax::default(),
515        |_| {
516            (
517                resolver(Mark::new(), Mark::new(), false),
518                object_super(),
519                shorthand(),
520                function_name(),
521            )
522        },
523        call,
524        "let obj = {
525            a(){
526                super.y(1,2,3);
527            }
528        }"
529    );
530    test!(
531        ::swc_ecma_parser::Syntax::default(),
532        |_| {
533            (
534                resolver(Mark::new(), Mark::new(), false),
535                object_super(),
536                shorthand(),
537                function_name(),
538            )
539        },
540        set,
541        "let obj = {
542            a(){
543                super.x = 1;
544            }
545        }"
546    );
547    test!(
548        ::swc_ecma_parser::Syntax::default(),
549        |_| {
550            (
551                resolver(Mark::new(), Mark::new(), false),
552                object_super(),
553                shorthand(),
554                function_name(),
555            )
556        },
557        nest,
558        "let obj = {
559            b(){
560                super.bar()
561                let o = {
562                    d(){
563                        super.d()
564                    }
565                }
566            },
567        }"
568    );
569    test!(
570        Syntax::Es(EsSyntax {
571            allow_super_outside_method: true,
572            ..Default::default()
573        }),
574        |_| {
575            (
576                resolver(Mark::new(), Mark::new(), false),
577                object_super(),
578                shorthand(),
579                function_name(),
580            )
581        },
582        do_not_transform,
583        "let outer = {
584            b(){
585                let inner = {
586                    d:function d(){
587                        super.d() // should not transform
588                    }
589                }
590            },
591        }"
592    );
593}