swc_ecma_transforms_proposal/decorators/
mod.rs

1use std::{iter, mem::take};
2
3use either::Either;
4use serde::Deserialize;
5use swc_common::{Spanned, DUMMY_SP};
6use swc_ecma_ast::{Pass, *};
7use swc_ecma_transforms_base::helper;
8use swc_ecma_transforms_classes::super_field::SuperFieldAccessFolder;
9use swc_ecma_utils::{
10    alias_ident_for, constructor::inject_after_super, default_constructor_with_span, prepend_stmt,
11    private_ident, prop_name_to_expr, prop_name_to_expr_value, quote_ident, quote_str, ExprFactory,
12};
13use swc_ecma_visit::{
14    fold_pass, noop_fold_type, visit_mut_pass, Fold, FoldWith, Visit, VisitMutWith, VisitWith,
15};
16
17mod legacy;
18
19/// ## Simple class decorator
20///
21/// ```js
22/// 
23/// @annotation
24/// class MyClass { }
25///
26/// function annotation(target) {
27///    target.annotated = true;
28/// }
29/// ```
30///
31/// ## Class decorator
32///
33/// ```js
34/// @isTestable(true)
35/// class MyClass { }
36///
37/// function isTestable(value) {
38///    return function decorator(target) {
39///       target.isTestable = value;
40///    }
41/// }
42/// ```
43///
44/// ## Class method decorator
45///
46/// ```js
47/// class C {
48///   @enumerable(false)
49///   method() { }
50/// }
51///
52/// function enumerable(value) {
53///   return function (target, key, descriptor) {
54///      descriptor.enumerable = value;
55///      return descriptor;
56///   }
57/// }
58/// ```
59pub fn decorators(c: Config) -> impl Pass {
60    if c.legacy {
61        Either::Left(visit_mut_pass(self::legacy::new(c.emit_metadata)))
62    } else {
63        if c.emit_metadata {
64            unimplemented!("emitting decorator metadata while using new proposal")
65        }
66        Either::Right(fold_pass(Decorators {
67            is_in_strict: false,
68            vars: Default::default(),
69        }))
70    }
71}
72
73#[derive(Debug, Default, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct Config {
76    pub legacy: bool,
77    #[serde(default)]
78    pub emit_metadata: bool,
79
80    pub use_define_for_class_fields: bool,
81}
82
83#[derive(Debug, Default)]
84struct Decorators {
85    is_in_strict: bool,
86
87    vars: Vec<VarDeclarator>,
88}
89
90/// TODO: VisitMut
91impl Fold for Decorators {
92    noop_fold_type!();
93
94    fn fold_decl(&mut self, decl: Decl) -> Decl {
95        let decl = decl.fold_children_with(self);
96
97        match decl {
98            Decl::Class(ClassDecl {
99                ident,
100                declare: false,
101                class,
102            }) => {
103                if !contains_decorator(&class) {
104                    return ClassDecl {
105                        ident,
106                        declare: false,
107                        class,
108                    }
109                    .into();
110                }
111
112                let decorate_call = Box::new(self.fold_class_inner(ident.clone(), class));
113
114                VarDecl {
115                    kind: VarDeclKind::Let,
116                    decls: vec![VarDeclarator {
117                        span: DUMMY_SP,
118                        name: ident.into(),
119                        definite: false,
120                        init: Some(decorate_call),
121                    }],
122                    ..Default::default()
123                }
124                .into()
125            }
126            _ => decl,
127        }
128    }
129
130    fn fold_expr(&mut self, expr: Expr) -> Expr {
131        let expr = expr.fold_children_with(self);
132
133        match expr {
134            Expr::Class(ClassExpr { ident, class }) => {
135                if !contains_decorator(&class) {
136                    return ClassExpr { ident, class }.into();
137                }
138
139                self.fold_class_inner(
140                    ident.unwrap_or_else(|| quote_ident!("_class").into()),
141                    class,
142                )
143            }
144            _ => expr,
145        }
146    }
147
148    fn fold_module_decl(&mut self, decl: ModuleDecl) -> ModuleDecl {
149        let decl = decl.fold_children_with(self);
150
151        match decl {
152            ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
153                span,
154                decl: DefaultDecl::Class(ClassExpr { ident, class }),
155                ..
156            }) => {
157                let decorate_call = Box::new(self.fold_class_inner(
158                    ident.unwrap_or_else(|| quote_ident!("_class").into()),
159                    class,
160                ));
161
162                ExportDefaultExpr {
163                    span,
164                    expr: decorate_call,
165                }
166                .into()
167            }
168            _ => decl,
169        }
170    }
171
172    fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
173        if !contains_decorator(&items) {
174            return items;
175        }
176
177        let old_strict = self.is_in_strict;
178        self.is_in_strict = true;
179
180        let mut buf = Vec::with_capacity(items.len() + 4);
181        items.into_iter().for_each(|item| {
182            if !contains_decorator(&item) {
183                buf.push(item);
184                return;
185            }
186
187            macro_rules! handle_class {
188                ($cls:expr, $ident:expr) => {{
189                    let class = $cls;
190                    let ident = $ident;
191                    let decorate_call = Box::new(self.fold_class_inner(ident.clone(), class));
192
193                    buf.push(
194                        VarDecl {
195                            span: DUMMY_SP,
196                            kind: VarDeclKind::Let,
197                            declare: false,
198                            decls: vec![VarDeclarator {
199                                span: DUMMY_SP,
200                                name: ident.clone().into(),
201                                init: Some(decorate_call),
202                                definite: false,
203                            }],
204                            ..Default::default()
205                        }
206                        .into(),
207                    );
208
209                    // export { Foo as default }
210                    buf.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
211                        NamedExport {
212                            span: DUMMY_SP,
213                            specifiers: vec![ExportNamedSpecifier {
214                                span: DUMMY_SP,
215                                orig: ModuleExportName::Ident(ident),
216                                exported: Some(ModuleExportName::Ident(
217                                    quote_ident!("default").into(),
218                                )),
219                                is_type_only: false,
220                            }
221                            .into()],
222                            src: None,
223                            type_only: false,
224                            with: None,
225                        },
226                    )));
227                }};
228            }
229            //
230            match item {
231                ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
232                    decl:
233                        DefaultDecl::Class(ClassExpr {
234                            ident: Some(ident),
235                            class,
236                        }),
237                    ..
238                })) => handle_class!(class, ident),
239                ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
240                    span,
241                    expr,
242                    ..
243                })) => match *expr {
244                    Expr::Class(ClassExpr {
245                        ident: Some(ident),
246                        class,
247                    }) => handle_class!(class, ident),
248
249                    _ => {
250                        let item: ModuleItem = ExportDefaultExpr { span, expr }.into();
251                        buf.push(item.fold_with(self));
252                    }
253                },
254                _ => {
255                    buf.push(item.fold_with(self));
256                }
257            }
258        });
259
260        self.is_in_strict = old_strict;
261
262        if !self.vars.is_empty() {
263            prepend_stmt(
264                &mut buf,
265                VarDecl {
266                    span: DUMMY_SP,
267                    kind: VarDeclKind::Var,
268                    declare: false,
269                    decls: take(&mut self.vars),
270                    ..Default::default()
271                }
272                .into(),
273            )
274        }
275
276        buf
277    }
278}
279
280impl Decorators {
281    #[allow(clippy::boxed_local)]
282    fn fold_class_inner(&mut self, ident: Ident, mut class: Box<Class>) -> Expr {
283        let initialize = private_ident!("_initialize");
284        let super_class_ident = class
285            .super_class
286            .as_ref()
287            .map(|expr| alias_ident_for(expr, "_super"));
288        let super_class_expr = class.super_class;
289        class.super_class = super_class_ident.clone().map(|i| i.into());
290
291        let constructor = {
292            let initialize_call = CallExpr {
293                span: DUMMY_SP,
294                callee: initialize.clone().as_callee(),
295                args: vec![ThisExpr { span: DUMMY_SP }.as_arg()],
296                ..Default::default()
297            }
298            .into();
299
300            // Inject initialize
301            let pos = class.body.iter().position(|member| {
302                matches!(
303                    *member,
304                    ClassMember::Constructor(Constructor { body: Some(..), .. })
305                )
306            });
307
308            match pos {
309                Some(pos) => {
310                    let mut c = match class.body.remove(pos) {
311                        ClassMember::Constructor(c) => c,
312                        _ => unreachable!(),
313                    };
314                    inject_after_super(&mut c, vec![initialize_call]);
315
316                    ClassMember::Constructor(c)
317                }
318                None => {
319                    let mut c =
320                        default_constructor_with_span(super_class_ident.is_some(), class.span);
321
322                    c.body
323                        .as_mut()
324                        .unwrap()
325                        .stmts
326                        .push(initialize_call.into_stmt());
327
328                    ClassMember::Constructor(c)
329                }
330            }
331        };
332
333        macro_rules! fold_method {
334            ($method:expr, $fn_name:expr, $key_prop_value:expr) => {{
335                let fn_name = $fn_name;
336                let mut method = $method;
337                let mut folder = SuperFieldAccessFolder {
338                    class_name: &ident,
339                    constructor_this_mark: None,
340                    is_static: method.is_static,
341                    folding_constructor: false,
342                    in_nested_scope: false,
343                    in_injected_define_property_call: false,
344                    this_alias_mark: None,
345                    // TODO: loose mode
346                    constant_super: false,
347                    super_class: &None,
348                    in_pat: false,
349                };
350
351                method.visit_mut_with(&mut folder);
352
353                //   kind: "method",
354                //   key: getKeyJ(),
355                //   value: function () {
356                //     return 2;
357                //   }
358                Some(
359                    ObjectLit {
360                        span: DUMMY_SP,
361                        props: iter::once(PropOrSpread::Prop(Box::new(Prop::KeyValue(
362                            KeyValueProp {
363                                key: PropName::Ident(quote_ident!("kind")),
364                                value: Box::new(Expr::Lit(Lit::Str(quote_str!(
365                                    match method.kind {
366                                        MethodKind::Method => "method",
367                                        MethodKind::Getter => "get",
368                                        MethodKind::Setter => "set",
369                                    }
370                                )))),
371                            },
372                        ))))
373                        .chain(if method.is_static {
374                            Some(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
375                                key: PropName::Ident(quote_ident!("static")),
376                                value: true.into(),
377                            }))))
378                        } else {
379                            None
380                        })
381                        .chain({
382                            //
383                            if method.function.decorators.is_empty() {
384                                None
385                            } else {
386                                Some(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
387                                    key: PropName::Ident(quote_ident!("decorators")),
388                                    value: Box::new(Expr::Array(ArrayLit {
389                                        span: DUMMY_SP,
390                                        elems: method
391                                            .function
392                                            .decorators
393                                            .into_iter()
394                                            .map(|dec| dec.expr.as_arg())
395                                            .map(Some)
396                                            .collect(),
397                                    })),
398                                }))))
399                            }
400                        })
401                        .chain(iter::once(PropOrSpread::Prop(Box::new(Prop::KeyValue(
402                            KeyValueProp {
403                                key: PropName::Ident(quote_ident!("key")),
404                                value: $key_prop_value,
405                            },
406                        )))))
407                        .chain(iter::once(PropOrSpread::Prop(Box::new(Prop::KeyValue(
408                            KeyValueProp {
409                                key: PropName::Ident(quote_ident!("value")),
410                                value: Box::new(
411                                    FnExpr {
412                                        ident: fn_name.map(Ident::from).map(Ident::into_private),
413                                        function: Function {
414                                            decorators: Vec::new(),
415                                            ..*method.function
416                                        }
417                                        .into(),
418                                    }
419                                    .into(),
420                                ),
421                            },
422                        )))))
423                        .collect(),
424                    }
425                    .as_arg(),
426                )
427            }};
428        }
429
430        let descriptors = class
431            .body
432            .into_iter()
433            .filter_map(|member| {
434                //
435                match member {
436                    ClassMember::Constructor(_) => unreachable!("multiple constructor?"),
437                    ClassMember::Empty(_) | ClassMember::TsIndexSignature(_) => None,
438                    ClassMember::Method(method) => {
439                        let fn_name = match method.key {
440                            PropName::Ident(ref i) => Some(i.clone()),
441                            PropName::Str(ref s) => Some(IdentName::new(s.value.clone(), s.span)),
442                            _ => None,
443                        };
444                        let key_prop_value = Box::new(prop_name_to_expr_value(method.key.clone()));
445
446                        fold_method!(method, fn_name, key_prop_value)
447                    }
448                    ClassMember::PrivateMethod(method) => {
449                        let fn_name = Ident::new_no_ctxt(
450                            format!("_{}", method.key.name).into(),
451                            method.key.span,
452                        );
453                        let key_prop_value = Lit::Str(Str {
454                            span: method.key.span,
455                            raw: None,
456                            value: method.key.name.clone(),
457                        })
458                        .into();
459                        fold_method!(method, Some(fn_name), key_prop_value)
460                    }
461                    ClassMember::ClassProp(prop) => {
462                        let prop_span = prop.span();
463                        let key_prop_value = match prop.key {
464                            PropName::Ident(i) => Lit::Str(Str {
465                                span: i.span,
466                                raw: None,
467                                value: i.sym,
468                            })
469                            .into(),
470                            _ => prop_name_to_expr(prop.key).into(),
471                        };
472                        //
473                        Some(
474                            ObjectLit {
475                                span: prop_span,
476                                props: iter::once(PropOrSpread::Prop(Box::new(Prop::KeyValue(
477                                    KeyValueProp {
478                                        key: PropName::Ident(quote_ident!("kind")),
479                                        value: Lit::Str(quote_str!("field")).into(),
480                                    },
481                                ))))
482                                .chain(if prop.is_static {
483                                    Some(PropOrSpread::Prop(Box::new(Prop::KeyValue(
484                                        KeyValueProp {
485                                            key: PropName::Ident(quote_ident!("static")),
486                                            value: true.into(),
487                                        },
488                                    ))))
489                                } else {
490                                    None
491                                })
492                                .chain({
493                                    //
494                                    if prop.decorators.is_empty() {
495                                        None
496                                    } else {
497                                        Some(PropOrSpread::Prop(Box::new(Prop::KeyValue(
498                                            KeyValueProp {
499                                                key: PropName::Ident(quote_ident!("decorators")),
500                                                value: ArrayLit {
501                                                    span: DUMMY_SP,
502                                                    elems: prop
503                                                        .decorators
504                                                        .into_iter()
505                                                        .map(|dec| dec.expr.as_arg())
506                                                        .map(Some)
507                                                        .collect(),
508                                                }
509                                                .into(),
510                                            },
511                                        ))))
512                                    }
513                                })
514                                .chain(iter::once(PropOrSpread::Prop(Box::new(Prop::KeyValue(
515                                    KeyValueProp {
516                                        key: PropName::Ident(quote_ident!("key")),
517                                        value: key_prop_value,
518                                    },
519                                )))))
520                                .chain(iter::once(PropOrSpread::Prop(Box::new(match prop.value {
521                                    Some(value) => Prop::Method(MethodProp {
522                                        key: PropName::Ident(quote_ident!("value")),
523                                        function: Function {
524                                            span: DUMMY_SP,
525                                            is_async: false,
526                                            is_generator: false,
527                                            decorators: Vec::new(),
528                                            params: Vec::new(),
529
530                                            body: Some(BlockStmt {
531                                                span: DUMMY_SP,
532                                                stmts: vec![Stmt::Return(ReturnStmt {
533                                                    span: DUMMY_SP,
534                                                    arg: Some(value),
535                                                })],
536                                                ..Default::default()
537                                            }),
538                                            ..Default::default()
539                                        }
540                                        .into(),
541                                    }),
542                                    _ => Prop::KeyValue(KeyValueProp {
543                                        key: PropName::Ident(quote_ident!("value")),
544                                        value: Expr::undefined(DUMMY_SP),
545                                    }),
546                                }))))
547                                .collect(),
548                            }
549                            .as_arg(),
550                        )
551                    }
552                    _ => unimplemented!("ClassMember::{:?}", member,),
553                }
554            })
555            .map(Some)
556            .collect();
557
558        make_decorate_call(
559            class.decorators,
560            iter::once({
561                // function(_initialize) {}
562                Function {
563                    span: DUMMY_SP,
564
565                    params: iter::once(initialize.into())
566                        .chain(super_class_ident.map(Pat::from))
567                        .map(|pat| Param {
568                            span: DUMMY_SP,
569                            decorators: Vec::new(),
570                            pat,
571                        })
572                        .collect(),
573
574                    decorators: Default::default(),
575                    is_async: false,
576                    is_generator: false,
577
578                    body: Some(BlockStmt {
579                        span: DUMMY_SP,
580                        stmts: if !self.is_in_strict {
581                            // 'use strict';
582                            Some(Lit::Str(quote_str!("use strict")).into_stmt())
583                        } else {
584                            None
585                        }
586                        .into_iter()
587                        .chain(iter::once(
588                            ClassDecl {
589                                ident: ident.clone(),
590                                class: Class {
591                                    decorators: Default::default(),
592                                    body: vec![constructor],
593                                    ..*class
594                                }
595                                .into(),
596                                declare: false,
597                            }
598                            .into(),
599                        ))
600                        .chain(iter::once(
601                            ReturnStmt {
602                                span: DUMMY_SP,
603                                arg: Some(
604                                    ObjectLit {
605                                        span: DUMMY_SP,
606                                        props: vec![
607                                            PropOrSpread::Prop(Box::new(Prop::KeyValue(
608                                                KeyValueProp {
609                                                    key: PropName::Ident(quote_ident!("F")),
610                                                    value: Box::new(Expr::Ident(ident)),
611                                                },
612                                            ))),
613                                            PropOrSpread::Prop(Box::new(Prop::KeyValue(
614                                                KeyValueProp {
615                                                    key: PropName::Ident(quote_ident!("d")),
616                                                    value: Box::new(Expr::Array(ArrayLit {
617                                                        span: DUMMY_SP,
618                                                        elems: descriptors,
619                                                    })),
620                                                },
621                                            ))),
622                                        ],
623                                    }
624                                    .into(),
625                                ),
626                            }
627                            .into(),
628                        ))
629                        .collect(),
630                        ..Default::default()
631                    }),
632                    ..Default::default()
633                }
634                .as_arg()
635            })
636            .chain(super_class_expr.map(|e| e.as_arg())),
637        )
638        .into()
639    }
640}
641
642fn make_decorate_call(
643    decorators: Vec<Decorator>,
644    args: impl Iterator<Item = ExprOrSpread>,
645) -> CallExpr {
646    CallExpr {
647        span: DUMMY_SP,
648        callee: helper!(decorate),
649        args: iter::once(
650            ArrayLit {
651                span: DUMMY_SP,
652                elems: decorators
653                    .into_iter()
654                    .map(|dec| Some(dec.expr.as_arg()))
655                    .collect(),
656            }
657            .as_arg(),
658        )
659        .chain(args)
660        .collect(),
661        ..Default::default()
662    }
663}
664
665struct DecoratorFinder {
666    found: bool,
667}
668impl Visit for DecoratorFinder {
669    fn visit_decorator(&mut self, _: &Decorator) {
670        self.found = true
671    }
672}
673
674fn contains_decorator<N>(node: &N) -> bool
675where
676    N: VisitWith<DecoratorFinder>,
677{
678    let mut v = DecoratorFinder { found: false };
679    node.visit_with(&mut v);
680    v.found
681}