swc_ecma_transforms_proposal/decorators/
mod.rs

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