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
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}