swc_ecma_compat_es2015/classes/
mod.rs

1use std::iter;
2
3use rustc_hash::FxBuildHasher;
4use serde::Deserialize;
5use swc_common::{util::take::Take, BytePos, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_base::{helper, native::is_native, perf::Check};
8use swc_ecma_transforms_classes::super_field::SuperFieldAccessFolder;
9use swc_ecma_transforms_macros::fast_path;
10use swc_ecma_utils::{
11    alias_if_required, contains_this_expr, is_valid_ident, is_valid_prop_ident, prepend_stmt,
12    private_ident, prop_name_to_expr, quote_expr, quote_ident, quote_str, replace_ident,
13    ExprFactory, ModuleItemLike, StmtLike,
14};
15use swc_ecma_visit::{
16    noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
17};
18use swc_trace_macro::swc_trace;
19
20use self::{
21    constructor::fold_constructor,
22    prop_name::{is_pure_prop_name, should_extract_class_prop_key, HashKey},
23};
24
25mod constructor;
26mod prop_name;
27
28pub fn classes(config: Config) -> impl Pass {
29    visit_mut_pass(Classes {
30        in_strict: false,
31        config,
32
33        params: Default::default(),
34        args: Default::default(),
35    })
36}
37
38type IndexMap<K, V> = indexmap::IndexMap<K, V, FxBuildHasher>;
39
40/// `@babel/plugin-transform-classes`
41///
42/// # In
43/// ```js
44/// class Test {
45///   constructor(name) {
46///     this.name = name;
47///   }
48///
49///   logger () {
50///     console.log("Hello", this.name);
51///   }
52/// }
53/// ```
54///
55/// # Out
56/// ```js
57/// var Test = function () {
58///   function Test(name) {
59///     _class_call_check(this, Test);
60///
61///     this.name = name;
62///   }
63///
64///   Test.prototype.logger = function logger() {
65///     console.log("Hello", this.name);
66///   };
67///
68///   return Test;
69/// }();
70/// ```
71#[derive(Default, Clone)]
72struct Classes {
73    in_strict: bool,
74    config: Config,
75
76    params: Vec<Param>,
77    args: Vec<ExprOrSpread>,
78}
79
80#[derive(Debug, Clone, Copy, Default, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct Config {
83    #[serde(default)]
84    pub constant_super: bool,
85    #[serde(default)]
86    pub no_class_calls: bool,
87    #[serde(default)]
88    pub set_class_methods: bool,
89    #[serde(default)]
90    pub super_is_callable_constructor: bool,
91}
92
93struct Data {
94    key_prop: Box<PropName>,
95    method: Option<Box<Expr>>,
96    set: Option<Box<Expr>>,
97    get: Option<Box<Expr>>,
98}
99
100#[swc_trace]
101impl Classes {
102    fn visit_mut_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
103    where
104        T: StmtLike + ModuleItemLike + VisitMutWith<Self> + Take,
105    {
106        let mut buf = Vec::with_capacity(stmts.len());
107        let mut first = true;
108        let old = self.in_strict;
109
110        for stmt in stmts.iter_mut() {
111            match T::try_into_stmt(stmt.take()) {
112                Err(node) => match node.try_into_module_decl() {
113                    Ok(mut decl) => {
114                        match decl {
115                            ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
116                                decl: DefaultDecl::Class(ClassExpr { ident, class }),
117                                ..
118                            }) => {
119                                let ident =
120                                    ident.unwrap_or_else(|| quote_ident!("_default").into());
121
122                                let mut decl = self.fold_class_as_var_decl(ident.clone(), class);
123                                decl.visit_mut_children_with(self);
124                                buf.push(T::from(decl.into()));
125
126                                buf.push(
127                                    match T::try_from_module_decl(
128                                        NamedExport {
129                                            span: DUMMY_SP,
130                                            specifiers: vec![ExportNamedSpecifier {
131                                                span: DUMMY_SP,
132                                                orig: ModuleExportName::Ident(ident),
133                                                exported: Some(ModuleExportName::Ident(
134                                                    quote_ident!("default").into(),
135                                                )),
136                                                is_type_only: false,
137                                            }
138                                            .into()],
139                                            src: None,
140                                            type_only: false,
141                                            with: None,
142                                        }
143                                        .into(),
144                                    ) {
145                                        Ok(t) => t,
146                                        Err(..) => unreachable!(),
147                                    },
148                                );
149                            }
150                            ModuleDecl::ExportDecl(ExportDecl {
151                                span,
152                                decl:
153                                    Decl::Class(ClassDecl {
154                                        ident,
155                                        declare: false,
156                                        class,
157                                    }),
158                                ..
159                            }) => {
160                                let mut decl = self.fold_class_as_var_decl(ident, class);
161                                decl.visit_mut_children_with(self);
162                                buf.push(
163                                    match T::try_from_module_decl(
164                                        ExportDecl {
165                                            span,
166                                            decl: decl.into(),
167                                        }
168                                        .into(),
169                                    ) {
170                                        Ok(t) => t,
171                                        Err(..) => unreachable!(),
172                                    },
173                                );
174                            }
175                            _ => buf.push({
176                                decl.visit_mut_children_with(self);
177                                match T::try_from_module_decl(decl) {
178                                    Ok(t) => t,
179                                    Err(..) => unreachable!(),
180                                }
181                            }),
182                        };
183                    }
184                    Err(..) => unreachable!(),
185                },
186                Ok(mut stmt) => {
187                    if first {
188                        self.in_strict |= stmt.is_use_strict();
189                    }
190
191                    stmt.visit_mut_children_with(self);
192                    buf.push(T::from(stmt));
193                }
194            }
195            first = false;
196        }
197
198        self.in_strict = old;
199        *stmts = buf;
200    }
201}
202
203#[swc_trace]
204#[fast_path(ClassFinder)]
205impl VisitMut for Classes {
206    noop_visit_mut_type!(fail);
207
208    fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
209        self.visit_mut_stmt_like(items)
210    }
211
212    fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
213        self.visit_mut_stmt_like(items)
214    }
215
216    fn visit_mut_decl(&mut self, n: &mut Decl) {
217        if let Decl::Class(decl) = n {
218            *n = self
219                .fold_class_as_var_decl(decl.ident.take(), decl.class.take())
220                .into()
221        };
222
223        n.visit_mut_children_with(self);
224    }
225
226    fn visit_mut_expr(&mut self, n: &mut Expr) {
227        match n {
228            Expr::Class(e) => {
229                let mut class = self.fold_class(e.ident.take(), e.class.take());
230                if let Expr::Call(call) = &mut class {
231                    self.add_pure_comments(&mut call.span.lo)
232                }
233                *n = class;
234                n.visit_mut_children_with(self)
235            }
236
237            _ => n.visit_mut_children_with(self),
238        }
239    }
240
241    fn visit_mut_var_declarator(&mut self, d: &mut VarDeclarator) {
242        // will there be anything else in var name at this point?
243        if let VarDeclarator {
244            name: Pat::Ident(i),
245            init: Some(init),
246            ..
247        } = d
248        {
249            if let Expr::Class(c @ ClassExpr { ident: None, .. }) = &mut **init {
250                c.ident = Some(Ident::from(&*i).into_private())
251            }
252        }
253
254        d.visit_mut_children_with(self)
255    }
256
257    /// `let { f = class /* f */ {} } = {};`
258    fn visit_mut_assign_pat_prop(&mut self, n: &mut AssignPatProp) {
259        if let Some(value) = &mut n.value {
260            if let Expr::Class(c @ ClassExpr { ident: None, .. }) = &mut **value {
261                c.ident = Some(Ident::from(&n.key).into_private());
262            }
263        }
264
265        n.visit_mut_children_with(self);
266    }
267
268    /// `let [c = class /* c */ {}] = [];`
269    /// `function foo(bar = class /* bar */ {}) {}`
270    fn visit_mut_assign_pat(&mut self, n: &mut AssignPat) {
271        if let (Pat::Ident(id), Expr::Class(c @ ClassExpr { ident: None, .. })) =
272            (&*n.left, &mut *n.right)
273        {
274            c.ident = Some(Ident::from(id).into_private());
275        }
276
277        n.visit_mut_children_with(self);
278    }
279
280    /// {
281    ///     hello: class {},
282    ///     "foo": class {},
283    ///     ["x"]: class {}
284    /// }
285    fn visit_mut_key_value_prop(&mut self, n: &mut KeyValueProp) {
286        if let Expr::Class(c @ ClassExpr { ident: None, .. }) = &mut *n.value {
287            match &n.key {
288                PropName::Ident(ident) => {
289                    c.ident = Some(Ident::from(ident.clone()).into_private());
290                }
291                PropName::Str(Str { value, span, .. }) => {
292                    if is_valid_prop_ident(value) {
293                        c.ident = Some(private_ident!(*span, value.clone()));
294                    }
295                }
296                PropName::Computed(ComputedPropName { expr, .. }) => {
297                    if let Expr::Lit(Lit::Str(Str { value, span, .. })) = &**expr {
298                        if is_valid_prop_ident(value) {
299                            c.ident = Some(private_ident!(*span, value.clone()));
300                        }
301                    }
302                }
303                _ => {}
304            }
305        }
306
307        n.visit_mut_children_with(self)
308    }
309
310    fn visit_mut_assign_expr(&mut self, a: &mut AssignExpr) {
311        if let AssignExpr {
312            op: op!("=") | op!("||=") | op!("??="),
313            left,
314            right,
315            ..
316        } = a
317        {
318            if let Expr::Class(c @ ClassExpr { ident: None, .. }) = &mut **right {
319                if let AssignTarget::Simple(SimpleAssignTarget::Ident(ident)) = left {
320                    c.ident = Some(Ident::from(&*ident).into_private())
321                }
322            }
323        }
324
325        a.visit_mut_children_with(self)
326    }
327}
328
329#[swc_trace]
330impl Classes {
331    fn add_pure_comments(&mut self, start: &mut BytePos) {
332        *start = BytePos::PURE;
333    }
334
335    fn fold_class_as_var_decl(&mut self, ident: Ident, class: Box<Class>) -> VarDecl {
336        let span = class.span;
337        let mut rhs = self.fold_class(Some(ident.clone()), class);
338
339        let mut new_name = ident.clone();
340        new_name.ctxt = new_name.ctxt.apply_mark(Mark::new());
341
342        replace_ident(&mut rhs, ident.to_id(), &new_name);
343
344        // let VarDecl take every comments except pure
345        if let Expr::Call(call) = &mut rhs {
346            let mut span = Span {
347                // after class
348                lo: span.lo + BytePos(5),
349                ..span
350            };
351            self.add_pure_comments(&mut span.lo);
352            call.span = span;
353        }
354
355        VarDecl {
356            span,
357            kind: VarDeclKind::Let,
358            decls: vec![VarDeclarator {
359                span,
360                init: Some(Box::new(rhs)),
361                // Foo in var Foo =
362                name: ident.into(),
363                definite: false,
364            }],
365            ..Default::default()
366        }
367    }
368
369    /// Turns class expression into iife.
370    ///
371    /// ```js
372    /// class Foo {}
373    /// ```
374    ///
375    /// ```js
376    /// function() {
377    ///   var Foo = function Foo(){
378    ///   };
379    /// }()
380    /// ```
381    fn fold_class(&mut self, class_name: Option<Ident>, class: Box<Class>) -> Expr {
382        let span = class.span;
383
384        // Ident of the super class *inside* function.
385        let super_ident = class
386            .super_class
387            .as_ref()
388            .map(|e| alias_if_required(e, "_superClass").0);
389        let has_super = super_ident.is_some();
390        let (mut params, mut args, super_ident) = if let Some(ref super_ident) = super_ident {
391            // Param should have a separate syntax context from arg.
392            let super_param = private_ident!(super_ident.sym.clone());
393            let params = vec![Param {
394                span: DUMMY_SP,
395                decorators: Default::default(),
396                pat: super_param.clone().into(),
397            }];
398
399            let super_class = class.super_class.clone().unwrap();
400            let is_super_native = match *super_class {
401                Expr::Ident(Ident { ref sym, .. }) => is_native(sym),
402                _ => false,
403            };
404            if is_super_native {
405                (
406                    params,
407                    vec![CallExpr {
408                        span: DUMMY_SP,
409                        callee: helper!(wrap_native_super),
410                        args: vec![super_class.as_arg()],
411                        ..Default::default()
412                    }
413                    .as_arg()],
414                    Some(super_param),
415                )
416            } else {
417                (params, vec![super_class.as_arg()], Some(super_param))
418            }
419        } else {
420            (Vec::new(), Vec::new(), None)
421        };
422
423        let mut stmts = self.class_to_stmts(class_name, super_ident, class);
424        params.extend(self.params.take());
425        args.extend(self.args.take());
426
427        let cnt_of_non_directive = stmts
428            .iter()
429            .filter(|stmt| match stmt {
430                Stmt::Expr(ExprStmt { expr, .. }) => !matches!(&**expr, Expr::Lit(Lit::Str(..))),
431                _ => true,
432            })
433            .count();
434        if !has_super && cnt_of_non_directive == 1 {
435            //    class Foo {}
436            //
437            // should be
438            //
439            //    var Foo = function Foo() {
440            //        _class_call_check(this, Foo);
441            //    };
442            //
443            // instead of
444            //
445            //    var Foo = function(){
446            //      function Foo() {
447            //          _class_call_check(this, Foo);
448            //      }
449            //
450            //      return Foo;
451            //    }();
452
453            let stmt = stmts.pop().unwrap();
454            match stmt {
455                Stmt::Decl(Decl::Fn(FnDecl {
456                    ident,
457                    mut function,
458                    ..
459                })) => {
460                    if let Some(use_strict) = stmts.pop() {
461                        prepend_stmt(&mut function.body.as_mut().unwrap().stmts, use_strict);
462                    }
463                    function.span = span;
464                    return FnExpr {
465                        ident: Some(ident),
466                        function,
467                    }
468                    .into();
469                }
470                _ => unreachable!(),
471            }
472        }
473
474        let body = BlockStmt {
475            span: DUMMY_SP,
476            stmts,
477            ..Default::default()
478        };
479
480        let call = CallExpr {
481            span,
482            callee: Function {
483                span,
484                is_async: false,
485                is_generator: false,
486                params,
487                body: Some(body),
488                ..Default::default()
489            }
490            .as_callee(),
491            args,
492            ..Default::default()
493        };
494
495        call.into()
496    }
497
498    /// Returned `stmts` contains `return Foo`
499    fn class_to_stmts(
500        &mut self,
501        class_name: Option<Ident>,
502        super_class_ident: Option<Ident>,
503        class: Box<Class>,
504    ) -> Vec<Stmt> {
505        let class_name = class_name.unwrap_or_else(|| quote_ident!("_class").into());
506        let mut stmts = Vec::new();
507
508        let mut methods = Vec::new();
509        let mut constructor = None;
510        for member in class.body {
511            match member {
512                ClassMember::Constructor(c) => constructor = Some(c),
513                ClassMember::Method(m) => methods.push(m),
514
515                ClassMember::PrivateMethod(_)
516                | ClassMember::ClassProp(..)
517                | ClassMember::PrivateProp(..)
518                | ClassMember::TsIndexSignature(..)
519                | ClassMember::StaticBlock(..)
520                | ClassMember::AutoAccessor(..) => {}
521                ClassMember::Empty(..) => {}
522            }
523        }
524
525        if let Some(ref super_class_ident) = super_class_ident {
526            // inject helper methods
527
528            let mut class_name_sym = class_name.clone();
529            class_name_sym.span = DUMMY_SP;
530            class_name_sym.ctxt = class_name.ctxt;
531
532            let mut super_class_name_sym = super_class_ident.clone();
533            super_class_name_sym.span = DUMMY_SP;
534            super_class_name_sym.ctxt = super_class_ident.ctxt;
535
536            stmts.push(
537                CallExpr {
538                    span: DUMMY_SP,
539                    callee: helper!(inherits),
540                    args: vec![class_name_sym.as_arg(), super_class_name_sym.as_arg()],
541                    ..Default::default()
542                }
543                .into_stmt(),
544            );
545        }
546
547        // constructor
548        stmts.push(
549            fold_constructor(
550                class.span,
551                constructor,
552                &class_name,
553                &super_class_ident,
554                self.config,
555            )
556            .into(),
557        );
558
559        // convert class methods
560        stmts.extend(self.fold_class_methods(&class_name, &super_class_ident, methods));
561
562        if stmts.first().map(|v| !v.is_use_strict()).unwrap_or(false) && !self.in_strict {
563            prepend_stmt(
564                &mut stmts,
565                Lit::Str(Str {
566                    span: DUMMY_SP,
567                    value: "use strict".into(),
568                    raw: Some("\"use strict\"".into()),
569                })
570                .into_stmt(),
571            );
572
573            if stmts.len() == 2 {
574                return stmts;
575            }
576        }
577
578        if super_class_ident.is_none()
579            && stmts
580                .iter()
581                .filter(|stmt| match stmt {
582                    Stmt::Expr(ExprStmt { expr, .. }) => {
583                        !matches!(&**expr, Expr::Lit(Lit::Str(..)))
584                    }
585                    _ => true,
586                })
587                .count()
588                == 1
589        {
590            return stmts;
591        }
592
593        let mut class_name_sym = class_name.clone();
594        class_name_sym.span = DUMMY_SP;
595        class_name_sym.ctxt = class_name.ctxt;
596
597        // `return Foo`
598        stmts.push(
599            ReturnStmt {
600                span: DUMMY_SP,
601                arg: Some(class_name_sym.into()),
602            }
603            .into(),
604        );
605
606        stmts
607    }
608
609    fn fold_class_methods(
610        &mut self,
611        class_name: &Ident,
612        super_class_ident: &Option<Ident>,
613        methods: Vec<ClassMethod>,
614    ) -> Vec<Stmt> {
615        if methods.is_empty() {
616            return Vec::new();
617        }
618
619        /// { key: "prop" }
620        fn mk_key_prop(key: PropName) -> Box<Prop> {
621            Box::new(Prop::KeyValue(KeyValueProp {
622                key: PropName::Ident(quote_ident!(Default::default(), key.span(), "key").into()),
623                value: match key {
624                    PropName::Ident(i) => Lit::Str(quote_str!(i.span, i.sym)).into(),
625                    PropName::Str(s) => s.into(),
626                    PropName::Num(n) => n.into(),
627                    PropName::BigInt(b) => Str {
628                        span: b.span,
629                        raw: None,
630                        value: b.value.to_string().into(),
631                    }
632                    .into(),
633                    PropName::Computed(c) => c.expr,
634                },
635            }))
636        }
637
638        fn mk_key_prop_member(key: PropName) -> MemberProp {
639            match key {
640                PropName::Ident(i) => MemberProp::Ident(i),
641                PropName::Str(s) => MemberProp::Computed(ComputedPropName {
642                    span: s.span,
643                    expr: Lit::Str(s).into(),
644                }),
645                PropName::Num(n) => MemberProp::Computed(ComputedPropName {
646                    span: n.span,
647                    expr: Lit::Num(n).into(),
648                }),
649                PropName::BigInt(b) => MemberProp::Computed(ComputedPropName {
650                    span: b.span,
651                    expr: Str {
652                        span: b.span,
653                        raw: None,
654                        value: b.value.to_string().into(),
655                    }
656                    .into(),
657                }),
658                PropName::Computed(c) => MemberProp::Computed(c),
659            }
660        }
661
662        fn mk_arg_obj_for_create_class(props: IndexMap<HashKey, Data>) -> ExprOrSpread {
663            if props.is_empty() {
664                return quote_expr!(DUMMY_SP, null).as_arg();
665            }
666            ArrayLit {
667                span: DUMMY_SP,
668                elems: props
669                    .into_iter()
670                    .map(|(_, data)| {
671                        let mut props = vec![PropOrSpread::Prop(mk_key_prop(*data.key_prop))];
672
673                        macro_rules! add {
674                            ($field:expr, $kind:expr, $s:literal) => {{
675                                if let Some(value) = $field {
676                                    let value = escape_keywords(value);
677                                    props.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
678                                        KeyValueProp {
679                                            key: PropName::Ident(quote_ident!($s)),
680                                            value,
681                                        },
682                                    ))));
683                                }
684                            }};
685                        }
686
687                        add!(data.get, MethodKind::Getter, "get");
688                        add!(data.set, MethodKind::Setter, "set");
689                        add!(data.method, MethodKind::Method, "value");
690
691                        ObjectLit {
692                            span: DUMMY_SP,
693                            props,
694                        }
695                        .as_arg()
696                    })
697                    .map(Some)
698                    .collect(),
699            }
700            .as_arg()
701        }
702
703        /// _create_class(Foo, [{}], [{}]);
704        fn mk_create_class_call(
705            class_name: Ident,
706            methods: ExprOrSpread,
707            static_methods: Option<ExprOrSpread>,
708        ) -> Stmt {
709            let mut class_name_sym = class_name.clone();
710            class_name_sym.span = DUMMY_SP;
711            class_name_sym.ctxt = class_name.ctxt;
712
713            CallExpr {
714                span: DUMMY_SP,
715                callee: helper!(create_class),
716                args: iter::once(class_name_sym.as_arg())
717                    .chain(iter::once(methods))
718                    .chain(static_methods)
719                    .collect(),
720                ..Default::default()
721            }
722            .into_stmt()
723        }
724
725        let (mut props, mut static_props) = (IndexMap::default(), IndexMap::default());
726
727        let should_extract = should_extract_class_prop_key(&methods);
728
729        for mut m in methods {
730            let key = HashKey::from(&m.key);
731            let key_is_pure = is_pure_prop_name(&m.key);
732            // class is always strict, however computed key is not part of class
733            let key_contain_this = !self.in_strict && contains_this_expr(&m.key);
734            let key_prop = Box::new(m.key.clone());
735            let computed = matches!(m.key, PropName::Computed(..));
736            let prop_name = prop_name_to_expr(m.key);
737
738            let key_prop = if should_extract && !key_is_pure || key_contain_this {
739                let ident = private_ident!("_prop");
740
741                self.params.push(ident.clone().into());
742                self.args.push(prop_name.clone().into());
743
744                Box::new(PropName::Computed(ComputedPropName {
745                    span: DUMMY_SP,
746                    expr: Box::new(ident.into()),
747                }))
748            } else {
749                key_prop
750            };
751
752            let append_to: &mut IndexMap<_, _> = if m.is_static {
753                &mut static_props
754            } else {
755                &mut props
756            };
757
758            let mut folder = SuperFieldAccessFolder {
759                class_name,
760
761                constructor_this_mark: None,
762                is_static: m.is_static,
763                folding_constructor: false,
764                in_nested_scope: false,
765                in_injected_define_property_call: false,
766                this_alias_mark: None,
767                constant_super: self.config.constant_super,
768                super_class: super_class_ident,
769                in_pat: false,
770            };
771            m.function.visit_mut_with(&mut folder);
772
773            if let Some(mark) = folder.this_alias_mark {
774                prepend_stmt(
775                    &mut m.function.body.as_mut().unwrap().stmts,
776                    VarDecl {
777                        span: DUMMY_SP,
778                        declare: false,
779                        kind: VarDeclKind::Var,
780                        decls: vec![VarDeclarator {
781                            span: DUMMY_SP,
782                            name: quote_ident!(SyntaxContext::empty().apply_mark(mark), "_this")
783                                .into(),
784                            init: Some(Box::new(Expr::This(ThisExpr { span: DUMMY_SP }))),
785                            definite: false,
786                        }],
787                        ..Default::default()
788                    }
789                    .into(),
790                );
791            }
792
793            let value = FnExpr {
794                ident: if m.kind == MethodKind::Method && !computed {
795                    match prop_name {
796                        Expr::Ident(ident) => Some(private_ident!(ident.span, ident.sym)),
797                        Expr::Lit(Lit::Str(Str { span, value, .. })) if is_valid_ident(&value) => {
798                            Some(Ident::new(
799                                value,
800                                span,
801                                SyntaxContext::empty().apply_mark(Mark::new()),
802                            ))
803                        }
804                        _ => None,
805                    }
806                } else {
807                    None
808                },
809                function: m.function,
810            }
811            .into();
812
813            let data = append_to.entry(key).or_insert_with(|| Data {
814                key_prop,
815                get: None,
816                set: None,
817                method: None,
818            });
819            match m.kind {
820                // https://github.com/swc-project/swc/issues/5029
821                MethodKind::Getter => {
822                    data.method = None;
823                    data.get = Some(value)
824                }
825                MethodKind::Setter => {
826                    data.method = None;
827                    data.set = Some(value)
828                }
829                MethodKind::Method => {
830                    data.get = None;
831                    data.set = None;
832                    data.method = Some(value)
833                }
834            }
835        }
836
837        let mut res = Vec::new();
838
839        if self.config.set_class_methods {
840            let proto = private_ident!("_proto");
841            props.retain(|_, v| {
842                if let Some(method) = v.method.take() {
843                    if res.is_empty() {
844                        res.push(
845                            VarDecl {
846                                span: DUMMY_SP,
847                                kind: VarDeclKind::Var,
848                                declare: false,
849                                decls: vec![VarDeclarator {
850                                    span: DUMMY_SP,
851                                    name: proto.clone().into(),
852                                    init: Some(
853                                        class_name
854                                            .clone()
855                                            .make_member(quote_ident!("prototype"))
856                                            .into(),
857                                    ),
858                                    definite: false,
859                                }],
860                                ..Default::default()
861                            }
862                            .into(),
863                        );
864                    }
865                    let span = method.span();
866                    let prop = *v.key_prop.clone();
867                    res.push(
868                        ExprStmt {
869                            span,
870                            expr: AssignExpr {
871                                span,
872                                op: op!("="),
873                                left: MemberExpr {
874                                    span,
875                                    obj: Box::new(proto.clone().into()),
876                                    prop: mk_key_prop_member(prop),
877                                }
878                                .into(),
879                                right: escape_keywords(method),
880                            }
881                            .into(),
882                        }
883                        .into(),
884                    );
885                    !(v.get.is_none() && v.set.is_none())
886                } else {
887                    true
888                }
889            });
890
891            static_props.retain(|_, v| {
892                if let Some(method) = v.method.take() {
893                    let span = method.span();
894                    let prop = *v.key_prop.clone();
895                    res.push(
896                        ExprStmt {
897                            span,
898                            expr: AssignExpr {
899                                span,
900                                op: op!("="),
901                                left: MemberExpr {
902                                    span,
903                                    obj: Box::new(class_name.clone().into()),
904                                    prop: mk_key_prop_member(prop),
905                                }
906                                .into(),
907                                right: escape_keywords(method),
908                            }
909                            .into(),
910                        }
911                        .into(),
912                    );
913                    !(v.get.is_none() && v.set.is_none())
914                } else {
915                    true
916                }
917            })
918        }
919
920        if props.is_empty() && static_props.is_empty() {
921            return res;
922        }
923
924        res.push(mk_create_class_call(
925            class_name.clone(),
926            mk_arg_obj_for_create_class(props),
927            if static_props.is_empty() {
928                None
929            } else {
930                Some(mk_arg_obj_for_create_class(static_props))
931            },
932        ));
933
934        res
935    }
936}
937
938#[tracing::instrument(level = "info", skip_all)]
939fn inject_class_call_check(c: &mut Vec<Stmt>, name: Ident) {
940    let mut class_name_sym = name.clone();
941    class_name_sym.span = DUMMY_SP;
942    class_name_sym.ctxt = name.ctxt;
943
944    let class_call_check = CallExpr {
945        span: DUMMY_SP,
946        callee: helper!(class_call_check),
947        args: vec![
948            Expr::This(ThisExpr { span: DUMMY_SP }).as_arg(),
949            class_name_sym.as_arg(),
950        ],
951        ..Default::default()
952    }
953    .into_stmt();
954
955    prepend_stmt(c, class_call_check)
956}
957
958/// Returns true if no `super` is used before `super()` call.
959#[tracing::instrument(level = "info", skip_all)]
960fn is_always_initialized(body: &[Stmt]) -> bool {
961    struct SuperFinder {
962        found: bool,
963    }
964
965    impl Visit for SuperFinder {
966        noop_visit_type!(fail);
967
968        fn visit_callee(&mut self, node: &Callee) {
969            match *node {
970                Callee::Super(..) => self.found = true,
971                _ => node.visit_children_with(self),
972            }
973        }
974
975        fn visit_super_prop_expr(&mut self, _: &SuperPropExpr) {
976            self.found = true
977        }
978    }
979
980    let pos = match body.iter().position(|s| match s {
981        Stmt::Expr(ExprStmt { expr, .. }) => matches!(
982            &**expr,
983            Expr::Call(CallExpr {
984                callee: Callee::Super(..),
985                ..
986            })
987        ),
988        _ => false,
989    }) {
990        Some(pos) => pos,
991        _ => return false,
992    };
993
994    let mut v = SuperFinder { found: false };
995    let body = &body[..pos];
996    v.visit_stmts(body);
997
998    !v.found
999}
1000
1001fn escape_keywords(mut e: Box<Expr>) -> Box<Expr> {
1002    if let Expr::Fn(f) = &mut *e {
1003        if let Some(i) = &mut f.ident {
1004            let sym = Ident::verify_symbol(&i.sym);
1005
1006            if let Err(new) = sym {
1007                i.sym = new.into();
1008            }
1009        }
1010    }
1011
1012    e
1013}
1014
1015#[derive(Default)]
1016struct ClassFinder {
1017    found: bool,
1018}
1019
1020impl Visit for ClassFinder {
1021    noop_visit_type!(fail);
1022
1023    fn visit_class(&mut self, _: &Class) {
1024        self.found = true
1025    }
1026}
1027
1028impl Check for ClassFinder {
1029    fn should_handle(&self) -> bool {
1030        self.found
1031    }
1032}