1use std::{cell::RefCell, mem::replace};
2
3use once_cell::sync::Lazy;
4use rustc_hash::FxHashMap;
5use swc_atoms::Atom;
6use swc_common::{FileName, FilePathMapping, Mark, SourceMap, SyntaxContext, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_utils::{prepend_stmts, quote_ident, DropSpan, ExprFactory};
9use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
10
11#[macro_export]
12macro_rules! enable_helper {
13 ($i:ident) => {{
14 $crate::helpers::HELPERS.with(|helpers| {
15 helpers.$i();
16 helpers.mark()
17 })
18 }};
19}
20
21fn parse(code: &str) -> Vec<Stmt> {
22 let cm = SourceMap::new(FilePathMapping::empty());
23
24 let fm = cm.new_source_file(
25 FileName::Custom(stringify!($name).into()).into(),
26 code.into(),
27 );
28 swc_ecma_parser::parse_file_as_script(
29 &fm,
30 Default::default(),
31 Default::default(),
32 None,
33 &mut Vec::new(),
34 )
35 .map(|mut script| {
36 script.body.visit_mut_with(&mut DropSpan);
37 script.body
38 })
39 .map_err(|e| {
40 unreachable!("Error occurred while parsing error: {:?}", e);
41 })
42 .unwrap()
43}
44
45macro_rules! add_to {
46 ($buf:expr, $name:ident, $b:expr, $mark:expr) => {{
47 static STMTS: Lazy<Vec<Stmt>> = 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 fn build_helpers(&self) -> Vec<Stmt> {
207 let mut buf = Vec::new();
208
209 HELPERS.with(|helpers|{
210 debug_assert!(!helpers.external);
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 debug_assert!(helpers.external);
226 $(
227 add_import_to!(buf, $name, inner.$name, helpers.mark.0);
228 )*
229 });
230
231 buf
232 }
233
234 fn build_requires(&self) -> Vec<Stmt>{
235 let mut buf = Vec::new();
236 HELPERS.with(|helpers|{
237 debug_assert!(helpers.external);
238 let inner = helpers.inner.borrow();
239 $(
240 let enable = inner.$name;
241 if enable {
242 buf.push(self.build_reqire(stringify!($name), helpers.mark.0))
243 }
244 )*
246 });
247 buf
248 }
249 }
250 };
251}
252
253define_helpers!(Helpers {
254 apply_decorated_descriptor: (),
255 array_like_to_array: (),
256 array_with_holes: (),
257 array_without_holes: (array_like_to_array),
258 assert_this_initialized: (),
259 async_generator: (await_value),
260 async_generator_delegate: (),
261 async_iterator: (),
262 async_to_generator: (),
263 await_async_generator: (await_value),
264 await_value: (),
265 call_super: (
266 get_prototype_of,
267 is_native_reflect_construct,
268 possible_constructor_return
269 ),
270 check_private_redeclaration: (),
271 class_apply_descriptor_destructure: (),
272 class_apply_descriptor_get: (),
273 class_apply_descriptor_set: (),
274 class_apply_descriptor_update: (),
275 class_call_check: (),
276 class_check_private_static_field_descriptor: (),
277 class_extract_field_descriptor: (),
278 class_name_tdz_error: (),
279 class_private_field_get: (class_extract_field_descriptor, class_apply_descriptor_get),
280 class_private_field_init: (check_private_redeclaration),
281 class_private_field_loose_base: (),
282 class_private_field_loose_key: (),
283 class_private_field_set: (class_extract_field_descriptor, class_apply_descriptor_set),
284 class_private_field_update: (
285 class_extract_field_descriptor,
286 class_apply_descriptor_update
287 ),
288 class_private_method_get: (),
289 class_private_method_init: (check_private_redeclaration),
290 class_private_method_set: (),
291 class_static_private_field_spec_get: (
292 class_check_private_static_access,
293 class_check_private_static_field_descriptor,
294 class_apply_descriptor_get
295 ),
296 class_static_private_field_spec_set: (
297 class_check_private_static_access,
298 class_check_private_static_field_descriptor,
299 class_apply_descriptor_set
300 ),
301 class_static_private_field_update: (
302 class_check_private_static_access,
303 class_check_private_static_field_descriptor,
304 class_apply_descriptor_update
305 ),
306 construct: (is_native_reflect_construct, set_prototype_of),
307 create_class: (),
308 decorate: (to_array, to_property_key),
309 defaults: (),
310 define_enumerable_properties: (),
311 define_property: (),
312 export_star: (),
313 extends: (),
314 get: (super_prop_base),
315 get_prototype_of: (),
316 inherits: (set_prototype_of),
317 inherits_loose: (),
318 initializer_define_property: (),
319 initializer_warning_helper: (),
320 instanceof: (),
321 interop_require_default: (),
322 interop_require_wildcard: (),
323 is_native_function: (),
324 iterable_to_array: (),
325 iterable_to_array_limit: (),
326 iterable_to_array_limit_loose: (),
327 jsx: (),
328 new_arrow_check: (),
329 non_iterable_rest: (),
330 non_iterable_spread: (),
331 object_destructuring_empty: (),
332 object_spread: (define_property),
333 object_spread_props: (),
334 object_without_properties: (object_without_properties_loose),
335 object_without_properties_loose: (),
336 possible_constructor_return: (type_of, assert_this_initialized),
337 read_only_error: (),
338 set: (super_prop_base, define_property),
339 set_prototype_of: (),
340 skip_first_generator_next: (),
341 sliced_to_array: (
342 array_with_holes,
343 iterable_to_array_limit,
344 unsupported_iterable_to_array,
345 non_iterable_rest
346 ),
347 sliced_to_array_loose: (
348 array_with_holes,
349 iterable_to_array_limit_loose,
350 unsupported_iterable_to_array,
351 non_iterable_rest
352 ),
353 super_prop_base: (get_prototype_of),
354 tagged_template_literal: (),
355 tagged_template_literal_loose: (),
356 throw: (),
359 to_array: (
360 array_with_holes,
361 iterable_to_array,
362 unsupported_iterable_to_array,
363 non_iterable_rest
364 ),
365 to_consumable_array: (
366 array_without_holes,
367 iterable_to_array,
368 unsupported_iterable_to_array,
369 non_iterable_spread
370 ),
371 to_primitive: (type_of),
372 to_property_key: (type_of, to_primitive),
373 update: (get, set),
374 type_of: (),
375 unsupported_iterable_to_array: (array_like_to_array),
376 wrap_async_generator: (async_generator),
377 wrap_native_super: (
378 construct,
379 get_prototype_of,
380 set_prototype_of,
381 is_native_function
382 ),
383 write_only_error: (),
384
385 class_private_field_destructure: (
386 class_extract_field_descriptor,
387 class_apply_descriptor_destructure
388 ),
389 class_static_private_field_destructure: (
390 class_check_private_static_access,
391 class_extract_field_descriptor,
392 class_apply_descriptor_destructure
393 ),
394
395 class_static_private_method_get: (class_check_private_static_access),
396 class_check_private_static_access: (),
397
398 is_native_reflect_construct: (),
399
400 create_super: (
401 get_prototype_of,
402 is_native_reflect_construct,
403 possible_constructor_return
404 ),
405
406 create_for_of_iterator_helper_loose: (unsupported_iterable_to_array),
407
408 ts_decorate: (),
409 ts_generator: (),
410 ts_metadata: (),
411 ts_param: (),
412 ts_values: (),
413 ts_add_disposable_resource: (),
414 ts_dispose_resources: (),
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 fn make_helpers_for_module(&mut self) -> Vec<ModuleItem> {
437 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
438 if external {
439 if self.is_helper_used() {
440 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
441 self.build_imports()
442 } else {
443 Vec::new()
444 }
445 } else {
446 self.build_helpers()
447 .into_iter()
448 .map(ModuleItem::Stmt)
449 .collect()
450 }
451 }
452
453 fn make_helpers_for_script(&mut self) -> Vec<Stmt> {
454 let (helper_mark, external) = HELPERS.with(|helper| (helper.mark(), helper.external()));
455
456 if external {
457 if self.is_helper_used() {
458 self.helper_ctxt = Some(SyntaxContext::empty().apply_mark(helper_mark));
459 self.build_requires()
460 } else {
461 Default::default()
462 }
463 } else {
464 self.build_helpers()
465 }
466 }
467
468 fn build_reqire(&self, name: &str, mark: Mark) -> Stmt {
469 let c = CallExpr {
470 span: DUMMY_SP,
471 callee: Expr::from(Ident {
472 span: DUMMY_SP,
473 ctxt: SyntaxContext::empty().apply_mark(self.global_mark),
474 sym: "require".into(),
475 ..Default::default()
476 })
477 .as_callee(),
478 args: vec![Str {
479 span: DUMMY_SP,
480 value: format!("@swc/helpers/_/_{}", name).into(),
481 raw: None,
482 }
483 .as_arg()],
484 ..Default::default()
485 };
486 let ctxt = SyntaxContext::empty().apply_mark(mark);
487 VarDecl {
488 kind: VarDeclKind::Var,
489 decls: vec![VarDeclarator {
490 span: DUMMY_SP,
491 name: Pat::Ident(Ident::new(format!("_{}", name).into(), DUMMY_SP, ctxt).into()),
492 init: Some(c.into()),
493 definite: false,
494 }],
495 ..Default::default()
496 }
497 .into()
498 }
499
500 fn map_helper_ref_ident(&mut self, ref_ident: &Ident) -> Option<Expr> {
501 self.helper_ctxt
502 .filter(|ctxt| ctxt == &ref_ident.ctxt)
503 .map(|_| {
504 let ident = ref_ident.clone().without_loc();
505
506 MemberExpr {
507 span: ref_ident.span,
508 obj: Box::new(ident.into()),
509 prop: MemberProp::Ident("_".into()),
510 }
511 .into()
512 })
513 }
514}
515
516impl VisitMut for InjectHelpers {
517 noop_visit_mut_type!();
518
519 fn visit_mut_module(&mut self, module: &mut Module) {
520 let helpers = self.make_helpers_for_module();
521
522 prepend_stmts(&mut module.body, helpers.into_iter());
523 }
524
525 fn visit_mut_script(&mut self, script: &mut Script) {
526 let helpers = self.make_helpers_for_script();
527 let helpers_is_empty = helpers.is_empty();
528
529 prepend_stmts(&mut script.body, helpers.into_iter());
530
531 if !helpers_is_empty {
532 script.visit_mut_children_with(self);
533 }
534 }
535
536 fn visit_mut_expr(&mut self, n: &mut Expr) {
537 match n {
538 Expr::Ident(ref_ident) => {
539 if let Some(expr) = self.map_helper_ref_ident(ref_ident) {
540 *n = expr;
541 }
542 }
543
544 _ => n.visit_mut_children_with(self),
545 };
546 }
547}
548
549struct Marker {
550 base: SyntaxContext,
551 decls: FxHashMap<Atom, SyntaxContext>,
552
553 decl_ctxt: SyntaxContext,
554}
555
556impl VisitMut for Marker {
557 noop_visit_mut_type!();
558
559 fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) {
560 let old_decl_ctxt = replace(
561 &mut self.decl_ctxt,
562 SyntaxContext::empty().apply_mark(Mark::new()),
563 );
564 let old_decls = self.decls.clone();
565
566 n.visit_mut_children_with(self);
567
568 self.decls = old_decls;
569 self.decl_ctxt = old_decl_ctxt;
570 }
571
572 fn visit_mut_fn_expr(&mut self, n: &mut FnExpr) {
573 let old_decl_ctxt = replace(
574 &mut self.decl_ctxt,
575 SyntaxContext::empty().apply_mark(Mark::new()),
576 );
577 let old_decls = self.decls.clone();
578
579 n.visit_mut_children_with(self);
580
581 self.decls = old_decls;
582 self.decl_ctxt = old_decl_ctxt;
583 }
584
585 fn visit_mut_ident(&mut self, i: &mut Ident) {
586 i.ctxt = self.decls.get(&i.sym).copied().unwrap_or(self.base);
587 }
588
589 fn visit_mut_member_prop(&mut self, p: &mut MemberProp) {
590 if let MemberProp::Computed(p) = p {
591 p.visit_mut_with(self);
592 }
593 }
594
595 fn visit_mut_param(&mut self, n: &mut Param) {
596 if let Pat::Ident(i) = &n.pat {
597 self.decls.insert(i.sym.clone(), self.decl_ctxt);
598 }
599
600 n.visit_mut_children_with(self);
601 }
602
603 fn visit_mut_prop_name(&mut self, n: &mut PropName) {
604 if let PropName::Computed(e) = n {
605 e.visit_mut_with(self);
606 }
607 }
608
609 fn visit_mut_super_prop(&mut self, p: &mut SuperProp) {
610 if let SuperProp::Computed(p) = p {
611 p.visit_mut_with(self);
612 }
613 }
614
615 fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
616 if let Pat::Ident(i) = &mut v.name {
617 if &*i.sym == "id" || &*i.sym == "resource" {
618 i.ctxt = self.base;
619 self.decls.insert(i.sym.clone(), self.base);
620 return;
621 }
622
623 if !(i.sym.starts_with("__") && i.sym.starts_with("_ts_")) {
624 self.decls.insert(i.sym.clone(), self.decl_ctxt);
625 }
626 }
627
628 v.visit_mut_children_with(self);
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use testing::DebugUsingDisplay;
635
636 use super::*;
637
638 #[test]
639 fn external_helper() {
640 let input = "_throw()";
641 crate::tests::Tester::run(|tester| {
642 HELPERS.set(&Helpers::new(true), || {
643 let expected = tester.apply_transform(
644 DropSpan,
645 "output.js",
646 Default::default(),
647 "import { _ as _throw } from \"@swc/helpers/_/_throw\";
648_throw();",
649 )?;
650 enable_helper!(throw);
651
652 eprintln!("----- Actual -----");
653
654 let tr = inject_helpers(Mark::new());
655 let actual = tester
656 .apply_transform(tr, "input.js", Default::default(), input)?
657 .apply(crate::hygiene::hygiene())
658 .apply(crate::fixer::fixer(None));
659
660 if actual == expected {
661 return Ok(());
662 }
663
664 let (actual_src, expected_src) = (tester.print(&actual), tester.print(&expected));
665
666 if actual_src == expected_src {
667 return Ok(());
668 }
669
670 println!(">>>>> Orig <<<<<\n{}", input);
671 println!(">>>>> Code <<<<<\n{}", actual_src);
672 assert_eq!(
673 DebugUsingDisplay(&actual_src),
674 DebugUsingDisplay(&expected_src)
675 );
676 Err(())
677 })
678 });
679 }
680
681 #[test]
682 fn use_strict_before_helper() {
683 crate::tests::test_transform(
684 Default::default(),
685 |_| {
686 enable_helper!(throw);
687 inject_helpers(Mark::new())
688 },
689 "'use strict'",
690 "'use strict'
691function _throw(e) {
692 throw e;
693}
694",
695 false,
696 Default::default,
697 )
698 }
699
700 #[test]
701 fn name_conflict() {
702 crate::tests::test_transform(
703 Default::default(),
704 |_| {
705 enable_helper!(throw);
706 inject_helpers(Mark::new())
707 },
708 "let _throw = null",
709 "function _throw(e) {
710 throw e;
711}
712let _throw1 = null;
713",
714 false,
715 Default::default,
716 )
717 }
718
719 #[test]
720 fn use_strict_abort() {
721 crate::tests::test_transform(
722 Default::default(),
723 |_| noop_pass(),
724 "'use strict'
725
726let x = 4;",
727 "'use strict'
728
729let x = 4;",
730 false,
731 Default::default,
732 );
733 }
734
735 #[test]
736 fn issue_8871() {
737 crate::tests::test_transform(
738 Default::default(),
739 |_| {
740 enable_helper!(using_ctx);
741 inject_helpers(Mark::new())
742 },
743 "let _throw = null",
744 r#"
745 function _using_ctx() {
746 var _disposeSuppressedError = typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed) {
747 var err = new Error();
748 err.name = "SuppressedError";
749 err.suppressed = suppressed;
750 err.error = error;
751 return err;
752 }, empty = {}, stack = [];
753 function using(isAwait, value) {
754 if (value != null) {
755 if (Object(value) !== value) {
756 throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
757 }
758 if (isAwait) {
759 var dispose = value[Symbol.asyncDispose || Symbol.for("Symbol.asyncDispose")];
760 }
761 if (dispose == null) {
762 dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")];
763 }
764 if (typeof dispose !== "function") {
765 throw new TypeError(`Property [Symbol.dispose] is not a function.`);
766 }
767 stack.push({
768 v: value,
769 d: dispose,
770 a: isAwait
771 });
772 } else if (isAwait) {
773 stack.push({
774 d: value,
775 a: isAwait
776 });
777 }
778 return value;
779 }
780 return {
781 e: empty,
782 u: using.bind(null, false),
783 a: using.bind(null, true),
784 d: function() {
785 var error = this.e;
786 function next() {
787 while(resource = stack.pop()){
788 try {
789 var resource, disposalResult = resource.d && resource.d.call(resource.v);
790 if (resource.a) {
791 return Promise.resolve(disposalResult).then(next, err);
792 }
793 } catch (e) {
794 return err(e);
795 }
796 }
797 if (error !== empty) throw error;
798 }
799 function err(e) {
800 error = error !== empty ? new _disposeSuppressedError(error, e) : e;
801 return next();
802 }
803 return next();
804 }
805 };
806 }
807
808let _throw = null;
809"#,
810 false,
811 Default::default,
812 )
813 }
814}