swc_ecma_transforms_compat/
class_fields_use_set.rs

1use std::mem;
2
3use swc_common::{util::take::Take, Span, Spanned, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{
6    constructor::inject_after_super, default_constructor_with_span, is_literal,
7    is_simple_pure_expr, private_ident, prop_name_to_member_prop, ExprFactory, ModuleItemLike,
8    StmtLike,
9};
10use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
11
12/// # What does this module do?
13///
14/// This module will transpile the class semantics
15/// from `[[Define]]` to `[[Set]]`.
16///
17///
18/// Note: class's native field is `[[Define]]` semantics.
19///
20/// # Why is it needed?
21/// The getter/setter from the super class won't be triggered in `[[Define]]`
22/// semantics.
23///
24/// Some decorators depend on super class getter/setter.
25/// Therefore, scenarios like this will require `[[Set]]` semantics.
26///
27/// ## Example
28///
29/// ```JavaScript
30/// class Foo {
31///     a = 1;
32///     #b = 2;
33///     static c = 3;
34///     static #d = 4;
35/// }
36/// ```
37///
38/// result:
39///
40/// ```JavaScript
41/// class Foo {
42///     #b;
43///     static {
44///         this.c = 3;
45///     }
46///     static #d = 4;
47///     constructor() {
48///         this.a = 1;
49///         this.#b = 2;
50///     }
51/// }
52/// ```
53///
54/// The variable `a` will be relocated to the constructor. Although the variable
55/// `#b` is not influenced by `[[Define]]` or `[[Set]]` semantics, its execution
56/// order is associated with variable `a`, thus its initialization is moved into
57/// the constructor.
58///
59/// The static variable `c` is moved to the static block for `[[Set]]` semantic
60/// conversion. Whereas, variable `#d` remains completely unaffected and
61/// conserved in its original location.
62///
63/// Furthermore, for class props that have side effects, an extraction and
64/// conversion will be performed.
65///
66/// For example,
67///
68/// ```JavaScript
69/// class Foo {
70///     [foo()] = 1;
71/// }
72/// ```
73///
74/// result:
75///
76/// ```JavaScript
77/// let prop;
78/// class Foo{
79///     static {
80///         prop = foo();
81///     }
82///     constructor() {
83///         this[prop] = 1;
84///     }
85/// }
86/// ```
87pub fn class_fields_use_set(pure_getters: bool) -> impl Pass {
88    visit_mut_pass(ClassFieldsUseSet { pure_getters })
89}
90
91#[derive(Debug)]
92struct ClassFieldsUseSet {
93    pure_getters: bool,
94}
95
96impl VisitMut for ClassFieldsUseSet {
97    noop_visit_mut_type!(fail);
98
99    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
100        self.visit_mut_stmts_like(n);
101    }
102
103    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
104        self.visit_mut_stmts_like(n);
105    }
106
107    fn visit_mut_class(&mut self, n: &mut Class) {
108        // visit inner classes first
109        n.visit_mut_children_with(self);
110
111        let mut fields_handler: FieldsHandler = FieldsHandler {
112            super_call_span: n.super_class.as_ref().map(|_| n.span),
113        };
114
115        n.body.visit_mut_with(&mut fields_handler);
116    }
117}
118
119impl ClassFieldsUseSet {
120    fn visit_mut_stmts_like<T>(&mut self, n: &mut Vec<T>)
121    where
122        T: StmtLike
123            + ModuleItemLike
124            + VisitMutWith<Self>
125            + VisitMutWith<ComputedFieldsHandler>
126            + From<Stmt>,
127    {
128        let mut stmts = Vec::with_capacity(n.len());
129
130        let mut computed_fields_handler = ComputedFieldsHandler {
131            var_decls: Default::default(),
132            static_init_blocks: Default::default(),
133            pure_getters: self.pure_getters,
134        };
135
136        for mut stmt in n.drain(..) {
137            stmt.visit_mut_with(&mut computed_fields_handler);
138
139            let var_decls = computed_fields_handler.var_decls.take();
140
141            if !var_decls.is_empty() {
142                stmts.push(T::from(
143                    VarDecl {
144                        span: DUMMY_SP,
145                        kind: VarDeclKind::Let,
146                        declare: false,
147                        decls: var_decls,
148                        ..Default::default()
149                    }
150                    .into(),
151                ))
152            }
153
154            stmt.visit_mut_with(self);
155
156            stmts.push(stmt);
157        }
158        *n = stmts;
159    }
160}
161
162#[derive(Debug)]
163struct FieldsHandler {
164    super_call_span: Option<Span>,
165}
166
167impl VisitMut for FieldsHandler {
168    noop_visit_mut_type!(fail);
169
170    fn visit_mut_class(&mut self, _: &mut Class) {
171        // skip inner classes
172        // In fact, FieldsHandler does not visit children recursively.
173        // We call FieldsHandler with the class.body as the only entry point.
174        // The FieldsHandler actually operates in a iterative way.
175    }
176
177    fn visit_mut_class_members(&mut self, n: &mut Vec<ClassMember>) {
178        let mut constructor_inits = Vec::new();
179
180        for member in n.iter_mut() {
181            match member {
182                ClassMember::ClassProp(ClassProp {
183                    ref span,
184                    ref is_static,
185                    key,
186                    value,
187                    ..
188                }) => {
189                    if let Some(value) = value.take() {
190                        let init_expr: Expr = AssignExpr {
191                            span: *span,
192                            op: op!("="),
193                            left: MemberExpr {
194                                span: DUMMY_SP,
195                                obj: ThisExpr::dummy().into(),
196                                prop: prop_name_to_member_prop(key.take()),
197                            }
198                            .into(),
199                            right: value,
200                        }
201                        .into();
202
203                        if *is_static {
204                            *member = StaticBlock {
205                                span: DUMMY_SP,
206                                body: BlockStmt {
207                                    span: DUMMY_SP,
208                                    stmts: vec![init_expr.into_stmt()],
209                                    ..Default::default()
210                                },
211                            }
212                            .into();
213
214                            continue;
215                        } else {
216                            constructor_inits.push(init_expr.into());
217                        }
218                    }
219
220                    member.take();
221                }
222                ClassMember::PrivateProp(PrivateProp {
223                    ref span,
224                    is_static: false,
225                    key,
226                    value,
227                    ..
228                }) => {
229                    if let Some(value) = value.take() {
230                        let init_expr: Expr = AssignExpr {
231                            span: *span,
232                            op: op!("="),
233                            left: MemberExpr {
234                                span: DUMMY_SP,
235                                obj: ThisExpr::dummy().into(),
236                                prop: MemberProp::PrivateName(key.clone()),
237                            }
238                            .into(),
239                            right: value,
240                        }
241                        .into();
242
243                        constructor_inits.push(init_expr.into());
244                    }
245                }
246                _ => {}
247            }
248        }
249
250        if constructor_inits.is_empty() {
251            return;
252        }
253
254        if let Some(c) = n.iter_mut().find_map(|m| m.as_mut_constructor()) {
255            inject_after_super(c, constructor_inits.take());
256        } else {
257            let mut c = default_constructor_with_span(
258                self.super_call_span.is_some(),
259                self.super_call_span.span(),
260            );
261            inject_after_super(&mut c, constructor_inits.take());
262            n.push(c.into());
263        }
264    }
265}
266
267#[derive(Debug)]
268struct ComputedFieldsHandler {
269    var_decls: Vec<VarDeclarator>,
270    static_init_blocks: Vec<Stmt>,
271    pure_getters: bool,
272}
273
274impl VisitMut for ComputedFieldsHandler {
275    noop_visit_mut_type!(fail);
276
277    fn visit_mut_class_prop(&mut self, n: &mut ClassProp) {
278        match &mut n.key {
279            PropName::Computed(ComputedPropName { expr, .. })
280                if !is_literal(expr) && !is_simple_pure_expr(expr, self.pure_getters) =>
281            {
282                let ref_key = private_ident!("prop");
283                let mut computed_expr = ref_key.clone().into();
284
285                mem::swap(expr, &mut computed_expr);
286
287                self.var_decls.push(VarDeclarator {
288                    span: DUMMY_SP,
289                    name: ref_key.clone().into(),
290                    init: None,
291                    definite: false,
292                });
293
294                self.static_init_blocks.push({
295                    let assign_expr = computed_expr.make_assign_to(op!("="), ref_key.into());
296
297                    assign_expr.into_stmt()
298                });
299            }
300            _ => (),
301        }
302    }
303
304    fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
305        if n.is_class_prop() {
306            n.visit_mut_children_with(self);
307        }
308    }
309
310    fn visit_mut_class_members(&mut self, n: &mut Vec<ClassMember>) {
311        n.visit_mut_children_with(self);
312
313        if !self.static_init_blocks.is_empty() {
314            n.insert(
315                0,
316                StaticBlock {
317                    span: DUMMY_SP,
318                    body: BlockStmt {
319                        span: DUMMY_SP,
320                        stmts: self.static_init_blocks.take(),
321                        ..Default::default()
322                    },
323                }
324                .into(),
325            );
326        }
327    }
328}