1use std::cell::RefCell;
2
3use swc_atoms::atom;
4use swc_common::{Mark, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::{prepend_stmts, quote_ident, DropSpan, ExprFactory};
7use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
8
9#[macro_export]
10macro_rules! enable_helper {
11 ($i:ident) => {{
12 $crate::helpers::HELPERS.with(|helpers| {
13 helpers.$i();
14 helpers.mark()
15 })
16 }};
17}
18
19#[cfg(feature = "inline-helpers")]
20fn parse(code: &str) -> Vec<Stmt> {
21 let cm = swc_common::SourceMap::default();
22
23 let fm = cm.new_source_file(
24 swc_common::FileName::Custom(stringify!($name).into()).into(),
25 code.to_string(),
26 );
27 swc_ecma_parser::parse_file_as_script(
28 &fm,
29 Default::default(),
30 Default::default(),
31 None,
32 &mut Vec::new(),
33 )
34 .map(|mut script| {
35 script.body.visit_mut_with(&mut DropSpan);
36 script.body
37 })
38 .map_err(|e| {
39 unreachable!("Error occurred while parsing error: {:?}", e);
40 })
41 .unwrap()
42}
43
44#[cfg(feature = "inline-helpers")]
45macro_rules! add_to {
46 ($buf:expr, $name:ident, $b:expr, $mark:expr) => {{
47 static STMTS: once_cell::sync::Lazy<Vec<Stmt>> = once_cell::sync::Lazy::new(|| {
48 let code = include_str!(concat!("./_", stringify!($name), ".js"));
49 parse(&code)
50 });
51
52 let enable = $b;
53 if enable {
54 $buf.extend(STMTS.iter().cloned().map(|mut stmt| {
55 stmt.visit_mut_with(&mut Marker {
56 base: SyntaxContext::empty().apply_mark($mark),
57 decls: Default::default(),
58
59 decl_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
60 });
61 stmt
62 }))
63 }
64 }};
65}
66
67macro_rules! add_import_to {
68 ($buf:expr, $name:ident, $b:expr, $mark:expr) => {{
69 let enable = $b;
70 if enable {
71 let ctxt = SyntaxContext::empty().apply_mark($mark);
72 let s = ImportSpecifier::Named(ImportNamedSpecifier {
73 span: DUMMY_SP,
74 local: Ident::new(concat!("_", stringify!($name)).into(), DUMMY_SP, ctxt),
75 imported: Some(quote_ident!("_").into()),
76 is_type_only: false,
77 });
78
79 let src: Str = concat!("@swc/helpers/_/_", stringify!($name)).into();
80
81 $buf.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
82 span: DUMMY_SP,
83 specifiers: vec![s],
84 src: Box::new(src),
85 with: Default::default(),
86 type_only: Default::default(),
87 phase: Default::default(),
88 })))
89 }
90 }};
91}
92
93better_scoped_tls::scoped_tls!(
94 pub static HELPERS: Helpers
98);
99
100#[derive(Debug, Default)]
102pub struct Helpers {
103 external: bool,
104 mark: HelperMark,
105 inner: RefCell<Inner>,
106}
107
108#[derive(Debug, Clone, Copy)]
109pub struct HelperData {
110 external: bool,
111 mark: HelperMark,
112 inner: Inner,
113}
114
115impl Helpers {
116 pub fn new(external: bool) -> Self {
117 Helpers {
118 external,
119 mark: Default::default(),
120 inner: Default::default(),
121 }
122 }
123
124 pub const fn mark(&self) -> Mark {
125 self.mark.0
126 }
127
128 pub const fn external(&self) -> bool {
129 self.external
130 }
131
132 pub fn data(&self) -> HelperData {
133 HelperData {
134 inner: *self.inner.borrow(),
135 external: self.external,
136 mark: self.mark,
137 }
138 }
139
140 pub fn from_data(data: HelperData) -> Self {
141 Helpers {
142 external: data.external,
143 mark: data.mark,
144 inner: RefCell::new(data.inner),
145 }
146 }
147}
148
149#[derive(Debug, Clone, Copy)]
150struct HelperMark(Mark);
151impl Default for HelperMark {
152 fn default() -> Self {
153 HelperMark(Mark::new())
154 }
155}
156
157macro_rules! define_helpers {
158 (
159 Helpers {
160 $( $name:ident : ( $( $dep:ident ),* ), )*
161 }
162 ) => {
163 #[derive(Debug,Default, Clone, Copy)]
164 struct Inner {
165 $( $name: bool, )*
166 }
167
168 impl Helpers {
169 $(
170 pub fn $name(&self) {
171 self.inner.borrow_mut().$name = true;
172
173 if !self.external {
174 $(
175 self.$dep();
176 )*
177 }
178 }
179 )*
180 }
181
182
183 impl Helpers {
184 pub fn extend_from(&self, other: &Self) {
185 let other = other.inner.borrow();
186 let mut me = self.inner.borrow_mut();
187 $(
188 if other.$name {
189 me.$name = true;
190 }
191 )*
192 }
193 }
194
195 impl InjectHelpers {
196 fn is_helper_used(&self) -> bool{
197
198 HELPERS.with(|helpers|{
199 let inner = helpers.inner.borrow();
200 false $(
201 || inner.$name
202 )*
203 })
204 }
205
206 #[cfg(feature = "inline-helpers")]
207 fn build_helpers(&self) -> Vec<Stmt> {
208 let mut buf = Vec::new();
209
210 HELPERS.with(|helpers|{
211 let inner = helpers.inner.borrow();
212 $(
213 add_to!(buf, $name, inner.$name, helpers.mark.0);
214 )*
215 });
216
217 buf
218 }
219
220 fn build_imports(&self) -> Vec<ModuleItem> {
221 let mut buf = Vec::new();
222
223 HELPERS.with(|helpers|{
224 let inner = helpers.inner.borrow();
225 $(
226 add_import_to!(buf, $name, inner.$name, helpers.mark.0);
227 )*
228 });
229
230 buf
231 }
232
233 fn build_requires(&self) -> Vec<Stmt>{
234 let mut buf = Vec::new();
235 HELPERS.with(|helpers|{
236 let inner = helpers.inner.borrow();
237 $(
238 let enable = inner.$name;
239 if enable {
240 buf.push(self.build_reqire(stringify!($name), helpers.mark.0))
241 }
242 )*
244 });
245 buf
246 }
247 }
248 };
249}
250
251define_helpers!(Helpers {
252 apply_decorated_descriptor: (),
253 array_like_to_array: (),
254 array_with_holes: (),
255 array_without_holes: (array_like_to_array),
256 assert_this_initialized: (),
257 async_generator: (overload_yield),
258 async_generator_delegate: (overload_yield),
259 async_iterator: (),
260 async_to_generator: (),
261 await_async_generator: (overload_yield),
262 await_value: (),
263 call_super: (
264 get_prototype_of,
265 is_native_reflect_construct,
266 possible_constructor_return
267 ),
268 check_private_redeclaration: (),
269 class_apply_descriptor_destructure: (),
270 class_apply_descriptor_get: (),
271 class_apply_descriptor_set: (),
272 class_apply_descriptor_update: (),
273 class_call_check: (),
274 class_check_private_static_field_descriptor: (),
275 class_extract_field_descriptor: (),
276 class_name_tdz_error: (),
277 class_private_field_get: (class_extract_field_descriptor, class_apply_descriptor_get),
278 class_private_field_init: (check_private_redeclaration),
279 class_private_field_loose_base: (),
280 class_private_field_loose_key: (),
281 class_private_field_set: (class_extract_field_descriptor, class_apply_descriptor_set),
282 class_private_field_update: (
283 class_extract_field_descriptor,
284 class_apply_descriptor_update
285 ),
286 class_private_method_get: (),
287 class_private_method_init: (check_private_redeclaration),
288 class_private_method_set: (),
289 class_static_private_field_spec_get: (
290 class_check_private_static_access,
291 class_check_private_static_field_descriptor,
292 class_apply_descriptor_get
293 ),
294 class_static_private_field_spec_set: (
295 class_check_private_static_access,
296 class_check_private_static_field_descriptor,
297 class_apply_descriptor_set
298 ),
299 class_static_private_field_update: (
300 class_check_private_static_access,
301 class_check_private_static_field_descriptor,
302 class_apply_descriptor_update
303 ),
304 construct: (is_native_reflect_construct, set_prototype_of),
305 create_class: (),
306 decorate: (to_array, to_property_key),
307 defaults: (),
308 define_enumerable_properties: (),
309 define_property: (),
310 export_star: (),
311 extends: (),
312 get: (super_prop_base),
313 get_prototype_of: (),
314 inherits: (set_prototype_of),
315 inherits_loose: (),
316 initializer_define_property: (),
317 initializer_warning_helper: (),
318 instanceof: (),
319 interop_require_default: (),
320 interop_require_wildcard: (),
321 is_native_function: (),
322 iterable_to_array: (),
323 iterable_to_array_limit: (),
324 iterable_to_array_limit_loose: (),
325 jsx: (),
326 new_arrow_check: (),
327 non_iterable_rest: (),
328 non_iterable_spread: (),
329 object_destructuring_empty: (),
330 object_spread: (define_property),
331 object_spread_props: (),
332 object_without_properties: (object_without_properties_loose),
333 object_without_properties_loose: (),
334 overload_yield: (),
335 possible_constructor_return: (type_of, assert_this_initialized),
336 read_only_error: (),
337 set: (super_prop_base, define_property),
338 set_prototype_of: (),
339 skip_first_generator_next: (),
340 sliced_to_array: (
341 array_with_holes,
342 iterable_to_array_limit,
343 unsupported_iterable_to_array,
344 non_iterable_rest
345 ),
346 sliced_to_array_loose: (
347 array_with_holes,
348 iterable_to_array_limit_loose,
349 unsupported_iterable_to_array,
350 non_iterable_rest
351 ),
352 super_prop_base: (get_prototype_of),
353 tagged_template_literal: (),
354 tagged_template_literal_loose: (),
355 throw: (),
358 to_array: (
359 array_with_holes,
360 iterable_to_array,
361 unsupported_iterable_to_array,
362 non_iterable_rest
363 ),
364 to_consumable_array: (
365 array_without_holes,
366 iterable_to_array,
367 unsupported_iterable_to_array,
368 non_iterable_spread
369 ),
370 to_primitive: (type_of),
371 to_property_key: (type_of, to_primitive),
372 update: (get, set),
373 type_of: (),
374 unsupported_iterable_to_array: (array_like_to_array),
375 wrap_async_generator: (async_generator),
376 wrap_native_super: (
377 construct,
378 get_prototype_of,
379 set_prototype_of,
380 is_native_function
381 ),
382 write_only_error: (),
383
384 class_private_field_destructure: (
385 class_extract_field_descriptor,
386 class_apply_descriptor_destructure
387 ),
388 class_static_private_field_destructure: (
389 class_check_private_static_access,
390 class_extract_field_descriptor,
391 class_apply_descriptor_destructure
392 ),
393
394 class_static_private_method_get: (class_check_private_static_access),
395 class_check_private_static_access: (),
396
397 is_native_reflect_construct: (),
398
399 create_super: (
400 get_prototype_of,
401 is_native_reflect_construct,
402 possible_constructor_return
403 ),
404
405 create_for_of_iterator_helper_loose: (unsupported_iterable_to_array),
406
407 ts_decorate: (),
408 ts_generator: (),
409 ts_metadata: (),
410 ts_param: (),
411 ts_values: (),
412 ts_add_disposable_resource: (),
413 ts_dispose_resources: (),
414
415 apply_decs_2203_r: (),
416 identity: (),
417 dispose: (),
418 using: (),
419 using_ctx: (),
420});
421
422pub fn inject_helpers(global_mark: Mark) -> impl Pass + VisitMut {
423 visit_mut_pass(InjectHelpers {
424 global_mark,
425 helper_ctxt: None,
426 })
427}
428
429struct InjectHelpers {
430 global_mark: Mark,
431 helper_ctxt: Option<SyntaxContext>,
432}
433
434impl InjectHelpers {
435 #[allow(unused_variables)]
436 fn make_helpers_for_module(&mut self) -> Vec<ModuleItem> {
437 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
438
439 #[cfg(feature = "inline-helpers")]
440 if !external {
441 return self
442 .build_helpers()
443 .into_iter()
444 .map(ModuleItem::Stmt)
445 .collect();
446 }
447
448 if self.is_helper_used() {
449 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
450 self.build_imports()
451 } else {
452 Vec::new()
453 }
454 }
455
456 #[allow(unused_variables)]
457 fn make_helpers_for_script(&mut self) -> Vec<Stmt> {
458 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
459
460 #[cfg(feature = "inline-helpers")]
461 if !external {
462 return self.build_helpers();
463 }
464
465 if self.is_helper_used() {
466 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
467 self.build_requires()
468 } else {
469 Default::default()
470 }
471 }
472
473 fn build_reqire(&self, name: &str, mark: Mark) -> Stmt {
474 let c = CallExpr {
475 span: DUMMY_SP,
476 callee: Expr::from(Ident {
477 span: DUMMY_SP,
478 ctxt: SyntaxContext::empty().apply_mark(self.global_mark),
479 sym: atom!("require"),
480 ..Default::default()
481 })
482 .as_callee(),
483 args: vec![Str {
484 span: DUMMY_SP,
485 value: format!("@swc/helpers/_/_{name}").into(),
486 raw: None,
487 }
488 .as_arg()],
489 ..Default::default()
490 };
491 let ctxt = SyntaxContext::empty().apply_mark(mark);
492 VarDecl {
493 kind: VarDeclKind::Var,
494 decls: vec![VarDeclarator {
495 span: DUMMY_SP,
496 name: Pat::Ident(Ident::new(format!("_{name}").into(), DUMMY_SP, ctxt).into()),
497 init: Some(c.into()),
498 definite: false,
499 }],
500 ..Default::default()
501 }
502 .into()
503 }
504
505 fn map_helper_ref_ident(&mut self, ref_ident: &Ident) -> Option<Expr> {
506 self.helper_ctxt
507 .filter(|ctxt| ctxt == &ref_ident.ctxt)
508 .map(|_| {
509 let ident = ref_ident.clone().without_loc();
510
511 MemberExpr {
512 span: ref_ident.span,
513 obj: Box::new(ident.into()),
514 prop: MemberProp::Ident(atom!("_").into()),
515 }
516 .into()
517 })
518 }
519}
520
521impl VisitMut for InjectHelpers {
522 noop_visit_mut_type!();
523
524 fn visit_mut_module(&mut self, module: &mut Module) {
525 let helpers = self.make_helpers_for_module();
526
527 prepend_stmts(&mut module.body, helpers.into_iter());
528 }
529
530 fn visit_mut_script(&mut self, script: &mut Script) {
531 let helpers = self.make_helpers_for_script();
532 let helpers_is_empty = helpers.is_empty();
533
534 prepend_stmts(&mut script.body, helpers.into_iter());
535
536 if !helpers_is_empty {
537 script.visit_mut_children_with(self);
538 }
539 }
540
541 fn visit_mut_expr(&mut self, n: &mut Expr) {
542 match n {
543 Expr::Ident(ref_ident) => {
544 if let Some(expr) = self.map_helper_ref_ident(ref_ident) {
545 *n = expr;
546 }
547 }
548
549 _ => n.visit_mut_children_with(self),
550 };
551 }
552}
553
554#[cfg(feature = "inline-helpers")]
555struct Marker {
556 base: SyntaxContext,
557 decls: rustc_hash::FxHashMap<swc_atoms::Atom, SyntaxContext>,
558
559 decl_ctxt: SyntaxContext,
560}
561
562#[cfg(feature = "inline-helpers")]
563impl VisitMut for Marker {
564 noop_visit_mut_type!();
565
566 fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
567 let old_decl_ctxt = std::mem::replace(
568 &mut self.decl_ctxt,
569 SyntaxContext::empty().apply_mark(Mark::new()),
570 );
571 let old_decls = self.decls.clone();
572
573 n.visit_mut_children_with(self);
574
575 self.decls = old_decls;
576 self.decl_ctxt = old_decl_ctxt;
577 }
578
579 fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
580 let old_decl_ctxt = std::mem::replace(
581 &mut self.decl_ctxt,
582 SyntaxContext::empty().apply_mark(Mark::new()),
583 );
584 let old_decls = self.decls.clone();
585
586 n.visit_mut_children_with(self);
587
588 self.decls = old_decls;
589 self.decl_ctxt = old_decl_ctxt;
590 }
591
592 fn visit_mut_ident(&mut self, i: &mut Ident) {
593 i.ctxt = self.decls.get(&i.sym).copied().unwrap_or(self.base);
594 }
595
596 fn visit_mut_member_prop(&mut self, p: &mut MemberProp) {
597 if let MemberProp::Computed(p) = p {
598 p.visit_mut_with(self);
599 }
600 }
601
602 fn visit_mut_param(&mut self, n: &mut Param) {
603 if let Pat::Ident(i) = &n.pat {
604 self.decls.insert(i.sym.clone(), self.decl_ctxt);
605 }
606
607 n.visit_mut_children_with(self);
608 }
609
610 fn visit_mut_prop_name(&mut self, n: &mut PropName) {
611 if let PropName::Computed(e) = n {
612 e.visit_mut_with(self);
613 }
614 }
615
616 fn visit_mut_super_prop(&mut self, p: &mut SuperProp) {
617 if let SuperProp::Computed(p) = p {
618 p.visit_mut_with(self);
619 }
620 }
621
622 fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
623 if let Pat::Ident(i) = &mut v.name {
624 if &*i.sym == "id" || &*i.sym == "resource" {
625 i.ctxt = self.base;
626 self.decls.insert(i.sym.clone(), self.base);
627 return;
628 }
629
630 if !(i.sym.starts_with("__") && i.sym.starts_with("_ts_")) {
631 self.decls.insert(i.sym.clone(), self.decl_ctxt);
632 }
633 }
634
635 v.visit_mut_children_with(self);
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use testing::DebugUsingDisplay;
642
643 use super::*;
644
645 #[test]
646 fn external_helper() {
647 let input = "_throw()";
648 crate::tests::Tester::run(|tester| {
649 HELPERS.set(&Helpers::new(true), || {
650 let expected = tester.apply_transform(
651 DropSpan,
652 "output.js",
653 Default::default(),
654 "import { _ as _throw } from \"@swc/helpers/_/_throw\";
655_throw();",
656 )?;
657 enable_helper!(throw);
658
659 eprintln!("----- Actual -----");
660
661 let tr = inject_helpers(Mark::new());
662 let actual = tester
663 .apply_transform(tr, "input.js", Default::default(), input)?
664 .apply(crate::hygiene::hygiene())
665 .apply(crate::fixer::fixer(None));
666
667 if actual == expected {
668 return Ok(());
669 }
670
671 let (actual_src, expected_src) = (tester.print(&actual), tester.print(&expected));
672
673 if actual_src == expected_src {
674 return Ok(());
675 }
676
677 println!(">>>>> Orig <<<<<\n{input}");
678 println!(">>>>> Code <<<<<\n{actual_src}");
679 assert_eq!(
680 DebugUsingDisplay(&actual_src),
681 DebugUsingDisplay(&expected_src)
682 );
683 Err(())
684 })
685 });
686 }
687
688 #[test]
689 #[cfg(feature = "inline-helpers")]
690 fn use_strict_before_helper() {
691 crate::tests::test_transform(
692 Default::default(),
693 |_| {
694 enable_helper!(throw);
695 inject_helpers(Mark::new())
696 },
697 "'use strict'",
698 "'use strict'
699function _throw(e) {
700 throw e;
701}
702",
703 false,
704 Default::default,
705 )
706 }
707
708 #[test]
709 #[cfg(feature = "inline-helpers")]
710 fn name_conflict() {
711 crate::tests::test_transform(
712 Default::default(),
713 |_| {
714 enable_helper!(throw);
715 inject_helpers(Mark::new())
716 },
717 "let _throw = null",
718 "function _throw(e) {
719 throw e;
720}
721let _throw1 = null;
722",
723 false,
724 Default::default,
725 )
726 }
727
728 #[test]
729 fn use_strict_abort() {
730 crate::tests::test_transform(
731 Default::default(),
732 |_| noop_pass(),
733 "'use strict'
734
735let x = 4;",
736 "'use strict'
737
738let x = 4;",
739 false,
740 Default::default,
741 );
742 }
743
744 #[test]
745 #[cfg(feature = "inline-helpers")]
746 fn issue_8871() {
747 crate::tests::test_transform(
748 Default::default(),
749 |_| {
750 enable_helper!(using_ctx);
751 inject_helpers(Mark::new())
752 },
753 "let _throw = null",
754 r#"
755 function _using_ctx() {
756 var _disposeSuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed) {
757 var err = new Error();
758 err.name = "SuppressedError";
759 err.suppressed = suppressed;
760 err.error = error;
761 return err;
762 }, empty = {}, stack = [];
763 function using(isAwait, value) {
764 if (value != null) {
765 if (Object(value) !== value) {
766 throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
767 }
768 if (isAwait) {
769 var dispose = value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")];
770 }
771 if (dispose == null) {
772 dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")];
773 }
774 if (typeof dispose !== "function") {
775 throw new TypeError(`Property [Symbol.dispose] is not a function.`);
776 }
777 stack.push({
778 v: value,
779 d: dispose,
780 a: isAwait
781 });
782 } else if (isAwait) {
783 stack.push({
784 d: value,
785 a: isAwait
786 });
787 }
788 return value;
789 }
790 return {
791 e: empty,
792 u: using.bind(null, false),
793 a: using.bind(null, true),
794 d: function() {
795 var error = this.e;
796 function next() {
797 while(resource = stack.pop()){
798 try {
799 var resource, disposalResult = resource.d && resource.d.call(resource.v);
800 if (resource.a) {
801 return Promise.resolve(disposalResult).then(next, err);
802 }
803 } catch (e) {
804 return err(e);
805 }
806 }
807 if (error !== empty) throw error;
808 }
809 function err(e) {
810 error = error !== empty ? new _disposeSuppressedError(error, e) : e;
811 return next();
812 }
813 return next();
814 }
815 };
816 }
817
818let _throw = null;
819"#,
820 false,
821 Default::default,
822 )
823 }
824}