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