1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
use std::mem;

use swc_common::{util::take::Take, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{
    constructor::inject_after_super, default_constructor, is_literal, is_simple_pure_expr,
    private_ident, prop_name_to_member_prop, ExprFactory, ModuleItemLike, StmtLike,
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};

/// # What does this module do?
///
/// This module will transpile the class semantics
/// from `[[Define]]` to `[[Set]]`.
///
///
/// Note: class's native field is `[[Define]]` semantics.
///
/// # Why is it needed?
/// The getter/setter from the super class won't be triggered in `[[Define]]`
/// semantics.
///
/// Some decorators depend on super class getter/setter.
/// Therefore, scenarios like this will require `[[Set]]` semantics.
///
/// ## Example
///
/// ```JavaScript
/// class Foo {
///     a = 1;
///     #b = 2;
///     static c = 3;
///     static #d = 4;
/// }
/// ```
///
/// result:
///
/// ```JavaScript
/// class Foo {
///     #b;
///     static {
///         this.c = 3;
///     }
///     static #d = 4;
///     constructor() {
///         this.a = 1;
///         this.#b = 2;
///     }
/// }
/// ```
///
/// The variable `a` will be relocated to the constructor. Although the variable
/// `#b` is not influenced by `[[Define]]` or `[[Set]]` semantics, its execution
/// order is associated with variable `a`, thus its initialization is moved into
/// the constructor.
///
/// The static variable `c` is moved to the static block for `[[Set]]` semantic
/// conversion. Whereas, variable `#d` remains completely unaffected and
/// conserved in its original location.
///
/// Furthermore, for class props that have side effects, an extraction and
/// conversion will be performed.
///
/// For example,
///
/// ```JavaScript
/// class Foo {
///     [foo()] = 1;
/// }
/// ```
///
/// result:
///
/// ```JavaScript
/// let prop;
/// class Foo{
///     static {
///         prop = foo();
///     }
///     constructor() {
///         this[prop] = 1;
///     }
/// }
/// ```
pub fn class_fields_use_set(pure_getters: bool) -> impl Fold + VisitMut {
    as_folder(ClassFieldsUseSet { pure_getters })
}

#[derive(Debug)]
struct ClassFieldsUseSet {
    pure_getters: bool,
}

impl VisitMut for ClassFieldsUseSet {
    noop_visit_mut_type!();

    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
        self.visit_mut_stmts_like(n);
    }

    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
        self.visit_mut_stmts_like(n);
    }

    fn visit_mut_class(&mut self, n: &mut Class) {
        // visit inner classes first
        n.visit_mut_children_with(self);

        let mut fields_handler = FieldsHandler {
            has_super: n.super_class.is_some(),
        };

        n.body.visit_mut_with(&mut fields_handler);
    }
}

impl ClassFieldsUseSet {
    fn visit_mut_stmts_like<T>(&mut self, n: &mut Vec<T>)
    where
        T: StmtLike
            + ModuleItemLike
            + VisitMutWith<Self>
            + VisitMutWith<ComputedFieldsHandler>
            + From<Stmt>,
    {
        let mut stmts = Vec::with_capacity(n.len());

        let mut computed_fields_handler = ComputedFieldsHandler {
            var_decls: Default::default(),
            static_init_blocks: Default::default(),
            pure_getters: self.pure_getters,
        };

        for mut stmt in n.drain(..) {
            stmt.visit_mut_with(&mut computed_fields_handler);

            let var_decls = computed_fields_handler.var_decls.take();

            if !var_decls.is_empty() {
                stmts.push(T::from(
                    VarDecl {
                        span: DUMMY_SP,
                        kind: VarDeclKind::Let,
                        declare: false,
                        decls: var_decls,
                    }
                    .into(),
                ))
            }

            stmt.visit_mut_with(self);

            stmts.push(stmt);
        }
        *n = stmts;
    }
}

#[derive(Debug)]
struct FieldsHandler {
    has_super: bool,
}

impl VisitMut for FieldsHandler {
    noop_visit_mut_type!();

    fn visit_mut_class(&mut self, _: &mut Class) {
        // skip inner classes
        // In fact, FieldsHandler does not visit children recursively.
        // We call FieldsHandler with the class.body as the only entry point.
        // The FieldsHandler actually operates in a iterative way.
    }

    fn visit_mut_class_members(&mut self, n: &mut Vec<ClassMember>) {
        let mut constructor_inits = vec![];

        for member in n.iter_mut() {
            match member {
                ClassMember::ClassProp(ClassProp {
                    ref span,
                    ref is_static,
                    key,
                    value,
                    ..
                }) => {
                    if let Some(value) = value.take() {
                        let init_expr: Expr = AssignExpr {
                            span: *span,
                            op: op!("="),
                            left: MemberExpr {
                                span: DUMMY_SP,
                                obj: ThisExpr::dummy().into(),
                                prop: prop_name_to_member_prop(key.take()),
                            }
                            .into(),
                            right: value,
                        }
                        .into();

                        if *is_static {
                            *member = StaticBlock {
                                span: DUMMY_SP,
                                body: BlockStmt {
                                    span: DUMMY_SP,
                                    stmts: vec![init_expr.into_stmt()],
                                },
                            }
                            .into();

                            continue;
                        } else {
                            constructor_inits.push(init_expr.into());
                        }
                    }

                    member.take();
                }
                ClassMember::PrivateProp(PrivateProp {
                    ref span,
                    is_static: false,
                    key,
                    value,
                    ..
                }) => {
                    if let Some(value) = value.take() {
                        let init_expr: Expr = AssignExpr {
                            span: *span,
                            op: op!("="),
                            left: MemberExpr {
                                span: DUMMY_SP,
                                obj: ThisExpr::dummy().into(),
                                prop: MemberProp::PrivateName(key.clone()),
                            }
                            .into(),
                            right: value,
                        }
                        .into();

                        constructor_inits.push(init_expr.into());
                    }
                }
                _ => {}
            }
        }

        if constructor_inits.is_empty() {
            return;
        }

        if let Some(c) = n.iter_mut().find_map(|m| m.as_mut_constructor()) {
            inject_after_super(c, constructor_inits.take());
        } else {
            let mut c = default_constructor(self.has_super);
            inject_after_super(&mut c, constructor_inits.take());
            n.push(c.into());
        }
    }
}

#[derive(Debug)]
struct ComputedFieldsHandler {
    var_decls: Vec<VarDeclarator>,
    static_init_blocks: Vec<Stmt>,
    pure_getters: bool,
}

impl VisitMut for ComputedFieldsHandler {
    noop_visit_mut_type!();

    fn visit_mut_class_prop(&mut self, n: &mut ClassProp) {
        match &mut n.key {
            PropName::Computed(ComputedPropName { expr, .. })
                if !is_literal(expr) && !is_simple_pure_expr(expr, self.pure_getters) =>
            {
                let ref_key = private_ident!("prop");
                let mut computed_expr = Box::new(Expr::Ident(ref_key.clone()));

                mem::swap(expr, &mut computed_expr);

                self.var_decls.push(VarDeclarator {
                    span: DUMMY_SP,
                    name: ref_key.clone().into(),
                    init: None,
                    definite: false,
                });

                self.static_init_blocks.push({
                    let assign_expr = computed_expr.make_assign_to(op!("="), ref_key.into());

                    assign_expr.into_stmt()
                });
            }
            _ => (),
        }
    }

    fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
        if n.is_class_prop() {
            n.visit_mut_children_with(self);
        }
    }

    fn visit_mut_class_members(&mut self, n: &mut Vec<ClassMember>) {
        n.visit_mut_children_with(self);

        if !self.static_init_blocks.is_empty() {
            n.insert(
                0,
                StaticBlock {
                    span: DUMMY_SP,
                    body: BlockStmt {
                        span: DUMMY_SP,
                        stmts: self.static_init_blocks.take(),
                    },
                }
                .into(),
            );
        }
    }
}