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
20pub 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
91impl 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 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 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 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 constant_super: false,
348 super_class: &None,
349 in_pat: false,
350 };
351
352 method.visit_mut_with(&mut folder);
353
354 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 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 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 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 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 {
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 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}