swc_ecma_transforms_proposal/decorators/legacy/
mod.rs

1use std::{iter, mem};
2
3use metadata::remove_span;
4use rustc_hash::FxHashMap;
5use swc_atoms::Atom;
6use swc_common::{util::take::Take, BytePos, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_transforms_base::helper;
9use swc_ecma_utils::{private_ident, prop_name_to_expr_value, quote_ident, ExprFactory, StmtLike};
10use swc_ecma_visit::{Visit, VisitMut, VisitMutWith, VisitWith};
11
12use self::metadata::{Metadata, ParamMetadata};
13use super::contains_decorator;
14
15mod metadata;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum EnumKind {
19    Mixed,
20    Str,
21    Num,
22}
23
24pub(super) fn new(metadata: bool) -> TscDecorator {
25    TscDecorator {
26        metadata,
27        enums: Default::default(),
28        vars: Default::default(),
29        appended_exprs: Default::default(),
30        appended_private_access_exprs: Default::default(),
31        prepended_exprs: Default::default(),
32        class_name: Default::default(),
33
34        assign_class_expr_to: Default::default(),
35    }
36}
37
38pub(super) struct TscDecorator {
39    metadata: bool,
40
41    enums: FxHashMap<Atom, EnumKind>,
42
43    /// Used for computed keys, and this variables are not initialized.
44    vars: Vec<VarDeclarator>,
45    appended_exprs: Vec<Box<Expr>>,
46    appended_private_access_exprs: Vec<Box<Expr>>,
47    prepended_exprs: Vec<Box<Expr>>,
48
49    class_name: Option<Ident>,
50
51    assign_class_expr_to: Option<Ident>,
52}
53
54impl TscDecorator {
55    fn visit_mut_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
56    where
57        T: StmtLike + VisitMutWith<Self>,
58    {
59        let old_vars = self.vars.take();
60        let old_appended_exprs = self.appended_exprs.take();
61        let old_prepended_exprs = self.prepended_exprs.take();
62
63        let mut new = Vec::new();
64
65        for mut s in stmts.take() {
66            debug_assert!(self.appended_exprs.is_empty());
67
68            s.visit_mut_with(self);
69
70            if !self.vars.is_empty() {
71                new.push(T::from(
72                    VarDecl {
73                        span: DUMMY_SP,
74                        kind: VarDeclKind::Var,
75                        declare: Default::default(),
76                        decls: self.vars.take(),
77                        ..Default::default()
78                    }
79                    .into(),
80                ));
81            }
82
83            new.extend(
84                self.prepended_exprs
85                    .drain(..)
86                    .map(|expr| {
87                        ExprStmt {
88                            span: DUMMY_SP,
89                            expr,
90                        }
91                        .into()
92                    })
93                    .map(T::from),
94            );
95
96            new.push(s);
97
98            new.extend(
99                self.appended_exprs
100                    .drain(..)
101                    .map(|expr| {
102                        ExprStmt {
103                            span: DUMMY_SP,
104                            expr,
105                        }
106                        .into()
107                    })
108                    .map(T::from),
109            );
110        }
111
112        *stmts = new;
113
114        self.prepended_exprs = old_prepended_exprs;
115        self.appended_exprs = old_appended_exprs;
116        self.vars = old_vars;
117    }
118
119    fn key(&mut self, k: &mut PropName) -> Expr {
120        match k {
121            PropName::Computed(k) if !k.expr.is_lit() => {
122                let var_name = private_ident!(k.span, "_key");
123
124                // Declare var
125                self.vars.push(VarDeclarator {
126                    span: DUMMY_SP,
127                    name: var_name.clone().into(),
128                    init: None,
129                    definite: Default::default(),
130                });
131
132                // Initialize var
133                self.prepended_exprs.push(
134                    AssignExpr {
135                        span: DUMMY_SP,
136                        op: op!("="),
137                        left: var_name.clone().into(),
138                        right: k.expr.take(),
139                    }
140                    .into(),
141                );
142
143                k.expr = var_name.clone().into();
144
145                return var_name.into();
146            }
147            PropName::Ident(i) => {
148                return Lit::Str(Str {
149                    span: DUMMY_SP,
150                    raw: None,
151                    value: i.sym.clone().into(),
152                })
153                .into()
154            }
155            _ => {}
156        }
157
158        prop_name_to_expr_value(k.clone())
159    }
160
161    fn has_private_access(mut expr: &Expr) -> bool {
162        while let Some(MemberExpr { obj, prop, .. }) = expr.as_member() {
163            if prop.is_private_name() {
164                return true;
165            }
166            expr = obj;
167        }
168
169        false
170    }
171
172    /// Creates `__decorate` calls.
173    fn add_decorate_call(
174        &mut self,
175        decorators: impl IntoIterator<Item = Box<Expr>>,
176        mut target: ExprOrSpread,
177        key: ExprOrSpread,
178        mut desc: ExprOrSpread,
179    ) {
180        let mut has_private_access = false;
181        let decorators = ArrayLit {
182            span: DUMMY_SP,
183            elems: decorators
184                .into_iter()
185                .inspect(|e| {
186                    if has_private_access {
187                        return;
188                    }
189                    has_private_access = Self::has_private_access(e);
190                })
191                .map(|mut v| {
192                    remove_span(&mut v);
193
194                    v.as_arg()
195                })
196                .map(Some)
197                .collect(),
198        }
199        .as_arg();
200
201        remove_span(&mut target.expr);
202        remove_span(&mut desc.expr);
203
204        let expr = CallExpr {
205            callee: helper!(ts, ts_decorate),
206            args: vec![decorators, target, key, desc],
207            ..Default::default()
208        }
209        .into();
210
211        if has_private_access {
212            self.appended_private_access_exprs.push(expr);
213        } else {
214            self.appended_exprs.push(expr);
215        }
216    }
217}
218
219impl Visit for TscDecorator {
220    fn visit_ts_enum_decl(&mut self, e: &TsEnumDecl) {
221        let enum_kind = e
222            .members
223            .iter()
224            .map(|member| member.init.as_ref())
225            .map(|init| match init {
226                Some(e) => match &**e {
227                    Expr::Unary(UnaryExpr {
228                        op: op!(unary, "-"),
229                        ..
230                    }) => EnumKind::Num,
231                    Expr::Lit(lit) => match lit {
232                        Lit::Str(_) => EnumKind::Str,
233                        Lit::Num(_) => EnumKind::Num,
234                        _ => EnumKind::Mixed,
235                    },
236                    _ => EnumKind::Mixed,
237                },
238                None => EnumKind::Num,
239            })
240            .fold(None, |opt: Option<EnumKind>, item| {
241                //
242                let a = match item {
243                    EnumKind::Mixed => return Some(EnumKind::Mixed),
244                    _ => item,
245                };
246
247                let b = match opt {
248                    Some(EnumKind::Mixed) => return Some(EnumKind::Mixed),
249                    Some(v) => v,
250                    None => return Some(item),
251                };
252                if a == b {
253                    Some(a)
254                } else {
255                    Some(EnumKind::Mixed)
256                }
257            });
258        if let Some(kind) = enum_kind {
259            self.enums.insert(e.id.sym.clone(), kind);
260        }
261    }
262}
263
264impl VisitMut for TscDecorator {
265    fn visit_mut_class(&mut self, n: &mut Class) {
266        let appended_private = self.appended_private_access_exprs.take();
267
268        n.visit_mut_with(&mut ParamMetadata);
269
270        if self.metadata {
271            let i = self.class_name.clone();
272
273            n.visit_mut_with(&mut Metadata::new(&self.enums, i.as_ref()));
274        }
275
276        n.visit_mut_children_with(self);
277
278        let appended_private =
279            mem::replace(&mut self.appended_private_access_exprs, appended_private);
280
281        if !appended_private.is_empty() {
282            let expr = if appended_private.len() == 1 {
283                *appended_private.into_iter().next().unwrap()
284            } else {
285                SeqExpr {
286                    exprs: appended_private,
287                    ..Default::default()
288                }
289                .into()
290            };
291
292            n.body.push(
293                StaticBlock {
294                    body: BlockStmt {
295                        stmts: vec![expr.into_stmt()],
296                        ..Default::default()
297                    },
298                    ..Default::default()
299                }
300                .into(),
301            )
302        }
303
304        if let Some(class_name) = self.class_name.clone() {
305            if !n.decorators.is_empty() {
306                let decorators = ArrayLit {
307                    span: DUMMY_SP,
308                    elems: n
309                        .decorators
310                        .take()
311                        .into_iter()
312                        .map(|mut v| {
313                            remove_span(&mut v.expr);
314
315                            v.expr.as_arg()
316                        })
317                        .map(Some)
318                        .collect(),
319                }
320                .as_arg();
321
322                let decorated = CallExpr {
323                    span: DUMMY_SP,
324                    callee: helper!(ts, ts_decorate),
325                    args: vec![
326                        decorators,
327                        class_name
328                            .clone()
329                            .with_pos(BytePos::DUMMY, BytePos::DUMMY)
330                            .as_arg(),
331                    ],
332                    ..Default::default()
333                }
334                .into();
335                self.appended_exprs.push(
336                    AssignExpr {
337                        span: DUMMY_SP,
338                        op: op!("="),
339                        left: class_name.with_pos(BytePos::DUMMY, BytePos::DUMMY).into(),
340                        right: decorated,
341                    }
342                    .into(),
343                );
344            }
345        }
346    }
347
348    fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) {
349        let old = self.class_name.replace(n.ident.clone());
350
351        n.visit_mut_children_with(self);
352
353        self.class_name = old;
354    }
355
356    fn visit_mut_expr(&mut self, e: &mut Expr) {
357        let appended_exprs = mem::take(&mut self.appended_exprs);
358        e.visit_mut_children_with(self);
359        let appended_exprs = mem::replace(&mut self.appended_exprs, appended_exprs);
360
361        if let Some(var_name) = self.assign_class_expr_to.take() {
362            self.vars.push(VarDeclarator {
363                span: DUMMY_SP,
364                name: var_name.clone().into(),
365                init: None,
366                definite: Default::default(),
367            });
368
369            *e = SeqExpr {
370                span: DUMMY_SP,
371                exprs: iter::once(AssignExpr {
372                    span: DUMMY_SP,
373                    op: op!("="),
374                    left: var_name.clone().into(),
375                    right: Box::new(e.take()),
376                })
377                .map(Into::into)
378                .chain(appended_exprs)
379                .chain(iter::once(var_name.into()))
380                .collect(),
381            }
382            .into();
383        }
384    }
385
386    fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) {
387        if !contains_decorator(n) {
388            return;
389        }
390
391        let ident = n
392            .ident
393            .get_or_insert_with(|| private_ident!("_class"))
394            .clone();
395
396        let old = self.class_name.replace(ident.clone());
397
398        n.visit_mut_children_with(self);
399
400        self.assign_class_expr_to = Some(ident);
401
402        self.class_name = old;
403    }
404
405    fn visit_mut_export_default_decl(&mut self, n: &mut ExportDefaultDecl) {
406        n.visit_mut_children_with(self);
407        // `export default class` is not expr
408        self.assign_class_expr_to = None;
409    }
410
411    fn visit_mut_class_method(&mut self, c: &mut ClassMethod) {
412        c.visit_mut_children_with(self);
413
414        if let Some(class_name) = self.class_name.clone() {
415            if !c.function.decorators.is_empty() {
416                let key = self.key(&mut c.key);
417
418                let target = if c.is_static {
419                    class_name.as_arg()
420                } else {
421                    class_name.make_member(quote_ident!("prototype")).as_arg()
422                };
423
424                self.add_decorate_call(
425                    c.function.decorators.drain(..).map(|d| d.expr),
426                    target,
427                    key.as_arg(),
428                    Lit::Null(Null::dummy()).as_arg(),
429                );
430            }
431        }
432    }
433
434    fn visit_mut_class_prop(&mut self, c: &mut ClassProp) {
435        c.visit_mut_children_with(self);
436
437        if let Some(class_name) = self.class_name.clone() {
438            if !c.decorators.is_empty() {
439                let key = self.key(&mut c.key);
440
441                let target = if c.is_static {
442                    class_name.as_arg()
443                } else {
444                    class_name.make_member(quote_ident!("prototype")).as_arg()
445                };
446
447                self.add_decorate_call(
448                    c.decorators.drain(..).map(|d| d.expr),
449                    target,
450                    key.as_arg(),
451                    Expr::undefined(DUMMY_SP).as_arg(),
452                );
453            }
454        }
455    }
456
457    fn visit_mut_module(&mut self, n: &mut Module) {
458        n.visit_with(self);
459
460        n.visit_mut_children_with(self);
461    }
462
463    fn visit_mut_module_items(&mut self, s: &mut Vec<ModuleItem>) {
464        self.visit_mut_stmt_likes(s);
465    }
466
467    fn visit_mut_script(&mut self, n: &mut Script) {
468        n.visit_with(self);
469
470        n.visit_mut_children_with(self);
471    }
472
473    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
474        self.visit_mut_stmt_likes(s)
475    }
476}