swc_ecma_compat_es2015/classes/
constructor.rs

1use std::mem;
2
3use swc_common::{util::take::Take, Span, Spanned, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::{helper, helper_expr};
6use swc_ecma_transforms_classes::super_field::SuperFieldAccessFolder;
7use swc_ecma_utils::{default_constructor_with_span, private_ident, quote_ident, ExprFactory};
8use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
9use swc_trace_macro::swc_trace;
10use tracing::debug;
11
12use super::Config;
13
14pub(super) fn fold_constructor(
15    class_span: Span,
16    constructor: Option<Constructor>,
17    class_name: &Ident,
18    class_super_name: &Option<Ident>,
19    config: Config,
20) -> FnDecl {
21    let is_derived = class_super_name.is_some();
22    let mut constructor =
23        constructor.unwrap_or_else(|| default_constructor_with_span(is_derived, class_span));
24
25    // Black magic to detect injected constructor.
26    let is_constructor_default = constructor.span.is_dummy();
27    if is_constructor_default {
28        debug!("Dropping constructor parameters because the constructor is injected");
29        constructor.params.take();
30    }
31
32    let params = constructor
33        .params
34        .take()
35        .into_iter()
36        .map(|p| p.param().expect("All TS params should be converted"))
37        .collect();
38
39    let mut stmts = vec![];
40
41    if !config.no_class_calls {
42        // _class_call_check(this, C)
43        stmts.push(
44            CallExpr {
45                callee: helper!(class_call_check),
46                args: vec![
47                    Expr::This(ThisExpr { span: DUMMY_SP }).as_arg(),
48                    class_name.clone().as_arg(),
49                ],
50                ..Default::default()
51            }
52            .into_stmt(),
53        );
54    }
55
56    // handle super prop by default
57    let mut has_super_prop = true;
58    let mut this_mark = None;
59
60    let mut body = constructor.body.take().unwrap();
61    if let Some(class_super_name) = class_super_name {
62        let is_last_super = (&*body.stmts).is_super_last_call();
63        let is_last_return = body.stmts.last().is_some_and(Stmt::is_return_stmt);
64
65        let mut constructor_folder = ConstructorFolder {
66            class_key_init: vec![],
67            class_name: class_name.clone(),
68            class_super_name: class_super_name.clone(),
69            in_arrow: false,
70            in_nested_class: false,
71            is_constructor_default,
72            is_super_callable_constructor: config.super_is_callable_constructor,
73            super_found: false,
74            super_prop_found: false,
75            this: None,
76            this_ref_count: 0,
77        };
78
79        body.visit_mut_with(&mut constructor_folder);
80
81        // disable SuperFieldAccessFolder if super prop is not used
82        has_super_prop = constructor_folder.super_prop_found;
83
84        // Optimize for last super call
85        if is_last_super {
86            if let Some(stmt) = body.stmts.last_mut() {
87                if let Some(expr_stmt) = stmt.as_mut_expr() {
88                    let span = expr_stmt.span;
89                    match expr_stmt.expr.as_mut() {
90                        Expr::Assign(assign) => {
91                            let arg = if constructor_folder.this_ref_count == 1 {
92                                constructor_folder.this_ref_count = 0;
93                                assign.right.take()
94                            } else {
95                                assign.take().into()
96                            };
97                            *stmt = ReturnStmt {
98                                span,
99                                arg: arg.into(),
100                            }
101                            .into();
102                        }
103                        arg @ Expr::Seq(..) | arg @ Expr::Paren(..) => {
104                            *stmt = ReturnStmt {
105                                span,
106                                arg: Some(Box::new(arg.take())),
107                            }
108                            .into();
109                        }
110
111                        _ => {}
112                    }
113                };
114            }
115        }
116
117        if constructor_folder.this_ref_count > 0 || constructor_folder.super_prop_found {
118            let this = constructor_folder
119                .this
120                .get_or_insert_with(|| private_ident!("_this"))
121                .clone();
122
123            this_mark = Some(this.ctxt.outer());
124
125            let var = VarDeclarator {
126                name: Pat::Ident(this.into()),
127                span: DUMMY_SP,
128                init: None,
129                definite: false,
130            };
131
132            let var = VarDecl {
133                decls: vec![var],
134                ..Default::default()
135            };
136
137            stmts.push(var.into());
138        }
139
140        if !is_last_super && !is_last_return {
141            let this = if constructor_folder.super_found {
142                Expr::Ident(constructor_folder.this.unwrap())
143            } else {
144                let this = constructor_folder
145                    .this
146                    .map_or_else(|| Expr::undefined(DUMMY_SP).as_arg(), |this| this.as_arg());
147
148                helper_expr!(assert_this_initialized).as_call(DUMMY_SP, vec![this])
149            };
150
151            let return_this = ReturnStmt {
152                span: DUMMY_SP,
153                arg: Some(this.into()),
154            };
155            body.stmts.push(return_this.into());
156        }
157    }
158
159    if has_super_prop {
160        let mut folder = SuperFieldAccessFolder {
161            class_name,
162            constructor_this_mark: this_mark,
163            // constructor cannot be static
164            is_static: false,
165            folding_constructor: true,
166            in_nested_scope: false,
167            in_injected_define_property_call: false,
168            this_alias_mark: None,
169            constant_super: config.constant_super,
170            super_class: class_super_name,
171            in_pat: false,
172        };
173
174        body.visit_mut_with(&mut folder);
175
176        if let Some(mark) = folder.this_alias_mark {
177            stmts.push(
178                VarDecl {
179                    span: DUMMY_SP,
180                    declare: false,
181                    kind: VarDeclKind::Var,
182                    decls: vec![VarDeclarator {
183                        span: DUMMY_SP,
184                        name: quote_ident!(SyntaxContext::empty().apply_mark(mark), "_this").into(),
185                        init: Some(Box::new(Expr::This(ThisExpr { span: DUMMY_SP }))),
186                        definite: false,
187                    }],
188                    ..Default::default()
189                }
190                .into(),
191            )
192        }
193    }
194
195    stmts.extend(body.stmts);
196
197    let function = Function {
198        params,
199        body: Some(BlockStmt {
200            stmts,
201            ..Default::default()
202        }),
203        ..Default::default()
204    };
205
206    FnDecl {
207        ident: class_name.clone(),
208        declare: false,
209        function: function.into(),
210    }
211}
212
213struct ConstructorFolder {
214    class_key_init: Vec<Stmt>,
215    class_name: Ident,
216    class_super_name: Ident,
217
218    in_arrow: bool,
219    in_nested_class: bool,
220
221    is_constructor_default: bool,
222    is_super_callable_constructor: bool,
223
224    // super_found will be inherited from parent scope
225    // but it should be reset when exiting from the conditional scope
226    // e.g. if/while/for/try
227    super_found: bool,
228    super_prop_found: bool,
229
230    this: Option<Ident>,
231    this_ref_count: usize,
232}
233
234#[swc_trace]
235impl VisitMut for ConstructorFolder {
236    noop_visit_mut_type!(fail);
237
238    fn visit_mut_constructor(&mut self, _: &mut Constructor) {
239        // skip
240    }
241
242    fn visit_mut_function(&mut self, _: &mut Function) {
243        // skip
244    }
245
246    fn visit_mut_getter_prop(&mut self, _: &mut GetterProp) {
247        // skip
248    }
249
250    fn visit_mut_setter_prop(&mut self, _: &mut SetterProp) {
251        // skip
252    }
253
254    fn visit_mut_if_stmt(&mut self, node: &mut IfStmt) {
255        node.test.visit_mut_with(self);
256        let super_found = self.super_found;
257        node.cons.visit_mut_with(self);
258        node.alt.visit_mut_with(self);
259        self.super_found = super_found;
260    }
261
262    fn visit_mut_while_stmt(&mut self, node: &mut WhileStmt) {
263        node.test.visit_mut_with(self);
264        let super_found = self.super_found;
265        node.body.visit_mut_with(self);
266        self.super_found = super_found;
267    }
268
269    fn visit_mut_do_while_stmt(&mut self, node: &mut DoWhileStmt) {
270        node.test.visit_mut_with(self);
271        let super_found = self.super_found;
272        node.body.visit_mut_with(self);
273        self.super_found = super_found;
274    }
275
276    fn visit_mut_for_stmt(&mut self, node: &mut ForStmt) {
277        node.init.visit_mut_with(self);
278        node.test.visit_mut_with(self);
279        let super_found = self.super_found;
280        node.body.visit_mut_with(self);
281        node.update.visit_mut_with(self);
282        self.super_found = super_found;
283    }
284
285    fn visit_mut_for_of_stmt(&mut self, node: &mut ForOfStmt) {
286        node.left.visit_mut_with(self);
287        node.right.visit_mut_with(self);
288        let super_found = self.super_found;
289        node.body.visit_mut_with(self);
290        self.super_found = super_found;
291    }
292
293    fn visit_mut_for_in_stmt(&mut self, node: &mut ForInStmt) {
294        node.left.visit_mut_with(self);
295        node.right.visit_mut_with(self);
296        let super_found = self.super_found;
297        node.body.visit_mut_with(self);
298        self.super_found = super_found;
299    }
300
301    fn visit_mut_cond_expr(&mut self, node: &mut CondExpr) {
302        node.test.visit_mut_with(self);
303        let super_found = self.super_found;
304        node.cons.visit_mut_with(self);
305        node.alt.visit_mut_with(self);
306        self.super_found = super_found;
307    }
308
309    fn visit_mut_switch_stmt(&mut self, node: &mut SwitchStmt) {
310        node.discriminant.visit_mut_with(self);
311        let super_found = self.super_found;
312        node.cases.visit_mut_with(self);
313        self.super_found = super_found;
314    }
315
316    fn visit_mut_try_stmt(&mut self, node: &mut TryStmt) {
317        let super_found = self.super_found;
318        node.block.visit_mut_with(self);
319        node.handler.visit_mut_with(self);
320        self.super_found = super_found;
321        node.finalizer.visit_mut_with(self);
322    }
323
324    fn visit_mut_labeled_stmt(&mut self, node: &mut LabeledStmt) {
325        if node.body.is_block() {
326            let super_found = self.super_found;
327            node.body.visit_mut_with(self);
328            self.super_found = super_found;
329        } else {
330            node.body.visit_mut_with(self);
331        }
332    }
333
334    fn visit_mut_bin_expr(&mut self, node: &mut BinExpr) {
335        match node.op {
336            op!("&&") | op!("||") => {
337                node.left.visit_mut_with(self);
338                let super_found = self.super_found;
339                node.right.visit_mut_with(self);
340                self.super_found = super_found;
341            }
342            _ => {
343                node.visit_mut_children_with(self);
344            }
345        }
346    }
347
348    fn visit_mut_class(&mut self, node: &mut Class) {
349        let in_nested_class = mem::replace(&mut self.in_nested_class, true);
350        node.visit_mut_children_with(self);
351        self.in_nested_class = in_nested_class;
352    }
353
354    fn visit_mut_arrow_expr(&mut self, node: &mut ArrowExpr) {
355        let in_arrow = mem::replace(&mut self.in_arrow, true);
356        let super_found = self.super_found;
357        node.visit_mut_children_with(self);
358        self.super_found = super_found;
359        self.in_arrow = in_arrow;
360    }
361
362    fn visit_mut_stmts(&mut self, node: &mut Vec<Stmt>) {
363        for mut stmt in node.take().drain(..) {
364            stmt.visit_mut_with(self);
365            let class_key_init = self.class_key_init.take();
366            if !class_key_init.is_empty() {
367                node.extend(class_key_init);
368            }
369            node.push(stmt);
370        }
371    }
372
373    fn visit_mut_expr(&mut self, node: &mut Expr) {
374        if node.is_this() {
375            if !self.super_found {
376                *node = helper_expr!(assert_this_initialized)
377                    .as_call(DUMMY_SP, vec![self.get_this().clone().as_arg()]);
378            } else {
379                *node = self.get_this().clone().into();
380            }
381            return;
382        }
383
384        node.visit_mut_children_with(self);
385
386        if self.transform_super_call(node) {
387            self.super_found = true;
388
389            let this = self.get_this().clone();
390            let assign_expr = node.take().make_assign_to(op!("="), this.clone().into());
391
392            if self.in_nested_class {
393                self.class_key_init.push(assign_expr.into_stmt());
394                *node = this.into();
395            } else {
396                *node = assign_expr;
397            }
398        }
399    }
400
401    fn visit_mut_return_stmt(&mut self, node: &mut ReturnStmt) {
402        node.visit_mut_children_with(self);
403
404        if !self.in_arrow {
405            let arg = node.arg.take().map(ExprFactory::as_arg);
406            let mut args = vec![self.get_this().clone().as_arg()];
407            args.extend(arg);
408            node.arg = Some(
409                helper_expr!(possible_constructor_return)
410                    .as_call(DUMMY_SP, args)
411                    .into(),
412            );
413        }
414    }
415
416    fn visit_mut_super_prop(&mut self, node: &mut SuperProp) {
417        self.super_prop_found = true;
418
419        node.visit_mut_children_with(self);
420    }
421}
422
423#[swc_trace]
424impl ConstructorFolder {
425    fn get_this(&mut self) -> &Ident {
426        self.this_ref_count += 1;
427        self.this.get_or_insert_with(|| private_ident!("_this"))
428    }
429
430    fn transform_super_call(&self, node: &mut Expr) -> bool {
431        let Expr::Call(call_expr) = node else {
432            return false;
433        };
434
435        let CallExpr {
436            callee: callee @ Callee::Super(..),
437            args: origin_args,
438            ..
439        } = call_expr
440        else {
441            return false;
442        };
443
444        if self.is_super_callable_constructor {
445            if self.is_constructor_default || is_spread_arguements(origin_args) {
446                *callee = self
447                    .class_super_name
448                    .clone()
449                    .make_member(quote_ident!("apply"))
450                    .as_callee();
451
452                let mut arguments = quote_ident!("arguments");
453
454                if let Some(e) = origin_args.first() {
455                    arguments.span = e.expr.span()
456                }
457
458                *origin_args = vec![ThisExpr { span: DUMMY_SP }.as_arg(), arguments.as_arg()];
459            } else {
460                *callee = self
461                    .class_super_name
462                    .clone()
463                    .make_member(quote_ident!("call"))
464                    .as_callee();
465                origin_args.insert(0, ThisExpr { span: DUMMY_SP }.as_arg());
466            }
467
468            *node = BinExpr {
469                span: DUMMY_SP,
470                left: Box::new(node.take()),
471                op: op!("||"),
472                right: Box::new(Expr::This(ThisExpr { span: DUMMY_SP })),
473            }
474            .into();
475
476            return true;
477        }
478
479        *callee = helper!(call_super);
480
481        let mut args = vec![
482            ThisExpr { span: DUMMY_SP }.as_arg(),
483            self.class_name.clone().as_arg(),
484        ];
485
486        if self.is_constructor_default || is_spread_arguements(origin_args) {
487            // super(...arguments)
488            // _call_super(this, _super_class_name, ...arguments)
489
490            let mut arguments = quote_ident!("arguments");
491
492            if let Some(e) = origin_args.first() {
493                arguments.span = e.expr.span()
494            }
495
496            args.push(arguments.as_arg())
497        } else if !origin_args.is_empty() {
498            // super(a, b)
499            // _call_super(this, _super_class_name, [a, b])
500
501            let array = ArrayLit {
502                elems: origin_args.take().into_iter().map(Some).collect(),
503                ..Default::default()
504            };
505
506            args.push(array.as_arg());
507        }
508
509        *origin_args = args;
510
511        true
512    }
513}
514
515// ...arguments
516fn is_spread_arguements(args: &[ExprOrSpread]) -> bool {
517    if args.len() != 1 {
518        return false;
519    }
520
521    let arg = &args[0];
522
523    if arg.spread.is_none() {
524        return false;
525    }
526
527    arg.expr
528        .as_ident()
529        .filter(|ident| ident.sym == *"arguments")
530        .is_some()
531}
532
533trait SuperLastCall {
534    fn is_super_last_call(&self) -> bool;
535}
536
537impl SuperLastCall for &[Stmt] {
538    fn is_super_last_call(&self) -> bool {
539        self.iter()
540            .rev()
541            .find(|s| !s.is_empty())
542            .is_some_and(|s| s.is_super_last_call())
543    }
544}
545
546impl SuperLastCall for &Stmt {
547    fn is_super_last_call(&self) -> bool {
548        match self {
549            Stmt::Expr(ExprStmt { expr, .. }) => (&**expr).is_super_last_call(),
550            Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => (&**arg).is_super_last_call(),
551            _ => false,
552        }
553    }
554}
555
556impl SuperLastCall for &Expr {
557    fn is_super_last_call(&self) -> bool {
558        match self {
559            Expr::Call(CallExpr {
560                callee: Callee::Super(..),
561                ..
562            }) => true,
563            Expr::Paren(ParenExpr { expr, .. }) => (&**expr).is_super_last_call(),
564            Expr::Seq(SeqExpr { exprs, .. }) => {
565                exprs.last().is_some_and(|e| (&**e).is_super_last_call())
566            }
567            _ => false,
568        }
569    }
570}