swc_ecma_transforms_base/helpers/
mod.rs

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    /// This variable is used to manage helper scripts like `_inherits` from babel.
95    ///
96    /// The instance contains flags where each flag denotes if a helper script should be injected.
97    pub static HELPERS: Helpers
98);
99
100/// Tracks used helper methods. (e.g. __extends)
101#[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                        // add_require_to!(buf, $name, helpers.inner.$name, helpers.mark.0, self.global_mark);
243                    )*
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    // temporal_ref: (temporal_undefined),
356    // temporal_undefined: (),
357    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}