swc_ecma_transforms_base/helpers/
mod.rs

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    /// 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            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                        // add_require_to!(buf, $name, helpers.inner.$name, helpers.mark.0, self.global_mark);
245                    )*
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    // temporal_ref: (temporal_undefined),
357    // temporal_undefined: (),
358    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}