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 ts_rewrite_relative_import_extension: (),
415
416 apply_decs_2203_r: (),
417 identity: (),
418 dispose: (),
419 using: (),
420 using_ctx: (),
421});
422
423pub fn inject_helpers(global_mark: Mark) -> impl Pass + VisitMut {
424 visit_mut_pass(InjectHelpers {
425 global_mark,
426 helper_ctxt: None,
427 })
428}
429
430struct InjectHelpers {
431 global_mark: Mark,
432 helper_ctxt: Option<SyntaxContext>,
433}
434
435impl InjectHelpers {
436 #[allow(unused_variables)]
437 fn make_helpers_for_module(&mut self) -> Vec<ModuleItem> {
438 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
439
440 #[cfg(feature = "inline-helpers")]
441 if !external {
442 return self
443 .build_helpers()
444 .into_iter()
445 .map(ModuleItem::Stmt)
446 .collect();
447 }
448
449 if self.is_helper_used() {
450 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
451 self.build_imports()
452 } else {
453 Vec::new()
454 }
455 }
456
457 #[allow(unused_variables)]
458 fn make_helpers_for_script(&mut self) -> Vec<Stmt> {
459 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
460
461 #[cfg(feature = "inline-helpers")]
462 if !external {
463 return self.build_helpers();
464 }
465
466 if self.is_helper_used() {
467 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
468 self.build_requires()
469 } else {
470 Default::default()
471 }
472 }
473
474 fn build_reqire(&self, name: &str, mark: Mark) -> Stmt {
475 let c = CallExpr {
476 span: DUMMY_SP,
477 callee: Expr::from(Ident {
478 span: DUMMY_SP,
479 ctxt: SyntaxContext::empty().apply_mark(self.global_mark),
480 sym: atom!("require"),
481 ..Default::default()
482 })
483 .as_callee(),
484 args: vec![Str {
485 span: DUMMY_SP,
486 value: format!("@swc/helpers/_/_{name}").into(),
487 raw: None,
488 }
489 .as_arg()],
490 ..Default::default()
491 };
492 let ctxt = SyntaxContext::empty().apply_mark(mark);
493 VarDecl {
494 kind: VarDeclKind::Var,
495 decls: vec![VarDeclarator {
496 span: DUMMY_SP,
497 name: Pat::Ident(Ident::new(format!("_{name}").into(), DUMMY_SP, ctxt).into()),
498 init: Some(c.into()),
499 definite: false,
500 }],
501 ..Default::default()
502 }
503 .into()
504 }
505
506 fn map_helper_ref_ident(&mut self, ref_ident: &Ident) -> Option<Expr> {
507 self.helper_ctxt
508 .filter(|ctxt| ctxt == &ref_ident.ctxt)
509 .map(|_| {
510 let ident = ref_ident.clone().without_loc();
511
512 MemberExpr {
513 span: ref_ident.span,
514 obj: Box::new(ident.into()),
515 prop: MemberProp::Ident(atom!("_").into()),
516 }
517 .into()
518 })
519 }
520}
521
522impl VisitMut for InjectHelpers {
523 noop_visit_mut_type!();
524
525 fn visit_mut_module(&mut self, module: &mut Module) {
526 let helpers = self.make_helpers_for_module();
527
528 prepend_stmts(&mut module.body, helpers.into_iter());
529 }
530
531 fn visit_mut_script(&mut self, script: &mut Script) {
532 let helpers = self.make_helpers_for_script();
533 let helpers_is_empty = helpers.is_empty();
534
535 prepend_stmts(&mut script.body, helpers.into_iter());
536
537 if !helpers_is_empty {
538 script.visit_mut_children_with(self);
539 }
540 }
541
542 fn visit_mut_expr(&mut self, n: &mut Expr) {
543 match n {
544 Expr::Ident(ref_ident) => {
545 if let Some(expr) = self.map_helper_ref_ident(ref_ident) {
546 *n = expr;
547 }
548 }
549
550 _ => n.visit_mut_children_with(self),
551 };
552 }
553}
554
555#[cfg(feature = "inline-helpers")]
556struct Marker {
557 base: SyntaxContext,
558 decls: rustc_hash::FxHashMap<swc_atoms::Atom, SyntaxContext>,
559
560 decl_ctxt: SyntaxContext,
561}
562
563#[cfg(feature = "inline-helpers")]
564impl VisitMut for Marker {
565 noop_visit_mut_type!();
566
567 fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
568 let old_decl_ctxt = std::mem::replace(
569 &mut self.decl_ctxt,
570 SyntaxContext::empty().apply_mark(Mark::new()),
571 );
572 let old_decls = self.decls.clone();
573
574 n.visit_mut_children_with(self);
575
576 self.decls = old_decls;
577 self.decl_ctxt = old_decl_ctxt;
578 }
579
580 fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
581 let old_decl_ctxt = std::mem::replace(
582 &mut self.decl_ctxt,
583 SyntaxContext::empty().apply_mark(Mark::new()),
584 );
585 let old_decls = self.decls.clone();
586
587 n.visit_mut_children_with(self);
588
589 self.decls = old_decls;
590 self.decl_ctxt = old_decl_ctxt;
591 }
592
593 fn visit_mut_ident(&mut self, i: &mut Ident) {
594 i.ctxt = self.decls.get(&i.sym).copied().unwrap_or(self.base);
595 }
596
597 fn visit_mut_member_prop(&mut self, p: &mut MemberProp) {
598 if let MemberProp::Computed(p) = p {
599 p.visit_mut_with(self);
600 }
601 }
602
603 fn visit_mut_param(&mut self, n: &mut Param) {
604 if let Pat::Ident(i) = &n.pat {
605 self.decls.insert(i.sym.clone(), self.decl_ctxt);
606 }
607
608 n.visit_mut_children_with(self);
609 }
610
611 fn visit_mut_prop_name(&mut self, n: &mut PropName) {
612 if let PropName::Computed(e) = n {
613 e.visit_mut_with(self);
614 }
615 }
616
617 fn visit_mut_super_prop(&mut self, p: &mut SuperProp) {
618 if let SuperProp::Computed(p) = p {
619 p.visit_mut_with(self);
620 }
621 }
622
623 fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
624 if let Pat::Ident(i) = &mut v.name {
625 if &*i.sym == "id" || &*i.sym == "resource" {
626 i.ctxt = self.base;
627 self.decls.insert(i.sym.clone(), self.base);
628 return;
629 }
630
631 if !(i.sym.starts_with("__") && i.sym.starts_with("_ts_")) {
632 self.decls.insert(i.sym.clone(), self.decl_ctxt);
633 }
634 }
635
636 v.visit_mut_children_with(self);
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use testing::DebugUsingDisplay;
643
644 use super::*;
645
646 #[test]
647 fn external_helper() {
648 let input = "_throw()";
649 crate::tests::Tester::run(|tester| {
650 HELPERS.set(&Helpers::new(true), || {
651 let expected = tester.apply_transform(
652 DropSpan,
653 "output.js",
654 Default::default(),
655 "import { _ as _throw } from \"@swc/helpers/_/_throw\";
656_throw();",
657 )?;
658 enable_helper!(throw);
659
660 eprintln!("----- Actual -----");
661
662 let tr = inject_helpers(Mark::new());
663 let actual = tester
664 .apply_transform(tr, "input.js", Default::default(), input)?
665 .apply(crate::hygiene::hygiene())
666 .apply(crate::fixer::fixer(None));
667
668 if actual == expected {
669 return Ok(());
670 }
671
672 let (actual_src, expected_src) = (tester.print(&actual), tester.print(&expected));
673
674 if actual_src == expected_src {
675 return Ok(());
676 }
677
678 println!(">>>>> Orig <<<<<\n{input}");
679 println!(">>>>> Code <<<<<\n{actual_src}");
680 assert_eq!(
681 DebugUsingDisplay(&actual_src),
682 DebugUsingDisplay(&expected_src)
683 );
684 Err(())
685 })
686 });
687 }
688
689 #[test]
690 #[cfg(feature = "inline-helpers")]
691 fn use_strict_before_helper() {
692 crate::tests::test_transform(
693 Default::default(),
694 |_| {
695 enable_helper!(throw);
696 inject_helpers(Mark::new())
697 },
698 "'use strict'",
699 "'use strict'
700function _throw(e) {
701 throw e;
702}
703",
704 false,
705 Default::default,
706 )
707 }
708
709 #[test]
710 #[cfg(feature = "inline-helpers")]
711 fn name_conflict() {
712 crate::tests::test_transform(
713 Default::default(),
714 |_| {
715 enable_helper!(throw);
716 inject_helpers(Mark::new())
717 },
718 "let _throw = null",
719 "function _throw(e) {
720 throw e;
721}
722let _throw1 = null;
723",
724 false,
725 Default::default,
726 )
727 }
728
729 #[test]
730 fn use_strict_abort() {
731 crate::tests::test_transform(
732 Default::default(),
733 |_| noop_pass(),
734 "'use strict'
735
736let x = 4;",
737 "'use strict'
738
739let x = 4;",
740 false,
741 Default::default,
742 );
743 }
744
745 #[test]
746 #[cfg(feature = "inline-helpers")]
747 fn issue_8871() {
748 crate::tests::test_transform(
749 Default::default(),
750 |_| {
751 enable_helper!(using_ctx);
752 inject_helpers(Mark::new())
753 },
754 "let _throw = null",
755 r#"
756 function _using_ctx() {
757 var _disposeSuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed) {
758 var err = new Error();
759 err.name = "SuppressedError";
760 err.suppressed = suppressed;
761 err.error = error;
762 return err;
763 }, empty = {}, stack = [];
764 function using(isAwait, value) {
765 if (value != null) {
766 if (Object(value) !== value) {
767 throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
768 }
769 if (isAwait) {
770 var dispose = value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")];
771 }
772 if (dispose == null) {
773 dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")];
774 }
775 if (typeof dispose !== "function") {
776 throw new TypeError(`Property [Symbol.dispose] is not a function.`);
777 }
778 stack.push({
779 v: value,
780 d: dispose,
781 a: isAwait
782 });
783 } else if (isAwait) {
784 stack.push({
785 d: value,
786 a: isAwait
787 });
788 }
789 return value;
790 }
791 return {
792 e: empty,
793 u: using.bind(null, false),
794 a: using.bind(null, true),
795 d: function() {
796 var error = this.e;
797 function next() {
798 while(resource = stack.pop()){
799 try {
800 var resource, disposalResult = resource.d && resource.d.call(resource.v);
801 if (resource.a) {
802 return Promise.resolve(disposalResult).then(next, err);
803 }
804 } catch (e) {
805 return err(e);
806 }
807 }
808 if (error !== empty) throw error;
809 }
810 function err(e) {
811 error = error !== empty ? new _disposeSuppressedError(error, e) : e;
812 return next();
813 }
814 return next();
815 }
816 };
817 }
818
819let _throw = null;
820"#,
821 false,
822 Default::default,
823 )
824 }
825}